From 6c6045b54ca44deb33a8209f029dabf657ba9aba Mon Sep 17 00:00:00 2001 From: yuler Date: Fri, 8 May 2026 13:33:45 +0800 Subject: [PATCH 01/15] =?UTF-8?q?=F0=9F=94=A7=20Reorganize=20Claude=20conf?= =?UTF-8?q?ig=20into=20.claude=20directory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move CLAUDE.md symlink to .claude/ subdirectory and remove .claude from .gitignore to track AI configuration files in the repo. --- .claude/CLAUDE.md | 1 + .gitignore | 5 +---- CLAUDE.md | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) create mode 100644 .claude/CLAUDE.md delete mode 120000 CLAUDE.md diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 0000000..38dc20c --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1 @@ +@../AGENTS.md \ No newline at end of file diff --git a/.gitignore b/.gitignore index fa21537..d86c13b 100644 --- a/.gitignore +++ b/.gitignore @@ -19,8 +19,5 @@ node_modules/ # Output pdfs/ -# AI -.claude - # Misc -screenshots \ No newline at end of file +screenshots diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 120000 index 47dc3e3..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1 +0,0 @@ -AGENTS.md \ No newline at end of file From 83d6bf479660cb9409ed5bae77b4e02e85d378eb Mon Sep 17 00:00:00 2001 From: yuler Date: Fri, 8 May 2026 13:36:59 +0800 Subject: [PATCH 02/15] =?UTF-8?q?=E2=9C=A8=20Add=20initial=20design=20spec?= =?UTF-8?q?ifications=20for=20yuler.dev,=20including=20color=20palette,=20?= =?UTF-8?q?typography,=20spacing,=20and=20component=20styles.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DESIGN.md | 199 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 DESIGN.md diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..f2434e9 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,199 @@ +--- +version: alpha +name: yuler.dev +description: Personal site — Astro, Tailwind CSS 4, Inter + monospace accents. Light, editorial cards on a neutral canvas. +colors: + primary: "#1a1a1a" + secondary: "#6b7280" + tertiary: "#3b82f6" + neutral: "#f5f5f5" + surface: "#ffffff" + border: "#e5e7eb" + border-muted: "#f3f4f6" + code-inline: "#be123c" + wip-bg: "#fef3c7" + wip-text: "#b45309" + heatmap-low: "#f3f4f6" + heatmap-mid: "#d1d5db" + heatmap-high: "#4b5563" + heatmap-max: "#000000" + decorative-shadow: "#fde047" +typography: + body: + fontFamily: Inter + fontSize: 1rem + fontWeight: 400 + lineHeight: 1.6 + mono-label: + fontFamily: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace + fontSize: 0.75rem + fontWeight: 500 + lineHeight: 1.25 + card-title: + fontFamily: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace + fontSize: 1.125rem + fontWeight: 600 + lineHeight: 1.375 + profile-name: + fontFamily: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace + fontSize: 1.5rem + fontWeight: 700 + lineHeight: 1.2 + post-title: + fontFamily: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace + fontSize: 1.875rem + fontWeight: 700 + lineHeight: 1.2 + prose-body: + fontFamily: Inter + fontSize: 1rem + fontWeight: 400 + lineHeight: 1.625 +rounded: + none: 0px + hairline: 2px +spacing: + xs: 4px + sm: 8px + md: 16px + lg: 24px + xl: 32px + section: 48px +components: + card: + backgroundColor: "{colors.surface}" + textColor: "{colors.primary}" + rounded: "{rounded.none}" + padding: 24px + list-row-interactive: + backgroundColor: "{colors.surface}" + textColor: "{colors.primary}" + padding: 12px + secondary-button: + backgroundColor: "{colors.border-muted}" + textColor: "#374151" + typography: "{typography.mono-label}" + padding: 12px 16px + wip-callout: + backgroundColor: "{colors.wip-bg}" + textColor: "{colors.wip-text}" + padding: 16px + caption-text: + backgroundColor: "{colors.surface}" + textColor: "{colors.secondary}" + typography: "{typography.body}" + padding: 0px + divider-rule: + backgroundColor: "{colors.border}" + textColor: "{colors.primary}" + height: 1px + padding: 0px + prose-inline-code: + backgroundColor: "{colors.border-muted}" + textColor: "{colors.code-inline}" + padding: 2px 6px + page-canvas: + backgroundColor: "{colors.neutral}" + textColor: "{colors.primary}" + padding: 0px + meta-muted: + backgroundColor: "{colors.surface}" + textColor: "{colors.secondary}" + typography: "{typography.mono-label}" + padding: 0px + heatmap-empty: + backgroundColor: "{colors.heatmap-low}" + padding: 0px + heatmap-light: + backgroundColor: "{colors.heatmap-mid}" + padding: 0px + heatmap-dark: + backgroundColor: "{colors.heatmap-high}" + padding: 0px + heatmap-full: + backgroundColor: "{colors.heatmap-max}" + padding: 0px + focus-accent: + backgroundColor: transparent + textColor: "{colors.tertiary}" + padding: 0px + decorative-button: + backgroundColor: "{colors.decorative-shadow}" + textColor: "{colors.primary}" + typography: "{typography.mono-label}" + padding: 12px 32px +--- + +## Overview + +yuler.dev is a **light, content-first** personal site: soft neutral canvas, white **surfaces** with **hairline borders**, and **monospace** for navigation, section titles, and article headings so the UI reads like a clean terminal or README. Body copy stays in **Inter** for readability. Motion is subtle (border darkens on hover, short transitions). There are almost no rounded “app” cards—corners stay **square**; optional **corner bracket** markers reinforce a drafted, editorial frame. Use this file when adding pages or components so new UI matches existing Tailwind usage and global styles in `src/styles/global.css`. + +## Colors + +- **Primary (`#1a1a1a`):** Default text on the canvas and on white surfaces; matches `body` in `global.css`. +- **Secondary (`#6b7280`):** Metadata, footer, timestamps, and de-emphasized labels (Tailwind `text-gray-500` class family). +- **Tertiary (`#3b82f6`):** Focus and selection affordances only—`::selection` and `:focus-visible` use this hue at partial opacity; do not flood large areas with blue. +- **Neutral (`#f5f5f5`):** Page background for home, posts, and workouts (`bg-[#f5f5f5]` / same as body background). +- **Surface (`#ffffff`):** All primary cards and article shells. +- **Border (`#e5e7eb`) / border-muted (`#f3f4f6`):** Default card and list borders; lighter rules for section dividers (`border-gray-100`). +- **Muted UI (`#9ca3af`, Tailwind `gray-400`):** Chevron and separator icons; decorative only, not for long text on white (contrast). Not a named YAML token—use Tailwind classes in code. +- **Code inline (`#e11d48`):** MDX `prose-code` (rose) inside articles—keep code blocks gray, not rose. +- **WIP / amber tokens:** Drafts and work-in-progress badges and callouts (`wip-*`); use consistently for “incomplete” messaging only. +- **Heatmap scale (`heatmap-*`):** Workout contribution cells only—from empty light gray through black for intensity; today’s cell may use an inset ring, not a fifth fill color. +- **Decorative shadow (`#fde047`):** Rare marketing-style `Button.astro` offset layer; do not use as a primary CTA color elsewhere. + +## Typography + +- **Body:** Inter, 400, comfortable line height (1.6 globally). This is the default for paragraphs, descriptions, and long-form `prose`. +- **Monospace stack** (`font-mono`): Section titles (“Posts”, “Workouts”), profile name, post H1, breadcrumbs, meta lines (`date:`, `read:`), and small UI labels. Keeps the site feeling like structured logs or docs. +- **Weights:** Semibold for card section titles and post H2 in prose; bold for profile name and post title; medium for list item titles. +- **Article prose:** Use `@tailwindcss/typography` with the existing `prose-gray` overrides in `src/pages/posts/[...slug].astro`: headings monospace and gray-900, links underlined with gray decoration, blockquotes left border + gray-50 fill, tables with gray borders and mono table headers. + +## Layout + +- **Canvas:** Full-width neutral background; content centered with horizontal padding (`px-4`), vertical rhythm `py-8` / `md:py-12`. +- **Home:** `max-w-7xl` container; **3-column** grid on large screens (profile column + two-column main), **single column** on small screens; consistent `gap-4` between cards. +- **Posts list / post detail / workouts:** `max-w-4xl` for reading width on article flows; breadcrumbs above the first card. +- **Cards:** Prefer **single white panel** with `p-6` (or `md:p-8` / `lg:p-12` for long article bodies). Headers often include a bottom rule (`border-b border-gray-100 pb-4`). +- **Spacing scale:** Use Tailwind’s 4-based rhythm aligned to tokens: `gap-4`, `p-6`, `mb-8`, etc.; avoid arbitrary large gaps unless matching an existing page. + +## Elevation & Depth + +- **No drop shadows** on standard cards; depth comes from **1px borders** and hover state (`hover:border-gray-400`). +- **Corner markers:** Optional `CornerMarkers`—small L-shaped borders at card corners; use together with cards, not on plain divs. +- **Lightbox / overlays:** Follow existing `LightBox` behavior; keep backdrop consistent with rest of site (do not introduce new blur tints without updating this doc). + +## Shapes + +- **Corners:** Default **square** (`rounded-none`) for cards, code blocks, and primary panels. Small rounded corners are acceptable only where already used (e.g. tiny badges, thought tags). +- **Borders:** 1px solid gray-200 default; lighten to gray-100 for internal dividers; interactive rows use transparent border then `hover:border-gray-200`. + +## Components + +- **Card (default):** White background, `border border-gray-200`, `p-6`, `relative`, transition on border color; include `CornerMarkers` when the pattern matches profile/posts/workouts cards. +- **Card header:** Flex row, space-between; title uses `card-title` token (mono, semibold); optional count badge `text-sm bg-gray-100 text-gray-600 font-mono px-2 py-0.5`. +- **List row (link):** `p-3`, `min-h-[3.25rem]`, hover `bg-gray-50`, optional border on hover; trailing chevron `text-gray-400` with slight translate on hover. +- **Secondary button / footer link:** `bg-gray-100 text-gray-600`, hover `bg-gray-200 text-gray-900`, `font-mono`, inline-flex with icon gap (see “View more” on posts). +- **Text link (muted):** `text-xs text-gray-400`, underline with `decoration-gray-200`, hover to gray-600 and stronger decoration (workouts “View all”). +- **Tags:** `border border-gray-300 text-gray-600`, hover darkens border and text to gray-900. +- **Status chips:** Draft = gray-200/gray-600; WIP = amber-100/amber-700; align padding `text-xs`. +- **Article callout (WIP):** Amber left border, light amber background, small mono note—match existing banner in post template. +- **Heatmap cells:** Four gray/black steps only; “today” uses inset ring; future days muted empty state. +- **Icons:** Implement as **small Astro components** under `src/components/icons/`; stroke/fill colors follow `muted` / `secondary`, inheriting `currentColor` where possible. +- **Decorative button (`Button.astro`):** Yellow offset block + black border; reserve for rare emphasis, not form submits site-wide. + +## Do's and Don'ts + +**Do** + +- Keep **Inter + monospace** split: prose body in Inter; UI chrome and headings in mono where the codebase already does. +- Reuse **gray border** vocabulary (`200` default, `100` dividers, `400` on card hover). +- Match **focus-visible** to global outline (blue tint, 2px-equivalent offset). +- Add new **icons** as dedicated Astro components in `src/components/icons/`. + +**Don’t** + +- Don’t introduce **dark mode** or new primary hues in one-off components without updating tokens and this file. +- Don’t default to **heavy rounding** or **shadow-heavy** cards; they conflict with the current editorial flat look. +- Don’t use **tertiary blue** as large fills; it’s for focus/selection semantics. +- Don’t paste **inline SVG** in random pages when an icon component pattern exists. From d56fd60c3ab7a374c32c1350a0b6e2750d9c7f28 Mon Sep 17 00:00:00 2001 From: yuler Date: Fri, 8 May 2026 15:46:29 +0800 Subject: [PATCH 03/15] =?UTF-8?q?=E2=9C=A8=20Add=20/design=20page=20with?= =?UTF-8?q?=20token=20previews?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Render DESIGN.md as a visual design system page with: - Color swatches for hex values - Typography samples for font families/weights/sizes - Spacing bars for px/rem values - Toggle between preview and raw markdown views Also cleans up unused design tokens (wip-*, code-inline, decorative-shadow) from DESIGN.md. --- DESIGN.md | 24 +-- src/content.config.ts | 5 + src/pages/design.astro | 453 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 459 insertions(+), 23 deletions(-) create mode 100644 src/pages/design.astro diff --git a/DESIGN.md b/DESIGN.md index f2434e9..af2b859 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -1,6 +1,6 @@ --- version: alpha -name: yuler.dev +name: yuler.dev's website design description: Personal site — Astro, Tailwind CSS 4, Inter + monospace accents. Light, editorial cards on a neutral canvas. colors: primary: "#1a1a1a" @@ -10,14 +10,10 @@ colors: surface: "#ffffff" border: "#e5e7eb" border-muted: "#f3f4f6" - code-inline: "#be123c" - wip-bg: "#fef3c7" - wip-text: "#b45309" heatmap-low: "#f3f4f6" heatmap-mid: "#d1d5db" heatmap-high: "#4b5563" heatmap-max: "#000000" - decorative-shadow: "#fde047" typography: body: fontFamily: Inter @@ -74,10 +70,6 @@ components: textColor: "#374151" typography: "{typography.mono-label}" padding: 12px 16px - wip-callout: - backgroundColor: "{colors.wip-bg}" - textColor: "{colors.wip-text}" - padding: 16px caption-text: backgroundColor: "{colors.surface}" textColor: "{colors.secondary}" @@ -88,10 +80,6 @@ components: textColor: "{colors.primary}" height: 1px padding: 0px - prose-inline-code: - backgroundColor: "{colors.border-muted}" - textColor: "{colors.code-inline}" - padding: 2px 6px page-canvas: backgroundColor: "{colors.neutral}" textColor: "{colors.primary}" @@ -117,11 +105,6 @@ components: backgroundColor: transparent textColor: "{colors.tertiary}" padding: 0px - decorative-button: - backgroundColor: "{colors.decorative-shadow}" - textColor: "{colors.primary}" - typography: "{typography.mono-label}" - padding: 12px 32px --- ## Overview @@ -137,10 +120,7 @@ yuler.dev is a **light, content-first** personal site: soft neutral canvas, whit - **Surface (`#ffffff`):** All primary cards and article shells. - **Border (`#e5e7eb`) / border-muted (`#f3f4f6`):** Default card and list borders; lighter rules for section dividers (`border-gray-100`). - **Muted UI (`#9ca3af`, Tailwind `gray-400`):** Chevron and separator icons; decorative only, not for long text on white (contrast). Not a named YAML token—use Tailwind classes in code. -- **Code inline (`#e11d48`):** MDX `prose-code` (rose) inside articles—keep code blocks gray, not rose. -- **WIP / amber tokens:** Drafts and work-in-progress badges and callouts (`wip-*`); use consistently for “incomplete” messaging only. - **Heatmap scale (`heatmap-*`):** Workout contribution cells only—from empty light gray through black for intensity; today’s cell may use an inset ring, not a fifth fill color. -- **Decorative shadow (`#fde047`):** Rare marketing-style `Button.astro` offset layer; do not use as a primary CTA color elsewhere. ## Typography @@ -177,10 +157,8 @@ yuler.dev is a **light, content-first** personal site: soft neutral canvas, whit - **Text link (muted):** `text-xs text-gray-400`, underline with `decoration-gray-200`, hover to gray-600 and stronger decoration (workouts “View all”). - **Tags:** `border border-gray-300 text-gray-600`, hover darkens border and text to gray-900. - **Status chips:** Draft = gray-200/gray-600; WIP = amber-100/amber-700; align padding `text-xs`. -- **Article callout (WIP):** Amber left border, light amber background, small mono note—match existing banner in post template. - **Heatmap cells:** Four gray/black steps only; “today” uses inset ring; future days muted empty state. - **Icons:** Implement as **small Astro components** under `src/components/icons/`; stroke/fill colors follow `muted` / `secondary`, inheriting `currentColor` where possible. -- **Decorative button (`Button.astro`):** Yellow offset block + black border; reserve for rare emphasis, not form submits site-wide. ## Do's and Don'ts diff --git a/src/content.config.ts b/src/content.config.ts index 99699ad..1b1ff1a 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -30,7 +30,12 @@ const thoughtsCollection = defineCollection({ }), }) +const designCollection = defineCollection({ + loader: glob({ pattern: 'DESIGN.md', base: '.' }), +}) + export const collections = { posts: postsCollection, thoughts: thoughtsCollection, + design: designCollection, } diff --git a/src/pages/design.astro b/src/pages/design.astro new file mode 100644 index 0000000..c0b6eb3 --- /dev/null +++ b/src/pages/design.astro @@ -0,0 +1,453 @@ +--- +import { readFile } from 'node:fs/promises' +import { getCollection, render } from 'astro:content' +import Layout from '../layouts/Layout.astro' + +const YAML_KEY_VALUE_RE = /^([^:]+):(.*)$/ +const [designDoc] = await getCollection('design') +if (!designDoc) + throw new Error('Missing DESIGN.md at repository root') +const rawDesignMd = await readFile(new URL('../../DESIGN.md', import.meta.url), 'utf-8') + +let yamlFrontmatter = '' + +if (rawDesignMd.startsWith('---\n')) { + const closingFenceIndex = rawDesignMd.indexOf('\n---\n', 4) + if (closingFenceIndex !== -1) { + yamlFrontmatter = rawDesignMd.slice(4, closingFenceIndex).trim() + } +} + +type TokenRow = { + path: string + value: string +} + +type TokenPreview = { + colorHex: string | null + pxValues: number[] + remValues: number[] + fontSize: string | null + fontFamily: string | null + fontWeight: number | null + lineHeight: string | null +} + +function stripWrappingQuotes(value: string): string { + if ( + (value.startsWith('"') && value.endsWith('"')) + || (value.startsWith('\'') && value.endsWith('\'')) + ) { + return value.slice(1, -1) + } + return value +} + +function parseYamlTokenRows(yaml: string): TokenRow[] { + const rows: TokenRow[] = [] + const stack: string[] = [] + + for (const rawLine of yaml.split('\n')) { + const line = rawLine.replace(/\t/g, ' ') + const trimmed = line.trim() + if (!trimmed || trimmed.startsWith('#')) + continue + + const indent = line.length - line.trimStart().length + const level = Math.floor(indent / 2) + const keyValueMatch = trimmed.match(YAML_KEY_VALUE_RE) + if (!keyValueMatch) + continue + + const key = keyValueMatch[1].trim() + const value = keyValueMatch[2].trim() + + stack.length = level + stack[level] = key + + if (value) { + rows.push({ + path: stack.join('.'), + value, + }) + } + } + + return rows +} + +const tokenRows = parseYamlTokenRows(yamlFrontmatter) +const HEX_COLOR_RE = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i +const PX_VALUE_RE = /(\d+(?:\.\d+)?)px/g +const REM_VALUE_RE = /(\d+(?:\.\d+)?)rem/g +const FONT_WEIGHT_RE = /^\d{2,3}$/ + +function getTokenPreview(tokenPath: string, value: string): TokenPreview { + const normalized = stripWrappingQuotes(value.trim()) + const colorHex = HEX_COLOR_RE.test(normalized) ? normalized : null + + const pxValues: number[] = [] + for (const match of normalized.matchAll(PX_VALUE_RE)) { + const parsed = Number.parseFloat(match[1]) + if (Number.isFinite(parsed)) + pxValues.push(parsed) + } + + const remValues: number[] = [] + for (const match of normalized.matchAll(REM_VALUE_RE)) { + const parsed = Number.parseFloat(match[1]) + if (Number.isFinite(parsed)) + remValues.push(parsed) + } + + const leafKey = tokenPath.split('.').at(-1) ?? '' + const fontSize = leafKey === 'fontSize' ? normalized : null + const remValuesForPreview = leafKey === 'fontSize' ? [] : remValues + const fontFamily = leafKey === 'fontFamily' ? normalized : null + + const fontWeight = leafKey === 'fontWeight' && FONT_WEIGHT_RE.test(normalized) + ? Number.parseInt(normalized, 10) + : null + + const lineHeight = leafKey === 'lineHeight' ? normalized : null + + return { colorHex, pxValues, remValues: remValuesForPreview, fontSize, fontFamily, fontWeight, lineHeight } +} + +type TokenRowWithPreview = TokenRow & { preview: TokenPreview } + +const TOKEN_REF_RE = /^\{([^}]+)\}$/ + +const tokenValueByPath = tokenRows.reduce>((acc, row) => { + acc[row.path] = row.value + return acc +}, {}) + +function resolveTokenValue(rawValue: string, visited = new Set()): string { + const normalized = stripWrappingQuotes(rawValue.trim()) + const refMatch = normalized.match(TOKEN_REF_RE) + if (!refMatch) + return normalized + + const refPath = refMatch[1].trim() + if (!refPath) + return normalized + + if (visited.has(refPath)) + return normalized + + const next = tokenValueByPath[refPath] + if (!next) + return normalized + + visited.add(refPath) + return resolveTokenValue(next, visited) +} + +type TokenRowResolved = TokenRowWithPreview & { resolvedValue: string } + +const tokenRowsResolved: TokenRowResolved[] = tokenRows.map((row) => { + const resolvedValue = resolveTokenValue(row.value) + return { + ...row, + resolvedValue, + preview: getTokenPreview(row.path, resolvedValue), + } +}) + +const GROUP_ORDER = ['colors', 'typography', 'rounded', 'spacing', 'components'] as const +const groupedTokens = tokenRowsResolved.reduce>((acc, row) => { + const group = row.path.split('.')[0] ?? 'other' + acc[group] ??= [] + acc[group].push(row) + return acc +}, {}) + +const totalTokens = tokenRowsResolved.length +const previewColorCount = tokenRowsResolved.filter(row => Boolean(row.preview.colorHex)).length +const previewPxCount = tokenRowsResolved.filter(row => row.preview.pxValues.length > 0).length +const previewRemCount = tokenRowsResolved.filter(row => row.preview.remValues.length > 0).length +const previewFontSizeCount = tokenRowsResolved.filter(row => Boolean(row.preview.fontSize)).length +const previewFontFamilyCount = tokenRowsResolved.filter(row => Boolean(row.preview.fontFamily)).length +const previewFontWeightCount = tokenRowsResolved.filter(row => row.preview.fontWeight !== null).length +const previewLineHeightCount = tokenRowsResolved.filter(row => Boolean(row.preview.lineHeight)).length + +const { Content } = await render(designDoc) +--- + + +
+
+ + + +
+

