@anglefeint/astro-theme 0.1.24 → 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
|
@@ -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 {
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
locale?: string;
|
|
22
|
+
related?: CollectionEntry<'blog'>[];
|
|
23
|
+
localeHrefs?: Partial<Record<Locale, string>>;
|
|
18
24
|
};
|
|
19
25
|
|
|
20
26
|
const {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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 ' ';
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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: < 200ms\n\n>> 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
|
-
|
|
30
|
-
|
|
31
|
-
if (
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
134
|
-
|
|
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
|
-
? ' '
|
|
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
|
-
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
206
|
-
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
241
|
-
|
|
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: < 200ms\n\n>> 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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
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',
|
|
341
|
-
if (
|
|
112
|
+
modalOverlay.addEventListener('click', (event) => {
|
|
113
|
+
if (event.target === modalOverlay) closeModal();
|
|
342
114
|
});
|
|
343
115
|
|
|
344
|
-
document.addEventListener('keydown',
|
|
345
|
-
if (
|
|
116
|
+
document.addEventListener('keydown', (event) => {
|
|
117
|
+
if (event.key === 'Escape' && modalOverlay.classList.contains('open')) closeModal();
|
|
346
118
|
});
|
|
347
119
|
}
|