andy-note-nuxt 0.2.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/LICENSE +21 -0
- package/README.md +127 -0
- package/app/.claude/skills/ai-annotator/SKILL.md +31 -0
- package/app/app.config.ts +20 -0
- package/app/app.vue +7 -0
- package/app/assets/css/main.css +609 -0
- package/app/components/ContentView.vue +838 -0
- package/app/components/LocalStorageChecklist.vue +372 -0
- package/app/components/StackedColumn.vue +81 -0
- package/app/components/StackedColumns.vue +216 -0
- package/app/composables/useStack.ts +331 -0
- package/app/layouts/default.vue +22 -0
- package/app/pages/[...slug].vue +3 -0
- package/app/types/app-config.d.ts +19 -0
- package/content/index.md +25 -0
- package/content/license.md +62 -0
- package/nuxt.config.ts +76 -0
- package/package.json +55 -0
- package/tailwind.config.js +64 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
/* =================================================================
|
|
2
|
+
* andy-note-nuxt — brutalist terminal theme
|
|
3
|
+
* Tailwind v3 base + custom @layer additions for prose / list rows.
|
|
4
|
+
* Tokens defined in tailwind.config.js; CSS vars below are kept only
|
|
5
|
+
* for inline-style usage in stacked-column layout (--column-width etc.)
|
|
6
|
+
* ================================================================= */
|
|
7
|
+
|
|
8
|
+
/* Fonts — @import MUST precede @tailwind directives per CSS spec.
|
|
9
|
+
Local @fontsource packages = self-host (no FOUT, no third-party request). */
|
|
10
|
+
@import "@fontsource/space-grotesk/300.css";
|
|
11
|
+
@import "@fontsource/space-grotesk/400.css";
|
|
12
|
+
@import "@fontsource/space-grotesk/500.css";
|
|
13
|
+
@import "@fontsource/space-grotesk/600.css";
|
|
14
|
+
@import "@fontsource/space-grotesk/700.css";
|
|
15
|
+
@import "@fontsource/literata/400.css";
|
|
16
|
+
@import "@fontsource/literata/400-italic.css";
|
|
17
|
+
@import "@fontsource/literata/600.css";
|
|
18
|
+
@import "@fontsource/literata/700.css";
|
|
19
|
+
|
|
20
|
+
@tailwind base;
|
|
21
|
+
@tailwind components;
|
|
22
|
+
@tailwind utilities;
|
|
23
|
+
|
|
24
|
+
@layer base {
|
|
25
|
+
/* Layout/spacing tokens that components reference via inline style. */
|
|
26
|
+
:root {
|
|
27
|
+
--column-width: 670px;
|
|
28
|
+
--column-min-width: 540px;
|
|
29
|
+
--content-width: 720px;
|
|
30
|
+
--content-width-wide: 960px;
|
|
31
|
+
|
|
32
|
+
/* Width of the peek strip when an earlier column is sticky-stacked at
|
|
33
|
+
the left edge (Andy Matuschak / Stripe-docs pattern). 48px = visible
|
|
34
|
+
border + a few characters of section header, and ≥ minimum tap target
|
|
35
|
+
(44px) so peek strips are clickable on touch devices. */
|
|
36
|
+
--stack-peek: 48px;
|
|
37
|
+
|
|
38
|
+
--space-1: 0.25rem;
|
|
39
|
+
--space-2: 0.5rem;
|
|
40
|
+
--space-3: 0.75rem;
|
|
41
|
+
--space-4: 1rem;
|
|
42
|
+
--space-5: 1.5rem;
|
|
43
|
+
--space-6: 2rem;
|
|
44
|
+
--space-7: 2.5rem;
|
|
45
|
+
--space-8: 3rem;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
49
|
+
* { margin: 0; padding: 0; }
|
|
50
|
+
|
|
51
|
+
html {
|
|
52
|
+
-webkit-text-size-adjust: 100%;
|
|
53
|
+
scroll-behavior: smooth;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
body {
|
|
57
|
+
@apply bg-terminal-bg text-terminal-text font-display;
|
|
58
|
+
-webkit-font-smoothing: antialiased;
|
|
59
|
+
-moz-osx-font-smoothing: grayscale;
|
|
60
|
+
text-rendering: optimizeLegibility;
|
|
61
|
+
min-height: 100vh;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
h1, h2, h3, h4, h5, h6 {
|
|
65
|
+
@apply font-display font-bold uppercase tracking-tight;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
::selection { @apply bg-primary text-terminal-bg; }
|
|
69
|
+
::-moz-selection { @apply bg-primary text-terminal-bg; }
|
|
70
|
+
|
|
71
|
+
/* Thin scrollbars with coral hover — matches the terminal accent. */
|
|
72
|
+
* {
|
|
73
|
+
scrollbar-width: thin;
|
|
74
|
+
scrollbar-color: theme('colors.terminal.border') transparent;
|
|
75
|
+
}
|
|
76
|
+
::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
77
|
+
::-webkit-scrollbar-track { @apply bg-transparent; }
|
|
78
|
+
::-webkit-scrollbar-thumb { @apply bg-terminal-border; }
|
|
79
|
+
::-webkit-scrollbar-thumb:hover { @apply bg-primary; }
|
|
80
|
+
::-webkit-scrollbar-corner { @apply bg-transparent; }
|
|
81
|
+
|
|
82
|
+
img, svg, video { max-width: 100%; height: auto; display: block; }
|
|
83
|
+
button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }
|
|
84
|
+
input, select, textarea { font: inherit; color: inherit; }
|
|
85
|
+
|
|
86
|
+
/* Tabular numerals for stat tables, prices, comparison columns. */
|
|
87
|
+
table, time, code, .tabular-nums {
|
|
88
|
+
font-variant-numeric: tabular-nums;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@layer components {
|
|
93
|
+
/* ---------- Brutalist primitives ---------- */
|
|
94
|
+
|
|
95
|
+
/* Section header pinned above the column's scroll body via flex layout.
|
|
96
|
+
Lives outside the overflow container, so the scrollbar never overlaps it. */
|
|
97
|
+
.section-card-header {
|
|
98
|
+
@apply px-4 py-3 border-b-[3px] border-terminal-border bg-terminal-bg;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* Copy + AI-deep-link split button — true button-group pattern. The
|
|
102
|
+
outer `.copy-actions` wrapper owns the border, background, and focus
|
|
103
|
+
stamp-shadow; child `.copy-btn` halves are border-less segments
|
|
104
|
+
separated only by an internal vertical divider. This eliminates the
|
|
105
|
+
double-border seam that earlier `margin: -1px` hacks couldn't fully
|
|
106
|
+
hide. Primary half carries the "Copy" / "Copied" / "Failed" text
|
|
107
|
+
label; `.copy-btn--menu` is the attached caret half opening
|
|
108
|
+
`.copy-menu`. `data-state` flips the primary half to a filled
|
|
109
|
+
coral affordance during the brief success window. */
|
|
110
|
+
.copy-actions {
|
|
111
|
+
@apply relative inline-flex items-stretch shrink-0 border border-terminal-border bg-terminal-bg transition-colors;
|
|
112
|
+
}
|
|
113
|
+
/* Group-level focus ring — single stamp shadow on the wrapper instead
|
|
114
|
+
of per-button shadows, so the focused half doesn't "lift" away from
|
|
115
|
+
its sibling and break the fused appearance. */
|
|
116
|
+
.copy-actions:focus-within {
|
|
117
|
+
@apply border-primary;
|
|
118
|
+
box-shadow: 2px 2px 0 0 theme('colors.primary');
|
|
119
|
+
}
|
|
120
|
+
/* Borderless segment. Width is fixed at the widest label ("Copied" /
|
|
121
|
+
"Failed", 6 chars in mono bold 10px) so "Copy" doesn't reflow when
|
|
122
|
+
state flips and the pair stays balanced with the icon-narrow caret. */
|
|
123
|
+
.copy-btn {
|
|
124
|
+
@apply inline-flex items-center justify-center text-terminal-text-secondary cursor-pointer transition-colors;
|
|
125
|
+
background: transparent;
|
|
126
|
+
border: 0;
|
|
127
|
+
height: 22px;
|
|
128
|
+
width: 48px;
|
|
129
|
+
padding: 0;
|
|
130
|
+
}
|
|
131
|
+
/* Internal divider — paints on the trailing edge of every segment
|
|
132
|
+
except the last, so it shows once between Copy and the caret half
|
|
133
|
+
and never leaks outside the group. */
|
|
134
|
+
.copy-btn:not(:last-child) {
|
|
135
|
+
border-right: 1px solid theme('colors.terminal.border');
|
|
136
|
+
}
|
|
137
|
+
.copy-btn--menu {
|
|
138
|
+
width: 22px;
|
|
139
|
+
}
|
|
140
|
+
.copy-btn__label {
|
|
141
|
+
@apply font-mono font-bold uppercase tracking-tight;
|
|
142
|
+
font-size: 10px;
|
|
143
|
+
line-height: 1;
|
|
144
|
+
}
|
|
145
|
+
.copy-btn__caret {
|
|
146
|
+
width: 10px;
|
|
147
|
+
height: 7px;
|
|
148
|
+
display: block;
|
|
149
|
+
transition: transform 140ms ease;
|
|
150
|
+
}
|
|
151
|
+
.copy-btn__caret--open {
|
|
152
|
+
transform: rotate(180deg);
|
|
153
|
+
}
|
|
154
|
+
.copy-btn:hover,
|
|
155
|
+
.copy-btn[aria-expanded='true'] {
|
|
156
|
+
@apply text-primary;
|
|
157
|
+
}
|
|
158
|
+
.copy-btn:focus-visible {
|
|
159
|
+
@apply outline-none text-primary;
|
|
160
|
+
}
|
|
161
|
+
.copy-btn[data-state='copied'] {
|
|
162
|
+
@apply bg-primary text-terminal-bg;
|
|
163
|
+
}
|
|
164
|
+
.copy-btn[data-state='error'] {
|
|
165
|
+
@apply text-terminal-text-muted;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* Dropdown menu — sharp-edged, stamp-shadow, mono. Teleported to <body>
|
|
169
|
+
so it escapes any column overflow clip; positioning comes from inline
|
|
170
|
+
styles written by Floating-UI's `useFloating`. We only own the visual
|
|
171
|
+
surface (border, shadow, palette, sizing) — never the coords. z-index
|
|
172
|
+
sits above stacked-column borders, which top out near 20–30 in practice. */
|
|
173
|
+
.copy-menu {
|
|
174
|
+
@apply bg-terminal-bg border-2 border-terminal-border;
|
|
175
|
+
position: absolute;
|
|
176
|
+
min-width: 168px;
|
|
177
|
+
box-shadow: 4px 4px 0 0 theme('colors.primary');
|
|
178
|
+
z-index: 100;
|
|
179
|
+
}
|
|
180
|
+
.copy-menu__item {
|
|
181
|
+
@apply flex items-center gap-2 px-3 py-2 font-mono text-[11px] font-bold uppercase tracking-widest text-terminal-text transition-colors no-underline;
|
|
182
|
+
}
|
|
183
|
+
.copy-menu__item + .copy-menu__item {
|
|
184
|
+
@apply border-t border-terminal-border;
|
|
185
|
+
}
|
|
186
|
+
.copy-menu__item:hover {
|
|
187
|
+
@apply bg-primary text-terminal-bg;
|
|
188
|
+
}
|
|
189
|
+
.copy-menu__arrow {
|
|
190
|
+
@apply text-terminal-text-faint;
|
|
191
|
+
}
|
|
192
|
+
.copy-menu__item:hover .copy-menu__arrow {
|
|
193
|
+
@apply text-terminal-bg;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* Dotted leader (between list-row title and counter). 2px dashes,
|
|
197
|
+
positioned slightly above baseline so it visually aligns with letter midline. */
|
|
198
|
+
.dotted-leader {
|
|
199
|
+
flex-grow: 1;
|
|
200
|
+
border-bottom: 2px dotted theme('colors.terminal.border');
|
|
201
|
+
margin: 0 0.75rem;
|
|
202
|
+
position: relative;
|
|
203
|
+
top: -4px;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* Hover-stamp effect on list items — outline + offset shadow on hover. */
|
|
207
|
+
.terminal-item:hover .title-text {
|
|
208
|
+
outline: 2px solid theme('colors.primary');
|
|
209
|
+
box-shadow: 4px 4px 0px 0px theme('colors.primary');
|
|
210
|
+
background: transparent;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/* Stacked-column trail highlight — when a list row's target path is open
|
|
214
|
+
as a deeper column in the stack, mark it with persistent filled bg so
|
|
215
|
+
the user can see which row led to which column (Miller-column UX). */
|
|
216
|
+
.terminal-item--active .title-text {
|
|
217
|
+
@apply bg-primary text-terminal-bg;
|
|
218
|
+
}
|
|
219
|
+
.terminal-item--active .dotted-leader {
|
|
220
|
+
border-bottom-color: theme('colors.primary');
|
|
221
|
+
}
|
|
222
|
+
.terminal-item--active:hover .title-text {
|
|
223
|
+
/* Hover on already-active item: keep filled bg + inverted text, add offset
|
|
224
|
+
shadow as click-affordance hint. Must re-declare bg/color because the
|
|
225
|
+
generic `.terminal-item:hover .title-text` rule has equal specificity
|
|
226
|
+
and resets `background: transparent` — without these, text becomes
|
|
227
|
+
terminal-bg on transparent (= invisible against the dark page). */
|
|
228
|
+
@apply bg-primary text-terminal-bg;
|
|
229
|
+
outline: 2px solid theme('colors.primary');
|
|
230
|
+
box-shadow: 4px 4px 0px 0px theme('colors.primary');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/* List row used in section listings (sections + articles). */
|
|
234
|
+
.list-row {
|
|
235
|
+
@apply flex flex-col gap-1 py-3 px-4 border-b border-terminal-border transition-colors;
|
|
236
|
+
}
|
|
237
|
+
.list-row:hover {
|
|
238
|
+
@apply bg-terminal-surface-0;
|
|
239
|
+
}
|
|
240
|
+
.list-row__title {
|
|
241
|
+
@apply text-base font-bold uppercase tracking-tight text-terminal-text transition-colors;
|
|
242
|
+
}
|
|
243
|
+
.list-row:hover .list-row__title {
|
|
244
|
+
@apply text-primary;
|
|
245
|
+
}
|
|
246
|
+
.list-row__description {
|
|
247
|
+
@apply text-sm text-terminal-text-muted normal-case;
|
|
248
|
+
font-family: 'Literata', Georgia, serif;
|
|
249
|
+
text-transform: none;
|
|
250
|
+
letter-spacing: 0;
|
|
251
|
+
}
|
|
252
|
+
.list-row__meta {
|
|
253
|
+
@apply text-[10px] text-terminal-text-faint uppercase tracking-widest font-mono mt-1 flex flex-wrap gap-x-3;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/* Tag badge — bracketed, mono, hover coral. */
|
|
257
|
+
.tag-badge {
|
|
258
|
+
@apply inline-block text-[10px] font-bold font-mono uppercase tracking-widest px-2 py-1 border-2 border-terminal-border text-terminal-text-secondary transition-colors;
|
|
259
|
+
}
|
|
260
|
+
.tag-badge::before {
|
|
261
|
+
content: '#';
|
|
262
|
+
@apply text-terminal-text-faint mr-0.5;
|
|
263
|
+
}
|
|
264
|
+
.tag-badge:hover {
|
|
265
|
+
@apply border-primary text-primary;
|
|
266
|
+
}
|
|
267
|
+
.tag-badge--active {
|
|
268
|
+
@apply bg-primary text-terminal-bg border-primary;
|
|
269
|
+
}
|
|
270
|
+
.tag-badge--active::before {
|
|
271
|
+
@apply text-terminal-bg;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/* Section heading inside list-view (e.g. "Sections" / "Articles"). The
|
|
275
|
+
leading icon (clock / folder / document) is inlined as SVG in the
|
|
276
|
+
template so each section gets a semantic glyph instead of a generic
|
|
277
|
+
coral square. */
|
|
278
|
+
.section-heading {
|
|
279
|
+
@apply text-xs font-bold uppercase tracking-widest-lg text-terminal-text-muted mt-8 mb-3 pb-2 border-b border-terminal-border flex items-center gap-2;
|
|
280
|
+
}
|
|
281
|
+
.section-heading__icon {
|
|
282
|
+
@apply text-primary shrink-0;
|
|
283
|
+
width: 14px;
|
|
284
|
+
height: 14px;
|
|
285
|
+
display: block;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/* Meta row (author · date · tags) — separated by middle dot. */
|
|
289
|
+
.meta {
|
|
290
|
+
@apply flex flex-wrap gap-x-4 gap-y-1 text-xs text-terminal-text-muted font-mono uppercase tracking-wider;
|
|
291
|
+
}
|
|
292
|
+
.meta > * + *::before {
|
|
293
|
+
content: '·';
|
|
294
|
+
@apply text-terminal-text-faint mr-2;
|
|
295
|
+
}
|
|
296
|
+
.meta > * + * {
|
|
297
|
+
margin-inline-start: -1rem;
|
|
298
|
+
padding-inline-start: 1rem;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/* ---------- Long-form prose (.content rendered markdown) ---------- */
|
|
302
|
+
/* Applied to <ContentRenderer> output — h2/h3/p/ul/ol/code/pre/table generated
|
|
303
|
+
from markdown. Literata serif body for readability, brutalist treatment for
|
|
304
|
+
code/tables/lists. */
|
|
305
|
+
.content {
|
|
306
|
+
@apply text-terminal-text;
|
|
307
|
+
font-family: 'Literata', Georgia, serif;
|
|
308
|
+
font-size: 1.0625rem;
|
|
309
|
+
line-height: 1.65;
|
|
310
|
+
letter-spacing: 0.008em;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.content > * + * { margin-top: 1.5rem; }
|
|
314
|
+
|
|
315
|
+
.content h1 {
|
|
316
|
+
@apply font-display font-bold uppercase tracking-tight text-2xl md:text-3xl mt-10 mb-4;
|
|
317
|
+
}
|
|
318
|
+
.content h2 {
|
|
319
|
+
@apply font-display font-bold uppercase tracking-tight text-xl md:text-2xl mt-10 mb-4;
|
|
320
|
+
font-family: 'Space Grotesk', sans-serif;
|
|
321
|
+
}
|
|
322
|
+
.content h3 {
|
|
323
|
+
@apply font-display font-bold uppercase tracking-tight text-base md:text-lg mt-8 mb-3;
|
|
324
|
+
font-family: 'Space Grotesk', sans-serif;
|
|
325
|
+
}
|
|
326
|
+
.content h4, .content h5, .content h6 {
|
|
327
|
+
@apply font-display font-bold uppercase tracking-widest text-xs text-terminal-text-muted mt-6 mb-2;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.content p { @apply my-4; }
|
|
331
|
+
|
|
332
|
+
.content a {
|
|
333
|
+
@apply text-primary transition-colors;
|
|
334
|
+
text-decoration: none;
|
|
335
|
+
}
|
|
336
|
+
.content a:hover {
|
|
337
|
+
@apply text-terminal-accent-hover;
|
|
338
|
+
text-decoration: underline;
|
|
339
|
+
text-decoration-style: wavy;
|
|
340
|
+
text-decoration-thickness: 1px;
|
|
341
|
+
text-underline-offset: 3px;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.content strong {
|
|
345
|
+
@apply text-terminal-text font-semibold;
|
|
346
|
+
/* Explicit text-decoration to ensure wavy underline inherits correctly
|
|
347
|
+
when <a> wraps <strong> */
|
|
348
|
+
text-decoration: inherit;
|
|
349
|
+
text-decoration-color: inherit;
|
|
350
|
+
text-decoration-style: inherit;
|
|
351
|
+
text-underline-offset: inherit;
|
|
352
|
+
}
|
|
353
|
+
/* Ensure strong inside links explicitly inherits the underline */
|
|
354
|
+
.content a strong {
|
|
355
|
+
text-decoration: underline;
|
|
356
|
+
text-decoration-style: wavy;
|
|
357
|
+
text-decoration-color: currentColor;
|
|
358
|
+
text-underline-offset: 3px;
|
|
359
|
+
}
|
|
360
|
+
.content em { font-style: italic; }
|
|
361
|
+
|
|
362
|
+
/* Lists — top level uses heavy brutalist markers (coral square, decimal-leading-zero
|
|
363
|
+
counter). Nested levels strip the box/shadow/zero-pad chrome and tighten padding,
|
|
364
|
+
because in stacked columns 540-640px wide, every depth was eating ~2-2.5rem of
|
|
365
|
+
padding plus a repeating stamp — three levels deep left almost nothing for prose. */
|
|
366
|
+
.content ul, .content ol {
|
|
367
|
+
@apply my-4 space-y-2 list-none pl-0;
|
|
368
|
+
}
|
|
369
|
+
.content li {
|
|
370
|
+
@apply pl-7 relative;
|
|
371
|
+
line-height: 1.6;
|
|
372
|
+
}
|
|
373
|
+
.content ul li::before {
|
|
374
|
+
content: '';
|
|
375
|
+
@apply absolute left-0 top-2 w-3 h-3 bg-primary border-2 border-terminal-border;
|
|
376
|
+
box-shadow: 2px 2px 0px theme('colors.terminal.border');
|
|
377
|
+
}
|
|
378
|
+
.content ol {
|
|
379
|
+
counter-reset: terminal-counter;
|
|
380
|
+
}
|
|
381
|
+
.content ol li {
|
|
382
|
+
counter-increment: terminal-counter;
|
|
383
|
+
@apply pl-10;
|
|
384
|
+
}
|
|
385
|
+
.content ol li::before {
|
|
386
|
+
content: counter(terminal-counter, decimal-leading-zero);
|
|
387
|
+
@apply absolute left-0 top-1 inline-flex items-center justify-center bg-terminal-surface-0 text-primary border-2 border-terminal-border text-xs font-bold font-display;
|
|
388
|
+
width: 1.6rem;
|
|
389
|
+
height: 1.3rem;
|
|
390
|
+
box-shadow: 2px 2px 0px theme('colors.terminal.border');
|
|
391
|
+
font-family: 'Space Grotesk', sans-serif;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/* Nested lists (depth ≥ 2): drop the box stamp, switch to flat text markers,
|
|
395
|
+
trim padding and vertical rhythm. Selectors `.content li ul/ol li` only match
|
|
396
|
+
items inside an outer <li>, so the top-level rules above stay untouched. */
|
|
397
|
+
.content li ul, .content li ol {
|
|
398
|
+
@apply my-1 space-y-1 pl-0;
|
|
399
|
+
}
|
|
400
|
+
.content li ul li,
|
|
401
|
+
.content li ol li {
|
|
402
|
+
padding-left: 1.25rem;
|
|
403
|
+
line-height: 1.55;
|
|
404
|
+
}
|
|
405
|
+
.content li ul li::before {
|
|
406
|
+
content: '›';
|
|
407
|
+
background: transparent;
|
|
408
|
+
border: 0;
|
|
409
|
+
box-shadow: none;
|
|
410
|
+
width: auto;
|
|
411
|
+
height: auto;
|
|
412
|
+
color: theme('colors.terminal.text-muted');
|
|
413
|
+
font-family: 'SF Mono', Monaco, Consolas, monospace;
|
|
414
|
+
font-weight: 700;
|
|
415
|
+
font-size: 1em;
|
|
416
|
+
top: 0;
|
|
417
|
+
left: 0.25rem;
|
|
418
|
+
line-height: inherit;
|
|
419
|
+
display: inline;
|
|
420
|
+
}
|
|
421
|
+
.content li ol li {
|
|
422
|
+
padding-left: 1.6rem;
|
|
423
|
+
}
|
|
424
|
+
.content li ol li::before {
|
|
425
|
+
content: counter(terminal-counter) '.';
|
|
426
|
+
background: transparent;
|
|
427
|
+
border: 0;
|
|
428
|
+
box-shadow: none;
|
|
429
|
+
width: auto;
|
|
430
|
+
height: auto;
|
|
431
|
+
padding: 0;
|
|
432
|
+
color: theme('colors.terminal.text-muted');
|
|
433
|
+
font-family: 'SF Mono', Monaco, Consolas, monospace;
|
|
434
|
+
font-weight: 700;
|
|
435
|
+
font-size: 0.85em;
|
|
436
|
+
top: 0.15rem;
|
|
437
|
+
left: 0;
|
|
438
|
+
display: inline;
|
|
439
|
+
line-height: inherit;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/* Blockquote — bg + italic carry the visual cue; no border/shadow chrome. */
|
|
443
|
+
.content blockquote {
|
|
444
|
+
@apply bg-terminal-surface-0 p-4 my-6 italic text-terminal-text-secondary;
|
|
445
|
+
}
|
|
446
|
+
.content blockquote p { @apply mb-0; }
|
|
447
|
+
.content blockquote p:first-child { @apply mt-0; }
|
|
448
|
+
|
|
449
|
+
/* Code — brutalist terminal aesthetic
|
|
450
|
+
Inline: "inverted tag" look with stamp shadow, muted text (no coral)
|
|
451
|
+
Blocks: coral rail accent + muted text for readability */
|
|
452
|
+
.content code:not(pre code) {
|
|
453
|
+
@apply bg-terminal-surface-0 text-terminal-text-secondary px-1.5 py-0.5 text-[0.85em] border border-terminal-border-strong;
|
|
454
|
+
font-family: 'SF Mono', Monaco, Consolas, monospace;
|
|
455
|
+
border-radius: 0;
|
|
456
|
+
white-space: nowrap;
|
|
457
|
+
box-shadow: 2px 2px 0px 0px theme('colors.terminal.border');
|
|
458
|
+
transition: all 0.08s ease-out;
|
|
459
|
+
}
|
|
460
|
+
.content code:not(pre code):hover {
|
|
461
|
+
@apply border-primary text-terminal-text;
|
|
462
|
+
box-shadow: 2px 2px 0px 0px theme('colors.primary');
|
|
463
|
+
}
|
|
464
|
+
.content pre {
|
|
465
|
+
@apply bg-terminal-surface-0 overflow-x-auto my-6 text-sm relative border border-terminal-border;
|
|
466
|
+
font-family: 'SF Mono', Monaco, Consolas, monospace;
|
|
467
|
+
line-height: 1.6;
|
|
468
|
+
border-radius: 0;
|
|
469
|
+
padding: 1rem 1.25rem;
|
|
470
|
+
box-shadow: 4px 4px 0px 0px theme('colors.terminal.border');
|
|
471
|
+
}
|
|
472
|
+
/* Lime accent rail on the left edge — terminal-cursor vibe. */
|
|
473
|
+
.content pre::before {
|
|
474
|
+
content: '';
|
|
475
|
+
@apply absolute left-0 top-0 bottom-0 w-1 bg-primary;
|
|
476
|
+
}
|
|
477
|
+
.content pre code {
|
|
478
|
+
@apply bg-transparent p-0 border-0 text-terminal-text-secondary;
|
|
479
|
+
font-size: inherit;
|
|
480
|
+
white-space: pre;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/* Tables — internal grid with cell padding. Edge cells flush horizontally
|
|
484
|
+
(leftmost: no pl, rightmost: no pr) and at the bottom (last tbody row: no
|
|
485
|
+
pb), but th keeps balanced vertical padding because the bg highlight needs
|
|
486
|
+
breathing room above the label. Saves horizontal space inside narrow
|
|
487
|
+
stacked columns while keeping internal cells legible. */
|
|
488
|
+
.content table {
|
|
489
|
+
@apply w-full border-collapse my-6 text-sm;
|
|
490
|
+
}
|
|
491
|
+
.content thead {
|
|
492
|
+
@apply border-b border-terminal-border;
|
|
493
|
+
}
|
|
494
|
+
.content th {
|
|
495
|
+
@apply bg-terminal-surface-0 font-bold font-display uppercase tracking-wider text-[11px] text-terminal-text-secondary;
|
|
496
|
+
@apply text-left align-bottom px-3 py-2 border-r border-terminal-border;
|
|
497
|
+
}
|
|
498
|
+
.content th:first-child { padding-left: 0; }
|
|
499
|
+
.content th:last-child { padding-right: 0; border-right: 0; }
|
|
500
|
+
.content td {
|
|
501
|
+
@apply text-left align-top px-3 py-2 border-r border-b border-terminal-border;
|
|
502
|
+
}
|
|
503
|
+
.content td:first-child { padding-left: 0; }
|
|
504
|
+
.content td:last-child { padding-right: 0; border-right: 0; }
|
|
505
|
+
.content tbody tr:last-child td { padding-bottom: 0; border-bottom: 0; }
|
|
506
|
+
.content tbody tr:hover td { @apply bg-terminal-surface-0; }
|
|
507
|
+
|
|
508
|
+
.content hr {
|
|
509
|
+
@apply border-0 my-10 h-0.5;
|
|
510
|
+
background: linear-gradient(
|
|
511
|
+
to right,
|
|
512
|
+
transparent,
|
|
513
|
+
theme('colors.terminal.border'),
|
|
514
|
+
transparent
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
.content img {
|
|
519
|
+
@apply w-full h-auto my-6 border-2 border-terminal-border;
|
|
520
|
+
box-shadow: 4px 4px 0px 0px theme('colors.terminal.border');
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
.content kbd {
|
|
524
|
+
@apply bg-terminal-surface-0 border border-terminal-border-strong text-terminal-text px-1.5 py-0.5 text-xs;
|
|
525
|
+
font-family: 'SF Mono', Monaco, Consolas, monospace;
|
|
526
|
+
border-bottom-width: 2px;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
@layer utilities {
|
|
531
|
+
.border-3 { border-width: 3px; }
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/* Focus ring — accent outline + offset stamp. */
|
|
535
|
+
:focus-visible {
|
|
536
|
+
outline: 2px solid theme('colors.primary');
|
|
537
|
+
outline-offset: 2px;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/* Reduced motion */
|
|
541
|
+
@media (prefers-reduced-motion: reduce) {
|
|
542
|
+
*, *::before, *::after {
|
|
543
|
+
animation-duration: 0.01ms !important;
|
|
544
|
+
animation-iteration-count: 1 !important;
|
|
545
|
+
transition-duration: 0.01ms !important;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
@media (max-width: 640px) {
|
|
550
|
+
.content { font-size: 1rem; line-height: 1.6; }
|
|
551
|
+
.terminal-item:hover .title-text {
|
|
552
|
+
outline: none;
|
|
553
|
+
box-shadow: none;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/* ---------- Brutalist toaster (vue-sonner) ----------
|
|
558
|
+
vue-sonner ships its own CSS via `vue-sonner/style.css` (loaded by the
|
|
559
|
+
nuxt module). We override the exposed CSS variables + a few structural
|
|
560
|
+
props so toasts look like the rest of the surface: sharp corners, stamp
|
|
561
|
+
offset shadow, monospace, terminal palette. Selectors target the
|
|
562
|
+
`data-sonner-*` attribute tree so they win over the package defaults
|
|
563
|
+
without `!important`. */
|
|
564
|
+
[data-sonner-toaster] {
|
|
565
|
+
--border-radius: 0;
|
|
566
|
+
--normal-bg: theme('colors.terminal.surface.0');
|
|
567
|
+
--normal-border: theme('colors.terminal.border');
|
|
568
|
+
--normal-text: theme('colors.terminal.text');
|
|
569
|
+
--success-bg: theme('colors.terminal.surface.0');
|
|
570
|
+
--success-border: theme('colors.primary');
|
|
571
|
+
--success-text: theme('colors.terminal.text');
|
|
572
|
+
--error-bg: theme('colors.terminal.surface.0');
|
|
573
|
+
--error-border: theme('colors.terminal.text-muted');
|
|
574
|
+
--error-text: theme('colors.terminal.text');
|
|
575
|
+
font-family: theme('fontFamily.display');
|
|
576
|
+
}
|
|
577
|
+
[data-sonner-toaster] [data-sonner-toast][data-styled='true'] {
|
|
578
|
+
border-radius: 0;
|
|
579
|
+
border-width: 2px;
|
|
580
|
+
box-shadow: 4px 4px 0 0 theme('colors.terminal.border');
|
|
581
|
+
padding: 14px 16px;
|
|
582
|
+
}
|
|
583
|
+
[data-sonner-toaster] [data-sonner-toast][data-styled='true'][data-type='success'] {
|
|
584
|
+
box-shadow: 4px 4px 0 0 theme('colors.primary');
|
|
585
|
+
}
|
|
586
|
+
[data-sonner-toaster] [data-sonner-toast][data-styled='true'][data-type='error'] {
|
|
587
|
+
box-shadow: 4px 4px 0 0 theme('colors.terminal.text-muted');
|
|
588
|
+
}
|
|
589
|
+
[data-sonner-toaster] [data-sonner-toast] [data-title] {
|
|
590
|
+
font-weight: 700;
|
|
591
|
+
text-transform: uppercase;
|
|
592
|
+
letter-spacing: 0.02em;
|
|
593
|
+
font-size: 12px;
|
|
594
|
+
}
|
|
595
|
+
[data-sonner-toaster] [data-sonner-toast] [data-description] {
|
|
596
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
597
|
+
font-size: 11px;
|
|
598
|
+
color: theme('colors.terminal.text-muted') !important;
|
|
599
|
+
margin-top: 2px;
|
|
600
|
+
letter-spacing: 0.01em;
|
|
601
|
+
}
|
|
602
|
+
/* Icon column — recolor the built-in success/error SVG so it tracks our palette
|
|
603
|
+
instead of the package's saturated greens/reds. */
|
|
604
|
+
[data-sonner-toaster] [data-sonner-toast][data-type='success'] [data-icon] {
|
|
605
|
+
color: theme('colors.primary');
|
|
606
|
+
}
|
|
607
|
+
[data-sonner-toaster] [data-sonner-toast][data-type='error'] [data-icon] {
|
|
608
|
+
color: theme('colors.terminal.text-muted');
|
|
609
|
+
}
|