DESIGN.md

+

+ Rendered from + + DESIGN.md + + at repository root. +

+ +
+
+
+
+ Total tokens: {totalTokens} +
+
+ Preview: {previewColorCount} color hex,{' '} + {previewPxCount} px values,{' '} + {previewRemCount} rem values,{' '} + {previewFontSizeCount} font sizes,{' '} + {previewFontFamilyCount} font families,{' '} + {previewFontWeightCount} font weights,{' '} + {previewLineHeightCount} line heights +
+
+
+ + +
+
+
+ Colors render as swatches, `px` as line blocks, `fontFamily`/`fontWeight` as an "Aa" sample. +
+
+
+ +
+
+ { + yamlFrontmatter && ( +
+

Design Tokens (YAML)

+ {GROUP_ORDER.map((group) => { + const rows = groupedTokens[group] ?? [] + if (rows.length === 0) + return null + + const title = group[0].toUpperCase() + group.slice(1) + + return ( +
+

{title}

+
+ + + + + + + + + + {rows.map((row) => { + const preview = row.preview + return ( + + + + + + ) + })} + +
Token PathValuePreview
{row.path}{row.value} +
+ {row.resolvedValue !== stripWrappingQuotes(row.value.trim()) && ( +
+ = {row.resolvedValue} +
+ )} + {preview.fontFamily && ( +
+
+ Aa +
+ {preview.fontFamily} +
+ )} + {preview.fontSize && ( +
+
+ {preview.fontSize} +
+ {preview.fontSize} +
+ )} + + {preview.fontWeight !== null && ( +
+
+ Aa +
+ {preview.fontWeight} +
+ )} + {preview.lineHeight && ( +
+
+ A
+ a +
+ {preview.lineHeight} +
+ )} + {preview.colorHex && ( +
+
+ {preview.colorHex} +
+ )} + {preview.pxValues.length > 0 && ( +
+ {preview.pxValues.map((px) => { + const width = Math.max(2, Math.min(160, px)) + return ( +
+
+ {px}px +
+ ) + })} +
+ )} + {preview.remValues.length > 0 && ( +
+ {preview.remValues.map((rem) => { + const width = Math.max(2, Math.min(160, rem * 16)) + return ( +
+
+ {rem}rem +
+ ) + })} +
+ )} + {!preview.colorHex && preview.pxValues.length === 0 && preview.remValues.length === 0 && !preview.fontSize && !preview.fontFamily && preview.fontWeight === null && !preview.lineHeight && ( + - + )} +
+
+
+
+ ) + })} +
+ ) + } + +
+
+ +
+
+
+ + +
+
+
+
+ + From b6b2466fcd6fd16224bafb6de6658122f7916dfd Mon Sep 17 00:00:00 2001 From: yuler Date: Fri, 8 May 2026 16:12:05 +0800 Subject: [PATCH 04/15] =?UTF-8?q?=E2=9C=A8=20Add=20agents=20page=20with=20?= =?UTF-8?q?Switch=20component=20and=20refactor=20design=20page?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 5 ++ src/components/Footer.astro | 5 +- src/components/Switch.astro | 99 +++++++++++++++++++++++++++++ src/content.config.ts | 5 -- src/pages/agents.astro | 120 ++++++++++++++++++++++++++++++++++++ src/pages/design.astro | 102 +++++++----------------------- 6 files changed, 251 insertions(+), 85 deletions(-) create mode 100644 src/components/Switch.astro create mode 100644 src/pages/agents.astro diff --git a/AGENTS.md b/AGENTS.md index 9164e7c..c5d9fe0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,3 +1,8 @@ +--- +name: yuler.dev +description: Agent instructions for the yuler.dev repository. +--- + # Agent instructions (yuler.dev) Personal site at https://yuler.dev — Astro 6, MDX, Tailwind CSS 4, TypeScript. Package manager: **pnpm**. diff --git a/src/components/Footer.astro b/src/components/Footer.astro index 82670e6..d1836b0 100644 --- a/src/components/Footer.astro +++ b/src/components/Footer.astro @@ -2,6 +2,9 @@ const today = new Date() --- -