eletrotupi / tcc/ api/src/static/js/app.js27bca510c14b3745f6fa52ae54fac66653498781
6.0 KB | 223 lines Raw
1
const { animate, utils, stagger, splitText, morphTo } = anime;
2

3
// Motion path: glowing dot traces the logo outline.
4
// morphTo on hover: logo → 16-pt star, tries to mimic the logo
5

6
// XXX: morphTo note: animejs tries to find the shorter path to match the
7
// logo's in ~300 commands, so the star gets hundreds of "stacked" points. The
8
// morph works but looks like a chaotic crystallisation rather than a clean
9
// shape-to-shape swap. Not gonna waste more time on this bullshit
10
async function setupLogoAnimations() {
11
  const container = document.getElementById('logo-container');
12
  if (!container) return;
13

14
  let pathD;
15
  try {
16
    const text = await fetch('/images/logo.svg').then(r => r.text());
17
    pathD = new DOMParser()
18
      .parseFromString(text, 'image/svg+xml')
19
      .querySelector('path')
20
      .getAttribute('d');
21
  } catch {
22
    return;
23
  }
24

25
  // Blah
26
  const NS = 'http://www.w3.org/2000/svg';
27
  const mk = (tag, attrs = {}) => {
28
    const el = document.createElementNS(NS, tag);
29
    Object.entries(attrs).forEach(([k, v]) => el.setAttribute(k, v));
30
    return el;
31
  };
32

33
  const svg = mk('svg', { viewBox: '0 0 600 600' });
34
  svg.style.cssText = 'width:100%;height:100%;display:block;overflow:visible';
35

36
  const defs = mk('defs');
37

38
  // Radial gradient: bright upper-left highlight fading to a deeper green at
39
  // edges
40
  const grad = mk('radialGradient', {
41
    id: 'logo-gradient',
42
    cx: '42%',
43
    cy: '36%',
44
    r: '65%',
45
    gradientUnits: 'objectBoundingBox'
46
  });
47

48
  grad.appendChild(mk('stop', { offset: '0%',   'stop-color': '#2dff80' }));
49
  grad.appendChild(mk('stop', { offset: '52%',  'stop-color': '#13ec5b' }));
50
  grad.appendChild(mk('stop', { offset: '100%', 'stop-color': '#09a042' }));
51
  defs.appendChild(grad);
52

53
  // Glow filter for the dot
54
  const dotFilter = mk('filter', {
55
    id: 'dot-glow',
56
    x: '-300%',
57
    y: '-300%',
58
    width: '700%',
59
    height: '700%'
60
  });
61

62
  dotFilter.appendChild(mk('feGaussianBlur', {
63
    in: 'SourceGraphic',
64
    stdDeviation: '6',
65
    result: 'blur'
66
  }));
67

68
  const merge = mk('feMerge');
69
  merge.appendChild(mk('feMergeNode', { in: 'blur' }));
70
  merge.appendChild(mk('feMergeNode', { in: 'SourceGraphic' }));
71
  dotFilter.appendChild(merge);
72
  defs.appendChild(dotFilter);
73

74
  svg.appendChild(defs);
75

76
  // Logo path: gradient fill
77
  const logoPathEl = mk('path', {
78
    fill: 'url(#logo-gradient)',
79
    filter: 'url(#logo-texture)',
80
    d: pathD
81
  });
82

83
  svg.appendChild(logoPathEl);
84

85
  // morphTo targets (same 0-600 coordinate space as the logo)
86
  const starD = 'M300,30 L322,192 L466,109 L371,258 L562,296 L371,340 L466,489 L322,412 L300,570 L278,412 L134,489 L229,340 L38,296 L229,258 L134,109 L278,192 Z';
87
  const starEl = mk('path', { d: starD, visibility: 'hidden' });
88
  const origEl = mk('path', { d: pathD, visibility: 'hidden' });
89
  svg.appendChild(starEl);
90
  svg.appendChild(origEl);
91

92
  // Motion dot
93
  const trail = mk('circle', {
94
    r: '24',
95
    fill: '#13ec5b',
96
    opacity: '0.15',
97
    filter: 'url(#dot-glow)',
98
    cx: '316',
99
    cy: '578'
100
  });
101

102
  const dot = mk('circle', {
103
    r: '8',
104
    fill: 'black',
105
    opacity: '0.85',
106
    filter: 'url(#dot-glow)',
107
    cx: '316',
108
    cy: '578'
109
  });
110

111
  svg.appendChild(trail);
112
  svg.appendChild(dot);
113

114
  container.appendChild(svg);
115

116
  // Motion path
117
  const totalLen = logoPathEl.getTotalLength();
118
  if (!totalLen) return;
119

120
  const DURATION = 12000;
121
  let rafStart = null;
122

123
  function traceDot(ts) {
124
    if (rafStart === null) rafStart = ts;
125
    const t = ((ts - rafStart) % DURATION) / DURATION;
126
    const pt = logoPathEl.getPointAtLength(t * totalLen);
127

128
    dot.setAttribute('cx', pt.x);
129
    dot.setAttribute('cy', pt.y);
130
    trail.setAttribute('cx', pt.x);
131
    trail.setAttribute('cy', pt.y);
132
    requestAnimationFrame(traceDot);
133
  }
134
  requestAnimationFrame(traceDot);
135

136
  // morphTo experiment
137
  container.addEventListener('pointerenter', () => {
138
    animate(logoPathEl, { d: morphTo(starEl), duration: 900, ease: 'inOutCubic' });
139
  });
140

141
  container.addEventListener('pointerleave', () => {
142
    animate(logoPathEl, { d: morphTo(origEl), duration: 900, ease: 'inOutCubic' });
143
  });
144
}
145

