Ctrl K
ComunidadGuías…

DESIGN.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
---
name: know-yourself
skin: "clean warm-white daylight (default) · dark night lamp · fresh cool gray-blue (2026-09-30)"
palette_source: "src/app/styles/base.css (:root / .dark) and src/app/styles/fresh.css (html.fresh); structural scale: tokens.css"
generated: 2026-10-01
note: "Generated by scripts/gen-design-tokens.mjs (npm run design:tokens). Never hand-edit this header — regenerate it."
colors:
  light:
    color-paper: "oklch(99.1% 0.005 85)"
    color-paper-strong: "oklch(99.4% 0.003 85)"
    color-ink: "oklch(25% 0.012 55)"
    color-night: "oklch(21% 0.008 60)"
    color-night-ink: "oklch(92% 0.01 85)"
    color-accent: "oklch(52% 0.1 45)"
    color-accent-ink: "oklch(99% 0.006 85)"
    color-accent-soft: "oklch(93% 0.025 60)"
    color-teal: "oklch(48% 0.05 155)"
    color-teal-soft: "oklch(94% 0.015 150)"
    color-signal: "oklch(78% 0.09 85)"
    color-signal-ink: "oklch(30% 0.035 70)"
    color-sky: "oklch(85% 0.05 80)"
    color-muted: "oklch(95.5% 0.008 85)"
    color-muted-text: "oklch(47% 0.014 60)"
    color-sheet-wash: "oklch(94.5% 0.012 85)"
    color-sheet-surface: "oklch(98.8% 0.008 87)"
    color-sheet-ink: "oklch(46% 0.028 55)"
    color-blue-wash: "oklch(92.5% 0.03 75)"
    color-danger: "oklch(52% 0.14 25)"
    color-overlay-ink: "oklch(100% 0 0)"
    color-print-paper: "oklch(100% 0 0)"
    color-print-ink: "oklch(25% 0.03 48)"
    color-print-muted: "oklch(44% 0.02 50)"
    color-topic-self: "oklch(50% 0.08 45)"
    color-topic-emotion: "oklch(51% 0.07 25)"
    color-topic-relationship: "oklch(51% 0.055 70)"
    color-topic-life: "oklch(49% 0.05 150)"
    surface-contrast: "oklch(25% 0.012 55)"
    surface-contrast-ink: "oklch(99.1% 0.005 85)"
  dark:
    color-paper: "oklch(21% 0.008 60)"
    color-paper-strong: "oklch(27% 0.01 60)"
    color-ink: "oklch(92% 0.01 85)"
    color-night: "oklch(21% 0.008 60)"
    color-night-ink: "oklch(92% 0.01 85)"
    color-accent: "oklch(74% 0.075 65)"
    color-accent-ink: "oklch(20% 0.02 55)"
    color-accent-soft: "oklch(30% 0.03 60)"
    color-teal: "oklch(72% 0.045 150)"
    color-teal-soft: "oklch(30% 0.022 150)"
    color-signal: "oklch(80% 0.09 82)"
    color-signal-ink: "oklch(22% 0.035 78)"
    color-sky: "oklch(76% 0.045 78)"
    color-muted: "oklch(29% 0.01 60)"
    color-muted-text: "oklch(76% 0.012 85)"
    color-sheet-wash: "oklch(29% 0.014 65)"
    color-sheet-surface: "oklch(26% 0.01 62)"
    color-sheet-ink: "oklch(80% 0.028 85)"
    color-blue-wash: "oklch(28% 0.03 60)"
    color-danger: "oklch(72% 0.12 25)"
    color-overlay-ink: "oklch(100% 0 0)"
    color-print-paper: "oklch(100% 0 0)"
    color-print-ink: "oklch(25% 0.03 48)"
    color-print-muted: "oklch(44% 0.02 50)"
    color-topic-self: "oklch(75% 0.09 45)"
    color-topic-emotion: "oklch(78% 0.08 25)"
    color-topic-relationship: "oklch(80% 0.06 70)"
    color-topic-life: "oklch(78% 0.05 150)"
    surface-contrast: "oklch(30% 0.012 60)"
    surface-contrast-ink: "oklch(92% 0.01 85)"
  fresh:
    color-paper: "oklch(96.8% 0.008 235)"
    color-paper-strong: "oklch(98.6% 0.004 235)"
    color-ink: "oklch(27% 0.022 255)"
    color-night: "oklch(20% 0.01 250)"
    color-night-ink: "oklch(93% 0.008 235)"
    color-accent: "oklch(48% 0.11 285)"
    color-accent-ink: "oklch(99% 0.004 285)"
    color-accent-soft: "oklch(92% 0.03 285)"
    color-teal: "oklch(48% 0.05 180)"
    color-teal-soft: "oklch(94% 0.015 180)"
    color-signal: "oklch(70% 0.11 280)"
    color-signal-ink: "oklch(26% 0.06 280)"
    color-sky: "oklch(85% 0.05 80)"
    color-muted: "oklch(94.5% 0.006 235)"
    color-muted-text: "oklch(44% 0.02 250)"
    color-sheet-wash: "oklch(93.5% 0.01 235)"
    color-sheet-surface: "oklch(98.2% 0.005 235)"
    color-sheet-ink: "oklch(42% 0.025 250)"
    color-blue-wash: "oklch(92.5% 0.03 75)"
    color-danger: "oklch(50% 0.14 25)"
    color-overlay-ink: "oklch(100% 0 0)"
    color-print-paper: "oklch(100% 0 0)"
    color-print-ink: "oklch(25% 0.03 48)"
    color-print-muted: "oklch(44% 0.02 50)"
    color-topic-self: "oklch(50% 0.09 280)"
    color-topic-emotion: "oklch(49% 0.08 300)"
    color-topic-relationship: "oklch(49% 0.06 230)"
    color-topic-life: "oklch(48% 0.05 170)"
    surface-contrast: "oklch(26% 0.015 250)"
    surface-contrast-ink: "oklch(93% 0.008 235)"
