diff --git a/apps/frontend/src/components/AppShell.tsx b/apps/frontend/src/components/AppShell.tsx index 6bc61f4d..62eea958 100644 --- a/apps/frontend/src/components/AppShell.tsx +++ b/apps/frontend/src/components/AppShell.tsx @@ -160,11 +160,10 @@ function NavItemLink({ title={collapsed ? label : undefined} className={({ isActive }) => cn( - // W13 — pill nav items match the AIS button shape; the active - // bg-primary/10 + text-primary tint then reads exactly like the - // AIS selected-nav state. Hover/active transitions stay on the - // W11 150 ms ease-out-soft tokens. - "flex items-center rounded-full py-2 text-sm font-medium transition-colors duration-fast ease-out-soft", + // W11-F polish — sidebar nav hover/active transitions land on the + // W11-A 150 ms ease-out-soft tokens for parity with every other + // hoverable affordance (buttons, dropdown items, tabs). + "flex items-center rounded-md py-2 text-sm font-medium transition-colors duration-fast ease-out-soft", collapsed ? "justify-center px-2" : "gap-2 px-3", "hover:bg-accent hover:text-accent-foreground", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", diff --git a/apps/frontend/src/components/ui/badge.tsx b/apps/frontend/src/components/ui/badge.tsx index 86ad8336..c6496cb2 100644 --- a/apps/frontend/src/components/ui/badge.tsx +++ b/apps/frontend/src/components/ui/badge.tsx @@ -49,17 +49,17 @@ const badgeVariants = cva( // critical text-red-700 → 5.54:1 // high text-orange-800 → 6.47:1 // medium text-yellow-800 → 5.91:1 - // low text-teal-800 → 6.59:1 (W13 — token moved to - // teal-700, text follows the hue) + // low text-blue-700 → 5.83:1 // info text-slate-600 → 6.41:1 // // The dot indicators (SeverityBadge / DependencyScopeBadge / chart // legends) still use `bg-risk-X` raw so the brand colour stays - // recognisable — only the text shade is darkened. + // recognisable — only the text shade is darkened. Token values in + // `index.css` are NOT changed (W11 prohibition: "Severity 색 변경 0"). critical: "border-transparent bg-risk-critical/10 text-red-700", high: "border-transparent bg-risk-high/10 text-orange-800", medium: "border-transparent bg-risk-medium/15 text-yellow-800", - low: "border-transparent bg-risk-low/10 text-teal-800", + low: "border-transparent bg-risk-low/10 text-blue-700", info: "border-transparent bg-risk-info/15 text-slate-600", success: "border-transparent bg-emerald-100 text-emerald-700", }, diff --git a/apps/frontend/src/components/ui/button.tsx b/apps/frontend/src/components/ui/button.tsx index 03169f27..eea5203e 100644 --- a/apps/frontend/src/components/ui/button.tsx +++ b/apps/frontend/src/components/ui/button.tsx @@ -5,26 +5,25 @@ import { forwardRef, type ButtonHTMLAttributes } from "react"; import { cn } from "@/lib/utils"; /** - * Button — W13 AIS adoption (pill shape) on top of the W11-A polish. + * Button — W11-A polish. * - * - `rounded-full` pills are the Google AI Studio button shape. This is - * deliberately NOT driven by the --radius token — raising the token to - * a pill value would full-round cards/dialogs via the tailwind.config - * calc() derivations. Inputs keep the token radius on purpose. - * - `transition-all duration-fast ease-out-soft` — 150 ms eased hover / - * focus transition (W11 Linear polish, retained). - * - Focus ring `ring-2` + `ring-offset-2` (matches --ring, now the blue - * primary, for a coherent focus signal). - * - shadow-sm on default/destructive/outline resolves to a zero-alpha - * no-op under W13 tokens (AIS keeps in-flow surfaces flat); kept in - * the class lists so a future token change re-enables elevation - * without touching this file. + * Changes vs. previous baseline: + * - `transition-colors` → `transition-all duration-fast ease-out-soft` so + * hover/focus is a 150 ms eased transition (Linear polish) covering + * background AND shadow. + * - Focus ring kept at `ring-2` + `ring-offset-2` (matches new --ring token + * which now lines up with --primary for a coherent focus signal). + * - Default variant picks up `shadow-sm` so the primary CTA reads as a + * raised affordance against the off-white page background (Vercel + * deployments-1 "Visit" button reference). + * - Outline variant gets `shadow-sm` too — light elevation, no border + * contrast bump needed. * * The hex colors themselves are NOT in this file — they flow from the - * tokens declared in `src/index.css` (W13). + * tokens declared in `src/index.css` (W11-A). */ const buttonVariants = cva( - "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-full text-sm font-medium ring-offset-background transition-all duration-fast ease-out-soft focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-all duration-fast ease-out-soft focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", { variants: { variant: { @@ -41,8 +40,8 @@ const buttonVariants = cva( }, size: { default: "h-9 px-4 py-2", - sm: "h-8 px-3 text-xs", - lg: "h-10 px-6", + sm: "h-8 rounded-md px-3 text-xs", + lg: "h-10 rounded-md px-6", icon: "h-9 w-9", }, }, diff --git a/apps/frontend/src/index.css b/apps/frontend/src/index.css index 6b744271..3d6eb07c 100644 --- a/apps/frontend/src/index.css +++ b/apps/frontend/src/index.css @@ -5,26 +5,24 @@ /* --------------------------------------------------------------------------- * TRUSCA — design tokens * - * W13 (2026-06-12) — Google AI Studio light tone, adopted from the dev - * prototype (PR #394) after user review. Supersedes the W11-A Vercel + - * Linear palette; the W11 typography hierarchy, focus ring, and motion - * polish are retained unchanged. + * W11-A (2026-05-27) — Vercel base + Linear polish (option C, light single). + * SoT: docs/ux/design-philosophy-evolution-plan-2026-05-27.md §4. * - * Tone reference — Google AI Studio (light): - * - plain white canvas, grey panel tint, #dadce0 hairline borders - * - Google-blue primary CTA + tonal (light-blue) secondary - * - pill buttons (see button.tsx) and flat cards (border + tone, no - * shadow); shadows are reserved for floating surfaces - * - fonts stay Inter + JetBrains Mono (Google Sans is proprietary) + * Tone reference: + * - Vercel light deployments / domains / deployment detail + * → background / surface / border / sidebar tint / dense rows / primary + * near-black button + * - Linear feature / dashboard + * → typography hierarchy (heading semibold), focus ring (visible 2 + 2 + * offset), hover transitions (150 ms ease-out), elevation shadows, + * dropdown polish. Dark variant intentionally NOT absorbed — Linear + * is dark-native, we re-interpret only the polish layer in light. * * Layout invariants (unchanged): * sidebar 224 px · header 48 px · table row 40 px · Inter + JetBrains Mono. - * Density is kept — only the skin changed. * - * Risk severity colors: - * Critical / High / Medium / Info unchanged. Low moved blue-600 → teal-700 - * in W13 (user decision): the old blue collided with the new Google-blue - * primary, making Low badges read like CTAs / links. + * Risk severity colors (unchanged — domain semantics fixed): + * Critical / High / Medium / Low / Info. * * Forward-compat note: * Dark mode tokens are NOT defined in this phase (2026-05-27 user decision). @@ -36,83 +34,79 @@ @layer base { :root { /* ------------------------------------------------------------------- - * shadcn/ui base tokens — Google AI Studio light palette (W13). + * shadcn/ui base tokens — re-tuned to Vercel light palette. * * HSL is shadcn convention. Hex equivalents are noted for review; * never reference the hex directly in components (use the CSS var or * the Tailwind utility — e.g. `bg-background`, `text-foreground`). * ----------------------------------------------------------------- */ - /* Page background — plain white canvas (AIS). Cards sit flush on it - * and separate via border + tone, not elevation. */ - --background: 0 0% 100%; /* #ffffff */ - --foreground: 0 0% 12%; /* #1f1f1f — Google grey-900 */ + /* Page background — slight off-white so elevated surfaces (cards, + * popovers) read as raised. Vercel uses ~#fafafa for the canvas. */ + --background: 0 0% 98%; /* #fafafa */ + --foreground: 240 6% 10%; /* #18181b — warm near-black (was #0f172a navy) */ - /* Surfaces share the white canvas; --muted carries the grey panel / - * table-header tint instead. */ + /* Elevated surfaces sit on top of --background. Pure white reads as + * "card on page" against the off-white canvas. */ --card: 0 0% 100%; /* #ffffff */ - --card-foreground: 0 0% 12%; + --card-foreground: 240 6% 10%; --popover: 0 0% 100%; - --popover-foreground: 0 0% 12%; + --popover-foreground: 240 6% 10%; /* Muted = table header / sidebar tint / placeholder fill. */ - --muted: 210 17% 98%; /* #f8f9fa */ - --muted-foreground: 213 5% 39%; /* #5f6368 — Google grey-700, passes AA */ - - /* Border / input outline — the AIS standard hairline. */ - --border: 220 9% 87%; /* #dadce0 */ - --input: 220 9% 87%; - - /* Primary CTA — Google blue. Hover flows through `bg-primary/90`. */ - --primary: 217 90% 43%; /* #0b57d0 */ - --primary-foreground: 0 0% 100%; - - /* Secondary = Google "tonal" button (light-blue fill, deep-blue text); - * accent = the faint blue-grey tint AIS uses on hover rows and ghost - * surfaces. */ - --secondary: 217 91% 91%; /* #d3e3fd */ - --secondary-foreground: 217 90% 15%; /* #041e49 */ - --accent: 213 43% 96%; /* #f0f4f9 */ - --accent-foreground: 0 0% 12%; - - /* Destructive — Google red. Near risk-critical (#dc2626) so destructive - * buttons still share the visual language of a Critical badge. */ - --destructive: 4 71% 50%; /* #d93025 */ - --destructive-foreground: 0 0% 100%; - - /* Focus ring — matches the blue primary so the 2 px outline reads as - * "the same thing this button is". */ - --ring: 217 90% 43%; + --muted: 240 5% 96%; /* #f4f4f5 */ + --muted-foreground: 240 4% 46%; /* #71717a — passes AA on --muted */ + + /* Border / input outline — softer than slate-200, more neutral. */ + --border: 240 5% 91%; /* #e5e5ea */ + --input: 240 5% 91%; + + /* Primary CTA — Vercel-style warm near-black instead of navy. + * Reads as "important action" without leaning blue. */ + --primary: 240 6% 10%; /* #18181b */ + --primary-foreground: 0 0% 98%; + + /* Secondary / accent share the muted tint — used for ghost-style + * surfaces and hover row backgrounds. */ + --secondary: 240 5% 96%; + --secondary-foreground: 240 6% 10%; + --accent: 240 5% 96%; + --accent-foreground: 240 6% 10%; + + /* Destructive — aligned with risk-critical hex so destructive + * buttons share the visual language of a Critical severity badge. */ + --destructive: 0 72% 51%; /* ~#dc2626 */ + --destructive-foreground: 0 0% 98%; + + /* Focus ring — matches primary tone so the 2 px outline reads as + * "the same thing this button is", per Linear polish. */ + --ring: 240 6% 10%; /* ------------------------------------------------------------------- - * Radius hierarchy (W13 — one step rounder than W11 across the board). + * Radius hierarchy (Linear polish — different sizes for different + * affordances so depth reads at a glance). * - * --radius-sm 6 px — small inputs, badges, chips - * --radius 8 px — cards, inputs, table chrome (DEFAULT) - * --radius-lg 10 px — drawer, large surfaces - * --radius-xl 14 px — modals, dialogs + * --radius-sm 4 px — small inputs, badges, chips + * --radius 6 px — buttons, cards, table chrome (DEFAULT) + * --radius-lg 8 px — drawer, large surfaces + * --radius-xl 12 px — modals, dialogs * - * Buttons are pills (rounded-full in button.tsx), NOT derived from - * this token — raising --radius to a pill value would full-round - * cards and dialogs via the calc() derivations in tailwind.config.ts. + * shadcn convention reads `--radius` as the card default; smaller / + * larger variants derive via `calc()` in tailwind.config.ts. * ----------------------------------------------------------------- */ - --radius: 0.5rem; + --radius: 0.375rem; /* ------------------------------------------------------------------- - * Elevation — AIS keeps in-flow surfaces flat (cards/buttons separate - * via border + tone), so shadow-sm is a zero-alpha no-op. It must stay - * a VALID box-shadow value (consumers do `box-shadow: var(--shadow-sm)` - * and an empty string / `none` inside a list breaks them). md / lg - * follow the Google elevation-1 / -2 recipes for floating surfaces - * (dropdown / popover / drawer / dialog). + * Elevation — subtle, Vercel-style. Light shadows only; no glow. + * Used by Card (sm), Dropdown / Popover (md), Drawer / Dialog (lg). * ----------------------------------------------------------------- */ - --shadow-sm: 0 0 0 0 rgb(0 0 0 / 0); + --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.04); --shadow-md: - 0 1px 2px 0 rgb(60 64 67 / 0.3), - 0 1px 3px 1px rgb(60 64 67 / 0.15); + 0 2px 8px -2px rgb(0 0 0 / 0.08), + 0 1px 2px 0 rgb(0 0 0 / 0.04); --shadow-lg: - 0 1px 3px 0 rgb(60 64 67 / 0.3), - 0 4px 8px 3px rgb(60 64 67 / 0.15); + 0 10px 28px -8px rgb(0 0 0 / 0.12), + 0 3px 8px -3px rgb(0 0 0 / 0.06); /* ------------------------------------------------------------------- * Motion (Linear polish) — short, ease-out. Three steps cover the @@ -128,19 +122,14 @@ --ease-out: cubic-bezier(0.16, 1, 0.3, 1); /* ------------------------------------------------------------------- - * Risk severity — domain meaning is fixed. + * Risk severity (unchanged — domain meaning is fixed). * Used directly by recharts / lucide as raw hex, also exposed as * Tailwind `text-risk-critical` / `bg-risk-high/10` etc. - * - * W13: --risk-low moved blue-600 (#2563eb) → teal-700 (#0f766e) so Low - * badges no longer share a hue with the Google-blue primary CTA. - * Contrast (WCAG): 5.47:1 on white, 4.76:1 on its own 10 % tint — - * both clear AA, slightly better than the old blue (5.17 / 4.49). * ----------------------------------------------------------------- */ --risk-critical: #dc2626; --risk-high: #ea580c; --risk-medium: #ca8a04; - --risk-low: #0f766e; + --risk-low: #2563eb; --risk-info: #71717a; /* Layout density (unchanged). */ diff --git a/apps/frontend/src/pages/dev/DesignSystemPreview.tsx b/apps/frontend/src/pages/dev/DesignSystemPreview.tsx index 9c434357..ae6cf9af 100644 --- a/apps/frontend/src/pages/dev/DesignSystemPreview.tsx +++ b/apps/frontend/src/pages/dev/DesignSystemPreview.tsx @@ -2,11 +2,9 @@ * Design System Preview — W11-A, expanded into a living reference in W12-E. * * Dev-only sample page. Originally the visual confirm gate for the W11-A - * token redefinition; W12-E grew it into a living component reference + a - * manual review / visual-regression surface covering the W12 primitives. - * W13 (2026-06-12) re-skinned the token set to the Google AI Studio light - * tone (white canvas, blue primary, pill buttons) after the PR #394 - * prototype review. + * token redefinition (Vercel base + Linear polish, light single-theme); W12-E + * grew it into a living component reference + a manual review / visual- + * regression surface covering the W12 primitives. * * Routing: * - Mounted at `/dev/design-preview` (see router.tsx). @@ -107,17 +105,17 @@ export function DesignSystemPreview() { Design system · living reference (dev only)
- Living sample of the W13 token set (adopted 2026-06-12): white - canvas, Google-blue primary, tonal secondary, pill buttons, flat - cards. The rest of the app uses these same tokens — walk a few - pages after this to spot any regressions. + Sample of the new token set applied to two foundational + components. The rest of the app still uses these same tokens — + walk a few pages after this to spot any regressions before we + green-light Phase B.
-
diff --git a/apps/frontend/tailwind.config.ts b/apps/frontend/tailwind.config.ts
index 81289642..bbee6e06 100644
--- a/apps/frontend/tailwind.config.ts
+++ b/apps/frontend/tailwind.config.ts
@@ -91,15 +91,12 @@ const config: Config = {
row: "var(--table-row)",
},
borderRadius: {
- // W13 — radius hierarchy, one step rounder than W11 (--radius 8px).
+ // W11-A — radius hierarchy. Different sizes for different affordances.
//
- // sm 6 px — chips / small inputs
- // md 8 px — cards / inputs / table chrome (= --radius default)
- // lg 10 px — drawer, large panels
- // xl 14 px — modals, dialogs
- //
- // Buttons are pills (`rounded-full` in button.tsx), not part of
- // this scale.
+ // sm 4 px — chips / small inputs
+ // md 6 px — buttons / cards / table chrome (= --radius default)
+ // lg 8 px — drawer, large panels
+ // xl 12 px — modals, dialogs
//
// shadcn's older `rounded-lg`/`rounded-md`/`rounded-sm` mapping
// (lg = --radius, md = lg-2, sm = lg-4) still works for existing
diff --git a/apps/frontend/tests/unit/pages/dev/DesignSystemPreview.test.tsx b/apps/frontend/tests/unit/pages/dev/DesignSystemPreview.test.tsx
index b3fb28e6..8f90bba5 100644
--- a/apps/frontend/tests/unit/pages/dev/DesignSystemPreview.test.tsx
+++ b/apps/frontend/tests/unit/pages/dev/DesignSystemPreview.test.tsx
@@ -17,11 +17,11 @@ import { describe, expect, it } from "vitest";
import { DesignSystemPreview } from "@/pages/dev/DesignSystemPreview";
-describe("DesignSystemPreview (W13)", () => {
+describe("DesignSystemPreview (W11-A)", () => {
it("renders the page header", () => {
render(W11 (2026-05-27) — the previous Vercel + Linear refresh
-
-W11 replaced the original "BD-style 2015" aesthetic (navy `#0f172a`, uniform 8 px radius, default easing) with a Vercel light base + Linear polish: warm near-black `#18181b` primary, `#fafafa` canvas, radius / shadow / motion token hierarchy, semibold headings, visible focus rings, and the drawer + page dual detail surface. The polish layer survives in W13; the colour skin does not.
-
-` block — extend `PageHeader` if a new layout
`apps/frontend/src/components/ui/button.tsx`
-- **Pill shape** (`rounded-full`, W13) on every variant and size — the AIS button silhouette. Inputs keep the token radius; only buttons (and sidebar nav items) are pills.
-- Default variant uses `bg-primary text-primary-foreground` — solid Google blue.
-- `secondary` variant is the Google **tonal** button — light-blue fill (`--secondary`), deep-blue text.
+- Default variant uses `bg-primary text-primary-foreground` — solid warm near-black.
- `outline` variant uses `border-input bg-background` — for secondary actions.
-- `ghost` variant uses no background, hover tint only (`--accent`) — for nav items and toolbar actions.
-- `destructive` uses `bg-destructive` — Google red near `--risk-critical`, reserved for irreversible actions (delete, revoke, reject).
+- `ghost` variant uses no background, hover tint only — for nav items and toolbar actions.
+- `destructive` uses `bg-destructive` — Critical-aligned red, reserved for irreversible actions (delete, revoke, reject).
- Hover and focus transitions use `transition-colors duration-fast ease-out` (150 ms).
- All variants include the focus ring.
@@ -230,9 +217,9 @@ Do not hand-roll a `
` block — extend `PageHeader` if a new layout
### Card
-- White surface (`bg-card`) flush on the white canvas — separation comes from the `--border` hairline and the `--muted` tone, not elevation (AIS flat-card pattern; `shadow-sm` resolves to a no-op).
-- `rounded-md` (8 px) by default; `rounded-lg` (10 px) for primary content cards.
-- `shadow-md` only on floating surfaces (popover / dropdown), never on in-flow cards.
+- Pure-white surface (`bg-card`) on the off-white canvas — lifts visually without a heavy shadow.
+- `rounded-md` (6 px) by default; `rounded-lg` (8 px) for primary content cards.
+- `shadow-sm` for stats / tiles; `shadow-md` for elevated popovers.
### Table
@@ -316,30 +303,28 @@ The portal targets **WCAG 2.1 Level AA**. Three policies make this concrete.
| Pair | Ratio | Note |
|---|---|---|
-| `--foreground` on `--background` / `--card` | 16.48:1 | Body text. AAA. |
-| `--muted-foreground` on `--background` / `--card` | 6.05:1 | Captions, secondary text. AA. |
-| `--muted-foreground` on `--muted` | 5.74:1 | Captions on tinted fills. AA. |
-| `--primary-foreground` on `--primary` | 6.39:1 | Primary button label. AA. |
-| `--secondary-foreground` on `--secondary` | 12.57:1 | Tonal button label. AAA. |
-| `--destructive-foreground` on `--destructive` | 4.77:1 | Destructive button label. AA. |
-| `--ring` on `--background` | 6.39:1 | Focus ring. AA (UI ≥ 3:1). |
+| `--foreground` on `--background` | 16.97:1 | Body text. AAA. |
+| `--foreground` on `--card` | 17.72:1 | Body text on card. AAA. |
+| `--muted-foreground` on `--background` | 4.63:1 | Captions, secondary text. AA. |
+| `--muted-foreground` on `--card` | 4.83:1 | Captions on card. AA. |
+| `--primary-foreground` on `--primary` | 16.97:1 | Primary button label. AAA. |
+| `--destructive-foreground` on `--destructive` | 4.63:1 | Destructive button label. AA. |
+| `--ring` on `--background` | 16.97:1 | Focus ring. AAA. |
-Decorative borders (`--border` on `--background`, 1.37:1) are **intentionally low-contrast** — they are visual separators, not informative UI elements, and WCAG 1.4.11 exempts them.
+Decorative borders (`--border` on `--background`, 1.20:1) are **intentionally low-contrast** — they are visual separators, not informative UI elements, and WCAG 1.4.11 exempts them.
### Severity colour accessibility
-Severity hex values (`#dc2626` / `#ea580c` / `#ca8a04` / `#0f766e` / `#71717a`) are brand-fixed. Used as **body text** on a light tint some would measure as low as 2.5:1, which fails AA. The fix is structural, not chromatic — when the severity tone is used as text, the rendered text colour uses a deeper shade from the same Tailwind hue family:
+Severity hex values (`#dc2626` / `#ea580c` / `#ca8a04` / `#2563eb` / `#71717a`) are brand-fixed. Used as **body text** on a light tint they would measure as low as 2.5:1, which fails AA. The fix is structural, not chromatic — when the severity tone is used as text, the rendered text colour uses a deeper shade from the same Tailwind hue family:
| Tone | Tint background | Text colour | Contrast |
|---|---|---|---|
| `critical` | `bg-risk-critical/10` | `text-red-700` (`#b91c1c`) | 5.54:1 |
| `high` | `bg-risk-high/10` | `text-orange-800` (`#9a3412`) | 6.47:1 |
| `medium` | `bg-risk-medium/15` | `text-yellow-800` (`#854d0e`) | 5.91:1 |
-| `low` | `bg-risk-low/10` | `text-teal-800` (`#115e59`) | 6.59:1 |
+| `low` | `bg-risk-low/10` | `text-blue-700` (`#1d4ed8`) | 5.83:1 |
| `info` | `bg-risk-info/15` | `text-slate-600` (`#52525b`) | 6.41:1 |
-The raw Low token itself (teal-700 `#0f766e`) also clears AA where it's used directly as text (5.47:1 on white, 4.76:1 on its own 10 % tint) — slightly better than the blue-600 it replaced.
-
The **dot indicators** (in `SeverityBadge`, chart legends, status pills) continue to use the raw `bg-risk-X` token — colour identity stays recognisable; only text shade is darkened. The reference implementation is `apps/frontend/src/components/ui/badge.tsx` (W11-H).
### Colour is not the only signal
@@ -388,9 +373,8 @@ All interactive elements are reachable by `Tab` and operable by `Enter` / `Space
| W12-C | 2026-06-11 | **Craft elevation — motion (CSS-only).** Route-change entrance fade (`
W11 (2026-05-27) — 직전의 Vercel + Linear 재정의
-
-W11 은 원래의 "BD-style 2015" 미감(navy `#0f172a` · 8 px 일관 radius · 브라우저 기본 easing)을 Vercel light base + Linear polish 로 교체했습니다: warm near-black `#18181b` primary · `#fafafa` canvas · radius/shadow/motion 토큰 계층 · semibold heading · 가시 focus ring · 드로어+페이지 dual 디테일 surface. polish 레이어는 W13 에 살아남았고, 색 스킨은 교체되었습니다.
-
-