andy-note-nuxt 0.3.0 → 0.4.1
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,55 @@
|
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@
|
|
18
|
-
|
|
9
|
+
Local @fontsource packages = self-host (no FOUT, no third-party request).
|
|
10
|
+
We import only the weights/subsets 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
|
+
`latin-<weight>.css` ships only the latin @font-face rule, skipping the
|
|
15
|
+
cyrillic / greek / vietnamese / latin-ext declarations whose subsets
|
|
16
|
+
never get fetched anyway (unicode-range gates the WOFF2 request, but the
|
|
17
|
+
@font-face rules themselves still pad critical CSS). Consumers serving
|
|
18
|
+
non-latin content should override main.css with the broader imports. */
|
|
19
|
+
@import "@fontsource/space-grotesk/latin-500.css";
|
|
20
|
+
@import "@fontsource/space-grotesk/latin-700.css";
|
|
21
|
+
@import "@fontsource/literata/latin-400.css";
|
|
22
|
+
@import "@fontsource/literata/latin-400-italic.css";
|
|
23
|
+
@import "@fontsource/literata/latin-600.css";
|
|
24
|
+
|
|
25
|
+
/* ===== Fallback @font-face overrides — CLS 0.12 → 0.00 =====
|
|
26
|
+
While the real WOFF2 is in flight, `font-display: swap` paints with a
|
|
27
|
+
system font. Without metric overrides Arial / Times New Roman render at
|
|
28
|
+
a different x-height + advance width than the real face, so the swap
|
|
29
|
+
shifts every line of text below it (measured 0.1215 CLS on root /).
|
|
30
|
+
These two @font-face declarations re-publish the system font under a
|
|
31
|
+
synthetic family name with `ascent-override`, `descent-override`,
|
|
32
|
+
`line-gap-override`, and `size-adjust` re-mapped to match the webfont.
|
|
33
|
+
Wire-up: in `tailwind.config.js`, `fontFamily.display` slots
|
|
34
|
+
"Space Grotesk Fallback" between the real family and `-apple-system`,
|
|
35
|
+
and `fontFamily.prose` slots "Literata Fallback" between Literata and
|
|
36
|
+
Georgia. The system font then renders dimensionally identical to the
|
|
37
|
+
webfont — the swap is invisible.
|
|
38
|
+
|
|
39
|
+
Computed once via `scripts/compute-font-fallback.mjs` (Capsize). Recompute
|
|
40
|
+
only when the webfonts themselves change; metrics are intrinsic to the
|
|
41
|
+
font file, not the weight/subset. */
|
|
42
|
+
@font-face {
|
|
43
|
+
font-family: "Space Grotesk Fallback";
|
|
44
|
+
src: local('Arial'), local('ArialMT');
|
|
45
|
+
ascent-override: 89.7072%;
|
|
46
|
+
descent-override: 26.6204%;
|
|
47
|
+
line-gap-override: 0%;
|
|
48
|
+
size-adjust: 109.6903%;
|
|
49
|
+
}
|
|
50
|
+
@font-face {
|
|
51
|
+
font-family: "Literata Fallback";
|
|
52
|
+
src: local('Times New Roman'), local('TimesNewRomanPSMT');
|
|
53
|
+
ascent-override: 99.6159%;
|
|
54
|
+
descent-override: 26.0677%;
|
|
55
|
+
line-gap-override: 0%;
|
|
56
|
+
size-adjust: 118.1538%;
|
|
57
|
+
}
|
|
19
58
|
|
|
20
59
|
@tailwind base;
|
|
21
60
|
@tailwind components;
|
|
@@ -50,7 +89,17 @@
|
|
|
50
89
|
|
|
51
90
|
html {
|
|
52
91
|
-webkit-text-size-adjust: 100%;
|
|
53
|
-
|
|
92
|
+
}
|
|
93
|
+
/* Smooth scroll only when motion is allowed. Global `html { scroll-behavior:
|
|
94
|
+
smooth }` would make every hash-link, anchor jump, and `scrollIntoView()`
|
|
95
|
+
animate on the main thread — measurable INP cost on long articles, and
|
|
96
|
+
redundant since useStack's programmatic stack scroll already passes
|
|
97
|
+
`behavior: 'smooth'` explicitly on each scrollTo call. Honors the OS
|
|
98
|
+
reduced-motion preference for free. */
|
|
99
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
100
|
+
html {
|
|
101
|
+
scroll-behavior: smooth;
|
|
102
|
+
}
|
|
54
103
|
}
|
|
55
104
|
|
|
56
105
|
body {
|
|
@@ -245,7 +294,7 @@
|
|
|
245
294
|
}
|
|
246
295
|
.list-row__description {
|
|
247
296
|
@apply text-sm text-terminal-text-muted normal-case;
|
|
248
|
-
font-family: 'Literata', Georgia, serif;
|
|
297
|
+
font-family: 'Literata', 'Literata Fallback', Georgia, serif;
|
|
249
298
|
text-transform: none;
|
|
250
299
|
letter-spacing: 0;
|
|
251
300
|
}
|
|
@@ -304,7 +353,7 @@
|
|
|
304
353
|
code/tables/lists. */
|
|
305
354
|
.content {
|
|
306
355
|
@apply text-terminal-text;
|
|
307
|
-
font-family: 'Literata', Georgia, serif;
|
|
356
|
+
font-family: 'Literata', 'Literata Fallback', Georgia, serif;
|
|
308
357
|
font-size: 1.0625rem;
|
|
309
358
|
line-height: 1.65;
|
|
310
359
|
letter-spacing: 0.008em;
|
|
@@ -317,11 +366,11 @@
|
|
|
317
366
|
}
|
|
318
367
|
.content h2 {
|
|
319
368
|
@apply font-display font-bold uppercase tracking-tight text-xl md:text-2xl mt-10 mb-4;
|
|
320
|
-
font-family: 'Space Grotesk', sans-serif;
|
|
369
|
+
font-family: 'Space Grotesk', 'Space Grotesk Fallback', sans-serif;
|
|
321
370
|
}
|
|
322
371
|
.content h3 {
|
|
323
372
|
@apply font-display font-bold uppercase tracking-tight text-base md:text-lg mt-8 mb-3;
|
|
324
|
-
font-family: 'Space Grotesk', sans-serif;
|
|
373
|
+
font-family: 'Space Grotesk', 'Space Grotesk Fallback', sans-serif;
|
|
325
374
|
}
|
|
326
375
|
.content h4, .content h5, .content h6 {
|
|
327
376
|
@apply font-display font-bold uppercase tracking-widest text-xs text-terminal-text-muted mt-6 mb-2;
|
|
@@ -388,7 +437,7 @@
|
|
|
388
437
|
width: 1.6rem;
|
|
389
438
|
height: 1.3rem;
|
|
390
439
|
box-shadow: 2px 2px 0px theme('colors.terminal.border');
|
|
391
|
-
font-family: 'Space Grotesk', sans-serif;
|
|
440
|
+
font-family: 'Space Grotesk', 'Space Grotesk Fallback', sans-serif;
|
|
392
441
|
}
|
|
393
442
|
|
|
394
443
|
/* 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;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Override `<img>` rendered from markdown via @nuxt/content's Prose components.
|
|
3
|
+
// Default ProseImg emits a plain `<img>`; we swap to `<NuxtImg>` so consumers
|
|
4
|
+
// get @nuxt/image's lazy loading, responsive `srcset`, format negotiation, and
|
|
5
|
+
// (for SSG builds) pre-rendered optimized variants under .output/public.
|
|
6
|
+
//
|
|
7
|
+
// Surface kept identical to the default ProseImg so existing markdown — both
|
|
8
|
+
// in this layer's `content/` and in consumer projects' content — continues to
|
|
9
|
+
// work without changes. Authors write `` and get an
|
|
10
|
+
// optimized, lazy-loaded image at the cost of nothing.
|
|
11
|
+
//
|
|
12
|
+
// External URLs (http*) pass through @nuxt/image's `remote` provider path;
|
|
13
|
+
// relative paths under `/public` get rewritten to the IPX-served route during
|
|
14
|
+
// dev and pre-rendered into static variants during `nuxt generate`.
|
|
15
|
+
|
|
16
|
+
defineProps<{
|
|
17
|
+
src?: string
|
|
18
|
+
alt?: string
|
|
19
|
+
width?: string | number
|
|
20
|
+
height?: string | number
|
|
21
|
+
title?: string
|
|
22
|
+
}>()
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
<template>
|
|
26
|
+
<NuxtImg
|
|
27
|
+
:src="src"
|
|
28
|
+
:alt="alt"
|
|
29
|
+
:width="width"
|
|
30
|
+
:height="height"
|
|
31
|
+
:title="title"
|
|
32
|
+
loading="lazy"
|
|
33
|
+
decoding="async"
|
|
34
|
+
format="webp"
|
|
35
|
+
densities="x1 x2"
|
|
36
|
+
/>
|
|
37
|
+
</template>
|
package/nuxt.config.ts
CHANGED
|
@@ -33,6 +33,7 @@ export default defineNuxtConfig({
|
|
|
33
33
|
modules: [
|
|
34
34
|
'vite-plugin-ai-annotator/nuxt',
|
|
35
35
|
'@nuxt/content',
|
|
36
|
+
'@nuxt/image',
|
|
36
37
|
'@nuxtjs/tailwindcss',
|
|
37
38
|
// Toast notifications. Auto-registers `<Toaster />` (client-only) and a
|
|
38
39
|
// plugin exposing `$toast` / the imported `toast()` helper from `vue-sonner`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "andy-note-nuxt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
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",
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"@fontsource/literata": "^5.2.8",
|
|
49
49
|
"@fontsource/space-grotesk": "^5.2.10",
|
|
50
50
|
"@nuxt/content": "^3.12.0",
|
|
51
|
+
"@nuxt/image": "^2.0.0",
|
|
51
52
|
"@nuxtjs/tailwindcss": "^6.14.0",
|
|
52
53
|
"nuxt": "^4.4.6",
|
|
53
54
|
"rehype-raw": "^7.0.0",
|
|
@@ -56,6 +57,9 @@
|
|
|
56
57
|
"vue-sonner": "^2.0.9"
|
|
57
58
|
},
|
|
58
59
|
"devDependencies": {
|
|
60
|
+
"@capsizecss/core": "^4.1.3",
|
|
61
|
+
"@capsizecss/metrics": "^4.0.0",
|
|
62
|
+
"@capsizecss/unpack": "^4.0.0",
|
|
59
63
|
"oxlint": "^1.65.0",
|
|
60
64
|
"tailwindcss": "^3",
|
|
61
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.
|