type:
  font-display: "var(--font-archivo), \"Segoe UI\", \"PingFang SC\", \"Microsoft YaHei\", \"Noto Sans CJK SC\", sans-serif"
  font-body: "\"Segoe UI\", \"PingFang SC\", \"Microsoft YaHei\", \"Noto Sans CJK SC\", sans-serif"
  font-code: "var(--font-plex-mono), \"Cascadia Code\", \"SFMono-Regular\", Consolas, monospace"
  scale:
    text-2xs: "0.6875rem"
    text-xs: "0.75rem"
    text-sm: "0.875rem"
    text-md: "1rem"
    text-lg: "1.25rem"
    text-xl: "1.75rem"
    text-2xl: "2.5rem"
    text-3xl: "3.25rem"
    text-display: "4rem"
  tracking:
    tracking-display: "-0.015em"
    tracking-title: "-0.01em"
    tracking-body: "0em"
    tracking-label: "0.08em"
space:
  space-3xs: "0.25rem"
  space-2xs: "0.5rem"
  space-xs: "0.75rem"
  space-sm: "1rem"
  space-md: "1.5rem"
  space-lg: "2rem"
  space-xl: "3rem"
  space-2xl: "4.5rem"
  space-3xl: "7rem"
radius:
  radius-control: "0.875rem"
  radius-panel: "1.375rem"
  radius-media: "1.125rem"
  radius-pill: "999px"
motion:
  duration-short: "180ms"
  duration-medium: "280ms"
  ease-out: "cubic-bezier(0.16, 1, 0.3, 1)"
layout:
  layout-max: "80rem"
  header-height: "5rem"
  banner-height: "4.5rem"
meter:
  meter-track: "color-mix(in oklab, var(--ink) 7%, transparent)"
  meter-rule: "color-mix(in oklab, var(--ink) 20%, transparent)"
  meter-fill: "oklch(52% 0.1 45)"
  meter-needle: "oklch(78% 0.09 85)"
shadows:
  shadow-card-hover: "0 1.25rem 2.5rem color-mix(in oklab, var(--color-ink) 14%, transparent)"
  shadow-feedback: "0 0.875rem 2rem color-mix(in oklab, var(--color-ink) 17%, transparent)"
  shadow-dialog: "0 1.5rem 4rem color-mix(in oklab, var(--color-night) 26%, transparent)"
  shadow-soft: "0 1.25rem 2.75rem color-mix(in oklab, var(--color-ink) 11%, transparent)"
  shadow-lift: "0 1.75rem 3.5rem color-mix(in oklab, var(--color-ink) 16%, transparent)"
---

# Design — 认识你自己 | Know Yourself

A locked design system for the rebuilt application. Product truth lives in
`PRODUCT.md`; this file owns the visual and interaction world.

## Direction

**Creative north star: a community broadsheet assembled one observation at a time.**

