Adive01 commited on
Commit
bc34a1f
Β·
verified Β·
1 Parent(s): 81a9711

Upload frontend/app.js with huggingface_hub

Browse files
Files changed (1) hide show
  1. frontend/app.js +371 -0
frontend/app.js ADDED
@@ -0,0 +1,371 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ════════════════════════════════════════════════════════════════════
2
+ PRISM STUDIO β€” APP.JS
3
+ GSAP Β· ScrollTrigger Β· Lenis Β· SplitType Β· Magnetic Cursor
4
+ ════════════════════════════════════════════════════════════════════ */
5
+
6
+ // ─────────────────────────────────────────────────────────────────────────────
7
+ // 1. LENIS β€” smooth scroll init, synced to GSAP ticker
8
+ // ─────────────────────────────────────────────────────────────────────────────
9
+ const lenis = new Lenis({
10
+ duration: 1.4,
11
+ easing: t => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
12
+ orientation: 'vertical',
13
+ smoothWheel: true,
14
+ wheelMultiplier: 0.9,
15
+ touchMultiplier: 1.5,
16
+ });
17
+
18
+ gsap.registerPlugin(ScrollTrigger);
19
+ lenis.on('scroll', ScrollTrigger.update);
20
+ gsap.ticker.add(t => lenis.raf(t * 1000));
21
+ gsap.ticker.lagSmoothing(0);
22
+
23
+ // ─────────────────────────────────────────────────────────────────────────────
24
+ // 2. CUSTOM CURSOR β€” dot + trailing ring + magnetic elements
25
+ // ─────────────────────────────────────────────────────────────────────────────
26
+ const cursorDot = document.getElementById('cursor-dot');
27
+ const cursorRing = document.getElementById('cursor-ring');
28
+ let mouse = { x: 0, y: 0 };
29
+ let ring = { x: 0, y: 0 };
30
+
31
+ window.addEventListener('mousemove', e => {
32
+ mouse.x = e.clientX;
33
+ mouse.y = e.clientY;
34
+ gsap.set(cursorDot, { x: mouse.x, y: mouse.y });
35
+ });
36
+
37
+ gsap.ticker.add(() => {
38
+ const lerp = (a, b, n) => a + (b - a) * n;
39
+ ring.x = lerp(ring.x, mouse.x, 0.12);
40
+ ring.y = lerp(ring.y, mouse.y, 0.12);
41
+ gsap.set(cursorRing, { x: ring.x, y: ring.y });
42
+ });
43
+
44
+ // Hover states
45
+ document.querySelectorAll('button, a, select').forEach(el => {
46
+ el.addEventListener('mouseenter', () => cursorRing.classList.add('hovered'));
47
+ el.addEventListener('mouseleave', () => cursorRing.classList.remove('hovered'));
48
+ });
49
+ document.querySelectorAll('textarea').forEach(el => {
50
+ el.addEventListener('mouseenter', () => cursorRing.classList.add('text-hovered'));
51
+ el.addEventListener('mouseleave', () => cursorRing.classList.remove('text-hovered'));
52
+ });
53
+
54
+ // Magnetic physics
55
+ function applyMagnetic(selector, strength = 0.35, restEase = 0.7) {
56
+ document.querySelectorAll(selector).forEach(el => {
57
+ el.addEventListener('mousemove', e => {
58
+ const r = el.getBoundingClientRect();
59
+ const dx = e.clientX - (r.left + r.width / 2);
60
+ const dy = e.clientY - (r.top + r.height / 2);
61
+ gsap.to(el, { x: dx * strength, y: dy * strength, duration: 0.4, ease: 'power2.out' });
62
+ });
63
+ el.addEventListener('mouseleave', () => {
64
+ gsap.to(el, { x: 0, y: 0, duration: restEase, ease: 'elastic.out(1, 0.4)' });
65
+ });
66
+ });
67
+ }
68
+
69
+ applyMagnetic('.magnetic', 0.35, 0.75);
70
+ applyMagnetic('.magnetic-sm', 0.2, 0.6);
71
+
72
+ // ─────────────────────────────────────────────────────────────────────────────
73
+ // 3. THEME TOGGLE
74
+ // ─────────────────────────────────────────────────────────────────────────────
75
+ const toggle = document.getElementById('theme-toggle');
76
+ let currentTheme = 'dark';
77
+
78
+ if (toggle) {
79
+ toggle.addEventListener('click', e => {
80
+ const opt = e.target.closest('.toggle-option');
81
+ if (!opt) return;
82
+ const val = opt.dataset.val;
83
+ currentTheme = val;
84
+ document.documentElement.setAttribute('data-theme', val);
85
+ toggle.setAttribute('data-active', val);
86
+ toggle.querySelectorAll('.toggle-option').forEach(o => o.classList.toggle('active', o.dataset.val === val));
87
+ });
88
+ }
89
+
90
+ // ─────────────────────────────────────────────────────────────────────────────
91
+ // 4. HERO TEXT REVEAL β€” Line by line with SplitType
92
+ // ─────��───────────────────────────────────────────────────────────────────────
93
+ // The hero lines are already wrapped in .line-mask / .line-inner in HTML.
94
+ const lineInners = document.querySelectorAll('#hero-title .line-inner');
95
+
96
+ const tlHero = gsap.timeline({ delay: 0.3 });
97
+ tlHero
98
+ .to(lineInners, {
99
+ y: 0,
100
+ duration: 1.1,
101
+ stagger: 0.12,
102
+ ease: 'expo.out',
103
+ })
104
+ .to('#hero-sub', {
105
+ opacity: 1,
106
+ y: 0,
107
+ duration: 1,
108
+ ease: 'expo.out',
109
+ }, '-=0.6')
110
+ .from('.badge, .scroll-indicator, #btn-scroll-down', {
111
+ opacity: 0,
112
+ y: 15,
113
+ duration: 0.8,
114
+ stagger: 0.1,
115
+ ease: 'expo.out',
116
+ }, '-=0.9');
117
+
118
+ gsap.set('#hero-sub', { y: 30 });
119
+
120
+ // ─────────────────────────────────────────────────────────────────────────────
121
+ // 5. MARQUEE β€” gsap infinite loop
122
+ // ─────────────────────────────────────────────────────────────────────────────
123
+ const marqueeTrack = document.getElementById('marquee-track');
124
+ if (marqueeTrack) {
125
+ gsap.to(marqueeTrack, {
126
+ x: '-=33.33%',
127
+ ease: 'none',
128
+ duration: 16,
129
+ repeat: -1,
130
+ modifiers: {
131
+ x: gsap.utils.unitize(x => parseFloat(x) % (marqueeTrack.scrollWidth / 3)),
132
+ },
133
+ });
134
+ }
135
+
136
+ // ─────────────────────────────────────────────────────────────────────────────
137
+ // 6. HORIZONTAL SCROLL β€” GSAP ScrollTrigger pinned section
138
+ // ─────────────────────────────────────────────────────────────────────────────
139
+ const studioTrack = document.getElementById('studio-track');
140
+ const panels = gsap.utils.toArray('.studio-panel');
141
+
142
+ if (studioTrack && panels.length) {
143
+ // Calculate total horizontal scroll distance
144
+ const getTotalWidth = () => {
145
+ return -(studioTrack.scrollWidth - window.innerWidth);
146
+ };
147
+
148
+ const horizontalTween = gsap.to(studioTrack, {
149
+ x: getTotalWidth,
150
+ ease: 'none',
151
+ });
152
+
153
+ ScrollTrigger.create({
154
+ trigger: '#studio',
155
+ start: 'top top',
156
+ end: () => `+=${studioTrack.scrollWidth - window.innerWidth}`,
157
+ pin: '#studio-pin-wrapper',
158
+ animation: horizontalTween,
159
+ scrub: 1.2,
160
+ invalidateOnRefresh: true,
161
+ onUpdate: self => {
162
+ // Parallax the background text slightly
163
+ const bgText = document.querySelector('.hero-bg-text');
164
+ if (bgText) {
165
+ gsap.set(bgText, { x: -self.progress * 80 });
166
+ }
167
+ }
168
+ });
169
+
170
+ // Panel title reveals as they scroll into horizontal view
171
+ panels.forEach((panel, i) => {
172
+ const lineInners = panel.querySelectorAll('.line-inner');
173
+ gsap.set(lineInners, { y: '110%' });
174
+
175
+ ScrollTrigger.create({
176
+ trigger: panel,
177
+ containerAnimation: horizontalTween,
178
+ start: 'left 80%',
179
+ onEnter: () => {
180
+ gsap.to(lineInners, {
181
+ y: 0,
182
+ duration: 1,
183
+ stagger: 0.1,
184
+ ease: 'expo.out',
185
+ });
186
+ },
187
+ });
188
+ });
189
+ }
190
+
191
+ // ─────────────────────────────────────────────────────────────────────────────
192
+ // 7. FOOTER TITLE REVEAL
193
+ // ─────────────────────────────────────────────────────────────────────────────
194
+ const footerTitle = document.getElementById('footer-title');
195
+ if (footerTitle) {
196
+ const splitFooter = new SplitType(footerTitle, { types: 'chars' });
197
+ gsap.set(splitFooter.chars, { yPercent: 120, opacity: 0 });
198
+ ScrollTrigger.create({
199
+ trigger: footerTitle,
200
+ start: 'top 85%',
201
+ onEnter: () => {
202
+ gsap.to(splitFooter.chars, {
203
+ yPercent: 0,
204
+ opacity: 1,
205
+ duration: 0.9,
206
+ stagger: 0.018,
207
+ ease: 'back.out(1.8)',
208
+ });
209
+ },
210
+ });
211
+ }
212
+
213
+ // ────────────────────────────────────────────���────────────────────────────────
214
+ // 8. SCROLL BUTTON β€” smooth scroll to studio
215
+ // ─────────────────────────────────────────────────────────────────────────────
216
+ const btnScrollDown = document.getElementById('btn-scroll-down');
217
+ if (btnScrollDown) {
218
+ btnScrollDown.addEventListener('click', () => {
219
+ lenis.scrollTo('#studio', { duration: 1.8, easing: t => Math.min(1, 1.001 - Math.pow(2, -10 * t)) });
220
+ });
221
+ }
222
+
223
+ // ─────────────────────────────────────────────────────────────────────────────
224
+ // 9. TOKEN COUNTER & ENGINE SELECTOR
225
+ // ─────────────────────────────────────────────────────────────────────────────
226
+ const sourceInput = document.getElementById('source-input');
227
+ const tokenCounter = document.getElementById('token-counter');
228
+ const engineSelect = document.getElementById('engine-select');
229
+ const presetRow = document.getElementById('preset-row');
230
+
231
+ const LIMITS = { bart: Infinity, gemini: 1_000_000 };
232
+ let MAX_TOKENS = LIMITS.bart;
233
+
234
+ function formatLimit(n) {
235
+ if (n === Infinity) return '∞';
236
+ return n >= 1000 ? n.toLocaleString() : n.toString();
237
+ }
238
+
239
+ function updateCounter() {
240
+ const text = sourceInput ? sourceInput.value.trim() : '';
241
+ const words = text ? text.split(/\s+/).filter(Boolean).length : 0;
242
+ const est = Math.floor(words * 1.3);
243
+ if (tokenCounter) {
244
+ const isUnlimited = MAX_TOKENS === Infinity;
245
+ if (isUnlimited) {
246
+ // BART chunked β€” show count, no warning possible
247
+ const chunks = Math.max(1, Math.ceil(est / 900));
248
+ tokenCounter.textContent = est === 0
249
+ ? '000 TOKENS'
250
+ : chunks > 1
251
+ ? `${est.toLocaleString()} TOKENS Β· ${chunks} CHUNKS`
252
+ : `${String(est).padStart(3, '0')} TOKENS`;
253
+ tokenCounter.classList.remove('warn');
254
+ } else {
255
+ tokenCounter.textContent = est > MAX_TOKENS
256
+ ? `⚠ ${est.toLocaleString()} / ${formatLimit(MAX_TOKENS)} β€” WILL TRUNCATE`
257
+ : `${String(est).padStart(3, '0')} / ${formatLimit(MAX_TOKENS)} TOKENS`;
258
+ tokenCounter.classList.toggle('warn', est > MAX_TOKENS);
259
+ }
260
+ }
261
+ }
262
+
263
+ if (engineSelect) {
264
+ engineSelect.addEventListener('change', () => {
265
+ const isGemini = engineSelect.value === 'gemini';
266
+ MAX_TOKENS = isGemini ? LIMITS.gemini : LIMITS.bart;
267
+ if (presetRow) presetRow.style.display = isGemini ? 'none' : '';
268
+ updateCounter();
269
+ });
270
+ }
271
+
272
+ if (sourceInput) sourceInput.addEventListener('input', updateCounter);
273
+
274
+ // ─────────────────────────────────────────────────────────────────────────────
275
+ // 10. SUMMARIZATION API CALL
276
+ // ─────────────────────────────────────────────────────────────────────────────
277
+ const outputDisplay = document.getElementById('output-display');
278
+ const btnSummarize = document.getElementById('btn-summarize');
279
+ const btnClear = document.getElementById('btn-clear');
280
+ const btnPolish = document.getElementById('btn-polish');
281
+ const loadingOverlay = document.getElementById('loading-overlay');
282
+ const presetSelect = document.getElementById('preset-select');
283
+
284
+ // Polish toggle state
285
+ let polishEnabled = false;
286
+ if (btnPolish) {
287
+ btnPolish.addEventListener('click', () => {
288
+ polishEnabled = !polishEnabled;
289
+ btnPolish.textContent = polishEnabled ? '✦ GEMINI POLISH ON' : '✦ GEMINI POLISH OFF';
290
+ btnPolish.style.borderColor = polishEnabled ? 'var(--lime)' : '';
291
+ btnPolish.style.color = polishEnabled ? 'var(--lime)' : '';
292
+ });
293
+ }
294
+
295
+ const PRESETS = {
296
+ quick: { max_new_tokens: 64, min_new_tokens: 15, num_beams: 4, length_penalty: 0.6 },
297
+ notes: { max_new_tokens: 100, min_new_tokens: 25, num_beams: 5, length_penalty: 0.8 },
298
+ deep: { max_new_tokens: 160, min_new_tokens: 40, num_beams: 6, length_penalty: 1.0 },
299
+ };
300
+
301
+ if (btnClear) {
302
+ btnClear.addEventListener('click', () => {
303
+ if (sourceInput) sourceInput.value = '';
304
+ if (outputDisplay) outputDisplay.value = '';
305
+ if (tokenCounter) { tokenCounter.textContent = '000 TOKENS'; tokenCounter.classList.remove('warn'); }
306
+ });
307
+ }
308
+
309
+ if (btnSummarize) {
310
+ btnSummarize.addEventListener('click', async () => {
311
+ const text = sourceInput ? sourceInput.value.trim() : '';
312
+ if (!text) { alert('PLEASE PROVIDE A SOURCE DOCUMENT.'); return; }
313
+
314
+ const engine = engineSelect ? engineSelect.value : 'bart';
315
+ const preset = PRESETS[presetSelect ? presetSelect.value : 'notes'];
316
+ if (loadingOverlay) loadingOverlay.classList.add('active');
317
+
318
+ try {
319
+ const body = engine === 'gemini'
320
+ ? { text, engine: 'gemini', gemini_model: 'gemini-3.0-flash' }
321
+ : {
322
+ text,
323
+ engine: 'bart',
324
+ max_new_tokens: preset.max_new_tokens,
325
+ min_new_tokens: preset.min_new_tokens,
326
+ num_beams: preset.num_beams,
327
+ length_penalty: preset.length_penalty,
328
+ polish: polishEnabled,
329
+ gemini_model: 'gemini-3.0-flash',
330
+ };
331
+
332
+ const res = await fetch('/api/summarize', {
333
+ method: 'POST',
334
+ headers: { 'Content-Type': 'application/json' },
335
+ body: JSON.stringify(body),
336
+ });
337
+
338
+ if (!res.ok) {
339
+ const err = await res.json();
340
+ throw new Error(err.detail || 'API error');
341
+ }
342
+
343
+ const data = await res.json();
344
+ if (outputDisplay) outputDisplay.value = data.summary;
345
+
346
+ if (data.engine_used && data.engine_used.includes('polish failed')) {
347
+ alert('Gemini Polish failed (likely due to API rate limits or quota). Displaying raw BART summary instead.');
348
+ }
349
+
350
+ } catch (err) {
351
+ console.error(err);
352
+ alert('INFERENCE FAILURE: ' + err.message);
353
+ } finally {
354
+ if (loadingOverlay) loadingOverlay.classList.remove('active');
355
+ }
356
+ });
357
+ }
358
+
359
+ // ─────────────────────────────────────────────────────────────────────────────
360
+ // 11. NAV HIDE ON SCROLL
361
+ // ─────────────────────────────────────────────────────────────────────────────
362
+ const nav = document.getElementById('nav');
363
+ let lastY = 0;
364
+ lenis.on('scroll', ({ scroll }) => {
365
+ if (!nav) return;
366
+ nav.style.transform = scroll > lastY && scroll > 80
367
+ ? 'translateY(-100%)'
368
+ : 'translateY(0)';
369
+ lastY = scroll;
370
+ nav.style.transition = 'transform 0.5s cubic-bezier(0.19,1,0.22,1)';
371
+ });