handzon-core 0.6.0
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 +74 -0
- package/src/collections.ts +150 -0
- package/src/components/Footer.astro +85 -0
- package/src/components/Navbar.astro +74 -0
- package/src/components/Progress.tsx +36 -0
- package/src/components/Sidebar.astro +162 -0
- package/src/components/StepNav.astro +107 -0
- package/src/components/ai/ByokSetup.tsx +90 -0
- package/src/components/ai/ChatButton.tsx +30 -0
- package/src/components/ai/ChatPanel.tsx +244 -0
- package/src/components/auth/SignInButton.astro +41 -0
- package/src/components/auth/UserMenu.astro +79 -0
- package/src/components/auth/UserMenu.tsx +136 -0
- package/src/components/home/FilterBar.tsx +152 -0
- package/src/components/home/Hero.astro +60 -0
- package/src/components/home/Pagination.tsx +89 -0
- package/src/components/home/ResumeRail.tsx +50 -0
- package/src/components/home/TutorialCard.astro +185 -0
- package/src/components/mdx/Callout.astro +77 -0
- package/src/components/mdx/Checkpoint.astro +14 -0
- package/src/components/mdx/Checkpoint.tsx +49 -0
- package/src/components/mdx/Diff.astro +6 -0
- package/src/components/mdx/Diff.tsx +100 -0
- package/src/components/mdx/Download.astro +37 -0
- package/src/components/mdx/Embed.astro +56 -0
- package/src/components/mdx/File.astro +28 -0
- package/src/components/mdx/FileTree.astro +6 -0
- package/src/components/mdx/FileTree.tsx +71 -0
- package/src/components/mdx/Hint.astro +51 -0
- package/src/components/mdx/Mermaid.astro +6 -0
- package/src/components/mdx/Mermaid.tsx +47 -0
- package/src/components/mdx/Playground.astro +6 -0
- package/src/components/mdx/Playground.tsx +34 -0
- package/src/components/mdx/Quiz.astro +6 -0
- package/src/components/mdx/Quiz.tsx +102 -0
- package/src/components/mdx/Recap.astro +65 -0
- package/src/components/mdx/Reveal.astro +7 -0
- package/src/components/mdx/Reveal.tsx +25 -0
- package/src/components/mdx/Step.astro +12 -0
- package/src/components/mdx/Steps.astro +40 -0
- package/src/components/mdx/Tab.astro +22 -0
- package/src/components/mdx/Tabs.astro +67 -0
- package/src/components/mdx/Terminal.astro +6 -0
- package/src/components/mdx/Terminal.tsx +47 -0
- package/src/index.ts +55 -0
- package/src/layouts/BaseLayout.astro +112 -0
- package/src/layouts/TutorialLayout.astro +218 -0
- package/src/lib/ai/client.ts +92 -0
- package/src/lib/ai/context.ts +97 -0
- package/src/lib/content.ts +73 -0
- package/src/lib/mdx-components.ts +47 -0
- package/src/lib/progress/local.ts +89 -0
- package/src/lib/progress/remote.ts +199 -0
- package/src/lib/progress/types.ts +63 -0
- package/src/lib/progress/useProgress.ts +117 -0
- package/src/lib/rehype-mermaid-passthrough.ts +31 -0
- package/src/pages/Home.astro +408 -0
- package/src/pages/TutorialLanding.astro +324 -0
- package/src/pages/TutorialStep.astro +67 -0
- package/src/pages/paths.ts +36 -0
- package/src/server/auth/config.ts +102 -0
- package/src/server/auth/schema.ts +66 -0
- package/src/server/auth/session.ts +27 -0
- package/src/server/auth.ts +127 -0
- package/src/server/db/client.ts +14 -0
- package/src/server/db/migrate.ts +29 -0
- package/src/server/db/schema.ts +65 -0
- package/src/server/handlers/healthz.ts +6 -0
- package/src/server/handlers/progress.ts +90 -0
- package/src/server/handlers/tutorialStats.ts +67 -0
- package/src/server/http.ts +33 -0
- package/src/types/ai.ts +17 -0
- package/styles/base.css +127 -0
- package/styles/components/a11y.css +12 -0
- package/styles/components/byok.css +50 -0
- package/styles/components/chat.css +304 -0
- package/styles/components/checkpoint.css +49 -0
- package/styles/components/diff.css +44 -0
- package/styles/components/expressive-code.css +61 -0
- package/styles/components/filetree.css +68 -0
- package/styles/components/mermaid.css +19 -0
- package/styles/components/modal.css +25 -0
- package/styles/components/progress.css +19 -0
- package/styles/components/quiz.css +101 -0
- package/styles/components/reveal.css +25 -0
- package/styles/components/tabs.css +60 -0
- package/styles/components/terminal.css +55 -0
- package/styles/components.css +28 -0
- package/styles/global.css +15 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/* Chat assistant — pill FAB, soft slide-out panel.
|
|
2
|
+
*
|
|
3
|
+
* Subtle diagonal gradient + accent-tinted glow so the assistant feels
|
|
4
|
+
* like a "smart" affordance without breaking the brutalist flat
|
|
5
|
+
* aesthetic everywhere else. Keep the hard edges (no border-radius).
|
|
6
|
+
*/
|
|
7
|
+
.chat-fab {
|
|
8
|
+
position: fixed;
|
|
9
|
+
bottom: 1.25rem;
|
|
10
|
+
right: 1.25rem;
|
|
11
|
+
z-index: 50;
|
|
12
|
+
display: inline-flex;
|
|
13
|
+
align-items: center;
|
|
14
|
+
gap: 0.5rem;
|
|
15
|
+
padding: 0.65rem 1rem;
|
|
16
|
+
background: linear-gradient(
|
|
17
|
+
135deg,
|
|
18
|
+
color-mix(in oklab, var(--color-accent) 92%, white),
|
|
19
|
+
var(--color-accent) 50%,
|
|
20
|
+
color-mix(in oklab, var(--color-accent) 80%, var(--color-fg))
|
|
21
|
+
);
|
|
22
|
+
color: var(--color-accent-fg);
|
|
23
|
+
border: 0;
|
|
24
|
+
font-weight: 600;
|
|
25
|
+
cursor: pointer;
|
|
26
|
+
/* Inset top-light for depth + a soft accent glow that lifts the
|
|
27
|
+
* button off the page bg without rounding the corners. */
|
|
28
|
+
box-shadow:
|
|
29
|
+
inset 0 1px 0 color-mix(in srgb, white 25%, transparent),
|
|
30
|
+
0 0 0 1px color-mix(in oklab, var(--color-accent) 60%, var(--color-fg)),
|
|
31
|
+
0 6px 20px color-mix(in oklab, var(--color-accent) 35%, transparent);
|
|
32
|
+
transition: transform 0.12s ease, box-shadow 0.12s ease, background-position 0.3s ease;
|
|
33
|
+
background-size: 140% 140%;
|
|
34
|
+
background-position: 0% 0%;
|
|
35
|
+
}
|
|
36
|
+
.chat-fab:hover {
|
|
37
|
+
transform: translateY(-1px);
|
|
38
|
+
background-position: 100% 100%;
|
|
39
|
+
box-shadow:
|
|
40
|
+
inset 0 1px 0 color-mix(in srgb, white 30%, transparent),
|
|
41
|
+
0 0 0 1px color-mix(in oklab, var(--color-accent) 70%, var(--color-fg)),
|
|
42
|
+
0 10px 26px color-mix(in oklab, var(--color-accent) 50%, transparent);
|
|
43
|
+
}
|
|
44
|
+
.chat-fab:active {
|
|
45
|
+
transform: translateY(0);
|
|
46
|
+
}
|
|
47
|
+
.chat-fab :is(svg) {
|
|
48
|
+
/* Tiny shadow under the Sparkles so it reads as an icon, not paint. */
|
|
49
|
+
filter: drop-shadow(0 1px 0 color-mix(in srgb, black 30%, transparent));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.chat-panel {
|
|
53
|
+
position: fixed;
|
|
54
|
+
top: 0;
|
|
55
|
+
right: 0;
|
|
56
|
+
width: min(420px, 92vw);
|
|
57
|
+
height: 100dvh;
|
|
58
|
+
background: var(--color-bg);
|
|
59
|
+
border-left: var(--border-default) solid var(--color-border);
|
|
60
|
+
display: flex;
|
|
61
|
+
flex-direction: column;
|
|
62
|
+
z-index: 60;
|
|
63
|
+
}
|
|
64
|
+
.chat-head {
|
|
65
|
+
display: flex;
|
|
66
|
+
align-items: center;
|
|
67
|
+
justify-content: space-between;
|
|
68
|
+
gap: 0.5rem;
|
|
69
|
+
padding: 0.85rem 1rem;
|
|
70
|
+
border-bottom: var(--border-default) solid var(--color-border);
|
|
71
|
+
background: var(--color-surface);
|
|
72
|
+
}
|
|
73
|
+
.chat-head-id { display: inline-flex; align-items: center; gap: 0.5rem; }
|
|
74
|
+
.chat-head-icon { color: var(--color-accent); flex-shrink: 0; }
|
|
75
|
+
.chat-title { margin: 0; font-size: 1rem; font-weight: 700; }
|
|
76
|
+
.chat-tagline { color: var(--color-muted); font-size: 0.8em; }
|
|
77
|
+
.chat-head-actions { display: inline-flex; gap: 0.25rem; }
|
|
78
|
+
.chat-head-actions button {
|
|
79
|
+
background: transparent;
|
|
80
|
+
border: 0;
|
|
81
|
+
color: var(--color-muted);
|
|
82
|
+
width: 28px;
|
|
83
|
+
height: 28px;
|
|
84
|
+
display: grid;
|
|
85
|
+
place-items: center;
|
|
86
|
+
cursor: pointer;
|
|
87
|
+
}
|
|
88
|
+
.chat-head-actions button:hover {
|
|
89
|
+
color: var(--color-fg);
|
|
90
|
+
background: var(--color-surface-2);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.chat-meta {
|
|
94
|
+
padding: 0.5rem 1rem;
|
|
95
|
+
font-family: var(--font-mono);
|
|
96
|
+
font-size: 0.72em;
|
|
97
|
+
color: var(--color-muted);
|
|
98
|
+
background: var(--color-bg);
|
|
99
|
+
border-bottom: var(--border-default) solid var(--color-border);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.chat-list {
|
|
103
|
+
flex: 1;
|
|
104
|
+
overflow-y: auto;
|
|
105
|
+
padding: 1rem;
|
|
106
|
+
/* flex column stacks messages tight at the top — `display: grid`
|
|
107
|
+
* stretched the auto rows to fill the container and produced big
|
|
108
|
+
* gaps between messages. */
|
|
109
|
+
display: flex;
|
|
110
|
+
flex-direction: column;
|
|
111
|
+
gap: 0.85rem;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* Shown in place of .chat-list when BYOK is required but the learner
|
|
115
|
+
* hasn't set a key yet. Same visual weight as a message so it doesn't
|
|
116
|
+
* feel like an error — just a prompt to set things up first. */
|
|
117
|
+
.chat-setup {
|
|
118
|
+
flex: 1;
|
|
119
|
+
display: grid;
|
|
120
|
+
place-content: center;
|
|
121
|
+
gap: 0.6rem;
|
|
122
|
+
padding: 2rem 1.25rem;
|
|
123
|
+
text-align: center;
|
|
124
|
+
color: var(--color-muted);
|
|
125
|
+
}
|
|
126
|
+
.chat-setup h3 {
|
|
127
|
+
margin: 0;
|
|
128
|
+
font-size: 1rem;
|
|
129
|
+
color: var(--color-fg);
|
|
130
|
+
}
|
|
131
|
+
.chat-setup p {
|
|
132
|
+
margin: 0;
|
|
133
|
+
font-size: 0.9em;
|
|
134
|
+
line-height: 1.5;
|
|
135
|
+
max-width: 30ch;
|
|
136
|
+
justify-self: center;
|
|
137
|
+
}
|
|
138
|
+
.chat-setup button {
|
|
139
|
+
justify-self: center;
|
|
140
|
+
margin-top: 0.5rem;
|
|
141
|
+
background: var(--color-accent);
|
|
142
|
+
color: var(--color-accent-fg);
|
|
143
|
+
border: 0;
|
|
144
|
+
padding: 0.55rem 1.1rem;
|
|
145
|
+
font: inherit;
|
|
146
|
+
font-weight: 600;
|
|
147
|
+
cursor: pointer;
|
|
148
|
+
border-radius: 0;
|
|
149
|
+
}
|
|
150
|
+
.chat-setup button:hover {
|
|
151
|
+
background: color-mix(in oklab, var(--color-accent) 88%, white);
|
|
152
|
+
}
|
|
153
|
+
/* Each message is a stack of role label + content bubble. The user/
|
|
154
|
+
* assistant roles get distinct backgrounds + alignment so the eye can
|
|
155
|
+
* parse the transcript at a glance. */
|
|
156
|
+
.chat-msg { display: flex; flex-direction: column; gap: 0.25rem; max-width: 100%; }
|
|
157
|
+
.chat-msg-user { align-items: flex-end; }
|
|
158
|
+
.chat-msg-assistant { align-items: flex-start; }
|
|
159
|
+
|
|
160
|
+
.chat-role {
|
|
161
|
+
font-family: var(--font-mono);
|
|
162
|
+
font-size: 0.68em;
|
|
163
|
+
text-transform: uppercase;
|
|
164
|
+
letter-spacing: 0.06em;
|
|
165
|
+
color: var(--color-muted);
|
|
166
|
+
}
|
|
167
|
+
.chat-msg-user .chat-role { color: var(--color-accent); }
|
|
168
|
+
|
|
169
|
+
.chat-content {
|
|
170
|
+
font-size: 0.95em;
|
|
171
|
+
line-height: 1.55;
|
|
172
|
+
padding: 0.55rem 0.8rem;
|
|
173
|
+
max-width: 88%;
|
|
174
|
+
border-radius: 0;
|
|
175
|
+
}
|
|
176
|
+
/* Distinct enough on both light and dark themes:
|
|
177
|
+
* - user: bold accent tint + thin accent border
|
|
178
|
+
* - assistant: surface-2 (one tier brighter than page bg) + muted border
|
|
179
|
+
* 18% accent on a near-black page bg was effectively invisible. */
|
|
180
|
+
.chat-msg-user .chat-content {
|
|
181
|
+
background: color-mix(in oklab, var(--color-accent) 35%, var(--color-bg));
|
|
182
|
+
color: var(--color-fg);
|
|
183
|
+
border: 1px solid color-mix(in oklab, var(--color-accent) 55%, var(--color-bg));
|
|
184
|
+
/* user-typed text isn't markdown; preserve their line breaks */
|
|
185
|
+
white-space: pre-wrap;
|
|
186
|
+
}
|
|
187
|
+
.chat-msg-assistant .chat-content {
|
|
188
|
+
background: var(--color-surface-2);
|
|
189
|
+
color: var(--color-fg);
|
|
190
|
+
border: 1px solid var(--color-border);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* Markdown prose inside an assistant bubble. Tight margins so a single
|
|
194
|
+
* paragraph isn't padded into a wall, and a sane inherit on code blocks
|
|
195
|
+
* so they don't overflow the panel. */
|
|
196
|
+
.chat-msg-assistant .chat-content > :first-child { margin-top: 0; }
|
|
197
|
+
.chat-msg-assistant .chat-content > :last-child { margin-bottom: 0; }
|
|
198
|
+
.chat-msg-assistant .chat-content p { margin: 0.5em 0; }
|
|
199
|
+
.chat-msg-assistant .chat-content ul,
|
|
200
|
+
.chat-msg-assistant .chat-content ol { margin: 0.5em 0; padding-left: 1.25em; }
|
|
201
|
+
.chat-msg-assistant .chat-content li { margin: 0.15em 0; }
|
|
202
|
+
.chat-msg-assistant .chat-content h1,
|
|
203
|
+
.chat-msg-assistant .chat-content h2,
|
|
204
|
+
.chat-msg-assistant .chat-content h3 {
|
|
205
|
+
margin: 0.8em 0 0.35em;
|
|
206
|
+
font-size: 1em;
|
|
207
|
+
font-weight: 700;
|
|
208
|
+
}
|
|
209
|
+
.chat-msg-assistant .chat-content a {
|
|
210
|
+
color: var(--color-accent);
|
|
211
|
+
text-decoration: underline;
|
|
212
|
+
text-underline-offset: 2px;
|
|
213
|
+
}
|
|
214
|
+
.chat-msg-assistant .chat-content code {
|
|
215
|
+
font-family: var(--font-mono);
|
|
216
|
+
font-size: 0.9em;
|
|
217
|
+
padding: 0.05em 0.3em;
|
|
218
|
+
background: var(--color-surface-2);
|
|
219
|
+
border-radius: 0;
|
|
220
|
+
}
|
|
221
|
+
.chat-msg-assistant .chat-content pre {
|
|
222
|
+
margin: 0.5em 0;
|
|
223
|
+
padding: 0.6rem 0.75rem;
|
|
224
|
+
background: var(--color-surface-2);
|
|
225
|
+
overflow-x: auto;
|
|
226
|
+
font-family: var(--font-mono);
|
|
227
|
+
font-size: 0.85em;
|
|
228
|
+
line-height: 1.45;
|
|
229
|
+
border-radius: 0;
|
|
230
|
+
}
|
|
231
|
+
.chat-msg-assistant .chat-content pre code {
|
|
232
|
+
padding: 0;
|
|
233
|
+
background: transparent;
|
|
234
|
+
}
|
|
235
|
+
.chat-msg-assistant .chat-content blockquote {
|
|
236
|
+
margin: 0.5em 0;
|
|
237
|
+
padding-left: 0.7em;
|
|
238
|
+
border-left: var(--border-thick) solid var(--color-accent);
|
|
239
|
+
color: var(--color-muted);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/* Three-dot "thinking" indicator. Shown only between user-send and
|
|
243
|
+
* first stream chunk; once chunks land the message text replaces it. */
|
|
244
|
+
.chat-thinking {
|
|
245
|
+
display: inline-flex;
|
|
246
|
+
align-items: center;
|
|
247
|
+
gap: 0.25rem;
|
|
248
|
+
padding: 0.15rem 0.2rem;
|
|
249
|
+
}
|
|
250
|
+
.chat-thinking span {
|
|
251
|
+
display: inline-block;
|
|
252
|
+
width: 6px;
|
|
253
|
+
height: 6px;
|
|
254
|
+
border-radius: 50%;
|
|
255
|
+
background: var(--color-muted);
|
|
256
|
+
animation: chat-thinking-bounce 1.2s infinite ease-in-out both;
|
|
257
|
+
}
|
|
258
|
+
.chat-thinking span:nth-child(1) { animation-delay: -0.32s; }
|
|
259
|
+
.chat-thinking span:nth-child(2) { animation-delay: -0.16s; }
|
|
260
|
+
@keyframes chat-thinking-bounce {
|
|
261
|
+
0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
|
|
262
|
+
40% { transform: scale(1); opacity: 1; }
|
|
263
|
+
}
|
|
264
|
+
@media (prefers-reduced-motion: reduce) {
|
|
265
|
+
.chat-thinking span { animation: none; opacity: 0.7; }
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.chat-error {
|
|
269
|
+
padding: 0.5rem 0.75rem;
|
|
270
|
+
background: color-mix(in oklab, var(--color-danger) 18%, var(--color-bg));
|
|
271
|
+
color: var(--color-danger);
|
|
272
|
+
font-size: 0.85em;
|
|
273
|
+
border-radius: 0;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.chat-input {
|
|
277
|
+
display: flex;
|
|
278
|
+
gap: 0.5rem;
|
|
279
|
+
padding: 0.85rem 1rem;
|
|
280
|
+
border-top: var(--border-default) solid var(--color-border);
|
|
281
|
+
background: var(--color-surface);
|
|
282
|
+
}
|
|
283
|
+
.chat-input input {
|
|
284
|
+
flex: 1;
|
|
285
|
+
background: var(--color-bg);
|
|
286
|
+
border: var(--border-default) solid var(--color-border);
|
|
287
|
+
color: var(--color-fg);
|
|
288
|
+
padding: 0.55rem 0.75rem;
|
|
289
|
+
font: inherit;
|
|
290
|
+
outline: none;
|
|
291
|
+
border-radius: 0;
|
|
292
|
+
}
|
|
293
|
+
.chat-input input:focus { border-color: var(--color-accent); }
|
|
294
|
+
.chat-input button {
|
|
295
|
+
background: var(--color-accent);
|
|
296
|
+
color: var(--color-accent-fg);
|
|
297
|
+
border: 0;
|
|
298
|
+
padding: 0 0.9rem;
|
|
299
|
+
cursor: pointer;
|
|
300
|
+
display: grid;
|
|
301
|
+
place-items: center;
|
|
302
|
+
border-radius: 0;
|
|
303
|
+
}
|
|
304
|
+
.chat-input button:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/* Checkpoint */
|
|
2
|
+
.checkpoint {
|
|
3
|
+
display: flex;
|
|
4
|
+
align-items: center;
|
|
5
|
+
gap: 0.85rem;
|
|
6
|
+
margin: 1.5rem 0;
|
|
7
|
+
padding: 0.85rem 1.1rem;
|
|
8
|
+
background: var(--color-surface);
|
|
9
|
+
border: var(--border-default) solid var(--color-border);
|
|
10
|
+
border-left: var(--border-thick) solid var(--color-accent);
|
|
11
|
+
border-radius: 0;
|
|
12
|
+
}
|
|
13
|
+
.checkpoint button {
|
|
14
|
+
display: inline-flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
gap: 0.7rem;
|
|
17
|
+
background: none;
|
|
18
|
+
border: 0;
|
|
19
|
+
font: inherit;
|
|
20
|
+
color: var(--color-fg);
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
text-align: left;
|
|
23
|
+
}
|
|
24
|
+
.checkpoint-box {
|
|
25
|
+
width: 20px;
|
|
26
|
+
height: 20px;
|
|
27
|
+
border: var(--border-default) solid var(--color-border-strong);
|
|
28
|
+
display: inline-grid;
|
|
29
|
+
place-items: center;
|
|
30
|
+
background: var(--color-bg);
|
|
31
|
+
border-radius: 0;
|
|
32
|
+
flex-shrink: 0;
|
|
33
|
+
}
|
|
34
|
+
.checkpoint.is-done {
|
|
35
|
+
border-left-color: var(--color-success);
|
|
36
|
+
}
|
|
37
|
+
.checkpoint.is-done .checkpoint-box {
|
|
38
|
+
background: var(--color-success);
|
|
39
|
+
border-color: var(--color-success);
|
|
40
|
+
color: var(--color-bg);
|
|
41
|
+
}
|
|
42
|
+
.checkpoint-msg {
|
|
43
|
+
font-family: var(--font-mono);
|
|
44
|
+
font-size: 0.75em;
|
|
45
|
+
color: var(--color-success);
|
|
46
|
+
text-transform: uppercase;
|
|
47
|
+
letter-spacing: 0.06em;
|
|
48
|
+
margin-left: auto;
|
|
49
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/* Diff */
|
|
2
|
+
.diff {
|
|
3
|
+
margin: 1.25rem 0;
|
|
4
|
+
background: var(--color-surface);
|
|
5
|
+
font-family: var(--font-mono);
|
|
6
|
+
font-size: 0.85em;
|
|
7
|
+
border-radius: 0;
|
|
8
|
+
overflow: hidden;
|
|
9
|
+
}
|
|
10
|
+
.diff-bar {
|
|
11
|
+
display: flex;
|
|
12
|
+
justify-content: flex-end;
|
|
13
|
+
padding: 0.4rem 0.6rem;
|
|
14
|
+
background: var(--color-surface-2);
|
|
15
|
+
}
|
|
16
|
+
.diff-bar button {
|
|
17
|
+
background: transparent;
|
|
18
|
+
border: var(--border-default) solid var(--color-border);
|
|
19
|
+
color: var(--color-muted);
|
|
20
|
+
padding: 0.2rem 0.6rem;
|
|
21
|
+
font: inherit;
|
|
22
|
+
cursor: pointer;
|
|
23
|
+
border-radius: 0;
|
|
24
|
+
}
|
|
25
|
+
.diff-bar button:hover { color: var(--color-fg); border-color: var(--color-border-strong); }
|
|
26
|
+
.diff-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; background: var(--color-border); }
|
|
27
|
+
.diff-col { background: var(--color-surface); }
|
|
28
|
+
.diff-label {
|
|
29
|
+
padding: 0.4rem 0.6rem;
|
|
30
|
+
background: var(--color-surface-2);
|
|
31
|
+
color: var(--color-muted);
|
|
32
|
+
font-size: 0.78em;
|
|
33
|
+
text-transform: uppercase;
|
|
34
|
+
letter-spacing: 0.06em;
|
|
35
|
+
}
|
|
36
|
+
.diff pre {
|
|
37
|
+
margin: 0;
|
|
38
|
+
padding: 0.6rem 0.8rem;
|
|
39
|
+
white-space: pre;
|
|
40
|
+
overflow-x: auto;
|
|
41
|
+
}
|
|
42
|
+
.diff-add { background: color-mix(in oklab, var(--color-success) 14%, transparent); color: var(--color-success); display: block; }
|
|
43
|
+
.diff-del { background: color-mix(in oklab, var(--color-danger) 14%, transparent); color: var(--color-danger); display: block; }
|
|
44
|
+
.diff-ctx { color: var(--color-muted); display: block; }
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/* Expressive Code bridge — pull every frame surface onto the page's
|
|
2
|
+
* palette so code blocks read as part of the page rather than a competing
|
|
3
|
+
* dark-blue island. EC's own selector
|
|
4
|
+
* `:root:not([data-theme='github-dark']) .expressive-code[data-theme='github-dark']`
|
|
5
|
+
* has specificity 0,4,0; rather than out-specifying the theme everywhere,
|
|
6
|
+
* we mark these bridge values !important since they're deliberate
|
|
7
|
+
* cross-cutting overrides.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/* Render EC's auto-detected terminal frames (bash/sh/zsh fences) as
|
|
11
|
+
* regular file-titled code blocks. The <Terminal> MDX component is the
|
|
12
|
+
* only place that gets the traffic-light dots look. */
|
|
13
|
+
.expressive-code .frame.is-terminal {
|
|
14
|
+
--code-background: var(--color-surface);
|
|
15
|
+
}
|
|
16
|
+
.expressive-code .frame.is-terminal pre {
|
|
17
|
+
background: var(--color-surface);
|
|
18
|
+
}
|
|
19
|
+
.expressive-code .frame.is-terminal .header {
|
|
20
|
+
display: flex;
|
|
21
|
+
justify-content: flex-start;
|
|
22
|
+
align-items: center;
|
|
23
|
+
background: var(--color-surface-2);
|
|
24
|
+
color: var(--color-fg);
|
|
25
|
+
border: 0;
|
|
26
|
+
border-top: 2px solid var(--color-accent);
|
|
27
|
+
border-bottom: var(--border-default) solid var(--color-border);
|
|
28
|
+
font-family: var(--font-mono);
|
|
29
|
+
font-size: 0.78em;
|
|
30
|
+
font-weight: 500;
|
|
31
|
+
text-transform: lowercase;
|
|
32
|
+
letter-spacing: 0;
|
|
33
|
+
padding: 0.45rem 0.9rem;
|
|
34
|
+
}
|
|
35
|
+
.expressive-code .frame.is-terminal .header::before,
|
|
36
|
+
.expressive-code .frame.is-terminal .header::after {
|
|
37
|
+
display: none;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
:root {
|
|
41
|
+
--ec-codeBg: var(--color-surface) !important;
|
|
42
|
+
--ec-codeFg: var(--color-fg) !important;
|
|
43
|
+
--ec-frm-edBg: var(--color-surface) !important;
|
|
44
|
+
/* Active tab: lifted (brighter than the code body), accent on top,
|
|
45
|
+
* no bottom indicator — mirrors our Tabs.astro active state. */
|
|
46
|
+
--ec-frm-edActTabBg: var(--color-surface-2) !important;
|
|
47
|
+
--ec-frm-edActTabFg: var(--color-fg) !important;
|
|
48
|
+
--ec-frm-edActTabIndTopCol: var(--color-accent) !important;
|
|
49
|
+
--ec-frm-edActTabIndBtmCol: transparent !important;
|
|
50
|
+
--ec-frm-edActTabIndHt: 2px !important;
|
|
51
|
+
--ec-frm-edTabBarBg: transparent !important;
|
|
52
|
+
--ec-frm-edTabBarBrdBtmCol: var(--color-border) !important;
|
|
53
|
+
--ec-frm-edTabBarBrdCol: transparent !important;
|
|
54
|
+
--ec-frm-trmBg: var(--color-surface) !important;
|
|
55
|
+
--ec-frm-trmTtbBg: var(--color-surface-2) !important;
|
|
56
|
+
--ec-frm-frameBoxShdCssVal: none !important;
|
|
57
|
+
/* Keep a thin 1px outline so code blocks have a perceptible shape on
|
|
58
|
+
* the pure-black page bg. */
|
|
59
|
+
--ec-brdCol: var(--color-border) !important;
|
|
60
|
+
--ec-brdWd: 1px !important;
|
|
61
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/* FileTree
|
|
2
|
+
*
|
|
3
|
+
* Inside the MDX `prose` wrapper, ul/li get article-sized vertical
|
|
4
|
+
* margins — that's what was producing the huge gaps between rows.
|
|
5
|
+
* Reset everything inside .ft-root explicitly so prose can't leak in,
|
|
6
|
+
* and use CSS-only indentation with a guide line per nesting level.
|
|
7
|
+
*/
|
|
8
|
+
/* Reset list defaults. .ft-list (nested ul) gets margin: 0 here too —
|
|
9
|
+
* leaving it out lets the browser-default `margin: 1em 0` add a fat
|
|
10
|
+
* gap between folders. Padding stays separate so .ft-list can use
|
|
11
|
+
* padding-left for the indent below. */
|
|
12
|
+
.ft-root,
|
|
13
|
+
.ft-root li,
|
|
14
|
+
.ft-list {
|
|
15
|
+
list-style: none;
|
|
16
|
+
margin: 0;
|
|
17
|
+
}
|
|
18
|
+
.ft-root,
|
|
19
|
+
.ft-root li {
|
|
20
|
+
padding: 0;
|
|
21
|
+
}
|
|
22
|
+
.ft-root {
|
|
23
|
+
/* prose margin override for the outer block */
|
|
24
|
+
margin: 1.25rem 0;
|
|
25
|
+
padding: 0.65rem 0.85rem;
|
|
26
|
+
background: var(--color-surface);
|
|
27
|
+
font-family: var(--font-mono);
|
|
28
|
+
font-size: 0.85em;
|
|
29
|
+
/* one consistent rhythm for every row */
|
|
30
|
+
line-height: 1.65;
|
|
31
|
+
border: 1px solid var(--color-border);
|
|
32
|
+
border-radius: 0;
|
|
33
|
+
}
|
|
34
|
+
/* Nested lists step in ~0.7rem per level with a faint guide line. */
|
|
35
|
+
.ft-list {
|
|
36
|
+
padding-left: 0.7rem;
|
|
37
|
+
margin-left: 0.25rem;
|
|
38
|
+
border-left: 1px solid color-mix(in oklab, var(--color-border) 70%, transparent);
|
|
39
|
+
}
|
|
40
|
+
.ft-row { display: block; }
|
|
41
|
+
.ft-btn {
|
|
42
|
+
display: inline-flex;
|
|
43
|
+
align-items: center;
|
|
44
|
+
gap: 0.4rem;
|
|
45
|
+
background: none;
|
|
46
|
+
border: 0;
|
|
47
|
+
color: var(--color-fg);
|
|
48
|
+
font: inherit;
|
|
49
|
+
padding: 0.05rem 0.35rem;
|
|
50
|
+
cursor: pointer;
|
|
51
|
+
border-radius: 0;
|
|
52
|
+
text-align: left;
|
|
53
|
+
}
|
|
54
|
+
.ft-btn:hover {
|
|
55
|
+
background: var(--color-surface-2);
|
|
56
|
+
color: var(--color-accent);
|
|
57
|
+
}
|
|
58
|
+
/* Keyboard nav shows the focus ring; mouse clicks don't leave it
|
|
59
|
+
* stuck after the click finishes. */
|
|
60
|
+
.ft-btn:focus-visible {
|
|
61
|
+
outline: 1px solid var(--color-accent);
|
|
62
|
+
outline-offset: 1px;
|
|
63
|
+
}
|
|
64
|
+
.ft-btn:focus:not(:focus-visible) {
|
|
65
|
+
outline: none;
|
|
66
|
+
}
|
|
67
|
+
.ft-btn svg { flex-shrink: 0; }
|
|
68
|
+
.ft-name { user-select: text; }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/* Mermaid */
|
|
2
|
+
.mermaid-wrap {
|
|
3
|
+
margin: 1.25rem 0;
|
|
4
|
+
padding: 1rem;
|
|
5
|
+
background: var(--color-surface);
|
|
6
|
+
display: grid;
|
|
7
|
+
place-items: center;
|
|
8
|
+
border-radius: 0;
|
|
9
|
+
}
|
|
10
|
+
.mermaid-wrap :global(svg) { max-width: 100%; height: auto; }
|
|
11
|
+
.mermaid-error {
|
|
12
|
+
padding: 0.75rem;
|
|
13
|
+
background: color-mix(in oklab, var(--color-danger) 14%, var(--color-bg));
|
|
14
|
+
color: var(--color-danger);
|
|
15
|
+
font-family: var(--font-mono);
|
|
16
|
+
font-size: 0.85em;
|
|
17
|
+
white-space: pre-wrap;
|
|
18
|
+
border-radius: 0;
|
|
19
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/* Modal overlay + close button (shared by AI chat + BYOK dialogs) */
|
|
2
|
+
/* Used only by BYOK (chat is non-modal). Soft dim, no blur — the
|
|
3
|
+
* blur made the tutorial text behind unreadable and gave the
|
|
4
|
+
* impression the assistant was hijacking the page. */
|
|
5
|
+
.ms-overlay {
|
|
6
|
+
position: fixed;
|
|
7
|
+
inset: 0;
|
|
8
|
+
background: oklch(0% 0 0 / 0.35);
|
|
9
|
+
}
|
|
10
|
+
.ms-close {
|
|
11
|
+
position: absolute;
|
|
12
|
+
top: 0.5rem;
|
|
13
|
+
right: 0.5rem;
|
|
14
|
+
background: transparent;
|
|
15
|
+
border: 0;
|
|
16
|
+
color: var(--color-muted);
|
|
17
|
+
width: 32px;
|
|
18
|
+
height: 32px;
|
|
19
|
+
display: grid;
|
|
20
|
+
place-items: center;
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
z-index: 1;
|
|
23
|
+
border-radius: 0;
|
|
24
|
+
}
|
|
25
|
+
.ms-close:hover { color: var(--color-fg); background: var(--color-surface); }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/* Sidebar progress bar */
|
|
2
|
+
.progress {
|
|
3
|
+
display: grid;
|
|
4
|
+
gap: 0.35rem;
|
|
5
|
+
margin: 0.75rem 0;
|
|
6
|
+
font-family: var(--font-mono);
|
|
7
|
+
font-size: 0.72em;
|
|
8
|
+
color: var(--color-muted);
|
|
9
|
+
}
|
|
10
|
+
.progress-bar {
|
|
11
|
+
height: 6px;
|
|
12
|
+
background: var(--color-surface);
|
|
13
|
+
border: var(--border-default) solid var(--color-border);
|
|
14
|
+
}
|
|
15
|
+
.progress-fill {
|
|
16
|
+
height: 100%;
|
|
17
|
+
background: var(--color-accent);
|
|
18
|
+
transition: width 0.2s ease;
|
|
19
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* Quiz */
|
|
2
|
+
.quiz {
|
|
3
|
+
margin: 1.5rem 0;
|
|
4
|
+
padding: 1.25rem 1.4rem;
|
|
5
|
+
background: var(--color-surface);
|
|
6
|
+
border: var(--border-default) solid var(--color-border);
|
|
7
|
+
border-radius: 0;
|
|
8
|
+
}
|
|
9
|
+
.quiz-q {
|
|
10
|
+
font-weight: 600;
|
|
11
|
+
padding: 0;
|
|
12
|
+
margin-bottom: 1rem;
|
|
13
|
+
font-size: 1.02em;
|
|
14
|
+
}
|
|
15
|
+
.quiz-options { display: grid; gap: 0.5rem; }
|
|
16
|
+
.quiz-opt {
|
|
17
|
+
display: flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
gap: 0.75rem;
|
|
20
|
+
padding: 0.7rem 0.95rem;
|
|
21
|
+
background: var(--color-surface-2);
|
|
22
|
+
border: 0;
|
|
23
|
+
cursor: pointer;
|
|
24
|
+
border-radius: 0;
|
|
25
|
+
transition: background 0.12s ease;
|
|
26
|
+
position: relative;
|
|
27
|
+
}
|
|
28
|
+
.quiz-opt:hover {
|
|
29
|
+
background: color-mix(in oklab, var(--color-fg) 8%, var(--color-surface-2));
|
|
30
|
+
}
|
|
31
|
+
/* Hide the native input but keep it accessible */
|
|
32
|
+
.quiz-opt input {
|
|
33
|
+
position: absolute;
|
|
34
|
+
opacity: 0;
|
|
35
|
+
pointer-events: none;
|
|
36
|
+
width: 1px; height: 1px;
|
|
37
|
+
}
|
|
38
|
+
/* Custom toggle indicator — visible on every option, fills on chosen */
|
|
39
|
+
.quiz-opt-toggle {
|
|
40
|
+
width: 16px;
|
|
41
|
+
height: 16px;
|
|
42
|
+
flex-shrink: 0;
|
|
43
|
+
border: var(--border-default) solid var(--color-border-strong);
|
|
44
|
+
background: var(--color-bg);
|
|
45
|
+
position: relative;
|
|
46
|
+
transition: border-color 0.12s ease, background 0.12s ease;
|
|
47
|
+
}
|
|
48
|
+
/* Both single and multi use a square box — keep the brutalist no-radius rule. */
|
|
49
|
+
.quiz-opt-toggle--single,
|
|
50
|
+
.quiz-opt-toggle--multi { border-radius: 0; }
|
|
51
|
+
.quiz-opt-toggle--single::after,
|
|
52
|
+
.quiz-opt-toggle--multi::after {
|
|
53
|
+
content: "";
|
|
54
|
+
position: absolute;
|
|
55
|
+
inset: 3px;
|
|
56
|
+
background: var(--color-accent);
|
|
57
|
+
opacity: 0;
|
|
58
|
+
transition: opacity 0.12s ease;
|
|
59
|
+
}
|
|
60
|
+
.quiz-opt.is-chosen .quiz-opt-toggle {
|
|
61
|
+
border-color: var(--color-accent);
|
|
62
|
+
}
|
|
63
|
+
.quiz-opt.is-chosen .quiz-opt-toggle::after { opacity: 1; }
|
|
64
|
+
.quiz-opt.is-correct {
|
|
65
|
+
background: color-mix(in oklab, var(--color-success) 18%, var(--color-surface));
|
|
66
|
+
}
|
|
67
|
+
.quiz-opt.is-correct .quiz-opt-toggle { border-color: var(--color-success); background: var(--color-success); color: var(--color-bg); }
|
|
68
|
+
.quiz-opt.is-wrong {
|
|
69
|
+
background: color-mix(in oklab, var(--color-danger) 16%, var(--color-surface));
|
|
70
|
+
}
|
|
71
|
+
.quiz-opt.is-wrong .quiz-opt-toggle { border-color: var(--color-danger); background: var(--color-danger); color: var(--color-bg); }
|
|
72
|
+
.quiz-marker {
|
|
73
|
+
display: inline-grid;
|
|
74
|
+
place-items: center;
|
|
75
|
+
width: 14px;
|
|
76
|
+
height: 14px;
|
|
77
|
+
}
|
|
78
|
+
.quiz-actions { display: flex; align-items: center; gap: 0.85rem; margin-top: 1rem; }
|
|
79
|
+
.quiz-actions button {
|
|
80
|
+
padding: 0.55rem 1.2rem;
|
|
81
|
+
background: var(--color-accent);
|
|
82
|
+
color: var(--color-accent-fg);
|
|
83
|
+
border: 0;
|
|
84
|
+
font-weight: 600;
|
|
85
|
+
cursor: pointer;
|
|
86
|
+
border-radius: 0;
|
|
87
|
+
}
|
|
88
|
+
.quiz-actions button:hover:not(:disabled) {
|
|
89
|
+
background: color-mix(in oklab, var(--color-accent) 88%, white);
|
|
90
|
+
}
|
|
91
|
+
.quiz-actions button:disabled { opacity: 0.35; cursor: not-allowed; }
|
|
92
|
+
.quiz-msg.ok { color: var(--color-success); font-weight: 600; }
|
|
93
|
+
.quiz-msg.no { color: var(--color-danger); font-weight: 600; }
|
|
94
|
+
.quiz-exp {
|
|
95
|
+
margin-top: 1rem;
|
|
96
|
+
padding: 0.75rem 0.95rem;
|
|
97
|
+
background: var(--color-bg);
|
|
98
|
+
font-size: 0.92em;
|
|
99
|
+
border-radius: 0;
|
|
100
|
+
border-left: var(--border-thick) solid var(--color-accent);
|
|
101
|
+
}
|