The interface borrows the hierarchy and material honesty of a small independent
publication without imitating distressed paper or retro decoration. Posts are
the issue. Assessments are recurring editorial instruments. Private drafts are
the writer's desk.

## Genre

Editorial social product: tactile, direct, text-led, and operationally clear.

## Macrostructure families

- **Front page:** full-bleed photo hero + four facet sections, each in its own layout family (two-column, reversed, inline pill strip, compact split). Every facet leads with its head — in the reversed family the sticker badge closes the head row instead of opening it, so the title still starts the section (2026-10-01). The inline strip lays its entries into equal cells across the whole band.
- **Community and discovery:** masthead + editorial statement + creation desk + single reading column.
- **Focused tasks:** compact masthead + narrow workbench.
- **Libraries and operations:** index rail + working column.
- **Long-form content:** bounded reading column with marginal metadata.

## Theme

- `--color-paper`: clean warm white, `oklch(99.1% 0.005 85)`
- `--color-paper-strong`: warm stock, `oklch(99.4% 0.003 85)`
- `--color-ink`: warm near-black, `oklch(25% 0.012 55)`
- `--color-muted-text`: warm grey, `oklch(47% 0.014 60)`
- `--color-accent`: deep clay, `oklch(52% 0.1 45)`
- `--color-teal`: sage, `oklch(48% 0.05 155)` — secondary information
- `--color-signal`: honey, `oklch(78% 0.09 85)`
- `--color-danger`: red, `oklch(52% 0.14 25)`
- Topics are four lighter, less saturated exposures of the same room: self apricot coral `oklch(50% 0.08 45)`, feeling rose `oklch(51% 0.07 25)`, relating honey `oklch(51% 0.055 70)`, living mint `oklch(49% 0.05 150)`.

The daylight skin (2026-09-26 refresh of the 2026-09-19 morning light): a
clean warm-white page with warm near-black ink. Deep clay marks actions and
current state. Sage identifies secondary information. Honey is reserved for
measured values — a golden pixel always means "you are here". The palette
reads as an everyday room in daylight rather than simulated newsprint or a
clinical instrument panel. The paper carries a faint tooth — a tileable grain
(baked by `scripts/generate-paper-grain.mjs`) laid over every surface at ~2%
opacity (3% in dark) through a fixed `body::after`; material honesty, never
distress (2026-09-26).

Dark (the night lamp): the page stays at `oklch(21%)` so the room stays dark,
and the elevation ladder does the separating — cards sit at 26–27%,
header/footer/panels at 30%, and dark draws its own stronger borders (24% /
17% ink mixes) instead of borrowing the light ones (2026-09-30; before this,
cards sat only four points over the page and dark borders all but vanished).

Fresh (2026-09-29): the cool gray-blue daylight skin, selectable alongside
the daylight and night skins. `html.fresh` overrides the palette in
`src/app/styles/fresh.css` — an airy near-white ground with blue-gray ink and
one violet-blue accent, four cool topic exposures, and its own hero wash
(`data-wash="fresh"`). It is a self-contained LIGHT skin: selecting it never
follows the system dark preference. All values are contrast-verified with
`node scripts/check-palette-contrast.mjs`, which reads all three palette
blocks — 45 foreground/background pairs at 5.4:1 or better.

The homepage carries a handmade cuteness layer (2026-09-20) on top of this
skin: a round CJK question voice, emoji at fixed decorative positions, sticker
badges, washi-tape tabs, and a watercolor hero wash. Every element is specified under
Signature components; none of them may drift onto other surfaces.

## Typography

- Display: Archivo variable, weights 600–700 in use (620 for section titles, 640 for headlines), upright only.
- Question voice: the system round CJK face (`YouYuan`/`幼圆`, weight 500) speaks in first person — it carries only the homepage hero question and the facet entry lines, then falls back through the display stack.
- Body: system sans with native CJK metrics.
- Data: IBM Plex Mono, only for scores, counts, dates, and question indices.
- Display tracking: `-0.025em` for Latin and `0` for Chinese.
- Body measure: 65–75 characters.
- Heavy 750–850 weights are prohibited: CJK falls back to system sans, where
  synthetic bold reads as chunky rather than editorial.
- Both display faces are committed to the repo and served through
  `next/font/local` (`src/app/fonts/`, 2026-10-01): builds never fetch from
  Google, so the type system cannot break on a blocked network the way the
  dev server did when `fonts.gstatic.com` was unreachable.

