andy-note-nuxt 0.4.0 → 0.4.2
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/app/assets/css/main.css
CHANGED
|
@@ -6,16 +6,60 @@
|
|
|
6
6
|
* ================================================================= */
|
|
7
7
|
|
|
8
8
|
/* Fonts — @import MUST precede @tailwind directives per CSS spec.
|
|
9
|
-
Local @fontsource packages = self-host (no FOUT, no third-party request).
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
Local @fontsource packages = self-host (no FOUT, no third-party request).
|
|
10
|
+
We import only the weights actually referenced by the layer:
|
|
11
|
+
- Space Grotesk 500 → LocalStorageChecklist `.lsc-label`
|
|
12
|
+
- Space Grotesk 700 → every `font-bold` headline/label
|
|
13
|
+
- Literata 400 / 400-italic / 600 → prose body / <em> / <strong>
|
|
14
|
+
The non-`latin-` entry ships @font-face for every unicode subset
|
|
15
|
+
(cyrillic, greek, latin-ext, vietnamese, latin). Each block carries a
|
|
16
|
+
`unicode-range` so the browser only downloads the woff2 that actually
|
|
17
|
+
maps to glyphs on the page — Vietnamese consumers get latin + vietnamese
|
|
18
|
+
subsets, latin-only consumers get just latin. Earlier we narrowed to
|
|
19
|
+
`latin-<weight>.css` to shave a few KB of @font-face rules; that broke
|
|
20
|
+
Vietnamese UI strings (poe1/poe2 nav, site description, builds) because
|
|
21
|
+
characters like ầ ế ợ have no @font-face declaration claiming them and
|
|
22
|
+
fell back to Arial. Reverted: subset gating now lives in unicode-range
|
|
23
|
+
inside the woff2 fetches, not in the @import surface. */
|
|
12
24
|
@import "@fontsource/space-grotesk/500.css";
|
|
13
|
-
@import "@fontsource/space-grotesk/600.css";
|
|
14
25
|
@import "@fontsource/space-grotesk/700.css";
|
|
15
26
|
@import "@fontsource/literata/400.css";
|
|
16
27
|
@import "@fontsource/literata/400-italic.css";
|
|
17
28
|
@import "@fontsource/literata/600.css";
|
|
18
|
-
|
|
29
|
+
|
|
30
|
+
/* ===== Fallback @font-face overrides — CLS 0.12 → 0.00 =====
|
|
31
|
+
While the real WOFF2 is in flight, `font-display: swap` paints with a
|
|
32
|
+
system font. Without metric overrides Arial / Times New Roman render at
|
|
33
|
+
a different x-height + advance width than the real face, so the swap
|
|
34
|
+
shifts every line of text below it (measured 0.1215 CLS on root /).
|
|
35
|
+
These two @font-face declarations re-publish the system font under a
|
|
36
|
+
synthetic family name with `ascent-override`, `descent-override`,
|
|
37
|
+
`line-gap-override`, and `size-adjust` re-mapped to match the webfont.
|
|
38
|
+
Wire-up: in `tailwind.config.js`, `fontFamily.display` slots
|
|
39
|
+
"Space Grotesk Fallback" between the real family and `-apple-system`,
|
|
40
|
+
and `fontFamily.prose` slots "Literata Fallback" between Literata and
|
|
41
|
+
Georgia. The system font then renders dimensionally identical to the
|
|
42
|
+
webfont — the swap is invisible.
|
|
43
|
+
|
|
44
|
+
Computed once via `scripts/compute-font-fallback.mjs` (Capsize). Recompute
|
|
45
|
+
only when the webfonts themselves change; metrics are intrinsic to the
|
|
46
|
+
font file, not the weight/subset. */
|
|
47
|
+
@font-face {
|
|
48
|
+
font-family: "Space Grotesk Fallback";
|
|
49
|
+
src: local('Arial'), local('ArialMT');
|
|
50
|
+
ascent-override: 89.7072%;
|
|
51
|
+
descent-override: 26.6204%;
|
|
52
|
+
line-gap-override: 0%;
|
|
53
|
+
size-adjust: 109.6903%;
|
|
54
|
+
}
|
|
55
|
+
@font-face {
|
|
56
|
+
font-family: "Literata Fallback";
|
|
57
|
+
src: local('Times New Roman'), local('TimesNewRomanPSMT');
|
|
58
|
+
ascent-override: 99.6159%;
|
|
59
|
+
descent-override: 26.0677%;
|
|
60
|
+
line-gap-override: 0%;
|
|
61
|
+
size-adjust: 118.1538%;
|
|
62
|
+
}
|
|
19
63
|
|
|
20
64
|
@tailwind base;
|
|
21
65
|
@tailwind components;
|
|
@@ -50,7 +94,17 @@
|
|
|
50
94
|
|
|
51
95
|
html {
|
|
52
96
|
-webkit-text-size-adjust: 100%;
|
|
53
|
-
|
|
97
|
+
}
|
|
98
|
+
/* Smooth scroll only when motion is allowed. Global `html { scroll-behavior:
|
|
99
|
+
smooth }` would make every hash-link, anchor jump, and `scrollIntoView()`
|
|
100
|
+
animate on the main thread — measurable INP cost on long articles, and
|
|
101
|
+
redundant since useStack's programmatic stack scroll already passes
|
|
102
|
+
`behavior: 'smooth'` explicitly on each scrollTo call. Honors the OS
|
|
103
|
+
reduced-motion preference for free. */
|
|
104
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
105
|
+
html {
|
|
106
|
+
scroll-behavior: smooth;
|
|
107
|
+
}
|
|
54
108
|
}
|
|
55
109
|
|
|
56
110
|
body {
|
|
@@ -245,7 +299,7 @@
|
|
|
245
299
|
}
|
|
246
300
|
.list-row__description {
|
|
247
301
|
@apply text-sm text-terminal-text-muted normal-case;
|
|
248
|
-
font-family: 'Literata', Georgia, serif;
|
|
302
|
+
font-family: 'Literata', 'Literata Fallback', Georgia, serif;
|
|
249
303
|
text-transform: none;
|
|
250
304
|
letter-spacing: 0;
|
|
251
305
|
}
|
|
@@ -304,7 +358,7 @@
|
|
|
304
358
|
code/tables/lists. */
|
|
305
359
|
.content {
|
|
306
360
|
@apply text-terminal-text;
|
|
307
|
-
font-family: 'Literata', Georgia, serif;
|
|
361
|
+
font-family: 'Literata', 'Literata Fallback', Georgia, serif;
|
|
308
362
|
font-size: 1.0625rem;
|
|
309
363
|
line-height: 1.65;
|
|
310
364
|
letter-spacing: 0.008em;
|
|
@@ -317,11 +371,11 @@
|
|
|
317
371
|
}
|
|
318
372
|
.content h2 {
|
|
319
373
|
@apply font-display font-bold uppercase tracking-tight text-xl md:text-2xl mt-10 mb-4;
|
|
320
|
-
font-family: 'Space Grotesk', sans-serif;
|
|
374
|
+
font-family: 'Space Grotesk', 'Space Grotesk Fallback', sans-serif;
|
|
321
375
|
}
|
|
322
376
|
.content h3 {
|
|
323
377
|
@apply font-display font-bold uppercase tracking-tight text-base md:text-lg mt-8 mb-3;
|
|
324
|
-
font-family: 'Space Grotesk', sans-serif;
|
|
378
|
+
font-family: 'Space Grotesk', 'Space Grotesk Fallback', sans-serif;
|
|
325
379
|
}
|
|
326
380
|
.content h4, .content h5, .content h6 {
|
|
327
381
|
@apply font-display font-bold uppercase tracking-widest text-xs text-terminal-text-muted mt-6 mb-2;
|
|
@@ -388,7 +442,7 @@
|
|
|
388
442
|
width: 1.6rem;
|
|
389
443
|
height: 1.3rem;
|
|
390
444
|
box-shadow: 2px 2px 0px theme('colors.terminal.border');
|
|
391
|
-
font-family: 'Space Grotesk', sans-serif;
|
|
445
|
+
font-family: 'Space Grotesk', 'Space Grotesk Fallback', sans-serif;
|
|
392
446
|
}
|
|
393
447
|
|
|
394
448
|
/* Nested lists (depth ≥ 2): drop the box stamp, switch to flat text markers,
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
// `minimark`
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
import { stringify as stringifyMinimark } from 'minimark/stringify'
|
|
2
|
+
// `minimark/stringify` is imported lazily inside copyMarkdown() so it stays
|
|
3
|
+
// out of the initial route bundle — `Copy as markdown` is a user-triggered
|
|
4
|
+
// action, the codepath only fires after a click. See https://content.nuxt.com/docs/integrations/llms
|
|
5
|
+
// for why we round-trip minimark AST → markdown rather than depending on rawbody.
|
|
7
6
|
import { toast } from 'vue-sonner'
|
|
8
7
|
import { useFloating, offset, flip, shift, autoUpdate } from '@floating-ui/vue'
|
|
9
8
|
|
|
@@ -66,32 +65,40 @@ if (malformedPath.value && !props.noThrow) {
|
|
|
66
65
|
|
|
67
66
|
// Guard all queries: when path is malformed, skip them entirely (queryCollection
|
|
68
67
|
// would crash with assertSafeQuery before we could handle the error).
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
68
|
+
//
|
|
69
|
+
// Page + children fire in parallel via Promise.all instead of two sequential
|
|
70
|
+
// awaits. With key=index TransitionGroup, every stack mutation mounts a fresh
|
|
71
|
+
// ContentView for each column (StackedColumn :key="path" remounts), so the
|
|
72
|
+
// SSR pass for a 4-deep stack used to issue 8 SQL queries serialized in
|
|
73
|
+
// setup order. Parallelizing halves wall-clock cost — both queries hit the
|
|
74
|
+
// same SQLite connection but the second no longer waits for the first to
|
|
75
|
+
// resolve before its `where(...)` plan is built and executed.
|
|
76
|
+
//
|
|
77
|
+
// Path = '/' must use prefix '/' (not '//') so the LIKE matches all rows.
|
|
79
78
|
const childrenPrefix = path.value === '/' ? '/' : `${path.value}/`
|
|
80
|
-
const { data: allChildren } = await
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
79
|
+
const [{ data: page }, { data: allChildren }] = await Promise.all([
|
|
80
|
+
useAsyncData(`content-${path.value}`, () => {
|
|
81
|
+
if (malformedPath.value) return Promise.resolve(null)
|
|
82
|
+
return queryCollection('content')
|
|
83
|
+
.where('path', '=', path.value)
|
|
84
|
+
.first()
|
|
85
|
+
}),
|
|
86
|
+
useAsyncData(`children-${path.value}`, () => {
|
|
87
|
+
if (malformedPath.value) return Promise.resolve([])
|
|
88
|
+
return queryCollection('content')
|
|
89
|
+
.where('path', 'LIKE', `${childrenPrefix}%`)
|
|
90
|
+
.where('path', '<>', path.value)
|
|
91
|
+
.where('path', 'NOT LIKE', '%/_index')
|
|
92
|
+
// The convention filter used to live here as a SQL `where`, but SQL's
|
|
93
|
+
// three-valued logic treats `NULL <> 'convention'` as NULL (not true),
|
|
94
|
+
// so any row whose schema doesn't set document_type — i.e. most rows —
|
|
95
|
+
// got silently filtered out and listings rendered as empty article
|
|
96
|
+
// views. The client-side hierarchy filter (`!== 'convention'`) handles
|
|
97
|
+
// this correctly in JS where `undefined !== 'convention'` is true.
|
|
98
|
+
.select('path', 'title', 'description', 'document_type', 'updated', 'created')
|
|
99
|
+
.all()
|
|
100
|
+
}),
|
|
101
|
+
])
|
|
95
102
|
|
|
96
103
|
// "Not found" only when path is malformed OR there's no page AND no children to list.
|
|
97
104
|
// Pure section paths (`/builds`, `/`) are valid even without `_index.md` if children exist.
|
|
@@ -366,7 +373,7 @@ type CopyState = 'idle' | 'copied' | 'error'
|
|
|
366
373
|
const copyState = ref<CopyState>('idle')
|
|
367
374
|
let copyResetTimer: ReturnType<typeof setTimeout> | null = null
|
|
368
375
|
|
|
369
|
-
function buildMarkdown(): string {
|
|
376
|
+
async function buildMarkdown(): Promise<string> {
|
|
370
377
|
const raw = (page.value as any)?.rawbody as string | undefined
|
|
371
378
|
if (typeof raw === 'string' && raw.trim().length > 0) {
|
|
372
379
|
const trimmed = raw.trimStart()
|
|
@@ -390,6 +397,10 @@ function buildMarkdown(): string {
|
|
|
390
397
|
const value = skipFirst ? body.value.slice(1) : body.value
|
|
391
398
|
if (value.length > 0) {
|
|
392
399
|
try {
|
|
400
|
+
// Lazy import — keeps minimark/stringify out of the initial route
|
|
401
|
+
// bundle. Vite code-splits this into a chunk fetched only on first
|
|
402
|
+
// copy-as-markdown click.
|
|
403
|
+
const { stringify: stringifyMinimark } = await import('minimark/stringify')
|
|
393
404
|
const md = stringifyMinimark({ type: 'minimark', value }).trim()
|
|
394
405
|
if (md) lines.push(md, '')
|
|
395
406
|
}
|
|
@@ -429,7 +440,7 @@ function buildMarkdown(): string {
|
|
|
429
440
|
|
|
430
441
|
async function copyMarkdown() {
|
|
431
442
|
if (!import.meta.client) return
|
|
432
|
-
const markdown = buildMarkdown()
|
|
443
|
+
const markdown = await buildMarkdown()
|
|
433
444
|
|
|
434
445
|
try {
|
|
435
446
|
if (navigator.clipboard?.writeText) {
|
|
@@ -174,7 +174,7 @@ function reset(): void {
|
|
|
174
174
|
background: #2e2f2c;
|
|
175
175
|
box-shadow: 4px 4px 0 0 #474541;
|
|
176
176
|
margin: 1.5rem 0;
|
|
177
|
-
font-family: 'Space Grotesk', -apple-system, sans-serif;
|
|
177
|
+
font-family: 'Space Grotesk', 'Space Grotesk Fallback', -apple-system, sans-serif;
|
|
178
178
|
color: #d5cfc5;
|
|
179
179
|
}
|
|
180
180
|
|
|
@@ -191,7 +191,7 @@ function reset(): void {
|
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
.lsc-title {
|
|
194
|
-
font-family: 'Space Grotesk', sans-serif;
|
|
194
|
+
font-family: 'Space Grotesk', 'Space Grotesk Fallback', sans-serif;
|
|
195
195
|
font-size: 0.95rem;
|
|
196
196
|
font-weight: 700;
|
|
197
197
|
text-transform: uppercase;
|
|
@@ -329,7 +329,7 @@ function reset(): void {
|
|
|
329
329
|
}
|
|
330
330
|
|
|
331
331
|
.lsc-label {
|
|
332
|
-
font-family: 'Space Grotesk', sans-serif;
|
|
332
|
+
font-family: 'Space Grotesk', 'Space Grotesk Fallback', sans-serif;
|
|
333
333
|
font-size: 0.9375rem;
|
|
334
334
|
font-weight: 500;
|
|
335
335
|
color: #d5cfc5;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "andy-note-nuxt",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Brutalist-terminal Nuxt Content theme for personal notes, guides, and second-brain knowledge bases. Use as a Nuxt layer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./nuxt.config.ts",
|
|
@@ -57,6 +57,9 @@
|
|
|
57
57
|
"vue-sonner": "^2.0.9"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
+
"@capsizecss/core": "^4.1.3",
|
|
61
|
+
"@capsizecss/metrics": "^4.0.0",
|
|
62
|
+
"@capsizecss/unpack": "^4.0.0",
|
|
60
63
|
"oxlint": "^1.65.0",
|
|
61
64
|
"tailwindcss": "^3",
|
|
62
65
|
"vite-plugin-ai-annotator": "^1.14.13"
|
package/tailwind.config.js
CHANGED
|
@@ -38,9 +38,14 @@ export default {
|
|
|
38
38
|
},
|
|
39
39
|
},
|
|
40
40
|
},
|
|
41
|
+
// The "* Fallback" entries are synthetic families declared via
|
|
42
|
+
// @font-face in app/assets/css/main.css. They re-map the system font's
|
|
43
|
+
// metrics to match the real webfont so `font-display: swap` doesn't
|
|
44
|
+
// shift the layout when the WOFF2 finally arrives. Order matters:
|
|
45
|
+
// real webfont → metric-matched fallback → ultimate system stack.
|
|
41
46
|
fontFamily: {
|
|
42
|
-
display: ['Space Grotesk', '-apple-system', 'BlinkMacSystemFont', 'sans-serif'],
|
|
43
|
-
prose: ['Literata', 'Georgia', 'serif'],
|
|
47
|
+
display: ['Space Grotesk', 'Space Grotesk Fallback', '-apple-system', 'BlinkMacSystemFont', 'sans-serif'],
|
|
48
|
+
prose: ['Literata', 'Literata Fallback', 'Georgia', 'serif'],
|
|
44
49
|
mono: ['SF Mono', 'Monaco', 'Consolas', 'ui-monospace', 'monospace'],
|
|
45
50
|
},
|
|
46
51
|
// Stamp shadows — flat 0-blur offsets are the brutalist signature.
|