146
document.addEventListener('DOMContentLoaded', function () {
147
  const colors = [];
148

149
  new Glide('.glide', {
150
    type: 'carousel',
151
    autoplay: 2000,
152
    startAt: 0,
153
    perView: 2.5,
154
    gap: 32,
155
    breakpoints: {
156
      1024: {
157
        perView: 2
158
      },
159
      768: {
160
        perView: 1
161
      }
162
    }
163
  }).mount();
164

165
  // Initialize Atropos 3D Effect
166
  const dash = Atropos({
167
    el: '.my-atropos',
168
    activeOffset: 40,
169
    shadow: true,
170
    shadowScale: 1.05,
171
    onEnter() {
172
      console.log('Atropos Enter');
173
    },
174
    onLeave() {
175
      console.log('Atropos Leave');
176
    }
177
  });
178

179

180
  // Anime js text bullshit
181
  splitText('.animated-text', {
182
    lines: true,
183
  })
184
    .addEffect(({ lines }) => animate(lines, {
185
      y: ['20%', '-20%'],
186
      loop: true,
187
      alternate: true,
188
      delay: stagger(15000),
189
      ease: 'inOutQuad',
190
    }))
191
    .addEffect(split => {
192
      split.words.forEach(($el, i) => {
193
        const color = colors[i];
194
        if (color) utils.set($el, { color });
195
        $el.addEventListener('pointerenter', () => {
196
          animate($el, {
197
            color: utils.randomPick(['#FF4B4B', '#FFCC2A', '#B7FF54', '#57F695']),
198
            duration: 250,
199
          })
200
        });
201
      });
202
      return () => {
203
        /* Called between each split */
204
        split.words.forEach((w, i) => colors[i] = utils.get(w, 'color'));
205
      }
206
    });
207

208
  setupLogoAnimations();
209

210
  document.querySelectorAll('.glide__slide .morph-icon').forEach(svg => {
211
    const path    = svg.querySelector(':scope > path');
212
    const resting = svg.querySelector('defs .morph-resting');
213
    const shape   = svg.querySelector('defs .morph-shape');
214
    const card    = svg.closest('.group');
215

216
    card.addEventListener('pointerenter', () => {
217
      animate(path, { d: morphTo(shape),   duration: 500, ease: 'inOutCubic' });
218
    });
219
    card.addEventListener('pointerleave', () => {
220
      animate(path, { d: morphTo(resting), duration: 500, ease: 'inOutCubic' });
221
    });
222
  });
223
});