## Spacing and shape

- Four-point named spacing scale in `tokens.css`.
- Page width: 80rem maximum; primary reading column: 46rem maximum.
- Corners are large and soft (since 2026-09-19): controls `--radius-control` 0.875rem, cards `--radius-panel` 1.375rem, media `--radius-media` 1.125rem.
- Pills are reserved for filters, tags, compact statuses, and the single catalog CTA.
- Separation prefers rules and whitespace. Shadows are reserved for overlays or a genuinely lifted editor surface.

## Motion

- Active publication/filter strips settle with a short overshoot.
- Ordinary feedback uses color, opacity, and transforms under 240ms.
- Content is visible before animation.
- On the quiz workbench the meter fill eases to its new width, a hovered
  answer rises one pixel with its key letter warming toward the accent, and a
  selected answer keeps a quiet accent ring (2026-10-01).
- Route arrival is the one exception (2026-09-26), and it speaks a small
  vocabulary — the destination picks the gesture:
  result = **letter** (the answer unfolds from both edges toward the center,
  420ms); test detail = **turn** (the page swings in from its left edge,
  460ms); quiz = **focus** (content blooms out of one point, 480ms); journal
  piece = **curl** (peels open from its bottom-right corner, 500ms);
  everything else = **rise** (the quiet lift, 260ms). Hard loads never
  animate, and reduced motion skips every gesture entirely; all other motion
  still shows content first.
- Reduced motion removes movement without removing state.
- No pointer-driven page distortion (2026-09-26): the liquid ripple
  (LiquidDistort, 2026-09-19) was judged ugly by the owner and removed — the
  page must never warp under the pointer.

## Shared component grammar

- The masthead is a horizontal editorial rule, not a floating navigation card.
- Feed items are articles. Their typography and media determine their rhythm.
- Author, date, content type, badges, and moderation state share one metadata system.
- Inputs resemble writing fields with visible labels and complete recovery text.
- Empty states explain the missing material and offer one useful next action.

## Signature components

The handmade homepage layer (2026-09-20). These are front-page-scoped; other
surfaces must not copy them piecemeal — scattered reuse is how a system loses
its signature and starts looking generated.

- **Watercolor hero** (`press-home-lede`): baked warm washes — clay, honey,
  honey-earth, and sage with turbulence-roughened edges (`public/bg/hero-wash-light/-dark.webp`,
  regenerated by `scripts/generate-hero-wash.mjs`, `npm run hero:wash`) — bloom
  full-bleed behind the lede on transparent grounds; a gentle paper scrim
  (62% → 0%, bottom to top) keeps the question readable in both themes; the
  band closes on a 1px rule (2026-09-26).
- **Sticker badge** (`press-home-facet-index`): a 2.1rem circle in
  `--accent-soft` with a 2px `--paper-strong` edge, holding the facet emoji,
  tilted −5° on odd facets and +4° on even ones.
- **Washi-tape tab** (`press-home-facet-anchor::before`): one 3.2rem × 1rem
  strip of accent-washed paper across the anchor card's top edge — 2px
  radius, +3° tilt, 0.85 opacity. One per anchor card, never a repeating
  texture.
- **Round question voice** (`press-home-lede h1`, `press-home-facet-entry`):
  the round CJK face at weight 500 speaks in first person — hero question and
  facet entry lines only.
- **Mascot rain** (`press-mascot-rain`, 2026-09-26; mascots switch to
  いらすとや 2026-09-30): eight sprites — five いらすとや mascots (bear, cat,
  hamster, cloud, sprout; converted to webp in `public/stickers/`,
  © みふねたかし / いらすとや, credited in the home footer) and two tiny
  hand-drawn shapes (heart, star)
  drift down through the hero's morning sky on invisible lanes, each with its
  own speed, sway, and gentle tilt (never a full spin — the mascots must not
  hang upside down). Decorative only: aria-hidden, pointer-transparent,
  painted behind the lede text and faded by the paper scrim near the bottom.
  The random layout is generated client-side only; under reduced motion no
  sprite exists.
- **Catalog CTA capsule** (`press-home-catalog-cta`): the page's one pill
  call-to-action, an accent outline that fills with `--accent` on hover.

## Accessibility and privacy

