@anglefeint/astro-theme 0.1.23 → 0.1.25

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anglefeint/astro-theme",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "type": "module",
5
5
  "description": "Anglefeint core theme package for Astro",
6
6
  "keywords": [
@@ -8,36 +8,42 @@ import AiShell from './shells/AiShell.astro';
8
8
  import blogPostCssUrl from '../styles/blog-post.css?url';
9
9
  import { SITE_AUTHOR } from '@anglefeint/site-config/site';
10
10
  import { THEME } from '@anglefeint/site-config/theme';
11
- import { DEFAULT_LOCALE, type Locale, isLocale, localePath, blogIdToSlugAnyLocale } from '@anglefeint/site-i18n/config';
11
+ import {
12
+ DEFAULT_LOCALE,
13
+ type Locale,
14
+ isLocale,
15
+ localePath,
16
+ blogIdToSlugAnyLocale,
17
+ } from '@anglefeint/site-i18n/config';
12
18
  import { getMessages } from '@anglefeint/site-i18n/messages';
13
19
 
14
20
  type Props = CollectionEntry<'blog'>['data'] & {
15
- locale?: string;
16
- related?: CollectionEntry<'blog'>[];
17
- localeHrefs?: Partial<Record<Locale, string>>;
21
+ locale?: string;
22
+ related?: CollectionEntry<'blog'>[];
23
+ localeHrefs?: Partial<Record<Locale, string>>;
18
24
  };
19
25
 
20
26
  const {
21
- title,
22
- subtitle,
23
- description,
24
- pubDate,
25
- updatedDate,
26
- heroImage,
27
- context,
28
- readMinutes,
29
- aiModel,
30
- aiMode,
31
- aiState,
32
- aiLatencyMs,
33
- aiConfidence,
34
- wordCount,
35
- tokenCount,
36
- author,
37
- tags,
38
- locale = DEFAULT_LOCALE,
39
- related = [],
40
- localeHrefs,
27
+ title,
28
+ subtitle,
29
+ description,
30
+ pubDate,
31
+ updatedDate,
32
+ heroImage,
33
+ context,
34
+ readMinutes,
35
+ aiModel,
36
+ aiMode,
37
+ aiState,
38
+ aiLatencyMs,
39
+ aiConfidence,
40
+ wordCount,
41
+ tokenCount,
42
+ author,
43
+ tags,
44
+ locale = DEFAULT_LOCALE,
45
+ related = [],
46
+ localeHrefs,
41
47
  } = Astro.props;
42
48
  const resolvedLocale: Locale = isLocale(locale) ? locale : DEFAULT_LOCALE;
43
49
  const messages = getMessages(resolvedLocale);
@@ -54,174 +60,241 @@ const enableRedQueen = THEME.EFFECTS.ENABLE_RED_QUEEN;
54
60
  ---
55
61
 
56
62
  <AiShell
57
- locale={resolvedLocale}
58
- title={title}
59
- description={description}
60
- image={heroImage}
61
- pageType="article"
62
- publishedTime={pubDate}
63
- modifiedTime={updatedDate}
64
- author={author ?? SITE_AUTHOR}
65
- tags={tags}
66
- localeHrefs={localeHrefs}
63
+ locale={resolvedLocale}
64
+ title={title}
65
+ description={description}
66
+ image={heroImage}
67
+ pageType="article"
68
+ publishedTime={pubDate}
69
+ modifiedTime={updatedDate}
70
+ author={author ?? SITE_AUTHOR}
71
+ tags={tags}
72
+ localeHrefs={localeHrefs}
67
73
  >
68
- <link slot="head" rel="stylesheet" href={blogPostCssUrl} />
69
- <Fragment slot="body-start">
70
- <div class="ai-bg" aria-hidden="true">
71
- <div class="ai-glow ai-glow-shift"></div>
72
- <div class="ai-haze" aria-hidden="true"></div>
73
- <div class="ai-vignette" aria-hidden="true"></div>
74
- <div class="ai-stripe"></div>
75
- <div class="ai-noise" aria-hidden="true"></div>
76
- <div class="ai-hex-grid" aria-hidden="true"></div>
77
- <div class="ai-thought-particles" aria-hidden="true">
78
- {[...Array(12)].map((_, i) => (
79
- <span class="ai-particle" style={`--x: ${20 + (i * 7) % 60}%; --y: ${10 + (i * 11) % 70}%; --d: ${3 + (i % 4)}s`}></span>
80
- ))}
81
- </div>
82
- <canvas class="ai-network-canvas" aria-hidden="true"></canvas>
83
- </div>
84
- {enableRedQueen && (
85
- <aside class="rq-tv rq-tv-collapsed">
86
- <div class="rq-tv-stage" data-rq-src={themeRedqueen1.src} data-rq-src2={themeRedqueen2.src}></div>
87
- <div class="rq-tv-badge">monitor feed<span class="rq-tv-dot"></span></div>
88
- <button type="button" class="rq-tv-toggle" aria-label="Replay monitor feed" aria-expanded="false">▶</button>
89
- </aside>
90
- )}
91
- <div class="ai-read-progress" aria-hidden="true"></div>
92
- <button type="button" class="ai-back-to-top" aria-label="Back to top" title="Back to top">↑</button>
93
- <div
94
- class="ai-stage-toast"
95
- aria-live="polite"
96
- aria-atomic="true"
97
- data-toast-p10={messages.blog.toastP10}
98
- data-toast-p30={messages.blog.toastP30}
99
- data-toast-p60={messages.blog.toastP60}
100
- data-toast-done={messages.blog.toastDone}
101
- ></div>
102
- <div class="ai-mouse-glow" aria-hidden="true"></div>
103
- <div class="ai-depth-blur" aria-hidden="true"></div>
104
- <div class="ai-thinking-dots" aria-hidden="true"><span></span><span></span><span></span></div>
105
- </Fragment>
106
- <div class="ai-load-scan" aria-hidden="true"></div>
107
- <article class="ai-article">
108
- {heroImage && (
109
- <div class="hero-shell">
110
- <div class="hero-pane">
111
- <div class="hero-image">
112
- <div class="hero-stack">
113
- <div class="hero-base-wrap">
114
- <Image
115
- class="hero-base-image"
116
- src={heroImage}
117
- alt={title}
118
- loading="eager"
119
- decoding="async"
120
- fetchpriority="high"
121
- />
122
- </div>
123
- <div class="hero-canvas-wrap" aria-hidden="true">
124
- <canvas class="hero-canvas" data-hero-src={heroImage.src}></canvas>
125
- </div>
126
- </div>
127
- <div class="hero-frame" aria-hidden="true">
128
- <span>neural monitor</span>
129
- <span class="hero-frame-dot"></span>
130
- </div>
131
- </div>
132
- <div class="hero-threat-bar" aria-hidden="true">
133
- <span>signal sync active</span>
134
- <span>model online</span>
135
- </div>
136
- </div>
137
- </div>
138
- )}
139
- <div class="prose">
140
- <div class="title ai-title">
141
- <div class="ai-meta-terminal">
142
- $ published {fmt(pubDate)}
143
- {updatedDate && <> | updated {fmt(updatedDate)}</>}
144
- {readMinutes !== undefined && <> | ~{readMinutes} min read</>}
145
- </div>
146
- {hasSystemMeta && (
147
- <div class="ai-system-row" aria-label="Model status">
148
- {aiModel && <span class="ai-system-chip">model: {aiModel}</span>}
149
- {aiMode && <span class="ai-system-chip">mode: {aiMode}</span>}
150
- {aiState && <span class="ai-system-chip">state: {aiState}</span>}
151
- </div>
152
- )}
153
- {contextText && (
154
- <div class="ai-prompt-line">
155
- Context: <span class="ai-prompt-topic">{contextText}</span>
156
- </div>
157
- )}
158
- <h1 class="ai-title-text">{title}</h1>
159
- {subtitle && <p class="ai-subtitle-text">{subtitle}</p>}
160
- <hr />
161
- <div class="ai-title-flow" aria-hidden="true"></div>
162
- </div>
163
- <div class="ai-response-wrap">
164
- <div class="ai-response-header">
165
- <div class="ai-response-avatar" aria-hidden="true"></div>
166
- <span class="ai-response-label">Output</span>
167
- {hasResponseMeta && (
168
- <div class="ai-response-meta">
169
- {aiLatencyMs !== undefined && <span>latency est <strong>{aiLatencyMs}</strong> ms</span>}
170
- {confidenceText !== undefined && <span>confidence <strong>{confidenceText}</strong></span>}
171
- </div>
172
- )}
173
- </div>
174
- <div class="ai-prose-body ai-prose-fade">
175
- <slot />
176
- <span class="ai-block-cursor" aria-hidden="true"></span>
177
- </div>
178
- </div>
179
- {hasStats && (
180
- <div class="ai-stats-corner">
181
- {aiModel && <span class="ai-model-id">{aiModel}</span>}
182
- {aiModel && (wordCount !== undefined || tokenCount !== undefined) && ' · '}
183
- {wordCount !== undefined && <span>{compact(wordCount)} words</span>}
184
- {wordCount !== undefined && tokenCount !== undefined && ' · '}
185
- {tokenCount !== undefined && <span>{compact(tokenCount)} tokens</span>}
186
- </div>
187
- )}
188
- <button type="button" class="ai-regenerate" aria-label={messages.blog.regenerate}>{messages.blog.regenerate}</button>
189
- </div>
190
- </article>
191
- {related.length > 0 && (
192
- <section class="ai-related" aria-label="Related posts">
193
- <h2 class="ai-related-title">{messages.blog.related}</h2>
194
- <div class="ai-related-grid">
195
- {related.map((p) => (
196
- <a href={localePath(resolvedLocale, `/blog/${blogIdToSlugAnyLocale(p.id)}`)} class="ai-related-card">
197
- {p.data.heroImage ? (
198
- <div class="ai-related-img">
199
- <Image width={320} height={180} src={p.data.heroImage} alt={p.data.title} />
200
- </div>
201
- ) : (
202
- <div class="ai-related-placeholder">
203
- <span>{p.data.title.charAt(0)}</span>
204
- </div>
205
- )}
206
- <h3 class="ai-related-card-title">{p.data.title}</h3>
207
- <p class="ai-related-card-date">
208
- <FormattedDate date={p.data.pubDate} locale={resolvedLocale} />
209
- </p>
210
- </a>
211
- ))}
212
- </div>
213
- </section>
214
- )}
215
- <nav class="ai-back-to-blog" aria-label="Back to blog">
216
- <a href={localePath(resolvedLocale, '/blog/')}>
217
- <span class="ai-back-prompt">$</span>
218
- <span class="ai-back-text">← {messages.blog.backToBlog}</span>
219
- </a>
220
- </nav>
221
- <Fragment slot="body-end">
222
- <script>
223
- import { initBlogpostEffects } from '../scripts/blogpost-effects.js';
224
- initBlogpostEffects();
225
- </script>
226
- </Fragment>
74
+ <link slot="head" rel="stylesheet" href={blogPostCssUrl} />
75
+ <Fragment slot="body-start">
76
+ <div class="ai-bg" aria-hidden="true">
77
+ <div class="ai-glow ai-glow-shift"></div>
78
+ <div class="ai-haze" aria-hidden="true"></div>
79
+ <div class="ai-vignette" aria-hidden="true"></div>
80
+ <div class="ai-stripe"></div>
81
+ <div class="ai-noise" aria-hidden="true"></div>
82
+ <div class="ai-hex-grid" aria-hidden="true"></div>
83
+ <div class="ai-thought-particles" aria-hidden="true">
84
+ {
85
+ [...Array(12)].map((_, i) => (
86
+ <span
87
+ class="ai-particle"
88
+ style={`--x: ${20 + ((i * 7) % 60)}%; --y: ${10 + ((i * 11) % 70)}%; --d: ${3 + (i % 4)}s`}
89
+ />
90
+ ))
91
+ }
92
+ </div>
93
+ <canvas class="ai-network-canvas" aria-hidden="true"></canvas>
94
+ </div>
95
+ {
96
+ enableRedQueen && (
97
+ <aside class="rq-tv rq-tv-collapsed">
98
+ <div
99
+ class="rq-tv-stage"
100
+ data-rq-src={themeRedqueen1.src}
101
+ data-rq-src2={themeRedqueen2.src}
102
+ />
103
+ <div class="rq-tv-badge">
104
+ monitor feed
105
+ <span class="rq-tv-dot" />
106
+ </div>
107
+ <button
108
+ type="button"
109
+ class="rq-tv-toggle"
110
+ aria-label="Replay monitor feed"
111
+ aria-expanded="false"
112
+ >
113
+
114
+ </button>
115
+ </aside>
116
+ )
117
+ }
118
+ <div class="ai-read-progress" aria-hidden="true"></div>
119
+ <button type="button" class="ai-back-to-top" aria-label="Back to top" title="Back to top"
120
+ >↑</button
121
+ >
122
+ <div
123
+ class="ai-stage-toast"
124
+ aria-live="polite"
125
+ aria-atomic="true"
126
+ data-toast-p10={messages.blog.toastP10}
127
+ data-toast-p30={messages.blog.toastP30}
128
+ data-toast-p60={messages.blog.toastP60}
129
+ data-toast-done={messages.blog.toastDone}
130
+ >
131
+ </div>
132
+ <div class="ai-mouse-glow" aria-hidden="true"></div>
133
+ <div class="ai-depth-blur" aria-hidden="true"></div>
134
+ <div class="ai-thinking-dots" aria-hidden="true"><span></span><span></span><span></span></div>
135
+ </Fragment>
136
+ <div class="ai-load-scan" aria-hidden="true"></div>
137
+ <article class="ai-article">
138
+ {
139
+ heroImage && (
140
+ <div class="hero-shell">
141
+ <div class="hero-pane">
142
+ <div class="hero-image">
143
+ <div class="hero-stack">
144
+ <div class="hero-base-wrap">
145
+ <Image
146
+ class="hero-base-image"
147
+ src={heroImage}
148
+ alt={title}
149
+ loading="eager"
150
+ decoding="async"
151
+ fetchpriority="high"
152
+ />
153
+ </div>
154
+ <div class="hero-canvas-wrap" aria-hidden="true">
155
+ <canvas class="hero-canvas" data-hero-src={heroImage.src} />
156
+ </div>
157
+ </div>
158
+ <div class="hero-frame" aria-hidden="true">
159
+ <span>neural monitor</span>
160
+ <span class="hero-frame-dot" />
161
+ </div>
162
+ </div>
163
+ <div class="hero-threat-bar" aria-hidden="true">
164
+ <span>signal sync active</span>
165
+ <span>model online</span>
166
+ </div>
167
+ </div>
168
+ </div>
169
+ )
170
+ }
171
+ <div class="prose">
172
+ <div class="title ai-title">
173
+ <div class="ai-meta-terminal">
174
+ $ published {fmt(pubDate)}
175
+ {updatedDate && <> | updated {fmt(updatedDate)}</>}
176
+ {readMinutes !== undefined && <> | ~{readMinutes} min read</>}
177
+ </div>
178
+ {
179
+ hasSystemMeta && (
180
+ <div class="ai-system-row" aria-label="Model status">
181
+ {aiModel && <span class="ai-system-chip">model: {aiModel}</span>}
182
+ {aiMode && <span class="ai-system-chip">mode: {aiMode}</span>}
183
+ {aiState && <span class="ai-system-chip">state: {aiState}</span>}
184
+ </div>
185
+ )
186
+ }
187
+ {
188
+ contextText && (
189
+ <div class="ai-prompt-line">
190
+ Context: <span class="ai-prompt-topic">{contextText}</span>
191
+ </div>
192
+ )
193
+ }
194
+ <h1 class="ai-title-text">{title}</h1>
195
+ {subtitle && <p class="ai-subtitle-text">{subtitle}</p>}
196
+ <hr />
197
+ <div class="ai-title-flow" aria-hidden="true"></div>
198
+ </div>
199
+ <div class="ai-response-wrap">
200
+ <div class="ai-response-header">
201
+ <div class="ai-response-avatar" aria-hidden="true"></div>
202
+ <span class="ai-response-label">Output</span>
203
+ {
204
+ hasResponseMeta && (
205
+ <div class="ai-response-meta">
206
+ {aiLatencyMs !== undefined && (
207
+ <span>
208
+ latency est <strong>{aiLatencyMs}</strong> ms
209
+ </span>
210
+ )}
211
+ {confidenceText !== undefined && (
212
+ <span>
213
+ confidence <strong>{confidenceText}</strong>
214
+ </span>
215
+ )}
216
+ </div>
217
+ )
218
+ }
219
+ </div>
220
+ <div class="ai-prose-body ai-prose-fade">
221
+ <slot />
222
+ <span class="ai-block-cursor" aria-hidden="true"></span>
223
+ </div>
224
+ </div>
225
+ {
226
+ hasStats && (
227
+ <div class="ai-stats-corner">
228
+ {aiModel && <span class="ai-model-id">{aiModel}</span>}
229
+ {aiModel && (wordCount !== undefined || tokenCount !== undefined) && ' · '}
230
+ {wordCount !== undefined && <span>{compact(wordCount)} words</span>}
231
+ {wordCount !== undefined && tokenCount !== undefined && ' · '}
232
+ {tokenCount !== undefined && <span>{compact(tokenCount)} tokens</span>}
233
+ </div>
234
+ )
235
+ }
236
+ <button type="button" class="ai-regenerate" aria-label={messages.blog.regenerate}
237
+ >{messages.blog.regenerate}</button
238
+ >
239
+ </div>
240
+ </article>
241
+ {
242
+ related.length > 0 && (
243
+ <section class="ai-related" aria-label="Related posts">
244
+ <h2 class="ai-related-title">{messages.blog.related}</h2>
245
+ <div class="ai-related-grid">
246
+ {related.map((p) => (
247
+ <a
248
+ href={localePath(resolvedLocale, `/blog/${blogIdToSlugAnyLocale(p.id)}`)}
249
+ class="ai-related-card"
250
+ >
251
+ {p.data.heroImage ? (
252
+ <div class="ai-related-img">
253
+ <Image width={320} height={180} src={p.data.heroImage} alt={p.data.title} />
254
+ </div>
255
+ ) : (
256
+ <div class="ai-related-placeholder">
257
+ <span>{p.data.title.charAt(0)}</span>
258
+ </div>
259
+ )}
260
+ <h3 class="ai-related-card-title">{p.data.title}</h3>
261
+ <p class="ai-related-card-date">
262
+ <FormattedDate date={p.data.pubDate} locale={resolvedLocale} />
263
+ </p>
264
+ </a>
265
+ ))}
266
+ </div>
267
+ </section>
268
+ )
269
+ }
270
+ <nav class="ai-back-to-blog" aria-label="Back to blog">
271
+ <a href={localePath(resolvedLocale, '/blog/')}>
272
+ <span class="ai-back-prompt">$</span>
273
+ <span class="ai-back-text">← {messages.blog.backToBlog}</span>
274
+ </a>
275
+ </nav>
276
+ <section class="prose ai-comments" aria-label="Comments">
277
+ <script
278
+ src="https://giscus.app/client.js"
279
+ data-repo="anglefeint/astro-theme-anglefeint"
280
+ data-repo-id="R_kgDORRyzkg"
281
+ data-category="Comments"
282
+ data-category-id="DIC_kwDORRyzks4C3pRw"
283
+ data-mapping="pathname"
284
+ data-strict="0"
285
+ data-reactions-enabled="1"
286
+ data-emit-metadata="0"
287
+ data-input-position="bottom"
288
+ data-theme="dark"
289
+ data-lang="en"
290
+ data-loading="lazy"
291
+ crossorigin="anonymous"
292
+ async></script>
293
+ </section>
294
+ <Fragment slot="body-end">
295
+ <script>
296
+ import { initBlogpostEffects } from '../scripts/blogpost-effects.js';
297
+ initBlogpostEffects();
298
+ </script>
299
+ </Fragment>
227
300
  </AiShell>
@@ -0,0 +1,73 @@
1
+ function randHex() {
2
+ const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
3
+ return chars[Math.floor(Math.random() * chars.length)];
4
+ }
5
+
6
+ function randKeyLine(pairs) {
7
+ const chunks = [];
8
+ for (let i = 0; i < pairs; i += 1) chunks.push(randHex() + randHex());
9
+ return chunks.join(' ');
10
+ }
11
+
12
+ function randPass() {
13
+ let pass = '';
14
+ for (let i = 0; i < 6; i += 1) pass += randHex().toLowerCase();
15
+ pass += '@' + randHex() + randHex() + randHex() + randHex() + randHex();
16
+ return pass;
17
+ }
18
+
19
+ function setDecryptorFields(keysLabel, keys = 0, sec = 1) {
20
+ let el = document.getElementById('dec-keys');
21
+ if (el) el.textContent = `[00:00:${String(sec).padStart(2, '0')}] ${keys} ${keysLabel}`;
22
+
23
+ el = document.getElementById('dec-pass');
24
+ if (el) el.textContent = randPass();
25
+
26
+ el = document.getElementById('dec-master1');
27
+ if (el) el.textContent = randKeyLine(8);
28
+ el = document.getElementById('dec-master2');
29
+ if (el) el.textContent = randKeyLine(8);
30
+
31
+ el = document.getElementById('dec-trans1');
32
+ if (el) el.textContent = randKeyLine(7);
33
+ el = document.getElementById('dec-trans2');
34
+ if (el) el.textContent = randKeyLine(7);
35
+ el = document.getElementById('dec-trans3');
36
+ if (el) el.textContent = randKeyLine(8);
37
+ el = document.getElementById('dec-trans4');
38
+ if (el) el.textContent = randKeyLine(8);
39
+ }
40
+
41
+ export function createDecryptorController(modalOverlay, prefersReducedMotion, keysLabel) {
42
+ let decryptorInterval = null;
43
+
44
+ const stop = () => {
45
+ if (!decryptorInterval) return;
46
+ clearInterval(decryptorInterval);
47
+ decryptorInterval = null;
48
+ };
49
+
50
+ const prime = () => {
51
+ setDecryptorFields(keysLabel, 0, 1);
52
+ };
53
+
54
+ const start = () => {
55
+ if (prefersReducedMotion) return;
56
+ stop();
57
+
58
+ let keys = 0;
59
+ let sec = 1;
60
+ decryptorInterval = setInterval(() => {
61
+ if (!modalOverlay.classList.contains('open')) {
62
+ stop();
63
+ return;
64
+ }
65
+
66
+ keys += 1 + Math.floor(Math.random() * 3);
67
+ sec = Math.min(59, Math.floor(keys / 15) + 1);
68
+ setDecryptorFields(keysLabel, keys, sec);
69
+ }, 180);
70
+ };
71
+
72
+ return { prime, start, stop };
73
+ }
@@ -0,0 +1,230 @@
1
+ const NAV_KEYS = new Set([
2
+ 'Shift',
3
+ 'Ctrl',
4
+ 'Alt',
5
+ 'CapsLock',
6
+ 'Tab',
7
+ 'Enter',
8
+ 'Backspace',
9
+ 'Space',
10
+ 'Ins',
11
+ 'Home',
12
+ 'PgUp',
13
+ 'Del',
14
+ 'End',
15
+ 'PgDn',
16
+ 'Purge',
17
+ '↑',
18
+ '↓',
19
+ '←',
20
+ '→',
21
+ ]);
22
+
23
+ const CODE_MAP = {
24
+ '`': 'Backquote',
25
+ 1: 'Digit1',
26
+ 2: 'Digit2',
27
+ 3: 'Digit3',
28
+ 4: 'Digit4',
29
+ 5: 'Digit5',
30
+ 6: 'Digit6',
31
+ 7: 'Digit7',
32
+ 8: 'Digit8',
33
+ 9: 'Digit9',
34
+ 0: 'Digit0',
35
+ '-': 'Minus',
36
+ '=': 'Equal',
37
+ Backspace: 'Backspace',
38
+ Tab: 'Tab',
39
+ Q: 'KeyQ',
40
+ W: 'KeyW',
41
+ E: 'KeyE',
42
+ R: 'KeyR',
43
+ T: 'KeyT',
44
+ Y: 'KeyY',
45
+ U: 'KeyU',
46
+ I: 'KeyI',
47
+ O: 'KeyO',
48
+ P: 'KeyP',
49
+ '[': 'BracketLeft',
50
+ ']': 'BracketRight',
51
+ CapsLock: 'CapsLock',
52
+ A: 'KeyA',
53
+ S: 'KeyS',
54
+ D: 'KeyD',
55
+ F: 'KeyF',
56
+ G: 'KeyG',
57
+ H: 'KeyH',
58
+ J: 'KeyJ',
59
+ K: 'KeyK',
60
+ L: 'KeyL',
61
+ ';': 'Semicolon',
62
+ "'": 'Quote',
63
+ Enter: 'Enter',
64
+ Shift: 'ShiftLeft',
65
+ ShiftRight: 'ShiftRight',
66
+ Z: 'KeyZ',
67
+ X: 'KeyX',
68
+ C: 'KeyC',
69
+ V: 'KeyV',
70
+ B: 'KeyB',
71
+ N: 'KeyN',
72
+ M: 'KeyM',
73
+ ',': 'Comma',
74
+ '.': 'Period',
75
+ '/': 'Slash',
76
+ Ctrl: 'ControlLeft',
77
+ CtrlRight: 'ControlRight',
78
+ Alt: 'AltLeft',
79
+ AltRight: 'AltRight',
80
+ Space: 'Space',
81
+ };
82
+
83
+ const KEY_ROWS = [
84
+ ['`', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 'Backspace'],
85
+ ['Tab', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '[', ']'],
86
+ ['CapsLock', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ';', "'", 'Enter'],
87
+ ['Shift', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', ',', '.', '/', 'ShiftRight'],
88
+ ['Ctrl', 'Alt', 'Space', 'AltRight', 'CtrlRight'],
89
+ ];
90
+
91
+ function keyLabel(key) {
92
+ if (key === 'Space') return '&nbsp;';
93
+ if (key === 'ShiftRight') return 'Shift';
94
+ if (key === 'CtrlRight') return 'Ctrl';
95
+ if (key === 'AltRight') return 'Alt';
96
+ if (key === 'Backspace') return 'Back ⌫';
97
+ return key;
98
+ }
99
+
100
+ function keyData(key) {
101
+ if (key === 'ShiftRight') return 'Shift';
102
+ if (key === 'CtrlRight') return 'Ctrl';
103
+ if (key === 'AltRight') return 'Alt';
104
+ if (key === 'Backspace') return 'Backspace';
105
+ return key;
106
+ }
107
+
108
+ function buildHelpKeyboardHtml(runtimeConfig) {
109
+ const keyboardConfig = runtimeConfig.keyboard || {};
110
+ const statsLabel = keyboardConfig.statsLabel || 'Stats & Achievements';
111
+ const typedPrefix = keyboardConfig.typedPrefix || 'You typed:';
112
+ const typedSuffix = keyboardConfig.typedSuffix || 'characters';
113
+
114
+ let html = '<div class="hacker-vkeyboard-wrap" id="help-keyboard">';
115
+ html += '<div class="hacker-vkeyboard hacker-vkeyboard-main">';
116
+
117
+ KEY_ROWS.forEach((row) => {
118
+ html += '<div class="hacker-vkeyboard-row">';
119
+ row.forEach((key) => {
120
+ const code = CODE_MAP[key] || key;
121
+ let cls = 'hacker-vkey';
122
+ if (key === 'Tab' || key === 'CapsLock' || key === 'Enter') cls += ' wide';
123
+ if (key === 'Space') cls += ' space';
124
+ if (key === 'Backspace') cls += ' acc backspace';
125
+ const dataKey = keyData(key).replace("'", "\\'");
126
+
127
+ html +=
128
+ '<span class="' +
129
+ cls +
130
+ '" data-code="' +
131
+ code +
132
+ '" data-key="' +
133
+ dataKey +
134
+ '"' +
135
+ (key === 'Backspace' ? ' title="Backspace or ESC"' : '') +
136
+ '>' +
137
+ keyLabel(key) +
138
+ '</span>';
139
+ });
140
+ html += '</div>';
141
+ });
142
+
143
+ html += '</div>';
144
+ html += '<div class="hacker-vkeyboard-side">';
145
+ html += '<div class="hacker-vkeyboard-side-block">';
146
+ html +=
147
+ '<div class="hacker-vkeyboard-side-row"><span class="hacker-vkey" data-code="Insert" data-key="Ins">Insert</span><span class="hacker-vkey nav-home" data-code="Home" data-key="Home">Home</span><span class="hacker-vkey" data-code="PageUp" data-key="PgUp">PgUp</span></div>';
148
+ html +=
149
+ '<div class="hacker-vkeyboard-side-row"><span class="hacker-vkey" data-code="Delete" data-key="Del">Delete</span><span class="hacker-vkey nav-end" data-code="End" data-key="End">End</span><span class="hacker-vkey" data-code="PageDown" data-key="PgDn">PgDn</span></div>';
150
+ html += '</div>';
151
+ html +=
152
+ '<div class="hacker-vkeyboard-side-row"><span class="hacker-vkey" data-code="Purge" data-key="Purge">Purge</span></div>';
153
+ html += '<div class="hacker-vkeyboard-arrows-wrap">';
154
+ html += '<div class="hacker-vkeyboard-arrows">';
155
+ html += '<span class="hacker-vkey arr-u" data-code="ArrowUp" data-key="↑">↑</span>';
156
+ html += '<span class="hacker-vkey arr-l" data-code="ArrowLeft" data-key="←">←</span>';
157
+ html += '<span class="hacker-vkey arr-r" data-code="ArrowRight" data-key="→">→</span>';
158
+ html += '<span class="hacker-vkey arr-d" data-code="ArrowDown" data-key="↓">↓</span>';
159
+ html += '</div>';
160
+ html += '</div></div></div>';
161
+ html += `<div class="hacker-vkeyboard-stats"><span class="hacker-vkeyboard-stats-label">${statsLabel}</span><br>${typedPrefix} <span id="help-char-count">0</span> ${typedSuffix}</div>`;
162
+ return html;
163
+ }
164
+
165
+ function highlightKey(modalBody, code) {
166
+ const normalizedCode = code === 'Escape' ? 'Backspace' : code;
167
+
168
+ let el = modalBody.querySelector(`.hacker-vkey[data-code="${normalizedCode}"]`);
169
+ if (!el) {
170
+ if (normalizedCode === 'ShiftRight')
171
+ el = modalBody.querySelector('.hacker-vkey[data-code="ShiftLeft"]');
172
+ else if (normalizedCode === 'ControlRight')
173
+ el = modalBody.querySelector('.hacker-vkey[data-code="ControlLeft"]');
174
+ else if (normalizedCode === 'AltRight')
175
+ el = modalBody.querySelector('.hacker-vkey[data-code="AltLeft"]');
176
+ }
177
+
178
+ if (!el) return;
179
+ el.classList.add('highlight');
180
+ setTimeout(() => {
181
+ el.classList.remove('highlight');
182
+ }, 150);
183
+ }
184
+
185
+ export function mountHelpKeyboard(modalOverlay, modalBody, runtimeConfig) {
186
+ let helpCharCount = 0;
187
+ modalBody.innerHTML = buildHelpKeyboardHtml(runtimeConfig);
188
+
189
+ const updateCount = () => {
190
+ const charEl = document.getElementById('help-char-count');
191
+ if (charEl) charEl.textContent = String(helpCharCount);
192
+ };
193
+
194
+ const onKeyClick = (keyEl) => {
195
+ const code = keyEl.getAttribute('data-code');
196
+ if (code) highlightKey(modalBody, code);
197
+
198
+ const key = keyEl.getAttribute('data-key');
199
+ if (!key) return;
200
+ if (!NAV_KEYS.has(key) || key === 'Space') {
201
+ helpCharCount += 1;
202
+ updateCount();
203
+ }
204
+ };
205
+
206
+ modalBody.querySelectorAll('.hacker-vkey').forEach((keyEl) => {
207
+ keyEl.addEventListener('click', () => onKeyClick(keyEl));
208
+ });
209
+ updateCount();
210
+
211
+ const onKeyDown = (event) => {
212
+ if (
213
+ !modalOverlay.classList.contains('open') ||
214
+ !modalBody.classList.contains('hacker-modal-keyboard')
215
+ )
216
+ return;
217
+ if (event.key === 'Escape') return;
218
+
219
+ event.preventDefault();
220
+ highlightKey(modalBody, event.code);
221
+ if (event.key.length !== 1) return;
222
+ helpCharCount += 1;
223
+ updateCount();
224
+ };
225
+
226
+ document.addEventListener('keydown', onKeyDown);
227
+ return () => {
228
+ document.removeEventListener('keydown', onKeyDown);
229
+ };
230
+ }
@@ -0,0 +1,18 @@
1
+ export function renderProgressModal() {
2
+ const bar = document.getElementById('dl-progress');
3
+ if (!bar) return;
4
+
5
+ bar.innerHTML = '';
6
+ for (let i = 0; i < 48; i += 1) {
7
+ bar.appendChild(document.createElement('span'));
8
+ }
9
+
10
+ let idx = 0;
11
+ const fillNext = () => {
12
+ if (idx >= 48) return;
13
+ bar.children[idx].classList.add('filled');
14
+ idx += 1;
15
+ setTimeout(fillNext, 80 + Math.random() * 60);
16
+ };
17
+ fillNext();
18
+ }
@@ -1,347 +1,119 @@
1
- function randHex() {
2
- var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
3
- return chars[Math.floor(Math.random() * chars.length)];
4
- }
5
-
6
- function randKeyLine(pairs) {
7
- var s = [];
8
- for (var i = 0; i < pairs; i++) s.push(randHex() + randHex());
9
- return s.join(' ');
10
- }
11
-
12
- function randPass() {
13
- var s = '';
14
- for (var i = 0; i < 6; i++) s += randHex().toLowerCase();
15
- s += '@' + randHex() + randHex() + randHex() + randHex() + randHex();
16
- return s;
17
- }
1
+ import { createDecryptorController } from './modal-decryptor.js';
2
+ import { mountHelpKeyboard } from './modal-keyboard.js';
3
+ import { renderProgressModal } from './modal-progress.js';
18
4
 
19
5
  export function initAboutModals(runtimeConfig, prefersReducedMotion) {
20
- var modalOverlay = document.getElementById('hacker-modal');
21
- var modalBody = document.getElementById('hacker-modal-body');
22
- var modalTitle = document.querySelector('.hacker-modal-title');
6
+ const modalOverlay = document.getElementById('hacker-modal');
7
+ const modalBody = document.getElementById('hacker-modal-body');
8
+ const modalTitle = document.querySelector('.hacker-modal-title');
23
9
  if (!modalOverlay || !modalBody || !modalTitle) return;
24
10
 
25
- var decryptorKeysLabel = runtimeConfig.decryptorKeysLabel || 'keys tested';
26
- var decryptorInterval = null;
27
- var helpCharCount = 0;
11
+ const decryptorKeysLabel = runtimeConfig.decryptorKeysLabel || 'keys tested';
12
+ const decryptor = createDecryptorController(
13
+ modalOverlay,
14
+ prefersReducedMotion,
15
+ decryptorKeysLabel
16
+ );
17
+ let cleanupKeyboard = null;
18
+
19
+ const scriptsTpl = document.getElementById('hacker-scripts-folders-tpl');
20
+ const fallbackModalContent = {
21
+ 'dl-data': {
22
+ title: 'Downloading...',
23
+ body: '<div class="hacker-modal-download"><div class="modal-subtitle">Critical Data</div><div class="hacker-modal-progress" id="dl-progress"></div></div>',
24
+ type: 'progress',
25
+ },
26
+ ai: {
27
+ title: 'AI',
28
+ body: '<pre>~ $ model --status\n\ninference: stable\ncontext: 8k tokens\nlatency: &lt; 200ms\n\n&gt;&gt; system online</pre>',
29
+ },
30
+ decryptor: {
31
+ title: 'Password Decryptor',
32
+ body: '<pre class="hacker-decryptor-pre">Calculating Hashes\n\n<span id="dec-keys">[00:00:01] 0 keys tested</span>\n\nCurrent passphrase: <span id="dec-pass">********</span>\n\nMaster key\n<span id="dec-master1"></span>\n<span id="dec-master2"></span>\n\nTransient key\n<span id="dec-trans1"></span>\n<span id="dec-trans2"></span>\n<span id="dec-trans3"></span>\n<span id="dec-trans4"></span></pre>',
33
+ type: 'decryptor',
34
+ },
35
+ help: { title: 'Help', body: '', type: 'keyboard' },
36
+ 'all-scripts': { title: '/root/bash/scripts', body: '', type: 'scripts' },
37
+ };
38
+ const modalContent = runtimeConfig.modalContent || fallbackModalContent;
28
39
 
29
- function startDecryptorFlash() {
30
- if (prefersReducedMotion) return;
31
- if (decryptorInterval) clearInterval(decryptorInterval);
32
- var keys = 0;
33
- var sec = 1;
34
- decryptorInterval = setInterval(function() {
35
- if (!modalOverlay.classList.contains('open')) {
36
- clearInterval(decryptorInterval);
37
- decryptorInterval = null;
38
- return;
39
- }
40
- keys += 1 + Math.floor(Math.random() * 3);
41
- sec = Math.min(59, Math.floor(keys / 15) + 1);
42
- var el = document.getElementById('dec-keys');
43
- if (el) el.textContent = '[00:00:' + String(sec).padStart(2, '0') + '] ' + keys + ' ' + decryptorKeysLabel;
44
- el = document.getElementById('dec-pass');
45
- if (el) el.textContent = randPass();
46
- el = document.getElementById('dec-master1');
47
- if (el) el.textContent = randKeyLine(8);
48
- el = document.getElementById('dec-master2');
49
- if (el) el.textContent = randKeyLine(8);
50
- el = document.getElementById('dec-trans1');
51
- if (el) el.textContent = randKeyLine(7);
52
- el = document.getElementById('dec-trans2');
53
- if (el) el.textContent = randKeyLine(7);
54
- el = document.getElementById('dec-trans3');
55
- if (el) el.textContent = randKeyLine(8);
56
- el = document.getElementById('dec-trans4');
57
- if (el) el.textContent = randKeyLine(8);
58
- }, 180);
59
- }
40
+ const closeModal = () => {
41
+ decryptor.stop();
42
+ if (cleanupKeyboard) {
43
+ cleanupKeyboard();
44
+ cleanupKeyboard = null;
45
+ }
60
46
 
61
- function buildHelpKeyboard() {
62
- var keyboardConfig = runtimeConfig.keyboard || {};
63
- var statsLabel = keyboardConfig.statsLabel || 'Stats & Achievements';
64
- var typedPrefix = keyboardConfig.typedPrefix || 'You typed:';
65
- var typedSuffix = keyboardConfig.typedSuffix || 'characters';
66
- var rows = [
67
- ['`', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 'Backspace'],
68
- ['Tab', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '[', ']'],
69
- ['CapsLock', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ';', "'", 'Enter'],
70
- ['Shift', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', ',', '.', '/', 'ShiftRight'],
71
- ['Ctrl', 'Alt', 'Space', 'AltRight', 'CtrlRight'],
72
- ];
73
- var codeMap = {
74
- '`': 'Backquote',
75
- '1': 'Digit1',
76
- '2': 'Digit2',
77
- '3': 'Digit3',
78
- '4': 'Digit4',
79
- '5': 'Digit5',
80
- '6': 'Digit6',
81
- '7': 'Digit7',
82
- '8': 'Digit8',
83
- '9': 'Digit9',
84
- '0': 'Digit0',
85
- '-': 'Minus',
86
- '=': 'Equal',
87
- Backspace: 'Backspace',
88
- Tab: 'Tab',
89
- Q: 'KeyQ',
90
- W: 'KeyW',
91
- E: 'KeyE',
92
- R: 'KeyR',
93
- T: 'KeyT',
94
- Y: 'KeyY',
95
- U: 'KeyU',
96
- I: 'KeyI',
97
- O: 'KeyO',
98
- P: 'KeyP',
99
- '[': 'BracketLeft',
100
- ']': 'BracketRight',
101
- CapsLock: 'CapsLock',
102
- A: 'KeyA',
103
- S: 'KeyS',
104
- D: 'KeyD',
105
- F: 'KeyF',
106
- G: 'KeyG',
107
- H: 'KeyH',
108
- J: 'KeyJ',
109
- K: 'KeyK',
110
- L: 'KeyL',
111
- ';': 'Semicolon',
112
- "'": 'Quote',
113
- Enter: 'Enter',
114
- Shift: 'ShiftLeft',
115
- ShiftRight: 'ShiftRight',
116
- Z: 'KeyZ',
117
- X: 'KeyX',
118
- C: 'KeyC',
119
- V: 'KeyV',
120
- B: 'KeyB',
121
- N: 'KeyN',
122
- M: 'KeyM',
123
- ',': 'Comma',
124
- '.': 'Period',
125
- '/': 'Slash',
126
- Ctrl: 'ControlLeft',
127
- CtrlRight: 'ControlRight',
128
- Alt: 'AltLeft',
129
- AltRight: 'AltRight',
130
- Space: 'Space',
131
- };
47
+ const modalEl = modalOverlay.querySelector('.hacker-modal');
48
+ if (modalEl) modalEl.classList.remove('hacker-modal-wide');
49
+ modalOverlay.classList.remove('open');
50
+ modalOverlay.setAttribute('aria-hidden', 'true');
51
+ };
132
52
 
133
- var html = '<div class="hacker-vkeyboard-wrap" id="help-keyboard">';
134
- html += '<div class="hacker-vkeyboard hacker-vkeyboard-main">';
135
- rows.forEach(function(row) {
136
- html += '<div class="hacker-vkeyboard-row">';
137
- row.forEach(function(k) {
138
- var code = codeMap[k] || k;
139
- var cls = 'hacker-vkey';
140
- if (k === 'Tab' || k === 'CapsLock' || k === 'Enter') cls += ' wide';
141
- if (k === 'Space') cls += ' space';
142
- if (k === 'Backspace') cls += ' acc backspace';
143
- var label =
144
- k === 'Space'
145
- ? '&nbsp;'
146
- : k === 'ShiftRight'
147
- ? 'Shift'
148
- : k === 'CtrlRight'
149
- ? 'Ctrl'
150
- : k === 'AltRight'
151
- ? 'Alt'
152
- : k === 'Backspace'
153
- ? 'Back ⌫'
154
- : k;
155
- html +=
156
- '<span class="' +
157
- cls +
158
- '" data-code="' +
159
- code +
160
- '" data-key="' +
161
- (k === 'ShiftRight' ? 'Shift' : k === 'CtrlRight' ? 'Ctrl' : k === 'AltRight' ? 'Alt' : k === 'Backspace' ? 'Backspace' : k).replace("'", "\\'") +
162
- '"' +
163
- (k === 'Backspace' ? ' title="Backspace or ESC"' : '') +
164
- '>' +
165
- label +
166
- '</span>';
167
- });
168
- html += '</div>';
169
- });
170
- html += '</div>';
171
- html += '<div class="hacker-vkeyboard-side">';
172
- html += '<div class="hacker-vkeyboard-side-block">';
173
- html += '<div class="hacker-vkeyboard-side-row"><span class="hacker-vkey" data-code="Insert" data-key="Ins">Insert</span><span class="hacker-vkey nav-home" data-code="Home" data-key="Home">Home</span><span class="hacker-vkey" data-code="PageUp" data-key="PgUp">PgUp</span></div>';
174
- html += '<div class="hacker-vkeyboard-side-row"><span class="hacker-vkey" data-code="Delete" data-key="Del">Delete</span><span class="hacker-vkey nav-end" data-code="End" data-key="End">End</span><span class="hacker-vkey" data-code="PageDown" data-key="PgDn">PgDn</span></div>';
175
- html += '</div>';
176
- html += '<div class="hacker-vkeyboard-side-row"><span class="hacker-vkey" data-code="Purge" data-key="Purge">Purge</span></div>';
177
- html += '<div class="hacker-vkeyboard-arrows-wrap">';
178
- html += '<div class="hacker-vkeyboard-arrows">';
179
- html += '<span class="hacker-vkey arr-u" data-code="ArrowUp" data-key="↑">↑</span>';
180
- html += '<span class="hacker-vkey arr-l" data-code="ArrowLeft" data-key="←">←</span>';
181
- html += '<span class="hacker-vkey arr-r" data-code="ArrowRight" data-key="→">→</span>';
182
- html += '<span class="hacker-vkey arr-d" data-code="ArrowDown" data-key="↓">↓</span>';
183
- html += '</div>';
184
- html += '</div></div></div>';
185
- html += '<div class="hacker-vkeyboard-stats"><span class="hacker-vkeyboard-stats-label">' + statsLabel + '</span><br>' + typedPrefix + ' <span id="help-char-count">0</span> ' + typedSuffix + '</div>';
186
- return html;
187
- }
53
+ const openModal = (data) => {
54
+ if (!data) return;
188
55
 
189
- function highlightKey(code) {
190
- if (code === 'Escape') code = 'Backspace';
191
- var el = modalBody.querySelector('.hacker-vkey[data-code="' + code + '"]');
192
- if (!el) {
193
- if (code === 'ShiftRight') el = modalBody.querySelector('.hacker-vkey[data-code="ShiftLeft"]');
194
- else if (code === 'ControlRight') el = modalBody.querySelector('.hacker-vkey[data-code="ControlLeft"]');
195
- else if (code === 'AltRight') el = modalBody.querySelector('.hacker-vkey[data-code="AltLeft"]');
56
+ decryptor.stop();
57
+ if (cleanupKeyboard) {
58
+ cleanupKeyboard();
59
+ cleanupKeyboard = null;
196
60
  }
197
- if (el) {
198
- el.classList.add('highlight');
199
- setTimeout(function() {
200
- el.classList.remove('highlight');
201
- }, 150);
202
- }
203
- }
204
61
 
205
- function initHelpKeyboard() {
206
- helpCharCount = 0;
207
- var charEl = document.getElementById('help-char-count');
208
- if (charEl) charEl.textContent = '0';
209
- modalBody.querySelectorAll('.hacker-vkey').forEach(function(k) {
210
- k.addEventListener('click', function() {
211
- var code = k.getAttribute('data-code');
212
- highlightKey(code);
213
- var key = k.getAttribute('data-key');
214
- var navKeys = ['Shift', 'Ctrl', 'Alt', 'CapsLock', 'Tab', 'Enter', 'Backspace', 'Space', 'Ins', 'Home', 'PgUp', 'Del', 'End', 'PgDn', 'Purge', '↑', '↓', '←', '→'];
215
- if (key && navKeys.indexOf(key) === -1) {
216
- helpCharCount++;
217
- charEl = document.getElementById('help-char-count');
218
- if (charEl) charEl.textContent = helpCharCount;
219
- } else if (key === 'Space') {
220
- helpCharCount++;
221
- charEl = document.getElementById('help-char-count');
222
- if (charEl) charEl.textContent = helpCharCount;
223
- }
224
- });
225
- });
226
- }
62
+ const modalEl = modalOverlay.querySelector('.hacker-modal');
63
+ if (modalEl) modalEl.classList.remove('hacker-modal-wide');
227
64
 
228
- function handleHelpKeydown(e) {
229
- if (!modalOverlay.classList.contains('open') || !modalBody.classList.contains('hacker-modal-keyboard')) return;
230
- if (e.key === 'Escape') return;
231
- e.preventDefault();
232
- highlightKey(e.code);
233
- if (e.key.length === 1) {
234
- helpCharCount++;
235
- var charEl = document.getElementById('help-char-count');
236
- if (charEl) charEl.textContent = helpCharCount;
65
+ modalTitle.textContent = data.title;
66
+ modalBody.innerHTML = data.body;
67
+ modalBody.className =
68
+ 'hacker-modal-body' +
69
+ (data.type === 'progress' ? ' hacker-modal-download' : '') +
70
+ (data.type === 'keyboard' ? ' hacker-modal-keyboard' : '') +
71
+ (data.type === 'scripts' ? ' hacker-modal-scripts-wrap' : '');
72
+
73
+ if (data.type === 'progress') {
74
+ renderProgressModal();
75
+ } else if (data.type === 'decryptor') {
76
+ decryptor.prime();
77
+ decryptor.start();
78
+ } else if (data.type === 'keyboard') {
79
+ if (modalEl) modalEl.classList.add('hacker-modal-wide');
80
+ cleanupKeyboard = mountHelpKeyboard(modalOverlay, modalBody, runtimeConfig);
81
+ } else if (data.type === 'scripts' && scriptsTpl) {
82
+ modalBody.innerHTML = '';
83
+ if ('content' in scriptsTpl && scriptsTpl.content) {
84
+ modalBody.appendChild(scriptsTpl.content.cloneNode(true));
85
+ } else {
86
+ modalBody.appendChild(scriptsTpl.cloneNode(true));
87
+ const cloned = modalBody.querySelector('#hacker-scripts-folders-tpl');
88
+ if (cloned) {
89
+ cloned.removeAttribute('id');
90
+ cloned.hidden = false;
91
+ cloned.removeAttribute('aria-hidden');
92
+ }
93
+ }
94
+ if (modalEl) modalEl.classList.add('hacker-modal-wide');
237
95
  }
238
- }
239
96
 
240
- var scriptsTpl = document.getElementById('hacker-scripts-folders-tpl');
241
- var fallbackModalContent = {
242
- 'dl-data': { title: 'Downloading...', body: '<div class="hacker-modal-download"><div class="modal-subtitle">Critical Data</div><div class="hacker-modal-progress" id="dl-progress"></div></div>', type: 'progress' },
243
- ai: { title: 'AI', body: '<pre>~ $ model --status\n\ninference: stable\ncontext: 8k tokens\nlatency: &lt; 200ms\n\n&gt;&gt; system online</pre>' },
244
- decryptor: { title: 'Password Decryptor', body: '<pre class="hacker-decryptor-pre">Calculating Hashes\n\n<span id="dec-keys">[00:00:01] 0 keys tested</span>\n\nCurrent passphrase: <span id="dec-pass">********</span>\n\nMaster key\n<span id="dec-master1"></span>\n<span id="dec-master2"></span>\n\nTransient key\n<span id="dec-trans1"></span>\n<span id="dec-trans2"></span>\n<span id="dec-trans3"></span>\n<span id="dec-trans4"></span></pre>', type: 'decryptor' },
245
- help: { title: 'Help', body: '', type: 'keyboard' },
246
- 'all-scripts': { title: '/root/bash/scripts', body: '', type: 'scripts' },
97
+ modalOverlay.classList.add('open');
98
+ modalOverlay.setAttribute('aria-hidden', 'false');
247
99
  };
248
100
 
249
- var modalContent = runtimeConfig.modalContent || fallbackModalContent;
250
- document.querySelectorAll('.hacker-folder[data-modal]').forEach(function(btn) {
251
- btn.addEventListener('click', function() {
252
- var id = btn.getAttribute('data-modal');
253
- var data = modalContent[id];
254
- if (!data) return;
255
-
256
- var modalEl = modalOverlay.querySelector('.hacker-modal');
257
- if (modalEl) modalEl.classList.remove('hacker-modal-wide');
258
- modalTitle.textContent = data.title;
259
- modalBody.innerHTML = data.body;
260
- modalBody.className =
261
- 'hacker-modal-body' +
262
- (data.type === 'progress' ? ' hacker-modal-download' : '') +
263
- (data.type === 'keyboard' ? ' hacker-modal-keyboard' : '') +
264
- (data.type === 'scripts' ? ' hacker-modal-scripts-wrap' : '');
265
-
266
- if (data.type === 'progress') {
267
- var bar = document.getElementById('dl-progress');
268
- if (bar) {
269
- bar.innerHTML = '';
270
- for (var i = 0; i < 48; i++) bar.appendChild(document.createElement('span'));
271
- var idx = 0;
272
- function fillNext() {
273
- if (idx < 48) {
274
- bar.children[idx].classList.add('filled');
275
- idx++;
276
- setTimeout(fillNext, 80 + Math.random() * 60);
277
- }
278
- }
279
- fillNext();
280
- }
281
- } else if (data.type === 'decryptor') {
282
- var el = document.getElementById('dec-keys');
283
- if (el) el.textContent = '[00:00:01] 0 ' + decryptorKeysLabel;
284
- el = document.getElementById('dec-pass');
285
- if (el) el.textContent = randPass();
286
- el = document.getElementById('dec-master1');
287
- if (el) el.textContent = randKeyLine(8);
288
- el = document.getElementById('dec-master2');
289
- if (el) el.textContent = randKeyLine(8);
290
- el = document.getElementById('dec-trans1');
291
- if (el) el.textContent = randKeyLine(7);
292
- el = document.getElementById('dec-trans2');
293
- if (el) el.textContent = randKeyLine(7);
294
- el = document.getElementById('dec-trans3');
295
- if (el) el.textContent = randKeyLine(8);
296
- el = document.getElementById('dec-trans4');
297
- if (el) el.textContent = randKeyLine(8);
298
- startDecryptorFlash();
299
- } else if (data.type === 'keyboard') {
300
- modalBody.innerHTML = buildHelpKeyboard();
301
- if (modalEl) modalEl.classList.add('hacker-modal-wide');
302
- initHelpKeyboard();
303
- document.addEventListener('keydown', handleHelpKeydown);
304
- } else if (data.type === 'scripts' && scriptsTpl) {
305
- modalBody.innerHTML = '';
306
- if ('content' in scriptsTpl && scriptsTpl.content) {
307
- modalBody.appendChild(scriptsTpl.content.cloneNode(true));
308
- } else {
309
- modalBody.appendChild(scriptsTpl.cloneNode(true));
310
- var cloned = modalBody.querySelector('#hacker-scripts-folders-tpl');
311
- if (cloned) {
312
- cloned.removeAttribute('id');
313
- cloned.hidden = false;
314
- cloned.removeAttribute('aria-hidden');
315
- }
316
- }
317
- if (modalEl) modalEl.classList.add('hacker-modal-wide');
318
- }
319
-
320
- modalOverlay.classList.add('open');
321
- modalOverlay.setAttribute('aria-hidden', 'false');
101
+ document.querySelectorAll('.hacker-folder[data-modal]').forEach((button) => {
102
+ button.addEventListener('click', () => {
103
+ const id = button.getAttribute('data-modal');
104
+ if (!id) return;
105
+ openModal(modalContent[id]);
322
106
  });
323
107
  });
324
108
 
325
- function closeModal() {
326
- if (decryptorInterval) {
327
- clearInterval(decryptorInterval);
328
- decryptorInterval = null;
329
- }
330
- document.removeEventListener('keydown', handleHelpKeydown);
331
- var modalEl = modalOverlay.querySelector('.hacker-modal');
332
- if (modalEl) modalEl.classList.remove('hacker-modal-wide');
333
- modalOverlay.classList.remove('open');
334
- modalOverlay.setAttribute('aria-hidden', 'true');
335
- }
336
-
337
- var closeButton = document.querySelector('.hacker-modal-close');
109
+ const closeButton = document.querySelector('.hacker-modal-close');
338
110
  if (closeButton) closeButton.addEventListener('click', closeModal);
339
111
 
340
- modalOverlay.addEventListener('click', function(e) {
341
- if (e.target === modalOverlay) closeModal();
112
+ modalOverlay.addEventListener('click', (event) => {
113
+ if (event.target === modalOverlay) closeModal();
342
114
  });
343
115
 
344
- document.addEventListener('keydown', function(e) {
345
- if (e.key === 'Escape' && modalOverlay.classList.contains('open')) closeModal();
116
+ document.addEventListener('keydown', (event) => {
117
+ if (event.key === 'Escape' && modalOverlay.classList.contains('open')) closeModal();
346
118
  });
347
119
  }