granite-mem 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,540 +1,17 @@
1
- /**
2
- * Granite — App Controller
3
- */
4
- (function () {
5
- // ── State ──
6
- let allNotes = [];
7
- let currentNote = null;
8
- let currentType = '';
9
- let currentView = 'note';
10
- let graphData = null;
11
- let previewCache = {};
12
- let previewTimeout = null;
13
- let commandIndex = -1;
14
- let commandResults = [];
15
- let releaseTrap = null;
16
-
17
- // ── DOM ──
18
- const noteList = document.getElementById('note-list');
19
- const typeFilters = document.getElementById('type-filters');
20
- const noteView = document.getElementById('note-view');
21
- const noteScroll = document.getElementById('note-scroll');
22
- const noteTypeLine = document.getElementById('note-type-line');
23
- const noteTitle = document.getElementById('note-title');
24
- const noteDate = document.getElementById('note-date');
25
- const noteTags = document.getElementById('note-tags');
26
- const noteBody = document.getElementById('note-body');
27
- const backlinks = document.getElementById('backlinks');
28
- const backlinksList = document.getElementById('backlinks-list');
29
- const backlinksCount = document.getElementById('backlinks-count');
30
- const emptyState = document.getElementById('empty-state');
31
- const graphView = document.getElementById('graph-view');
32
- const graphCanvas = document.getElementById('graph-canvas');
33
- const previewEl = document.getElementById('wikilink-preview');
34
-
35
- // Command bar
36
- const commandOverlay = document.getElementById('command-bar-overlay');
37
- const commandInput = document.getElementById('command-input');
38
- const commandResultsEl = document.getElementById('command-results');
39
-
40
- // Create
41
- const createOverlay = document.getElementById('create-overlay');
42
-
43
- // ── API ──
44
- async function api(url, opts) {
45
- try {
46
- const res = await fetch(url, opts);
47
- if (!res.ok) return { error: `HTTP ${res.status}` };
48
- return res.json();
49
- } catch (err) {
50
- return { error: err.message };
51
- }
52
- }
53
-
54
- // ── Init ──
55
- async function init() {
56
- const { types } = await api('/api/types');
57
- for (const name of Object.keys(types)) {
58
- const btn = document.createElement('button');
59
- btn.className = 'type-pill';
60
- btn.dataset.type = name;
61
- btn.textContent = name;
62
- btn.addEventListener('click', () => filterByType(name));
63
- typeFilters.appendChild(btn);
64
- }
65
- typeFilters.querySelector('[data-type=""]').addEventListener('click', () => filterByType(''));
66
-
67
- const createType = document.getElementById('create-type');
68
- for (const name of Object.keys(types)) {
69
- const opt = document.createElement('option');
70
- opt.value = name;
71
- opt.textContent = name;
72
- createType.appendChild(opt);
73
- }
74
-
75
- await loadNotes();
76
- setupKeyboard();
77
- setupNav();
78
- setupCreate();
79
- setupCommand();
80
- setupWikilinkPreviews();
81
- initGrain();
82
- }
83
-
84
- // ═══ GRAIN TEXTURE (static, rendered once) ═══
85
- function initGrain() {
86
- const c = document.getElementById('grain');
87
- const ctx = c.getContext('2d');
88
- // Render a small 256x256 noise tile, CSS will repeat it
89
- const size = 256;
90
- c.width = size;
91
- c.height = size;
92
- c.style.width = '100%';
93
- c.style.height = '100%';
94
- c.style.imageRendering = 'auto';
95
- const imageData = ctx.createImageData(size, size);
96
- const data = imageData.data;
97
- for (let i = 0; i < data.length; i += 4) {
98
- const v = Math.random() * 255;
99
- data[i] = v; data[i + 1] = v; data[i + 2] = v; data[i + 3] = 255;
100
- }
101
- ctx.putImageData(imageData, 0, 0);
102
- }
103
-
104
- // ═══ COMMAND BAR ═══
105
- function setupCommand() {
106
- let debounce = null;
107
- commandInput.addEventListener('input', () => {
108
- clearTimeout(debounce);
109
- debounce = setTimeout(async () => {
110
- const q = commandInput.value.trim();
111
- if (q.length === 0) {
112
- // Show all notes
113
- renderCommandResults(allNotes.map(n => ({
114
- slug: n.slug, title: n.title, type: n.type, modified: n.modified,
115
- })));
116
- return;
117
- }
118
- const data = await api(`/api/search?q=${encodeURIComponent(q)}`);
119
- renderCommandResults(data.results.map(r => ({
120
- slug: r.slug, title: r.title, snippet: r.snippet,
121
- })));
122
- }, 120);
123
- });
124
-
125
- commandInput.addEventListener('keydown', (e) => {
126
- if (e.key === 'Escape') { closeCommand(); return; }
127
- if (e.key === 'ArrowDown') { e.preventDefault(); navigateCommand(1); }
128
- if (e.key === 'ArrowUp') { e.preventDefault(); navigateCommand(-1); }
129
- if (e.key === 'Enter') {
130
- e.preventDefault();
131
- const item = commandResults[commandIndex >= 0 ? commandIndex : 0];
132
- if (item) { closeCommand(); loadNote(item.slug); }
133
- }
134
- });
135
-
136
- commandOverlay.addEventListener('click', (e) => {
137
- if (e.target === commandOverlay) closeCommand();
138
- });
139
- }
140
-
141
- function openCommand() {
142
- commandOverlay.classList.remove('hidden');
143
- commandInput.value = '';
144
- commandInput.focus();
145
- commandInput.setAttribute('aria-expanded', 'true');
146
- releaseTrap = trapFocus(document.getElementById('command-bar'));
147
- commandIndex = -1;
148
- // Show all notes initially
149
- renderCommandResults(allNotes.map(n => ({
150
- slug: n.slug, title: n.title, type: n.type, modified: n.modified,
151
- })));
152
- }
153
-
154
- function closeCommand() {
155
- if (releaseTrap) { releaseTrap(); releaseTrap = null; }
156
- commandOverlay.classList.add('hidden');
157
- commandInput.setAttribute('aria-expanded', 'false');
158
- commandInput.removeAttribute('aria-activedescendant');
159
- commandResultsEl.innerHTML = '';
160
- commandResults = [];
161
- }
162
-
163
- function renderCommandResults(results) {
164
- commandResults = results;
165
- commandIndex = -1;
166
- commandResultsEl.innerHTML = '';
167
- for (let i = 0; i < results.length; i++) {
168
- const r = results[i];
169
- const el = document.createElement('div');
170
- el.className = 'command-result';
171
- el.setAttribute('role', 'option');
172
- el.id = `cmd-result-${i}`;
173
- let html = `<div class="command-result-title">${esc(r.title)}</div>`;
174
- if (r.type || r.modified) {
175
- const parts = [];
176
- if (r.type) parts.push(r.type);
177
- if (r.modified) parts.push(formatDate(r.modified));
178
- html += `<div class="command-result-meta">${parts.join(' · ')}</div>`;
179
- }
180
- if (r.snippet) {
181
- html += `<div class="command-result-snippet">${r.snippet.replace(/>>>/g, '<mark>').replace(/<<</g, '</mark>')}</div>`;
182
- }
183
- el.innerHTML = html;
184
- el.addEventListener('click', () => { closeCommand(); loadNote(r.slug); });
185
- commandResultsEl.appendChild(el);
186
- }
187
- }
188
-
189
- function navigateCommand(delta) {
190
- const items = commandResultsEl.querySelectorAll('.command-result');
191
- if (!items.length) return;
192
- items.forEach(el => el.classList.remove('focused'));
193
- commandIndex += delta;
194
- if (commandIndex < 0) commandIndex = items.length - 1;
195
- if (commandIndex >= items.length) commandIndex = 0;
196
- items[commandIndex].classList.add('focused');
197
- items[commandIndex].scrollIntoView({ block: 'nearest' });
198
- commandInput.setAttribute('aria-activedescendant', items[commandIndex].id);
199
- }
200
-
201
- // ═══ KEYBOARD ═══
202
- function setupKeyboard() {
203
- document.addEventListener('keydown', (e) => {
204
- if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
205
- e.preventDefault();
206
- openCommand();
207
- }
208
- if ((e.metaKey || e.ctrlKey) && e.key === 'n') {
209
- e.preventDefault();
210
- openCreate();
211
- }
212
- if ((e.metaKey || e.ctrlKey) && e.key === 'g') {
213
- e.preventDefault();
214
- toggleGraph();
215
- }
216
- if (e.key === 'Escape') {
217
- if (!commandOverlay.classList.contains('hidden')) { closeCommand(); return; }
218
- if (!createOverlay.classList.contains('hidden')) { closeCreate(); return; }
219
- if (currentView === 'graph') { setView('note'); return; }
220
- }
221
- });
222
- }
223
-
224
- // ═══ NAV ═══
225
- const nav = document.getElementById('nav');
226
- const navBackdrop = document.getElementById('nav-backdrop');
227
-
228
- function setupNav() {
229
- document.getElementById('nav-search').addEventListener('click', openCommand);
230
- document.getElementById('nav-new').addEventListener('click', openCreate);
231
- document.getElementById('nav-graph').addEventListener('click', toggleGraph);
232
-
233
- // Mobile
234
- const mobileMenu = document.getElementById('mobile-menu');
235
- const mobileSearch = document.getElementById('mobile-search');
236
- if (mobileMenu) mobileMenu.addEventListener('click', openMobileNav);
237
- if (mobileSearch) mobileSearch.addEventListener('click', openCommand);
238
- if (navBackdrop) navBackdrop.addEventListener('click', closeMobileNav);
239
- }
240
-
241
- function openMobileNav() {
242
- nav.classList.add('open');
243
- navBackdrop.classList.remove('hidden');
244
- requestAnimationFrame(() => navBackdrop.classList.add('visible'));
245
- }
246
-
247
- function closeMobileNav() {
248
- nav.classList.remove('open');
249
- navBackdrop.classList.remove('visible');
250
- setTimeout(() => navBackdrop.classList.add('hidden'), 200);
251
- }
252
-
253
- // ═══ CREATE ═══
254
- function setupCreate() {
255
- document.getElementById('create-close').addEventListener('click', closeCreate);
256
- document.getElementById('create-submit').addEventListener('click', submitCreate);
257
- createOverlay.addEventListener('click', (e) => {
258
- if (e.target === createOverlay) closeCreate();
259
- });
260
- document.getElementById('create-title').addEventListener('keydown', (e) => {
261
- if (e.key === 'Enter' && !e.shiftKey) {
262
- e.preventDefault();
263
- document.getElementById('create-body').focus();
264
- }
265
- });
266
- }
267
-
268
- let releaseCreateTrap = null;
269
- function openCreate() {
270
- createOverlay.classList.remove('hidden');
271
- releaseCreateTrap = trapFocus(document.getElementById('create-dialog'));
272
- setTimeout(() => document.getElementById('create-title').focus(), 50);
273
- }
274
- function closeCreate() {
275
- if (releaseCreateTrap) { releaseCreateTrap(); releaseCreateTrap = null; }
276
- createOverlay.classList.add('hidden');
277
- document.getElementById('create-title').value = '';
278
- document.getElementById('create-body').value = '';
279
- }
280
-
281
- let isSubmitting = false;
282
- async function submitCreate() {
283
- if (isSubmitting) return;
284
- const type = document.getElementById('create-type').value;
285
- const title = document.getElementById('create-title').value.trim();
286
- const body = document.getElementById('create-body').value;
287
- if (!title) { document.getElementById('create-title').focus(); return; }
288
- const btn = document.getElementById('create-submit');
289
- isSubmitting = true;
290
- btn.disabled = true;
291
- btn.textContent = 'Creating...';
292
- const result = await api('/api/notes', {
293
- method: 'POST',
294
- headers: { 'Content-Type': 'application/json' },
295
- body: JSON.stringify({ type, title, body }),
296
- });
297
- isSubmitting = false;
298
- btn.disabled = false;
299
- btn.textContent = 'Create';
300
- if (result.error) return;
301
- if (result.slug) {
302
- closeCreate();
303
- await loadNotes();
304
- loadNote(result.slug);
305
- }
306
- }
307
-
308
- // ═══ GRAPH ═══
309
- function toggleGraph() {
310
- setView(currentView === 'graph' ? 'note' : 'graph');
311
- }
312
-
313
- function setView(view) {
314
- currentView = view;
315
- const graphBtn = document.getElementById('nav-graph');
316
- if (view === 'graph') {
317
- graphBtn.classList.add('active');
318
- noteView.style.display = 'none';
319
- emptyState.style.display = 'none';
320
- graphView.classList.remove('hidden');
321
- loadGraph();
322
- } else {
323
- graphBtn.classList.remove('active');
324
- graphView.classList.add('hidden');
325
- GraphEngine.stop();
326
- if (currentNote) {
327
- noteView.style.display = 'flex';
328
- emptyState.style.display = 'none';
329
- } else {
330
- noteView.style.display = 'none';
331
- emptyState.style.display = 'flex';
332
- }
333
- }
334
- }
335
-
336
- async function loadGraph() {
337
- if (!graphData) graphData = await api('/api/graph');
338
- GraphEngine.init(graphCanvas, graphData, {
339
- activeSlug: currentNote?.slug || null,
340
- onNavigate: (slug) => { loadNote(slug); setView('note'); },
341
- });
342
- }
343
-
344
- // ═══ WIKILINK PREVIEWS ═══
345
- function setupWikilinkPreviews() {
346
- document.addEventListener('mouseover', (e) => {
347
- const link = e.target.closest('.wikilink');
348
- if (!link) return;
349
- clearTimeout(previewTimeout);
350
- previewTimeout = setTimeout(() => showPreview(link), 300);
351
- });
352
- document.addEventListener('mouseout', (e) => {
353
- const link = e.target.closest('.wikilink');
354
- if (link || e.relatedTarget?.closest?.('#wikilink-preview')) return;
355
- clearTimeout(previewTimeout);
356
- hidePreview();
357
- });
358
- previewEl.addEventListener('mouseleave', hidePreview);
359
- document.addEventListener('click', (e) => {
360
- const link = e.target.closest('.wikilink');
361
- if (!link) return;
362
- e.preventDefault();
363
- hidePreview();
364
- const slug = link.dataset.slug;
365
- if (slug) loadNote(slug);
366
- });
367
- }
368
-
369
- async function showPreview(linkEl) {
370
- const slug = linkEl.dataset.slug;
371
- if (!slug) return;
372
- let data = previewCache[slug];
373
- if (!data) {
374
- data = await api(`/api/notes/${slug}`);
375
- if (data.error) return;
376
- previewCache[slug] = data;
377
- }
378
- const rect = linkEl.getBoundingClientRect();
379
- let left = rect.left;
380
- let top = rect.bottom + 8;
381
- if (left + 280 > window.innerWidth - 16) left = window.innerWidth - 296;
382
- if (top + 160 > window.innerHeight) {
383
- top = rect.top - 8;
384
- previewEl.style.transform = 'translateY(-100%)';
385
- } else {
386
- previewEl.style.transform = '';
387
- }
388
- previewEl.style.left = left + 'px';
389
- previewEl.style.top = top + 'px';
390
- previewEl.querySelector('.preview-type').textContent = data.type || '';
391
- previewEl.querySelector('.preview-title').textContent = data.title || '';
392
- previewEl.querySelector('.preview-snippet').textContent = (data.body || '').slice(0, 150).replace(/[#*_`>\[\]]/g, '');
393
- const tagsEl = previewEl.querySelector('.preview-tags');
394
- tagsEl.innerHTML = (data.tags || []).map(t => `<span class="preview-tag">${esc(t)}</span>`).join('');
395
- previewEl.classList.add('visible');
396
- }
397
-
398
- function hidePreview() {
399
- clearTimeout(previewTimeout);
400
- previewEl.classList.remove('visible');
401
- }
402
-
403
- // ═══ DATA ═══
404
- async function loadNotes() {
405
- const url = currentType ? `/api/notes?type=${currentType}` : '/api/notes';
406
- const data = await api(url);
407
- allNotes = data.notes;
408
- graphData = null;
409
- renderNoteList(allNotes);
410
- }
411
-
412
- function filterByType(type) {
413
- currentType = type;
414
- typeFilters.querySelectorAll('.type-pill').forEach(btn => {
415
- btn.classList.toggle('active', btn.dataset.type === type);
416
- });
417
- loadNotes();
418
- }
419
-
420
- // ═══ RENDER ═══
421
- function renderNoteList(notes) {
422
- noteList.innerHTML = '';
423
- if (notes.length === 0) {
424
- noteList.innerHTML = '<div style="padding:16px 12px;color:var(--text-3);font-size:12px;">No notes</div>';
425
- return;
426
- }
427
- for (const note of notes) {
428
- const item = document.createElement('div');
429
- item.className = 'note-item' + (currentNote?.slug === note.slug ? ' active' : '');
430
- item.setAttribute('role', 'listitem');
431
- item.setAttribute('tabindex', '0');
432
- item.setAttribute('aria-label', `${note.title}${note.type ? ', type ' + note.type : ''}`);
433
- let meta = '';
434
- if (note.type) meta += `<span class="note-item-type ${note.type}">${note.type}</span>`;
435
- if (note.modified) {
436
- if (note.type) meta += '<span class="note-item-dot"></span>';
437
- meta += `<span>${formatDate(note.modified)}</span>`;
438
- }
439
- item.innerHTML = `
440
- <div class="note-item-title">${esc(note.title)}</div>
441
- <div class="note-item-meta">${meta}</div>
442
- `;
443
- item.addEventListener('click', () => { closeMobileNav(); loadNote(note.slug); });
444
- item.addEventListener('keydown', (e) => {
445
- if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); closeMobileNav(); loadNote(note.slug); }
446
- });
447
- noteList.appendChild(item);
448
- }
449
- }
450
-
451
- async function loadNote(slug) {
452
- const note = await api(`/api/notes/${slug}`);
453
- if (note.error) return;
454
- currentNote = note;
455
-
456
- if (currentView === 'graph') setView('note');
457
-
458
- emptyState.style.display = 'none';
459
- noteView.style.display = 'flex';
460
-
461
- // Re-trigger animation via class toggle
462
- const article = document.getElementById('note-article');
463
- article.classList.remove('animate');
464
- requestAnimationFrame(() => {
465
- requestAnimationFrame(() => article.classList.add('animate'));
466
- });
467
-
468
- noteTypeLine.textContent = note.type;
469
- noteTitle.textContent = note.title;
470
- noteDate.textContent = formatDate(note.created);
471
- noteTags.innerHTML = (note.tags || []).map(t => `<span class="tag">${esc(t)}</span>`).join('');
472
-
473
- noteBody.innerHTML = MarkdownRenderer.render(note.body, note.outgoing_links);
474
-
475
- if (note.backlinks && note.backlinks.length > 0) {
476
- backlinks.style.display = 'block';
477
- backlinksCount.textContent = note.backlinks.length;
478
- backlinksList.innerHTML = '';
479
- for (const bl of note.backlinks) {
480
- const item = document.createElement('div');
481
- item.className = 'backlink-item';
482
- item.setAttribute('role', 'link');
483
- item.setAttribute('tabindex', '0');
484
- item.innerHTML = `
485
- <div class="backlink-title"><span class="backlink-arrow">\u2190</span>${esc(bl.source_title)}</div>
486
- ${bl.context ? `<div class="backlink-context">${esc(bl.context)}</div>` : ''}
487
- `;
488
- item.addEventListener('click', () => loadNote(bl.source_slug));
489
- item.addEventListener('keydown', (e) => {
490
- if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); loadNote(bl.source_slug); }
491
- });
492
- backlinksList.appendChild(item);
493
- }
494
- } else {
495
- backlinks.style.display = 'none';
496
- }
497
-
498
- noteList.querySelectorAll('.note-item').forEach((el, idx) => {
499
- el.classList.toggle('active', allNotes[idx]?.slug === slug);
500
- });
501
-
502
- if (graphData) GraphEngine.setActiveSlug(slug);
503
- noteScroll.scrollTop = 0;
504
- }
505
-
506
- // ═══ FOCUS TRAP ═══
507
- function trapFocus(container) {
508
- const focusable = container.querySelectorAll(
509
- 'input, select, textarea, button:not([disabled]), [tabindex]:not([tabindex="-1"])'
510
- );
511
- if (!focusable.length) return null;
512
- const first = focusable[0];
513
- const last = focusable[focusable.length - 1];
514
- function handler(e) {
515
- if (e.key !== 'Tab') return;
516
- if (e.shiftKey) {
517
- if (document.activeElement === first) { e.preventDefault(); last.focus(); }
518
- } else {
519
- if (document.activeElement === last) { e.preventDefault(); first.focus(); }
520
- }
521
- }
522
- container.addEventListener('keydown', handler);
523
- return () => container.removeEventListener('keydown', handler);
524
- }
525
-
526
- // ═══ HELPERS ═══
527
- function esc(str) {
528
- const d = document.createElement('div');
529
- d.textContent = str;
530
- return d.innerHTML;
531
- }
532
-
533
- function formatDate(iso) {
534
- if (!iso) return '';
535
- const d = new Date(iso);
536
- return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
537
- }
538
-
539
- init();
540
- })();
1
+ "use strict";(()=>{(function(){"use strict";const K=[{hex:"#ba6844",soft:"rgba(186,104,68,0.16)"},{hex:"#2e6b5b",soft:"rgba(46,107,91,0.18)"},{hex:"#735bb0",soft:"rgba(113,88,170,0.18)"},{hex:"#987b43",soft:"rgba(152,123,67,0.18)"}],zt={hex:"rgba(255,236,220,0.6)",soft:"rgba(255,236,220,0.12)"};let s=null,A=null,kt=()=>{},T=[],_=[],D={},B={},xt=[],O={},F="all",x=null,bt=null,z=[];const lt=new Map;let dt=null,ut=0,W=-1;const o={tx:0,ty:0,k:1},h=t=>document.getElementById(t),E=h("stage"),Xt=h("graph-canvas"),Yt=h("graph-labels"),ft=h("type-filters"),ht=h("dirA-sub"),jt=h("stat-notes"),qt=h("stat-edges"),At=h("stat-communities"),Vt=h("stat-density"),X=h("zoom-val"),Lt=h("empty-hint"),U=h("reader"),G=h("reader-body"),it=h("reader-breadcrumb"),Pt=h("reader-kicker"),Kt=h("reader-title"),Ut=h("reader-deck"),Gt=h("reader-metaline"),ot=h("reader-article"),Mt=h("reader-links"),Et=h("reader-progress"),Jt=h("reader-resizer"),Y=h("wikilink-preview"),j=h("command-bar-overlay"),q=h("command-input"),V=h("command-results");function b(t){return String(t??"").replace(/[&<>"']/g,e=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[e])}function Zt(t){if(!t)return"";const e=new Date(t);return isNaN(e.getTime())?"":`${["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][e.getMonth()]} ${e.getDate()}, ${e.getFullYear()}`}function pt(t){return((t||"").trim().match(/\S+/g)||[]).length}function $t(t){return Math.max(1,Math.ceil(pt(t)/220))}function Ct(t){if(!t)return"";const e=t.split(`
2
+ `);for(const i of e){const r=i.trim();if(!r||r.startsWith("#")||r.startsWith(">")||r.startsWith("```")||r.startsWith("|"))continue;const a=r.replace(/`([^`]+)`/g,"$1").replace(/\*\*([^*]+)\*\*/g,"$1").replace(/\[\[([^\]|]+)(\|([^\]]+))?\]\]/g,(c,n,l,u)=>u||n).replace(/\[([^\]]+)\]\([^)]+\)/g,"$1");if(!(a.length<12))return a.length>160?a.slice(0,157).trimEnd()+"\u2026":a}return""}function J(t){return O[t]||K[0]}async function Tt(){try{const[t,e]=await Promise.all([fetch("/api/graph").then(n=>n.json()),fetch("/api/types").then(n=>n.json())]);T=t.nodes||[];const i=t.edges||[],r=e.types||[];xt=Array.isArray(r)?r.map(n=>typeof n=="string"?n:n.name||n.type):Object.keys(r),O={},xt.forEach((n,l)=>{O[n]=K[l%K.length]});for(const n of T)O[n.type]||(O[n.type]=K[Object.keys(O).length%K.length]);const a=new Set(T.map(n=>n.slug)),c=new Set;for(const n of i){if(!n.source||!n.target||n.source===n.target||!a.has(n.source)||!a.has(n.target))continue;const l=n.source<n.target?n.source+"|"+n.target:n.target+"|"+n.source;c.add(l)}_=[...c].map(n=>{const[l,u]=n.split("|");return{source:l,target:u}}),D=Object.fromEntries(T.map(n=>[n.slug,n])),B={};for(const n of T)B[n.slug]=new Set;for(const n of _)B[n.source].add(n.target),B[n.target].add(n.source);Qt(),Dt(),ne(),fe(),T.length||(ht.textContent="Your vault is empty. Create a note with `granite new` to light up the sky.")}catch(t){console.error("[granite] boot failed",t),ht.textContent="Could not load the vault. Is the API running?"}}function Qt(){const t=[{k:"all",label:"All",c:zt.hex}];for(const e of Object.keys(O))t.push({k:e,label:e,c:O[e].hex});ft.innerHTML="";for(const e of t){const i=document.createElement("button");i.type="button",i.className="dirA-filter"+(F===e.k?" active":""),i.style.setProperty("--tc",e.c),i.dataset.k=e.k,i.innerHTML=`<span class="dot" style="--tc:${e.c}"></span>${b(e.label)}`,i.addEventListener("click",()=>te(e.k)),ft.appendChild(i)}}function te(t){F=t;for(const e of ft.querySelectorAll(".dirA-filter"))e.classList.toggle("active",e.dataset.k===t);ee(),mt="",kt()}function ee(){if(!s)return;if(F==="all"){for(let e=0;e<s.n;e++)s.alpha[e]=1;return}const t=x?s.idxOf[x]:-1;for(let e=0;e<s.n;e++){const i=s.slugOf[e],r=D[i];r&&(r.type===F||e===t?s.alpha[e]=1:s.alpha[e]=.12)}}function Dt(){jt.textContent=T.length,qt.textContent=_.length,Vt.textContent=T.length?(_.length/T.length).toFixed(1):"0.0",s&&(At.textContent=s.K),ht.textContent=`${T.length} notes \xB7 ${_.length} edges${s?" \xB7 "+s.K+" communities":""}, laid out in real time. Drag any star, hover to part the crowd, scroll to zoom deep.`}function ne(){if(!T.length)return;s=window.GraphEngine.createSimulation(T,_,2400,1800),A=window.GraphEngine.createRenderer(Xt,s),window.__granite={sim:s,renderer:A,view:o,nodes:T,links:_},At.textContent=s.K,Dt(),s.prebake(280),s.freeze(),new ResizeObserver(()=>{A.resize(),x||gt(!1)}).observe(E);function t(c=0){const n=E.getBoundingClientRect();if(n.width===0||n.height===0)return c>30?void 0:requestAnimationFrame(()=>t(c+1));try{A.resize(),gt(!0),se()}catch(l){console.error("[granite] arrival boot failed",l)}}requestAnimationFrame(()=>t(0));let e=0;const i={k:NaN,tx:NaN,ty:NaN,active:null,hovered:-2,revealDone:!1};kt=()=>{i.k=NaN,i.revealDone=!1};function r(){if(s.cooling>0||s.pinnedIdx>=0)return!0;if(!i.revealDone){let c=!0;for(let n=0;n<s.n;n++)if(s.alpha[n]<.999){c=!1;break}if(!c||s.edgeAlpha<.999)return!0;i.revealDone=!0}return o.k!==i.k||o.tx!==i.tx||o.ty!==i.ty||x!==i.active||W!==i.hovered}function a(){if(r()){const c=s.cooling>0||s.pinnedIdx>=0,n=!i.revealDone;(c||n)&&s.step(1/60),A.draw(e),e+=.016,oe(),i.k=o.k,i.tx=o.tx,i.ty=o.ty,i.active=x,i.hovered=W}requestAnimationFrame(a)}requestAnimationFrame(a)}function se(){if(!s||!A)return;for(let p=0;p<s.n;p++)s.alpha[p]=0;s.edgeAlpha=0;const t=performance.now(),e=1400,i=900,r=1800,a=o.k,c=Math.min(1.2,a*1.45),n=E.getBoundingClientRect();let l=1/0,u=-1/0,f=1/0,m=-1/0;for(let p=0;p<s.n;p++){const C=s.x[p],H=s.y[p];C<l&&(l=C),C>u&&(u=C),H<f&&(f=H),H>m&&(m=H)}const v=(l+u)/2,w=(f+m)/2;o.k=c,o.tx=n.width/(2*c)-v,o.ty=n.height/(2*c)-w,A.setView(o);const d=new Float32Array(s.n),g=new Map;for(let p=0;p<s.n;p++){const C=s.community[p];g.has(C)||g.set(C,[]),g.get(C).push(p)}const L=[...g.keys()].sort((p,C)=>g.get(C).length-g.get(p).length),y=e*.6;L.forEach((p,C)=>{const H=g.get(p).sort((I,st)=>s.degree[st]-s.degree[I]),yt=C/L.length*(y*.6);H.forEach((I,st)=>{const wt=st/Math.max(1,H.length-1)*(y*.4);d[I]=yt+wt})});const $=p=>1-Math.pow(1-p,3),M=p=>p<.5?2*p*p:1-Math.pow(-2*p+2,3)/2;function nt(){const p=performance.now()-t,C=520;for(let S=0;S<s.n;S++){const Bt=p-d[S];if(Bt<=0){s.alpha[S]=0;continue}const he=Math.min(1,Bt/C);s.alpha[S]=$(he)}const H=Math.min(1,Math.max(0,(p-(e-i))/i));s.edgeAlpha=$(H);const yt=Math.min(1,p/r),I=c+(a-c)*M(yt),st=n.width/(2*I)-v,wt=n.height/(2*I)-w;if(o.k=I,o.tx=st,o.ty=wt,A.setView(o),X.textContent=Math.round(I*100)+"%",p<Math.max(e,r)+60)requestAnimationFrame(nt);else{for(let S=0;S<s.n;S++)s.alpha[S]=1;s.edgeAlpha=1,o.k=a,o.tx=n.width/(2*a)-v,o.ty=n.height/(2*a)-w,A.setView(o),X.textContent=Math.round(a*100)+"%",s.freeze()}}requestAnimationFrame(nt)}function gt(t=!0){const e=E.getBoundingClientRect();if(e.width===0||e.height===0||!s)return;let i=1/0,r=-1/0,a=1/0,c=-1/0;for(let L=0;L<s.n;L++){const y=s.x[L],$=s.y[L],M=s.radius[L];y-M<i&&(i=y-M),y+M>r&&(r=y+M),$-M<a&&(a=$-M),$+M>c&&(c=$+M)}if(!isFinite(i))return;const n=80,l=Math.max(1,r-i),u=Math.max(1,c-a),f=Math.min((e.width-n*2)/l,(e.height-n*2)/u),m=Math.max(.22,Math.min(1.2,f)),v=(i+r)/2,w=(a+c)/2,d=e.width/(2*m)-v,g=e.height/(2*m)-w;o.tx=d,o.ty=g,o.k=m,A.setView(o),t&&(X.textContent=Math.round(m*100)+"%")}let mt="",ie=0;const Z=new Set,Q=new Map;function oe(){if(!s)return;const t=ie++%4===0,e=E.getBoundingClientRect(),i=o.k,r=s.n,a=tt.hop1;let c;if(t){const l=new Set;if(x!=null){const d=s.idxOf[x];d!=null&&l.add(d);for(const g of a)s.idxOf[g]!=null&&l.add(s.idxOf[g])}W>=0&&l.add(W);const u=i<.3?30:i<.6?14:i<1?7:3;for(let d=0;d<r;d++)s.degree[d]>=u&&l.add(d);for(const d of Z)l.add(d);const f=[];for(const d of l){if(d==null||d<0)continue;const g=s.slugOf[d],L=D[g];if(!L||F!=="all"&&L.type!==F&&d!==s.idxOf[x])continue;const y=(s.x[d]+o.tx)*i,$=(s.y[d]+o.ty)*i;y<-60||y>e.width+60||$<-40||$>e.height+40||f.push({i:d,sx:y,sy:$,r:s.radius[d]*i,title:L.title||g})}const m=d=>{if(d.i===s.idxOf[x])return 1e4;if(d.i===W)return 9e3;if(a.has(s.slugOf[d.i]))return 5e3+s.degree[d.i];const g=Z.has(d.i)?2e3+Math.min(500,(Q.get(d.i)||0)*20):0;return s.degree[d.i]+g};f.sort((d,g)=>m(g)-m(d));const v=[],w=[];for(const d of f){const g=Math.min(140,6.5*d.title.length+10),y={x:d.sx-g/2,y:d.sy+d.r+4,w:g,h:16};let $=!1;for(const M of v)if(y.x<M.x+M.w&&y.x+y.w>M.x&&y.y<M.y+M.h&&y.y+y.h>M.y){$=!0;break}if(!$&&(v.push(y),w.push({...d,box:y}),w.length>80))break}c=w,Z.clear();for(const d of c)Z.add(d.i),Q.set(d.i,(Q.get(d.i)||0)+1);for(const[d]of Q)Z.has(d)||Q.delete(d);Rt=c}else{c=[];for(const l of Rt){const u=l.i,f=s.slugOf[u],m=D[f];if(!m)continue;const v=(s.x[u]+o.tx)*i,w=(s.y[u]+o.ty)*i;if(v<-60||v>e.width+60||w<-40||w>e.height+40)continue;const d=m.title||f,g=Math.min(140,6.5*d.length+10);c.push({i:u,sx:v,sy:w,r:s.radius[u]*i,title:d,box:{x:v-g/2,y:w+s.radius[u]*i+4,w:g,h:16}})}}let n="";for(const l of c){const u=s.slugOf[l.i],f=u===x,m=a.has(u);n+=`<div class="${f?"dirA-lbl active":m?"dirA-lbl hop1":"dirA-lbl"}" style="left:${l.box.x.toFixed(1)}px;top:${l.box.y.toFixed(1)}px;width:${l.box.w.toFixed(0)}px">${b(l.title)}</div>`}n!==mt&&(Yt.innerHTML=n,mt=n)}let Rt=[],tt={hop1:new Set,hop2:new Set};function re(t){const e=new Set(B[t]||[]),i=new Set;for(const r of e)for(const a of B[r]||[])a!==t&&!e.has(a)&&i.add(a);return{hop1:e,hop2:i}}function Ht(){!s||!A||(s.setActive(x),A.setHops(tt))}let k=null;E.addEventListener("mousedown",t=>{if(!s)return;const e=E.getBoundingClientRect(),i=t.clientX-e.left,r=t.clientY-e.top,a=i/o.k-o.tx,c=r/o.k-o.ty,n=s.pickNode(a,c,14/o.k);n>=0?k={kind:"node",idx:n,moved:!1,x0:t.clientX,y0:t.clientY}:k={kind:"pan",x0:t.clientX,y0:t.clientY,tx:o.tx,ty:o.ty}}),window.addEventListener("mousemove",t=>{if(!s)return;const e=E.getBoundingClientRect(),i=t.clientX-e.left,r=t.clientY-e.top;if((i<0||r<0||i>e.width||r>e.height)&&!k)return;const a=i/o.k-o.tx,c=r/o.k-o.ty;if(s.clearHover(),!k){const n=s.pickNode(a,c,14/o.k);W=n,A.setHovered(n),E.style.cursor=n>=0?"pointer":"grab";return}if(k.kind==="pan")o.tx=k.tx+(t.clientX-k.x0)/o.k,o.ty=k.ty+(t.clientY-k.y0)/o.k,A.setView(o),E.classList.add("dragging");else if(k.kind==="node"){const n=t.clientX-k.x0,l=t.clientY-k.y0;!k.moved&&n*n+l*l>9&&(k.moved=!0),k.moved&&(s.pinnedIdx=k.idx,s.pinX=a,s.pinY=c,s.cooling=Math.max(s.cooling,.8),E.classList.add("dragging"))}}),window.addEventListener("mouseup",()=>{if(s){if(k&&k.kind==="node"&&!k.moved){const t=s.slugOf[k.idx];if(t){const e=D[t];(F==="all"||!e||e.type===F)&&N(t)}}s.pinnedIdx=-1,k=null,E.classList.remove("dragging")}}),E.addEventListener("mouseleave",()=>{s&&(s.clearHover(),W=-1,A.setHovered(-1))}),E.addEventListener("wheel",t=>{if(!s)return;t.preventDefault();const e=E.getBoundingClientRect(),i=t.clientX-e.left,r=t.clientY-e.top,a=-t.deltaY*.0015,c=Math.max(.15,Math.min(3,o.k*(1+a))),n=i/o.k-o.tx,l=r/o.k-o.ty;o.k=c,o.tx=i/c-n,o.ty=r/c-l,A.setView(o),X.textContent=Math.round(c*100)+"%"},{passive:!1});function St(t){if(!s)return;const e=E.getBoundingClientRect(),i=e.width/2,r=e.height/2,a=Math.max(.15,Math.min(3,o.k*t)),c=i/o.k-o.tx,n=r/o.k-o.ty;o.k=a,o.tx=i/a-c,o.ty=r/a-n,A.setView(o),X.textContent=Math.round(a*100)+"%"}h("zoom-in").addEventListener("click",()=>St(1.2)),h("zoom-out").addEventListener("click",()=>St(1/1.2)),h("zoom-reset").addEventListener("click",()=>gt(!0));async function N(t){if(!(!t||!D[t])){x=t,tt=re(t),Ht(),z=z[z.length-1]===t?z:[...z,t].slice(-6),s&&(s.cooling=Math.min(1,s.cooling+.15)),at(),Lt.hidden=!0,ae();try{const e=lt.get(t)||await Ot(t);if(x!==t)return;bt=e,ce(e)}catch(e){console.error("[granite] fetch note failed",t,e)}}}async function Ot(t){const e=await fetch("/api/notes/"+encodeURIComponent(t));if(!e.ok)throw new Error("fetch "+t+": "+e.status);const i=await e.json();return lt.set(t,i),i}let et=null;function ae(){if(!s||!x)return;const t=s.idxOf[x];if(t==null||t<0)return;const e=E.getBoundingClientRect();if(e.width===0)return;const i=dt??Math.min(900,window.innerWidth*.62),c=Math.max(280,e.width-i-24)/2,n=e.height/2,l=Math.max(o.k,.55),u=performance.now(),f=520,m=o.tx,v=o.ty,w=o.k;et&&cancelAnimationFrame(et);const d=L=>1-Math.pow(1-L,3);function g(){const L=Math.min(1,(performance.now()-u)/f),y=d(L),$=s.x[t],M=s.y[t],nt=c/l-$,p=n/l-M;o.tx=m+(nt-m)*y,o.ty=v+(p-v)*y,o.k=w+(l-w)*y,A.setView(o),X.textContent=Math.round(o.k*100)+"%",L<1?et=requestAnimationFrame(g):(o.tx=nt,o.ty=p,o.k=l,A.setView(o),et=null)}et=requestAnimationFrame(g)}function Ft(){x=null,bt=null,tt={hop1:new Set,hop2:new Set},Ht(),U.hidden=!0,Lt.hidden=!1,at()}function ce(t){U.hidden=!1;const e=J(t.type),i=pt(t.body),r=$t(t.body),a=Ct(t.body),c=z.map(f=>D[f]).filter(Boolean);it.innerHTML="";const n=document.createElementNS("http://www.w3.org/2000/svg","svg");n.setAttribute("width","12"),n.setAttribute("height","12"),n.setAttribute("viewBox","0 0 24 24"),n.setAttribute("fill","none"),n.setAttribute("stroke","currentColor"),n.setAttribute("stroke-width","1.5"),n.setAttribute("stroke-linecap","round"),n.setAttribute("stroke-linejoin","round"),n.innerHTML='<path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/>',it.appendChild(n),c.forEach((f,m)=>{if(m>0){const w=document.createElement("span");w.className="sep",w.textContent="\u203A",it.appendChild(w)}const v=document.createElement("span");v.className="crumb"+(m===c.length-1?" current":""),v.title=f.title||f.slug,v.textContent=f.title||f.slug,m!==c.length-1&&v.addEventListener("click",()=>N(f.slug)),it.appendChild(v)});const l=[`<span class="dirA-reader-type" style="background:${e.soft};color:${e.hex}">${b(t.type||"note")}</span>`];t.modified&&l.push(`<span>${b(Zt(t.modified))}</span>`),l.push(`<span>\xB7 ${r} min read</span>`),l.push(`<span>\xB7 ${i} words</span>`),Pt.innerHTML=l.join(""),Kt.textContent=t.title||t.slug,Ut.textContent=a;const u=[`<span class="slug">${b(t.slug)}</span>`];for(const f of t.tags||[])u.push(`<span class="tag">#${b(f)}</span>`);Gt.innerHTML=u.join(""),ot.innerHTML=MarkdownRenderer.render(t.body||"",t.outgoing_links||[]),le(t),G.scrollTop=0,ut=0,Et.style.width="0%"}function le(t){const e=[],i=(t.outgoing_links||[]).filter(n=>n.resolved&&n.resolved_slug),r=new Set,a=[];for(const n of i)n.resolved_slug!==t.slug&&(r.has(n.resolved_slug)||(r.add(n.resolved_slug),a.push(n.resolved_slug)));if(a.length){e.push(`<div><div class="dirA-links-section-label">Links out <span class="count">${a.length}</span></div><div class="dirA-links-list">`);for(const n of a){const l=D[n]||{title:n,slug:n,type:"note"},u=J(l.type);e.push(`<button class="dirA-linkcard" data-nav="${b(n)}"><span class="dirA-linkcard-dot" style="background:${u.hex}"></span><div><div class="dirA-linkcard-title">${b(l.title||n)}</div><div class="dirA-linkcard-deck">${b(l.deck||"")}</div></div><span class="dirA-linkcard-arrow">\u2192</span></button>`)}e.push("</div></div>")}const c=t.backlinks||[];if(c.length){const n=new Set,l=[];for(const u of c){const f=u.source_slug||u.slug;if(!f||n.has(f))continue;n.add(f);const m=D[f],v=m?.title||u.source_title||f,w=m?.type||"note";l.push({slug:f,title:v,type:w,context:u.context||""})}e.push(`<div><div class="dirA-links-section-label">Backlinks <span class="count">${l.length}</span></div><div class="dirA-links-list">`);for(const u of l){const f=J(u.type);e.push(`<button class="dirA-linkcard" data-nav="${b(u.slug)}"><span class="dirA-linkcard-dot" style="background:${f.hex}"></span><div><div class="dirA-linkcard-title">${b(u.title)}</div><div class="dirA-linkcard-deck">${b(u.context)}</div></div><span class="dirA-linkcard-arrow">\u2190</span></button>`)}e.push("</div></div>")}Mt.innerHTML=e.join("")}Mt.addEventListener("click",t=>{const e=t.target.closest("[data-nav]");e&&N(e.getAttribute("data-nav"))}),G.addEventListener("scroll",()=>{const t=G.scrollHeight-G.clientHeight;ut=t>0?Math.min(1,G.scrollTop/t):0,Et.style.width=ut*100+"%"}),Jt.addEventListener("mousedown",t=>{t.preventDefault(),U.classList.add("resizing");const e=t.clientX,i=dt??Math.min(900,window.innerWidth*.62);function r(c){const n=e-c.clientX,l=Math.max(480,Math.min(window.innerWidth-80,i+n));dt=l,U.style.setProperty("--reader-w",l+"px")}function a(){U.classList.remove("resizing"),window.removeEventListener("mousemove",r),window.removeEventListener("mouseup",a)}window.addEventListener("mousemove",r),window.addEventListener("mouseup",a)}),h("reader-close").addEventListener("click",Ft),h("reader-prev").addEventListener("click",()=>rt(-1)),h("reader-next").addEventListener("click",()=>rt(1));function rt(t){if(!x){if(!s)return;let a=0;for(let n=1;n<s.n;n++)s.degree[n]>s.degree[a]&&(a=n);const c=s.slugOf[a];c&&N(c);return}const e=[x,...tt.hop1].filter(a=>D[a]);if(e.length<2)return;const i=e.indexOf(x),r=e[((i+t)%e.length+e.length)%e.length];r&&N(r)}ot.addEventListener("click",t=>{const e=t.target.closest("a.wikilink");if(!e)return;t.preventDefault();const i=e.getAttribute("data-slug");i&&N(i)});let vt=0;ot.addEventListener("mouseover",async t=>{const e=t.target.closest("a.wikilink");if(!e)return;const i=e.getAttribute("data-slug");if(!i)return;const r=e.getBoundingClientRect(),a=++vt;try{const c=lt.get(i)||await Ot(i);if(a!==vt||!document.contains(e))return;de(c,r)}catch{}}),ot.addEventListener("mouseout",t=>{t.target.closest("a.wikilink")&&(vt++,at())});function de(t,e){const i=J(t.type),r=Ct(t.body),a=pt(t.body),c=$t(t.body);Y.innerHTML=`
3
+ <div class="wl-preview-header">
4
+ <span class="wl-preview-type" style="background:${i.soft};color:${i.hex}">${b(t.type||"note")}</span>
5
+ <span class="wl-preview-meta">${c}min \xB7 ${a} words</span>
6
+ </div>
7
+ <div class="wl-preview-title">${b(t.title||t.slug)}</div>
8
+ <div class="wl-preview-snippet">${b(r)}</div>
9
+ <div class="wl-preview-footer"><span class="wl-preview-slug">${b(t.slug)}</span></div>
10
+ `,Y.hidden=!1;const n=340;let l=e.bottom+10,u=e.left-20;l+240>window.innerHeight&&(l=e.top-240),u+n+12>window.innerWidth&&(u=window.innerWidth-n-12),u<12&&(u=12),Y.style.top=l+"px",Y.style.left=u+"px"}function at(){Y.hidden=!0,Y.innerHTML=""}h("search-trigger").addEventListener("click",Nt);function Nt(){j.classList.remove("hidden"),q.value="",V.innerHTML='<div class="command-empty">Type to search\u2026</div>',q.focus()}function P(){j.classList.add("hidden"),q.blur()}j.addEventListener("click",t=>{t.target===j&&P()});let It=0,_t=null,R=0,ct=[];q.addEventListener("input",()=>{const t=q.value.trim();if(clearTimeout(_t),!t){V.innerHTML='<div class="command-empty">Type to search\u2026</div>',ct=[],R=0;return}const e=++It;_t=setTimeout(async()=>{try{const r=await(await fetch("/api/search?q="+encodeURIComponent(t))).json();if(e!==It)return;ue(r.results||[])}catch(i){console.error("[granite] search failed",i)}},120)});function ue(t){if(ct=t,R=0,!t.length){V.innerHTML='<div class="command-empty">No matches.</div>';return}V.innerHTML=t.map((e,i)=>{const r=J(e.type);return`<div class="command-result${i===0?" active":""}" data-idx="${i}" data-slug="${b(e.slug)}">
11
+ <span class="command-result-dot" style="background:${r.hex}"></span>
12
+ <div class="command-result-body">
13
+ <div class="command-result-title">${b(e.title||e.slug)}</div>
14
+ <div class="command-result-slug">${b(e.slug)}</div>
15
+ </div>
16
+ <span class="command-result-type" style="background:${r.soft};color:${r.hex}">${b(e.type||"note")}</span>
17
+ </div>`}).join("")}V.addEventListener("click",t=>{const e=t.target.closest(".command-result");if(!e)return;const i=e.getAttribute("data-slug");i&&(P(),N(i))}),q.addEventListener("keydown",t=>{if(t.key==="Escape"){t.preventDefault(),P();return}if(t.key==="ArrowDown"){t.preventDefault(),R=Math.min(ct.length-1,R+1),Wt();return}if(t.key==="ArrowUp"){t.preventDefault(),R=Math.max(0,R-1),Wt();return}if(t.key==="Enter"){t.preventDefault();const e=ct[R];e&&(P(),N(e.slug))}});function Wt(){const t=V.querySelectorAll(".command-result");t.forEach((i,r)=>i.classList.toggle("active",r===R));const e=t[R];e&&e.scrollIntoView({block:"nearest"})}function fe(){window.addEventListener("keydown",t=>{const e=t.target&&t.target.tagName||"",i=e==="INPUT"||e==="TEXTAREA";if((t.metaKey||t.ctrlKey)&&(t.key==="k"||t.key==="K")){t.preventDefault(),j.classList.contains("hidden")?Nt():P();return}if(t.key==="Escape"){if(!j.classList.contains("hidden")){P();return}if(x){Ft();return}return}if(!i){if(t.key==="ArrowLeft"||t.key==="k"){t.preventDefault(),rt(-1);return}if(t.key==="ArrowRight"||t.key==="j"){t.preventDefault(),rt(1);return}}}),window.addEventListener("scroll",at,!0)}document.readyState==="loading"?document.addEventListener("DOMContentLoaded",Tt):Tt()})();})();