- All controls remain keyboard operable with visible focus.
- Touch targets are at least 44 CSS pixels.
- Layouts work at 320, 375, 414, and 768px, at 200% zoom, and without motion.
- Chinese and English keep equivalent hierarchy and actions.
- Publication, draft, processing, hidden, unpublished, and deletion states are named before the user acts.

## Prohibited defaults

Each ban names the default it blocks and the do that replaces it.

- No bento dashboard, glass cards, gradient text, decorative mesh, fake browser chrome, or icon-tile feature grid. Do: separate material with 1px `--line` rules and `--paper-strong` surfaces; mark emphasis with `--accent-soft` washes.
- No diagnosis language, invented usage figures, testimonials, or clinical claims. Do: describe tendencies in plain language; every number shown is a real measurement set in the mono face.
- No decorative monospace, italic display headings, or duplicated heading kickers. Do: monospace only for scores, counts, dates, and indices; display headings stay upright; one kicker per block.
- No emoji as functional icons, status indicators, or body-text decoration. Do: emoji only at the fixed decorative positions named in Signature components (hero question, hero lede copy, facet badges and titles, catalog CTA, voice headings).
- No inaccessible hover-only actions or drag-only ordering. Do: actions stay visible on keyboard focus; ordering always offers a keyboard path.
- No runtime third-party fonts, analytics, or AI image generation. Do: system stacks plus locally served variable fonts; first-party aggregate events only.

## Iteration guide

1. Change one component at a time and reference token names, never hex values.
2. After any palette change, run `node scripts/check-palette-contrast.mjs` —
   the 45 foreground/background pairs must stay at 5.4:1 or better across all
   three skins (daylight, dark, fresh).
3. After any import or file-structure change, run `npm run check:registry`;
   a new runtime relative import must also be added to the compile allow-list
   in `scripts/test-storage.mjs`.
4. For skin trials, screenshot `design-preview.html` (it renders both themes)
   with a throwaway Edge profile (`--user-data-dir` on a fresh temp dir).
   Byte-identical screenshots mean a cached fake render, not a real one.
5. Local `npm test` only runs with `NODE_ENV` cleared.

## Known gaps

- Resolved 2026-10-01: ink-ground text no longer borrows page-ink tones. The
  assessment catalog's intro band, the result letter panel's summary and note,
  and the focus headers (quiz / result / journal piece) now derive their text
  from the band's own ink (`--surface-contrast-ink`); the result page's focus
  header also returned to the ink band the quiz workbench wears, instead of a
  light band carrying white legacy link text. The feed's empty state is one
  grid item again (the community feed root wraps its toolbar and states, so
  the quiet-hours card can no longer auto-place under the heading), the
  compact facet's heading carries the display tier again, and the wordmark
  seal keeps its 己 glyph on `wellness-page` routes whose legacy sheet zeroed
  the mark's font size.
- Resolved 2026-09-27: `--accent-soft` is now declared as
  `var(--color-accent-soft)`. It had been referenced by nine `refactor.css`
  rules (selected quiz answers, active toolbar buttons, mobile-nav state,
  sticker badge) while declared nowhere, so every selected/active surface
  rendered transparent — production users read the missing states as
  "parts of the page not loading". The sticker badge gains the soft wash
  that was originally written for it.
- Homepage micro-interactions use literal 160/200ms while the motion tokens
  (`--duration-short` 180ms, `--duration-medium` 280ms) sit unused by them.
- The round question voice depends on a system font; devices without YouYuan
  fall back to the display stack, so the cute voice varies by platform.
- The hero washes are baked at 16:9; the portrait crop keeps the quiet text
  zone but trims the right-side blooms, and the far-left sage whisper may
  touch the question's first line — recheck if the composition moves.
- The washi-tape tab is positioned for the current 16–22rem anchor column;
  it does not adapt to other card widths.

## Implementation source

`tokens.css` keeps the structural scale; the active colour palette lives in the
`:root` and `.dark` blocks of `src/app/styles/base.css`, with the fresh skin's
overrides in the `html.fresh` block of `src/app/styles/fresh.css`. The other
seven `styles/*.css` modules (shell, assessments, quiz, result, journal, home,
motion — split from the old single refactor.css on 2026-09-27) implement the
shell and migrated surfaces. Legacy selectors may remain only while an
unmigrated route still consumes them; new components use the `press-*`
namespace. The YAML header of this file is generated from the palette blocks
by `scripts/gen-design-tokens.mjs` (`npm run design:tokens`), which reads
tokens.css, base.css, and fresh.css; never hand-edit the header — regenerate
it.