@@ -148,43 +107,17 @@ export function DesignSystemPreview() {
Design system · living reference (dev only)
- Vercel base + Linear polish
+ TRUSCA — Google AI Studio light
- 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.
+ 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.
-
-
-
- Global flip of the TRUSCA / Google AI Studio theme prototype —
- persists across reloads, covers portals (dialogs, toasts), and
- follows you onto real screens.
-
-
-
-
-
- Current — Vercel + Linear
-
-
-
- Prototype — Google AI Studio light
-
-
-
-
-
-
-
-
+
@@ -217,7 +150,7 @@ export function DesignSystemPreview() {
-
+
@@ -236,7 +169,7 @@ export function DesignSystemPreview() {
-
+
@@ -330,16 +263,16 @@ export function DesignSystemPreview() {
- rounded-sm · 4px
+ rounded-sm · 6px
- rounded-md · 6px
+ rounded-md · 8px
- rounded-lg · 8px
+ rounded-lg · 10px
- rounded-xl · 12px
+ rounded-xl · 14px
diff --git a/apps/frontend/src/styles/theme-ais.css b/apps/frontend/src/styles/theme-ais.css
deleted file mode 100644
index 56f0618c..00000000
--- a/apps/frontend/src/styles/theme-ais.css
+++ /dev/null
@@ -1,101 +0,0 @@
-/* ---------------------------------------------------------------------------
- * TRUSCA — Google AI Studio light theme PROTOTYPE (dev only).
- *
- * Scoped override of the design tokens defined in src/index.css. The base
- * `:root` values are untouched; this file redefines the SAME variable names
- * under `.theme-ais` so the prototype can be flipped on/off at runtime:
- *
- * - Global flip: `document.documentElement.classList.add("theme-ais")`
- * (see src/lib/devTheme.ts) — covers Radix portals (Dialog / Popover /
- * Toast render into document.body, which is still a descendant of html).
- * - Local flip: a `
` wrapper, used by the
- * side-by-side comparison section in /dev/design-preview. Portals are
- * NOT covered by the wrapper form.
- *
- * Cascade note: index.css declares tokens inside `@layer base`; this file is
- * intentionally NOT layered, so it wins regardless of import order. Keep it
- * plain CSS — no `@layer`, no `@apply` (it must not create new utilities).
- *
- * This file is loaded only behind `import.meta.env.DEV` in main.tsx, so the
- * production bundle contains none of it.
- *
- * PROTOTYPE-ONLY: if the AIS direction is adopted, promote these values into
- * the `:root` block of index.css and DELETE this file plus the dev toggle.
- *
- * Format contract (same as index.css): shadcn-family tokens are HSL triplets
- * because tailwind.config.ts wraps them as `hsl(var(--X) / )` — a hex
- * value here would silently break alpha variants like `bg-primary/10` (the
- * active sidebar nav tint). Risk severity tokens stay raw hex but are NOT
- * overridden — domain semantics are fixed.
- * ------------------------------------------------------------------------- */
-
-.theme-ais {
- /* Surfaces — AIS uses a plain white canvas; panels/table headers get the
- * Google grey tint and separation comes from borders, not elevation. */
- --background: 0 0% 100%; /* #ffffff */
- --foreground: 0 0% 12%; /* #1f1f1f — Google grey-900 */
- --card: 0 0% 100%;
- --card-foreground: 0 0% 12%;
- --popover: 0 0% 100%;
- --popover-foreground: 0 0% 12%;
- --muted: 210 17% 98%; /* #f8f9fa */
- --muted-foreground: 213 5% 39%; /* #5f6368 — Google grey-700, AA on muted */
- --border: 220 9% 87%; /* #dadce0 — AIS standard hairline */
- --input: 220 9% 87%;
-
- /* Primary CTA — Google blue replaces the warm near-black. Hover keeps
- * working through the existing `bg-primary/90` utility. */
- --primary: 217 90% 43%; /* #0b57d0 */
- --primary-foreground: 0 0% 100%;
-
- /* Secondary becomes the Google "tonal" button (light blue fill, deep
- * blue text); accent is the faint blue-grey hover tint AIS uses on 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: 4 71% 50%; /* #d93025 — Google red */
- --destructive-foreground: 0 0% 100%;
-
- /* Focus ring follows primary, so the outline reads blue like AIS. */
- --ring: 217 90% 43%;
-
- /* Rounder overall: derived scale becomes sm 6 / md 8 / lg 10 / xl 14 px.
- * Pill buttons need more than a token bump — see the rule below. */
- --radius: 0.5rem;
-
- /* Elevation — AIS keeps cards flat (border + tone only) and reserves
- * shadows for floating surfaces. Must stay a VALID box-shadow value
- * (an empty string or `none` would break `boxShadow: var(--shadow-sm)`
- * consumers), hence the zero-alpha shadow. md/lg follow the Google
- * elevation-1/-2 recipes. */
- --shadow-sm: 0 0 0 0 rgb(0 0 0 / 0);
- --shadow-md:
- 0 1px 2px 0 rgb(60 64 67 / 0.3),
- 0 1px 3px 1px rgb(60 64 67 / 0.15);
- --shadow-lg:
- 0 1px 3px 0 rgb(60 64 67 / 0.3),
- 0 4px 8px 3px rgb(60 64 67 / 0.15);
-
- /* NOT overridden, deliberately:
- * --duration-* / --ease-out — motion timing is kept as-is.
- * --risk-* — severity semantics are fixed. Known
- * follow-up: --risk-low (#2563eb) sits in
- * the same blue family as the new primary;
- * whether to separate them is an adoption-
- * phase decision, not a prototype one.
- * --layout-* / --table-row — density and chrome sizes are unchanged
- * so e2e selectors and layout math hold. */
-}
-
-/* PROTOTYPE-ONLY pill buttons: raising --radius to 9999px would full-round
- * cards and dialogs too (they derive via calc()), so instead we re-round the
- * two affordances AIS renders as pills — buttons (every shadcn Button variant
- * has `rounded-md` in its cva base) and the sidebar NavLink items. Inputs
- * keep the 8px radius on purpose. If adopted, delete this rule and add a
- * proper `rounded-full` shape to buttonVariants instead. */
-.theme-ais :is(button, a).rounded-md {
- border-radius: 9999px;
-}
diff --git a/apps/frontend/tailwind.config.ts b/apps/frontend/tailwind.config.ts
index bbee6e06..81289642 100644
--- a/apps/frontend/tailwind.config.ts
+++ b/apps/frontend/tailwind.config.ts
@@ -91,12 +91,15 @@ const config: Config = {
row: "var(--table-row)",
},
borderRadius: {
- // W11-A — radius hierarchy. Different sizes for different affordances.
+ // W13 — radius hierarchy, one step rounder than W11 (--radius 8px).
//
- // 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
+ // 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.
//
// 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/devTheme.test.ts b/apps/frontend/tests/unit/devTheme.test.ts
deleted file mode 100644
index 17461e48..00000000
--- a/apps/frontend/tests/unit/devTheme.test.ts
+++ /dev/null
@@ -1,64 +0,0 @@
-/**
- * Dev-only AIS theme prototype switch (src/lib/devTheme.ts).
- *
- * The contract under test: the class and the localStorage flag move
- * together, and initAisTheme() restores the persisted flag on boot — that is
- * what keeps the theme stable across reloads on any route, login included.
- */
-import { beforeEach, describe, expect, it } from "vitest";
-
-import {
- AIS_STORAGE_KEY,
- AIS_THEME_CLASS,
- initAisTheme,
- isAisThemeEnabled,
- setAisTheme,
-} from "@/lib/devTheme";
-
-describe("devTheme", () => {
- beforeEach(() => {
- window.localStorage.removeItem(AIS_STORAGE_KEY);
- document.documentElement.classList.remove(AIS_THEME_CLASS);
- });
-
- it("setAisTheme(true) applies the html class and persists the flag", () => {
- setAisTheme(true);
-
- expect(document.documentElement.classList.contains(AIS_THEME_CLASS)).toBe(
- true,
- );
- expect(window.localStorage.getItem(AIS_STORAGE_KEY)).toBe("1");
- expect(isAisThemeEnabled()).toBe(true);
- });
-
- it("setAisTheme(false) removes the class and clears the flag", () => {
- setAisTheme(true);
- setAisTheme(false);
-
- expect(document.documentElement.classList.contains(AIS_THEME_CLASS)).toBe(
- false,
- );
- expect(window.localStorage.getItem(AIS_STORAGE_KEY)).toBeNull();
- expect(isAisThemeEnabled()).toBe(false);
- });
-
- it("initAisTheme restores a persisted flag on boot", () => {
- window.localStorage.setItem(AIS_STORAGE_KEY, "1");
-
- initAisTheme();
-
- expect(document.documentElement.classList.contains(AIS_THEME_CLASS)).toBe(
- true,
- );
- });
-
- it("initAisTheme clears a stale class when no flag is persisted", () => {
- document.documentElement.classList.add(AIS_THEME_CLASS);
-
- initAisTheme();
-
- expect(document.documentElement.classList.contains(AIS_THEME_CLASS)).toBe(
- false,
- );
- });
-});
diff --git a/apps/frontend/tests/unit/pages/dev/DesignSystemPreview.test.tsx b/apps/frontend/tests/unit/pages/dev/DesignSystemPreview.test.tsx
index 8f90bba5..b3fb28e6 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 (W11-A)", () => {
+describe("DesignSystemPreview (W13)", () => {
it("renders the page header", () => {
render();
expect(
- screen.getByRole("heading", { name: /Vercel base \+ Linear polish/i }),
+ screen.getByRole("heading", { name: /Google AI Studio light/i }),
).toBeInTheDocument();
});
@@ -36,10 +36,11 @@ describe("DesignSystemPreview (W11-A)", () => {
it("renders the four radius hierarchy samples", () => {
render();
- expect(screen.getByText(/rounded-sm · 4px/)).toBeInTheDocument();
- expect(screen.getByText(/rounded-md · 6px/)).toBeInTheDocument();
- expect(screen.getByText(/rounded-lg · 8px/)).toBeInTheDocument();
- expect(screen.getByText(/rounded-xl · 12px/)).toBeInTheDocument();
+ // W13 — one step rounder than W11 (--radius 8px; see tailwind.config.ts).
+ expect(screen.getByText(/rounded-sm · 6px/)).toBeInTheDocument();
+ expect(screen.getByText(/rounded-md · 8px/)).toBeInTheDocument();
+ expect(screen.getByText(/rounded-lg · 10px/)).toBeInTheDocument();
+ expect(screen.getByText(/rounded-xl · 14px/)).toBeInTheDocument();
});
it("renders the three shadow elevation samples", () => {
diff --git a/docs-site/docs/reference/design-system.md b/docs-site/docs/reference/design-system.md
index c950d46f..42c9bd7e 100644
--- a/docs-site/docs/reference/design-system.md
+++ b/docs-site/docs/reference/design-system.md
@@ -1,14 +1,14 @@
---
id: design-system
title: Design system
-description: TRUSCA design system — tokens (colour, spacing, radius, shadow, motion, typography), component conventions, micro-interactions, accessibility, and the W11 visual-identity refresh.
+description: TRUSCA design system — tokens (colour, spacing, radius, shadow, motion, typography), component conventions, micro-interactions, accessibility, and the W13 Google AI Studio re-skin.
sidebar_label: Design system
sidebar_position: 10
---
# Design system
-The portal frontend follows a single, light-mode design system inspired by **Vercel** (light base — surfaces, dense rows, sidebar tint) and **Linear** (typography hierarchy, motion, focus polish). Dark mode is deferred to +.
+The portal frontend follows a single, light-mode design system in the **Google AI Studio** tone (W13, 2026-06-12): white canvas, Google-blue primary, tonal secondary, pill buttons, flat bordered cards. The typography hierarchy, motion, and focus polish introduced with W11 (Linear-inspired) are retained. Dark mode is deferred to v2.5+.
:::note Audience
Frontend contributors, designers, and reviewers. The tokens here are the canonical reference — components should never hard-code hex values or magic spacing.
@@ -26,48 +26,59 @@ TRUSCA is a **risk-first, information-dense, modern enterprise SCA** tool. The v
1. **Communicate severity at a glance.** Risk colours (Critical / High / Medium / Low / Info) appear next to a textual label and an icon or dot — colour is never the sole signal.
2. **Pack data without feeling cramped.** Compact 40 px table rows; 224 px sidebar; 48 px header; 16 / 20 / 24 px card padding scale.
-3. **Read as a modern enterprise product.** Warm near-black (`#18181b`) instead of navy (`#0f172a`); off-white canvas (`#fafafa`) so cards lift visually; subtle shadows; semibold headings; visible focus rings.
+3. **Read as a modern product (Google AI Studio tone).** White canvas (`#ffffff`) with `#dadce0` hairline borders; Google-blue primary (`#0b57d0`) with a tonal light-blue secondary; pill buttons; flat cards that separate via border + tone rather than shadow; semibold headings; visible focus rings. Fonts stay Inter + JetBrains Mono (Google Sans is proprietary).
4. **Move only as much as needed.** Motion is short and ease-out — 150 ms for hover / focus, 200 ms for drawer slide, 250 ms for page-level chrome. No bounce, no fade-in delays.
-### W11 (2026-05-27) — visual refresh
+### W13 (2026-06-12) — Google AI Studio re-skin
-The W11 milestone replaced the previous "BD-style 2015" aesthetic with the current Vercel+Linear blend. The structural decisions (sidebar nav, 40 px row, drawer-for-detail, risk semantics) carry over unchanged. What changed:
+W13 replaced the W11 Vercel + Linear skin with the Google AI Studio light tone, adopted after a dev-toggle prototype review (PR #394). Structural decisions (sidebar nav, 40 px row, drawer-for-detail, risk semantics) and the W11 polish layer (typography, motion, focus ring) carry over unchanged. What changed:
-| Surface | Before | After |
+| Surface | W11 (before) | W13 (after) |
|---|---|---|
-| Primary CTA | `#0f172a` cool navy | `#18181b` warm near-black |
-| Page background | `#ffffff` pure white | `#fafafa` off-white canvas |
-| Card surface | grey-tinted | `#ffffff` pure white (lifts off the canvas) |
-| Border | `slate-200` | `#e5e5ea` neutral hairline |
-| Radius | 8 px uniform | hierarchy — sm 4 / md 6 / lg 8 / xl 12 |
-| Shadow | none / default | sm (card) / md (popover) / lg (drawer · dialog) |
-| Motion | default browser | 150 / 200 / 250 ms ease-out |
-| Heading weight | bold | semibold + tracking-tight |
-| Focus ring | shadcn default | 2 px outline + 2 px offset (a11y) |
-| Detail surface | drawer-only | dual surface — drawer (quick check) + page nav (deep work) |
-
-The risk colour palette (Critical / High / Medium / Low / Info) is intentionally **unchanged** — the brand semantics are fixed across releases. Where the raw severity hex fails WCAG AA as body text on a light tint, the badge text shade is darkened within the same hue family (see [Severity colour accessibility](#severity-colour-accessibility) below).
+| Primary CTA | `#18181b` warm near-black | `#0b57d0` Google blue |
+| Secondary | muted grey tint | `#d3e3fd` tonal light blue |
+| Page background | `#fafafa` off-white canvas | `#ffffff` white canvas |
+| Card surface | white, lifts via shadow-sm | white, flat — separates via border + tone |
+| Hover / ghost tint | muted grey | `#f0f4f9` faint blue-grey |
+| Border | `#e5e5ea` | `#dadce0` AIS hairline |
+| Button shape | rounded-md (6 px) | pill (`rounded-full`) |
+| Radius scale | sm 4 / md 6 / lg 8 / xl 12 | sm 6 / md 8 / lg 10 / xl 14 |
+| Shadow | sm (card) / md / lg | sm = none (flat); md / lg = Google elevation-1/-2 |
+| Focus ring | near-black | `#0b57d0` blue (matches primary) |
+| `--risk-low` | `#2563eb` blue-600 | `#0f766e` teal-700 (no longer collides with the blue primary) |
+
+Risk severity colours for Critical / High / Medium / Info are **unchanged** — the domain semantics are fixed. Low is the single W13 exception (user decision): the old blue read like a CTA / link next to the new Google-blue primary. Where a severity hex fails WCAG AA as body text on a light tint, the badge text shade is darkened within the same hue family (see [Severity colour accessibility](#severity-colour-accessibility) below).
+
+
+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.
+
+
## Colour tokens
All colour decisions reference the CSS custom properties declared in `index.css`. Components should never reference hex values directly — use the Tailwind utility (`bg-background`, `text-foreground`, `bg-risk-critical/10`) or the CSS variable.
-### Neutral palette (Vercel base)
+### Neutral palette (Google AI Studio light)
| Token | Hex | HSL | Use |
|---|---|---|---|
-| `--background` | `#fafafa` | `0 0% 98%` | Page canvas. Lets cards lift visually. |
-| `--card` | `#ffffff` | `0 0% 100%` | Elevated surfaces — cards, popovers, drawer body, tooltip. |
-| `--foreground` | `#18181b` | `240 6% 10%` | Body text. Warm near-black, not navy. |
-| `--muted` | `#f4f4f5` | `240 5% 96%` | Subtle fills — table headers, sidebar tint, placeholder backgrounds, disabled inputs. |
-| `--muted-foreground` | `#71717a` | `240 4% 46%` | Secondary text, captions, table column headers. |
-| `--border` | `#e5e5ea` | `240 5% 91%` | Hairline borders. Decorative separator only — never the sole means of identifying a UI region. |
-| `--input` | `#e5e5ea` | `240 5% 91%` | Input outline. |
-| `--primary` | `#18181b` | `240 6% 10%` | Primary CTA — "the important action on the page". |
-| `--primary-foreground` | `#fafafa` | `0 0% 98%` | Text on primary. |
-| `--destructive` | `#dc2626` | `0 72% 51%` | Destructive CTA. Aligned with `--risk-critical` so destructive buttons share severity-badge visual language. |
-| `--destructive-foreground` | `#fafafa` | `0 0% 98%` | Text on destructive. |
-| `--ring` | `#18181b` | `240 6% 10%` | Focus ring. Matches primary so the outline reads as "the same action this is". |
+| `--background` | `#ffffff` | `0 0% 100%` | Page canvas — plain white (AIS). |
+| `--card` | `#ffffff` | `0 0% 100%` | Cards, popovers, drawer body, tooltip — flush with the canvas, separated by border + tone. |
+| `--foreground` | `#1f1f1f` | `0 0% 12%` | Body text — Google grey-900. |
+| `--muted` | `#f8f9fa` | `210 17% 98%` | Subtle fills — table headers, sidebar tint, placeholder backgrounds, disabled inputs. |
+| `--muted-foreground` | `#5f6368` | `213 5% 39%` | Secondary text, captions, table column headers — Google grey-700. |
+| `--border` | `#dadce0` | `220 9% 87%` | Hairline borders — the AIS standard. Decorative separator only, never the sole means of identifying a UI region. |
+| `--input` | `#dadce0` | `220 9% 87%` | Input outline. |
+| `--primary` | `#0b57d0` | `217 90% 43%` | Primary CTA — Google blue, "the important action on the page". |
+| `--primary-foreground` | `#ffffff` | `0 0% 100%` | Text on primary. |
+| `--secondary` | `#d3e3fd` | `217 91% 91%` | Tonal button fill (Google "tonal" pattern). |
+| `--secondary-foreground` | `#041e49` | `217 90% 15%` | Text on tonal fill. |
+| `--accent` | `#f0f4f9` | `213 43% 96%` | Hover rows, ghost-button hover — faint blue-grey tint. |
+| `--destructive` | `#d93025` | `4 71% 50%` | Destructive CTA — Google red, near `--risk-critical` so destructive buttons share severity-badge visual language. |
+| `--destructive-foreground` | `#ffffff` | `0 0% 100%` | Text on destructive. |
+| `--ring` | `#0b57d0` | `217 90% 43%` | Focus ring. Matches primary so the outline reads as "the same action this is". |
### Risk severity (domain semantics — fixed)
@@ -76,10 +87,10 @@ All colour decisions reference the CSS custom properties declared in `index.css`
| `--risk-critical` | `#dc2626` | Critical CVE, forbidden licence, build-blocking finding. |
| `--risk-high` | `#ea580c` | High-severity CVE, conditional licence at risk. |
| `--risk-medium` | `#ca8a04` | Medium CVE, conditional licence awaiting review. |
-| `--risk-low` | `#2563eb` | Low CVE, informational status. |
+| `--risk-low` | `#0f766e` | Low CVE, informational status. Teal-700 since W13 — moved out of the blue family so Low badges don't read like the blue primary CTA. |
| `--risk-info` | `#71717a` | Neutral informational state. |
-The severity hex values are **never changed** between releases. They appear in:
+The severity hex values are stable across releases (the W13 Low move is the documented exception). They appear in:
- Recharts fills and chart legends (raw hex via the `--risk-X` variable).
- `bg-risk-X/N` tints on badges and dot indicators.
@@ -109,26 +120,26 @@ When severity colour is used as **body text** (a coloured word inside a badge or
## Radius hierarchy
-Different affordances use different radii so depth reads at a glance.
+Different affordances use different radii so depth reads at a glance. W13 moved the whole scale one step rounder.
| Token | Value | Affordance |
|---|---|---|
-| `--radius-sm` | 4 px | Small inputs, badges, chips. |
-| `--radius` | 6 px | **Default** — buttons, cards, table chrome. |
-| `--radius-lg` | 8 px | Drawer, large surfaces. |
-| `--radius-xl` | 12 px | Modals, dialogs. |
+| `--radius-sm` | 6 px | Small inputs, badges, chips. |
+| `--radius` | 8 px | **Default** — cards, inputs, table chrome. |
+| `--radius-lg` | 10 px | Drawer, large surfaces. |
+| `--radius-xl` | 14 px | Modals, dialogs. |
-The Tailwind config derives `rounded-sm`, `rounded-md`, `rounded-lg`, `rounded-xl` from these tokens via `calc()`.
+The Tailwind config derives `rounded-sm`, `rounded-md`, `rounded-lg`, `rounded-xl` from these tokens via `calc()`. **Buttons are pills** (`rounded-full` in `button.tsx`) and deliberately not part of this scale — raising `--radius` to a pill value would full-round cards and dialogs through the `calc()` derivations.
## Shadow scale
-Vercel-style subtle elevation. Light shadows only — no glow.
+AIS keeps in-flow surfaces flat — cards and buttons separate via border + tone. Shadows are reserved for floating surfaces and follow the Google elevation recipes.
| Token | Value | Use |
|---|---|---|
-| `--shadow-sm` | `0 1px 2px 0 rgb(0 0 0 / 0.04)` | Cards, stat tiles. |
-| `--shadow-md` | `0 2px 8px -2px rgb(0 0 0 / 0.08), 0 1px 2px 0 rgb(0 0 0 / 0.04)` | Dropdown, popover, tooltip. |
-| `--shadow-lg` | `0 10px 28px -8px rgb(0 0 0 / 0.12), 0 3px 8px -3px rgb(0 0 0 / 0.06)` | Drawer, dialog. |
+| `--shadow-sm` | `0 0 0 0 rgb(0 0 0 / 0)` | Intentionally a zero-alpha no-op (flat cards / buttons). Kept a valid box-shadow value so `var(--shadow-sm)` consumers don't break, and so a future token change can re-enable elevation in one place. |
+| `--shadow-md` | `0 1px 2px 0 rgb(60 64 67 / 0.3), 0 1px 3px 1px rgb(60 64 67 / 0.15)` | Dropdown, popover, tooltip (Google elevation-1). |
+| `--shadow-lg` | `0 1px 3px 0 rgb(60 64 67 / 0.3), 0 4px 8px 3px rgb(60 64 67 / 0.15)` | Drawer, dialog (Google elevation-2). |
## Motion
@@ -202,10 +213,12 @@ Do not hand-roll a `
` block — extend `PageHeader` if a new layout
`apps/frontend/src/components/ui/button.tsx`
-- Default variant uses `bg-primary text-primary-foreground` — solid warm near-black.
+- **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.
- `outline` variant uses `border-input bg-background` — for secondary actions.
-- `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).
+- `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).
- Hover and focus transitions use `transition-colors duration-fast ease-out` (150 ms).
- All variants include the focus ring.
@@ -217,9 +230,9 @@ Do not hand-roll a `
` block — extend `PageHeader` if a new layout
### Card
-- 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.
+- 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.
### Table
@@ -303,28 +316,30 @@ The portal targets **WCAG 2.1 Level AA**. Three policies make this concrete.
| Pair | Ratio | Note |
|---|---|---|
-| `--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. |
+| `--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). |
-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.
+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.
### Severity colour accessibility
-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:
+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:
| 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-blue-700` (`#1d4ed8`) | 5.83:1 |
+| `low` | `bg-risk-low/10` | `text-teal-800` (`#115e59`) | 6.59: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
@@ -373,8 +388,9 @@ 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 (`` keyed on pathname, 250 ms), sidebar collapse aligned to 250 ms, and a global `prefers-reduced-motion` guard. No new dependency (tailwindcss-animate only). Skeleton doc corrected to the real 2000 ms `animate-pulse`. |
| W12-D | 2026-06-12 | **Craft elevation — empty / loading polish.** EmptyState gains a layered icon medallion + optional `illustration` slot; new `TableRowsSkeleton` renders per-column loading cells (replacing single full-width bars) on the Scans and Admin Users tables. |
| W12-E/F | 2026-06-12 | **Craft elevation — guardrails + docs.** Grew `/dev/design-preview` into a living component reference (typography, badges, empty / loading, feedback) and added a "Frontend UI" section to the contributor coding standards. Visual-regression baseline expansion (4 → ~15) is a CI / operator follow-up — correct linux baselines cannot be generated from a darwin dev box. |
+| W13 | 2026-06-12 | **Google AI Studio re-skin (TRUSCA).** Token promotion from the PR #394 dev prototype after user review: white canvas, `#0b57d0` blue primary, `#d3e3fd` tonal secondary, `#dadce0` borders, radius scale +2 px, flat cards (shadow-sm → no-op, md/lg → Google elevation recipes), pill buttons (`rounded-full` in `button.tsx` + sidebar nav), `--risk-low` blue-600 → teal-700 to separate Low badges from the blue primary. Layout density, motion, typography, and the other severity colours unchanged. Visual-regression baselines require a linux refresh after merge. |
-The previous "BD-style 2015" aesthetic (`#0f172a` navy, pure white canvas, uniform 8 px radius, no shadow, default browser easing) is fully retired by W11.
+The W11 Vercel + Linear skin is retired by W13; the earlier "BD-style 2015" aesthetic was retired by W11.
## See also
diff --git a/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/reference/design-system.md b/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/reference/design-system.md
index 06776618..a5d80eec 100644
--- a/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/reference/design-system.md
+++ b/docs-site/i18n/ko/docusaurus-plugin-content-docs/current/reference/design-system.md
@@ -1,14 +1,14 @@
---
id: design-system
title: 디자인 시스템
-description: TRUSCA 디자인 시스템 — 토큰(색·spacing·radius·shadow·motion·typography)·컴포넌트 규약·마이크로인터랙션·접근성·W11 시각 정체성 재정의.
+description: TRUSCA 디자인 시스템 — 토큰(색·spacing·radius·shadow·motion·typography)·컴포넌트 규약·마이크로인터랙션·접근성·W13 Google AI Studio 재스킨.
sidebar_label: 디자인 시스템
sidebar_position: 10
---
# 디자인 시스템
-포털 프론트엔드는 **Vercel** (light base — surface · dense row · sidebar tint) 과 **Linear** (타이포 hierarchy · motion · focus polish) 에 영감을 받은 단일 light 모드 디자인 시스템을 따릅니다. Dark 모드는 + 이후로 미룹니다.
+포털 프론트엔드는 **Google AI Studio** 톤(W13, 2026-06-12)의 단일 light 모드 디자인 시스템을 따릅니다 — 흰 canvas, Google 블루 primary, tonal secondary, 필(pill) 버튼, 보더로 구분되는 평면 카드. W11에서 도입한 타이포 hierarchy·motion·focus polish(Linear 영감)는 그대로 유지합니다. Dark 모드는 v2.5+ 이후로 미룹니다.
:::note 대상 독자
프론트엔드 기여자, 디자이너, 리뷰어. 본 문서의 토큰은 canonical reference 입니다 — 컴포넌트는 hex 값이나 magic spacing 을 직접 박지 않습니다.
@@ -26,48 +26,59 @@ TRUSCA는 **리스크 우선 · 정보 밀도 · 모던 엔터프라이즈** SCA
1. **한눈에 심각도 전달.** Severity 색 (Critical / High / Medium / Low / Info) 은 항상 텍스트 라벨 + 아이콘/dot 와 함께 — 색은 신호의 단독 수단이 아닙니다.
2. **답답하지 않게 데이터 밀도.** 40 px compact 행 · 224 px 사이드바 · 48 px 헤더 · 16/20/24 px 카드 padding 표준화.
-3. **모던 엔터프라이즈 제품 톤.** Navy `#0f172a` 가 아닌 warm near-black `#18181b` · 순수 흰색이 아닌 off-white canvas `#fafafa` · subtle shadow · semibold heading · 가시 focus ring.
+3. **모던 제품 톤 (Google AI Studio).** 흰 canvas `#ffffff` + `#dadce0` hairline border · Google 블루 primary `#0b57d0` + tonal 라이트블루 secondary · 필 버튼 · shadow 대신 보더+톤으로 구분되는 평면 카드 · semibold heading · 가시 focus ring. 폰트는 Inter + JetBrains Mono 유지(Google Sans 는 비공개 폰트).
4. **필요한 만큼만 움직임.** Hover/focus 150 ms · drawer slide 200 ms · 페이지 크롬 250 ms. ease-out 만. Bounce 없음, fade-in delay 없음.
-### W11 (2026-05-27) — 시각 재정의
+### W13 (2026-06-12) — Google AI Studio 재스킨
-W11 마일스톤에서 기존 "BD-style 2015" 미감을 Vercel+Linear 혼합으로 교체했습니다. 구조적 결정 (사이드바 nav · 40 px row · drawer-for-detail · severity 의미) 은 유지. 바뀐 것:
+W13 에서 W11 의 Vercel + Linear 스킨을 Google AI Studio light 톤으로 교체했습니다. dev 토글 프로토타입(PR #394) 검토 후 채택한 결정입니다. 구조적 결정 (사이드바 nav · 40 px row · drawer-for-detail · severity 의미) 과 W11 polish 레이어 (타이포 · motion · focus ring) 는 유지. 바뀐 것:
-| Surface | Before | After |
+| Surface | W11 (before) | W13 (after) |
|---|---|---|
-| Primary CTA | `#0f172a` cool navy | `#18181b` warm near-black |
-| 페이지 배경 | `#ffffff` 순백 | `#fafafa` off-white canvas |
-| 카드 surface | grey 톤 | `#ffffff` 순백 (canvas 위로 떠 보임) |
-| Border | `slate-200` | `#e5e5ea` neutral hairline |
-| Radius | 8 px 일관 | 계층 — sm 4 / md 6 / lg 8 / xl 12 |
-| Shadow | 없음 / 기본 | sm (card) / md (popover) / lg (drawer · dialog) |
-| Motion | 브라우저 기본 | 150 / 200 / 250 ms ease-out |
-| Heading weight | bold | semibold + tracking-tight |
-| Focus ring | shadcn 기본 | 2 px outline + 2 px offset (a11y) |
-| 디테일 surface | 드로어 전용 | dual surface — 드로어 (빠른 확인) + 페이지 nav (깊은 작업) |
-
-Severity 팔레트 (Critical / High / Medium / Low / Info) 는 **의도적으로 유지** — 브랜드 의미가 릴리스 간 고정입니다. Severity hex 가 light tint 위 본문 텍스트로 사용될 때 WCAG AA 가 안 나오면, 같은 hue family 의 더 짙은 shade 로 텍스트 색만 어둡게 — 아래 [Severity 색 접근성](#severity-색-접근성) 참고.
+| Primary CTA | `#18181b` warm near-black | `#0b57d0` Google 블루 |
+| Secondary | muted grey tint | `#d3e3fd` tonal 라이트블루 |
+| 페이지 배경 | `#fafafa` off-white canvas | `#ffffff` 흰 canvas |
+| 카드 surface | 흰색, shadow-sm 으로 떠 보임 | 흰색, 평면 — 보더+톤으로 구분 |
+| Hover / ghost tint | muted grey | `#f0f4f9` 옅은 blue-grey |
+| Border | `#e5e5ea` | `#dadce0` AIS hairline |
+| 버튼 형태 | rounded-md (6 px) | 필 (`rounded-full`) |
+| Radius 스케일 | sm 4 / md 6 / lg 8 / xl 12 | sm 6 / md 8 / lg 10 / xl 14 |
+| Shadow | sm (card) / md / lg | sm = 없음 (평면); md/lg = Google elevation-1/-2 |
+| Focus ring | near-black | `#0b57d0` 블루 (primary 와 매칭) |
+| `--risk-low` | `#2563eb` blue-600 | `#0f766e` teal-700 (블루 primary 와 더 이상 충돌하지 않음) |
+
+Critical / High / Medium / Info 의 severity 색은 **유지** — 도메인 의미가 고정입니다. Low 만 W13 의 단일 예외(사용자 결정)입니다: 기존 블루가 새 Google 블루 primary 옆에서 CTA/링크처럼 읽혔습니다. Severity hex 가 light tint 위 본문 텍스트로 사용될 때 WCAG AA 가 안 나오면, 같은 hue family 의 더 짙은 shade 로 텍스트 색만 어둡게 — 아래 [Severity 색 접근성](#severity-색-접근성) 참고.
+
+
+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 에 살아남았고, 색 스킨은 교체되었습니다.
+
+
## 색 토큰
모든 색 결정은 `index.css` 의 CSS custom property 를 참조합니다. 컴포넌트는 hex 값을 직접 박지 않습니다 — Tailwind utility (`bg-background`, `text-foreground`, `bg-risk-critical/10`) 나 CSS 변수를 사용합니다.
-### Neutral 팔레트 (Vercel base)
+### Neutral 팔레트 (Google AI Studio light)
| 토큰 | Hex | HSL | 용도 |
|---|---|---|---|
-| `--background` | `#fafafa` | `0 0% 98%` | 페이지 canvas. 카드가 시각적으로 뜨도록. |
-| `--card` | `#ffffff` | `0 0% 100%` | Elevated surface — 카드 · popover · 드로어 본문 · 툴팁. |
-| `--foreground` | `#18181b` | `240 6% 10%` | 본문 텍스트. Warm near-black (navy 아님). |
-| `--muted` | `#f4f4f5` | `240 5% 96%` | 미묘한 fill — 테이블 헤더 · 사이드바 tint · placeholder · disabled input. |
-| `--muted-foreground` | `#71717a` | `240 4% 46%` | 보조 텍스트 · caption · 테이블 컬럼 헤더. |
-| `--border` | `#e5e5ea` | `240 5% 91%` | Hairline border. 장식용 separator 만 — UI 영역 식별의 유일한 수단이 되지 않습니다. |
-| `--input` | `#e5e5ea` | `240 5% 91%` | Input outline. |
-| `--primary` | `#18181b` | `240 6% 10%` | Primary CTA — "이 페이지의 중요 액션". |
-| `--primary-foreground` | `#fafafa` | `0 0% 98%` | Primary 위 텍스트. |
-| `--destructive` | `#dc2626` | `0 72% 51%` | Destructive CTA. `--risk-critical` 와 같아서 destructive 버튼이 severity 와 같은 시각 언어. |
-| `--destructive-foreground` | `#fafafa` | `0 0% 98%` | Destructive 위 텍스트. |
-| `--ring` | `#18181b` | `240 6% 10%` | Focus ring. Primary 와 매칭 — outline 이 액션과 같은 색 패밀리로 읽힘. |
+| `--background` | `#ffffff` | `0 0% 100%` | 페이지 canvas — 순백 (AIS). |
+| `--card` | `#ffffff` | `0 0% 100%` | 카드 · popover · 드로어 본문 · 툴팁 — canvas 와 같은 면이며 보더+톤으로 구분. |
+| `--foreground` | `#1f1f1f` | `0 0% 12%` | 본문 텍스트 — Google grey-900. |
+| `--muted` | `#f8f9fa` | `210 17% 98%` | 미묘한 fill — 테이블 헤더 · 사이드바 tint · placeholder · disabled input. |
+| `--muted-foreground` | `#5f6368` | `213 5% 39%` | 보조 텍스트 · caption · 테이블 컬럼 헤더 — Google grey-700. |
+| `--border` | `#dadce0` | `220 9% 87%` | Hairline border — AIS 표준. 장식용 separator 만 — UI 영역 식별의 유일한 수단이 되지 않습니다. |
+| `--input` | `#dadce0` | `220 9% 87%` | Input outline. |
+| `--primary` | `#0b57d0` | `217 90% 43%` | Primary CTA — Google 블루, "이 페이지의 중요 액션". |
+| `--primary-foreground` | `#ffffff` | `0 0% 100%` | Primary 위 텍스트. |
+| `--secondary` | `#d3e3fd` | `217 91% 91%` | Tonal 버튼 fill (Google "tonal" 패턴). |
+| `--secondary-foreground` | `#041e49` | `217 90% 15%` | Tonal fill 위 텍스트. |
+| `--accent` | `#f0f4f9` | `213 43% 96%` | Hover 행 · ghost 버튼 hover — 옅은 blue-grey tint. |
+| `--destructive` | `#d93025` | `4 71% 50%` | Destructive CTA — Google red. `--risk-critical` 과 비슷해 destructive 버튼이 severity 와 같은 시각 언어. |
+| `--destructive-foreground` | `#ffffff` | `0 0% 100%` | Destructive 위 텍스트. |
+| `--ring` | `#0b57d0` | `217 90% 43%` | Focus ring. Primary 와 매칭 — outline 이 액션과 같은 색 패밀리로 읽힘. |
### Severity (도메인 의미 — 고정)
@@ -76,10 +87,10 @@ Severity 팔레트 (Critical / High / Medium / Low / Info) 는 **의도적으로
| `--risk-critical` | `#dc2626` | Critical CVE · forbidden 라이선스 · build-blocking finding. |
| `--risk-high` | `#ea580c` | High-severity CVE · conditional 라이선스 위험. |
| `--risk-medium` | `#ca8a04` | Medium CVE · 검토 대기 conditional 라이선스. |
-| `--risk-low` | `#2563eb` | Low CVE · 정보성 상태. |
+| `--risk-low` | `#0f766e` | Low CVE · 정보성 상태. W13 부터 teal-700 — Low 배지가 블루 primary CTA 처럼 읽히지 않도록 블루 계열에서 분리. |
| `--risk-info` | `#71717a` | 중립 정보. |
-Severity hex 는 **릴리스 간 변경하지 않습니다**. 사용처:
+Severity hex 는 릴리스 간 안정적으로 유지합니다 (W13 의 Low 이동이 문서화된 단일 예외). 사용처:
- Recharts fill · 차트 범례 (raw hex, `--risk-X` 변수 참조).
- 배지 · dot 인디케이터의 `bg-risk-X/N` tint.
@@ -109,26 +120,26 @@ Severity 톤이 **본문 텍스트** 로 사용될 때 (배지 안의 색 단어
## Radius 계층
-Affordance 별로 다른 radius — depth 가 한눈에 읽힘.
+Affordance 별로 다른 radius — depth 가 한눈에 읽힘. W13 에서 전체 스케일을 한 단계 더 둥글게 올렸습니다.
| 토큰 | 값 | Affordance |
|---|---|---|
-| `--radius-sm` | 4 px | 작은 input · 배지 · 칩. |
-| `--radius` | 6 px | **기본** — 버튼 · 카드 · 테이블 크롬. |
-| `--radius-lg` | 8 px | 드로어 · 큰 surface. |
-| `--radius-xl` | 12 px | 모달 · 다이얼로그. |
+| `--radius-sm` | 6 px | 작은 input · 배지 · 칩. |
+| `--radius` | 8 px | **기본** — 카드 · input · 테이블 크롬. |
+| `--radius-lg` | 10 px | 드로어 · 큰 surface. |
+| `--radius-xl` | 14 px | 모달 · 다이얼로그. |
-Tailwind config 가 `calc()` 로 `rounded-sm`/`rounded-md`/`rounded-lg`/`rounded-xl` 를 이 토큰에서 파생.
+Tailwind config 가 `calc()` 로 `rounded-sm`/`rounded-md`/`rounded-lg`/`rounded-xl` 를 이 토큰에서 파생. **버튼은 필 형태** (`button.tsx` 의 `rounded-full`) 로 이 스케일에 포함하지 않습니다 — `--radius` 를 필 값으로 올리면 `calc()` 파생을 통해 카드·다이얼로그까지 완전히 둥글어지기 때문입니다.
## Shadow 스케일
-Vercel 톤의 subtle elevation. 가벼운 그림자만 — glow 없음.
+AIS 는 본문 흐름 위의 surface 를 평면으로 둡니다 — 카드·버튼은 보더+톤으로 구분합니다. Shadow 는 떠 있는 surface 전용이며 Google elevation 레시피를 따릅니다.
| 토큰 | 값 | 용도 |
|---|---|---|
-| `--shadow-sm` | `0 1px 2px 0 rgb(0 0 0 / 0.04)` | 카드 · stat 타일. |
-| `--shadow-md` | `0 2px 8px -2px rgb(0 0 0 / 0.08), 0 1px 2px 0 rgb(0 0 0 / 0.04)` | 드롭다운 · popover · 툴팁. |
-| `--shadow-lg` | `0 10px 28px -8px rgb(0 0 0 / 0.12), 0 3px 8px -3px rgb(0 0 0 / 0.06)` | 드로어 · 다이얼로그. |
+| `--shadow-sm` | `0 0 0 0 rgb(0 0 0 / 0)` | 의도적 zero-alpha no-op (평면 카드·버튼). `var(--shadow-sm)` 소비자가 깨지지 않도록 유효한 box-shadow 값을 유지 — 미래에 한 곳만 바꿔 elevation 을 되살릴 수 있습니다. |
+| `--shadow-md` | `0 1px 2px 0 rgb(60 64 67 / 0.3), 0 1px 3px 1px rgb(60 64 67 / 0.15)` | 드롭다운 · popover · 툴팁 (Google elevation-1). |
+| `--shadow-lg` | `0 1px 3px 0 rgb(60 64 67 / 0.3), 0 4px 8px 3px rgb(60 64 67 / 0.15)` | 드로어 · 다이얼로그 (Google elevation-2). |
## Motion
@@ -202,10 +213,12 @@ stacked 변형에는 선택적 `meta` 슬롯도 있습니다 — 부제 아래
`apps/frontend/src/components/ui/button.tsx`
-- 기본 variant: `bg-primary text-primary-foreground` — solid warm near-black.
+- **필 형태** (`rounded-full`, W13) — 모든 variant·size 에 적용되는 AIS 버튼 실루엣. Input 은 토큰 radius 유지, 필은 버튼(과 사이드바 nav 항목)만.
+- 기본 variant: `bg-primary text-primary-foreground` — solid Google 블루.
+- `secondary` variant: Google **tonal** 버튼 — 라이트블루 fill (`--secondary`) + 짙은 블루 텍스트.
- `outline` variant: `border-input bg-background` — 보조 액션.
-- `ghost` variant: 배경 없음, hover tint 만 — nav 항목 · toolbar 액션.
-- `destructive`: `bg-destructive` — Critical-aligned 빨강, 되돌릴 수 없는 액션 전용 (delete · revoke · reject).
+- `ghost` variant: 배경 없음, hover tint (`--accent`) 만 — nav 항목 · toolbar 액션.
+- `destructive`: `bg-destructive` — `--risk-critical` 에 가까운 Google red, 되돌릴 수 없는 액션 전용 (delete · revoke · reject).
- Hover / focus transition: `transition-colors duration-fast ease-out` (150 ms).
- 모든 variant 에 focus ring 포함.
@@ -217,9 +230,9 @@ stacked 변형에는 선택적 `meta` 슬롯도 있습니다 — 부제 아래
### Card
-- Off-white canvas 위 순백 surface (`bg-card`) — 무거운 shadow 없이 시각적으로 뜸.
-- 기본 `rounded-md` (6 px) · 메인 콘텐츠 카드는 `rounded-lg` (8 px).
-- Stat / 타일은 `shadow-sm` · 떠 있는 popover 는 `shadow-md`.
+- 흰 canvas 와 같은 면의 흰 surface (`bg-card`) — 구분은 `--border` hairline 과 `--muted` 톤으로 (AIS 평면 카드 패턴; `shadow-sm` 은 no-op).
+- 기본 `rounded-md` (8 px) · 메인 콘텐츠 카드는 `rounded-lg` (10 px).
+- `shadow-md` 는 떠 있는 surface (popover · 드롭다운) 전용 — 본문 흐름 위 카드에는 쓰지 않습니다.
### Table
@@ -303,28 +316,30 @@ W11-F polish phase 가 모든 인터랙티브 transition 의 타이밍 · easing
| 쌍 | 비율 | 비고 |
|---|---|---|
-| `--foreground` on `--background` | 16.97:1 | 본문. AAA. |
-| `--foreground` on `--card` | 17.72:1 | 카드 위 본문. AAA. |
-| `--muted-foreground` on `--background` | 4.63:1 | Caption · 보조 텍스트. AA. |
-| `--muted-foreground` on `--card` | 4.83:1 | 카드 위 caption. AA. |
-| `--primary-foreground` on `--primary` | 16.97:1 | Primary 버튼 라벨. AAA. |
-| `--destructive-foreground` on `--destructive` | 4.63:1 | Destructive 버튼 라벨. AA. |
-| `--ring` on `--background` | 16.97:1 | Focus ring. AAA. |
+| `--foreground` on `--background` / `--card` | 16.48:1 | 본문. AAA. |
+| `--muted-foreground` on `--background` / `--card` | 6.05:1 | Caption · 보조 텍스트. AA. |
+| `--muted-foreground` on `--muted` | 5.74:1 | Tint fill 위 caption. AA. |
+| `--primary-foreground` on `--primary` | 6.39:1 | Primary 버튼 라벨. AA. |
+| `--secondary-foreground` on `--secondary` | 12.57:1 | Tonal 버튼 라벨. AAA. |
+| `--destructive-foreground` on `--destructive` | 4.77:1 | Destructive 버튼 라벨. AA. |
+| `--ring` on `--background` | 6.39:1 | Focus ring. AA (UI ≥ 3:1). |
-장식용 border (`--border` on `--background`, 1.20:1) 는 **의도적으로 저대비** — 시각 분리용일 뿐 정보성 UI 요소가 아니며 WCAG 1.4.11 이 면제 대상.
+장식용 border (`--border` on `--background`, 1.37:1) 는 **의도적으로 저대비** — 시각 분리용일 뿐 정보성 UI 요소가 아니며 WCAG 1.4.11 이 면제 대상.
### Severity 색 접근성
-Severity hex (`#dc2626` / `#ea580c` / `#ca8a04` / `#2563eb` / `#71717a`) 는 브랜드 고정. Light tint 위 **본문 텍스트** 로 쓰이면 2.5:1 까지 떨어져 AA 실패. 해결은 색이 아닌 구조 — severity 톤이 텍스트로 쓰일 때 렌더링 텍스트 색은 같은 Tailwind hue family 의 더 짙은 shade:
+Severity hex (`#dc2626` / `#ea580c` / `#ca8a04` / `#0f766e` / `#71717a`) 는 브랜드 고정. Light tint 위 **본문 텍스트** 로 쓰이면 일부는 2.5:1 까지 떨어져 AA 실패. 해결은 색이 아닌 구조 — severity 톤이 텍스트로 쓰일 때 렌더링 텍스트 색은 같은 Tailwind hue family 의 더 짙은 shade:
| Tone | Tint 배경 | 텍스트 색 | 대비 |
|---|---|---|---|
| `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-blue-700` (`#1d4ed8`) | 5.83:1 |
+| `low` | `bg-risk-low/10` | `text-teal-800` (`#115e59`) | 6.59:1 |
| `info` | `bg-risk-info/15` | `text-slate-600` (`#52525b`) | 6.41:1 |
+Low 토큰 자체(teal-700 `#0f766e`)도 텍스트로 직접 쓰이는 곳에서 AA 를 통과합니다 (흰 배경 5.47:1, 자기 10 % tint 위 4.76:1) — 교체 전 blue-600 보다 약간 높습니다.
+
**Dot 인디케이터** (SeverityBadge · 차트 범례 · status pill) 는 계속 raw `bg-risk-X` 토큰 사용 — 색 정체성은 그대로, 텍스트 shade 만 어둡게. 참조 구현은 `apps/frontend/src/components/ui/badge.tsx` (W11-H).
### 색이 신호의 단독 수단 아님
@@ -373,8 +388,9 @@ Severity 가 표시되는 모든 곳에서 색은 다음 중 하나와 짝지움
| W12-C | 2026-06-11 | **Craft 격상 — 모션 (CSS-only).** 라우트 전환 진입 페이드(`` 을 pathname 으로 key, 250 ms), 사이드바 접기 250 ms 정렬, 전역 `prefers-reduced-motion` 가드. 새 의존성 없음(tailwindcss-animate 만). 스켈레톤 문서를 실제 2000 ms `animate-pulse` 로 정정. |
| W12-D | 2026-06-12 | **Craft 격상 — 빈 상태 · 로딩 폴리시.** EmptyState 에 레이어드 아이콘 메달리온 + 선택적 `illustration` 슬롯 추가, 신규 `TableRowsSkeleton` 이 Scans · Admin Users 테이블에서 컬럼별 로딩 셀(전폭 바 대체)을 렌더링. |
| W12-E/F | 2026-06-12 | **Craft 격상 — 가드레일 + 문서.** `/dev/design-preview` 를 살아있는 컴포넌트 레퍼런스(타이포그래피 · 배지 · 빈 / 로딩 · 피드백)로 확장하고, 기여자 coding standards 에 "프론트엔드 UI" 섹션 추가. 시각 회귀 베이스라인 확장(4 → ~15)은 CI / 운영자 후속 — darwin 개발 머신에서는 올바른 linux 베이스라인을 생성할 수 없음. |
+| W13 | 2026-06-12 | **Google AI Studio 재스킨 (TRUSCA).** PR #394 dev 프로토타입을 사용자 검토 후 토큰 승격: 흰 canvas · `#0b57d0` 블루 primary · `#d3e3fd` tonal secondary · `#dadce0` border · radius 스케일 +2 px · 평면 카드 (shadow-sm → no-op, md/lg → Google elevation) · 필 버튼 (`button.tsx` `rounded-full` + 사이드바 nav) · `--risk-low` blue-600 → teal-700 (Low 배지를 블루 primary 와 분리). 레이아웃 밀도 · motion · 타이포 · 다른 severity 색은 불변. 시각 회귀 베이스라인은 머지 후 linux 재생성 필요. |
-이전 "BD-style 2015" 미감 (`#0f172a` navy · 순백 canvas · 일관 8 px radius · shadow 없음 · 브라우저 기본 easing) 은 W11 로 완전 은퇴.
+W11 의 Vercel + Linear 스킨은 W13 으로 은퇴. 그 이전 "BD-style 2015" 미감은 W11 로 은퇴.
## 참고