/* Snapshot from VOSS-team/app/globals.css, 2026-10-12. Regenerate from that source; do not redefine system tokens here. */
:root {
  color-scheme: light;

  /* ── Surfaces ─────────────────────────────────────────────────────
     Max four steps above canvas. Depth is tonal + hairline, never shadow. */
  --cb-canvas: #ffffff;
  --cb-surface: #fafafa;
  --cb-surface-strong: #ededed;
  --cb-surface-active: rgba(0, 0, 0, 0.043);
  --cb-surface-pressed: #e8e8e8;

  /* ── Text ─────────────────────────────────────────────────────────
     Solid, never alpha: an alpha text colour composites against whatever
     is behind it, so one token would carry four different real contrast
     ratios. Per-surface legality — canvas / quiet / strong / pressed:
       --cb-text            19.16  18.36  16.36  15.64   legal everywhere
       --cb-text-secondary   5.74   5.50   4.90   4.69   legal everywhere
       --cb-text-disabled    2.43   2.33   2.08   1.99   inactive controls
     One grey, not two (2026-10-06): a tertiary #727272 sat 0.9 of a
     contrast step from #666666, too close to read as a different tier,
     and could not go lighter without dropping below AA. Hierarchy comes
     from dark names vs grey notes, see DESIGN_SYSTEM.md §3「文字角色」.
     Disabled is deliberately below AA — SC 1.4.3 exempts inactive
     controls, and raising it makes a dead control read as available. */
  --cb-text: #0f0f10;
  --cb-text-secondary: #666666;
  --cb-text-disabled: #a6a6a6;

  /* ── Lines ────────────────────────────────────────────────────────
     These stay alpha on purpose — a line is meant to take its surface
     with it rather than sit on top of it. */
  --cb-border: rgba(0, 0, 0, 0.075);
  --cb-border-control: rgba(0, 0, 0, 0.1);
  --cb-border-width: 1px;

  /* Scrim behind modals and the compact-breakpoint inspector drawer. The
     design system described this value in prose but never tokenised it, so
     it was living as a literal in two components. */
  --cb-scrim: rgba(0, 0, 0, 0.32);
  /* The one dark veil allowed over a media thumbnail, to reveal a hover
     action such as 替换. Never appears in a view where the user is judging
     the image. */
  --cb-scrim-media: rgba(0, 0, 0, 0.45);

  /* ── Focus ────────────────────────────────────────────────────────
     OPEN DECISION: #007aff is a borrowed system blue standing in for a
     second brand colour VOSS has not chosen. It is the only hue in the
     chrome besides the brand mark. Replace when the value is decided. */
  --cb-focus: #007aff;
  --cb-focus-ring-width: 2px;
  --cb-focus-ring-offset: 2px;

  /* ── Action — exactly one, near-black ─────────────────────────── */
  --cb-action: #0f0f10;
  --cb-action-hover: #2e2e2e;
  --cb-action-pressed: #000000;
  --cb-action-text: #ffffff;

  /* Button skin for a persistent CTA pinned to a photo (MediaCard's
     `overlay`, Button's `onMedia` variant) — always paired with the
     bottom scrim gradient built from --cb-scrim-media, never used bare. */
  --cb-border-on-media: rgba(255, 255, 255, 0.5);
  --cb-surface-on-media-hover: rgba(255, 255, 255, 0.16);
  --cb-surface-on-media-pressed: rgba(255, 255, 255, 0.28);
  /* ── On-dark — chrome floating on a dark full-screen judging surface ──
     ⚠️ NOT the on-media family above, and the difference is load-bearing.
     `--cb-*-on-media` is a button skin pinned to a *thumbnail*, and its
     contract says it is always paired with the --cb-scrim-media gradient
     and never used bare. A lightbox is the opposite situation: the user is
     judging the image itself (§7 成品类), nothing may veil it, and the
     chrome is bare by definition. Reusing the on-media values here both
     breaks that contract and brightens chrome that is competing with the
     image — which is exactly what happened on 2026-09-02 before this family
     existed. Values are the lightbox's original ones; this names them, it
     does not restyle anything. */
  /* ⚠️ --cb-scrim-fullscreen and --cb-text-secondary-on-dark were removed
     on 2026-10-07 with the viewer rebuild: the viewer is now a floating
     panel over --cb-scrim, its text sits on the white panel, and only its
     stage (--cb-viewer-stage) is dark. */
  --cb-text-on-dark: #ffffff;
  --cb-border-on-dark: rgba(255, 255, 255, 0.35);
  --cb-surface-on-dark: rgba(255, 255, 255, 0.08);
  /* A corner label reading over the image itself (CompareSlider's 原图 /
     结果 tags). ⚠️ Deliberately NOT --cb-scrim-media: §7 通则 3 bars that
     value from any view where the user is judging the image, and a compare
     slider is exactly such a view. Same darkness, different permission. */
  --cb-surface-tag-on-dark: rgba(0, 0, 0, 0.5);
  /* A control that must stay legible against *any* photo goes fully opaque
     rather than translucent — the compare handle is the only one so far. */
  --cb-surface-floating-on-dark: #ffffff;

  /* ── Emphasis outline ─────────────────────────────────────────────
     2px near-black, drawn outside the box so it never shifts layout.
     ⚠️ Two meanings share it deliberately: a selected card and a live drop
     target both say 「这个就是要被作用的对象」. They never appear in the same
     region (selection is on the subject grid, drop targets are upload
     surfaces), so one value stays honest. If they ever do collide, split
     this into two tokens — do not start hand-writing the literal again,
     which is how it ended up in seven files before it had a name. */
  --cb-outline-emphasis: 2px solid var(--cb-action);

  /* ── Status — never appear alone, words carry the state ────────── */
  --cb-status-success: #137333;
  --cb-status-warning: #8a5a00;
  --cb-status-error: #b42318;

  /* ── Type ─────────────────────────────────────────────────────────
     One family. Body, meta AND page/section titles are all Regular 400 —
     a heading is not automatically bolder. Controls are nominally 500;
     PP Charlevoix has no Medium cut so this renders as 400 today, kept
     at 500 so it becomes correct the moment a Medium ships. The wordmark
     is nominally 600, which resolves upward to the real Bold 700.
     "The written number is not the rendered number" is a deliberate
     convention here, not a bug. */
  --cb-font-sans: var(--font-charlevoix), "Noto Sans SC", "PingFang SC",
    "Hiragino Sans GB", "Microsoft YaHei", system-ui, sans-serif;

  --cb-text-page: 400 24px/32px var(--cb-font-sans);
  --cb-text-section: 400 18px/24px var(--cb-font-sans);
  --cb-text-body: 400 16px/24px var(--cb-font-sans);
  --cb-text-control: 500 14px/20px var(--cb-font-sans);
  --cb-text-control-sm: 500 13px/20px var(--cb-font-sans);
  --cb-text-meta: 400 12px/16px var(--cb-font-sans);

  /* Line heights, exposed separately so a component can reserve an exact
     number of lines — a card grid only stays aligned if every caption
     occupies the same height whether its text is one line or three. */
  --cb-lh-page: 32px;
  --cb-lh-section: 24px;
  --cb-lh-body: 24px;
  --cb-lh-control: 20px;
  --cb-lh-meta: 16px;

  --cb-weight-thin: 100;
  --cb-weight-regular: 400;
  --cb-weight-medium: 500;
  --cb-weight-semibold: 600;
  --cb-weight-bold: 700;
  --cb-weight-black: 900;

  /* ── Spacing — 4 / 8 / 12 / 16 / 24 / 32 / 48, applied through
     container gap, never ad-hoc margins. Matches Tailwind's default
     1/2/3/4/6/8/12 exactly, so utilities need no redefinition. ────── */
  --cb-space-1: 4px;
  --cb-space-2: 8px;
  --cb-space-3: 12px;
  --cb-space-4: 16px;
  --cb-space-6: 24px;
  --cb-space-8: 32px;
  --cb-space-12: 48px;

  /* ── Geometry — radius is role-specific, not one global softening ──
     nav: nav items and anything smaller than a control — small thumbnails,
     icon buttons, badges (the name is historical; the role is "small").
     control / card: embedded in the page. overlay: floating above it.
     pill: only where the thing really is a pill or a circle.
     A shape nested inside another takes the outer radius minus the padding
     between them, as a calc of the two tokens — never a new step. */
  --cb-radius-nav: 8px;
  --cb-radius-control: 10px;
  --cb-radius-card: 10px;
  --cb-radius-overlay: 16px;
  /* A large floating surface that takes most of the screen — the image
     viewer (2026-10-07, Louis). The bigger the surface, the rounder: small
     overlays (menus, tooltips, toasts, form modals) stay on overlay 16. */
  --cb-radius-sheet: 24px;
  --cb-radius-pill: 9999px;

  /* Shadow is none by default. Only a floating overlay may carry weak
     elevation — never a card, never a panel. */
  --cb-shadow-none: none;
  --cb-shadow-overlay: 0 8px 24px rgba(0, 0, 0, 0.08);
  /* A control that floats on a dark judging surface — the compare handle. Tighter
     and darker than --cb-shadow-overlay because it has to separate from
     arbitrary photo content, not from a known neutral surface. */
  --cb-shadow-on-dark: 0 2px 14px rgba(0, 0, 0, 0.18);

  /* ── Grid columns — the `minmax()` floor of a card grid ───────────
     Role-specific like radius, not a free number per grid. Before these
     existed the app had nine hand-picked values between 140 and 300, one
     per grid, which is why no two grids ever felt like the same system.
     Pick by what the cell is for, never by how the page happens to look. */
  --cb-grid-tile: 140px;    /* dense pick targets: subject tiles, asset picker */
  /* 分组列表的行（2026-10-09，DESIGN_SYSTEM §3）：悬停 / 选中底色超出内容的距离，
     行用等量的负外边距撑开，文字因此与分组标题左对齐。 */
  --cb-row-bleed: var(--cb-space-3);
  --cb-grid-media: 220px;   /* media the user reads but does not judge */
  --cb-grid-result: 260px;  /* finished images the user judges */
  --cb-grid-card: 300px;    /* content cards carrying text: projects, Recipes */

  /* ── Motion — confirms a state change, never performs ──────────── */
  --cb-duration-fast: 140ms;
  --cb-duration-standard: 180ms;
  --cb-easing: ease;
  --motion-hover: var(--cb-duration-fast) var(--cb-easing);
  --motion-panel: var(--cb-duration-standard) var(--cb-easing);

  /* ── Layout — verified at 1440×900 ─────────────────────────────── */
  --cb-sidebar-width: 256px;
  --cb-sidebar-rail: 64px;
  /* Optical inset for the sidebar brand band; its total height stays fixed. */
  --cb-sidebar-brand-padding-top: 5px;
  --cb-topbar-height: 50px;
  --cb-inspector-width: 430px;
  --cb-inspector-width-tablet: 360px;
  --cb-control-height: 36px;
  --cb-control-height-sm: 32px;
  /* The large step (2026-10-07): full-width stacked action buttons, like
     the image viewer's panel. Not the page default — that stays 36. */
  --cb-control-height-lg: 40px;
  /* Parameter rows (`SettingRow`): the label column. In em, so it is a count
     of characters at the label's own size — four 汉字 at 13px is 52px. A
     label longer than that is a label to shorten, not a column to widen. */
  --cb-setting-label-width: 4em;
  /* A metadata tag (MetaTag): a quiet pill, 12 text + 12 icon. */
  --cb-tag-height: 24px;
  /* The round controls on the viewer's stage: 原始尺寸 and close top-right,
     previous / next at the sides (2026-10-07). They sit over the image —
     the image is full-bleed. */
  --cb-viewer-control-size: 36px;
  /* Edit workspace (2026-10-10, preview): the version band under the canvas
     and its tiles. The band's height is fixed so the canvas image can be
     sized against it. */
  --cb-edit-band-height: 212px;
  --cb-edit-version-size: 80px;
  /* The edit request box's starting height (Louis 2026-10-10). */
  --cb-edit-input-height: 360px;
  /* The image viewer (MediaViewer, 2026-10-07, after ElevenLabs): a floating
     panel sized to the window, an info column, a dark stage. The column
     follows the window like ElevenLabs' (about a quarter of the viewer =
     22.5% of the window, Louis 2026-10-08): 280 at a 1024 window, ~430 at
     1920, 576 at 2560; never narrower than 280 (tags and buttons stop
     fitting), never wider than 640 (ultrawide). Type and control sizes do
     not scale — only the column does. */
  --cb-viewer-width: 90vw;
  --cb-viewer-height: 87dvh;
  --cb-viewer-panel-width: clamp(280px, 22.5vw, 640px);
  /* Contrast (2026-10-07): --cb-text-on-dark on it 18.4:1; on a stage
     control (--cb-surface-tag-on-dark, 2026-10-07) over the brightest
     possible backdrop (a white image's blurred copy at full ambient
     opacity) 13.6:1. */
  --cb-viewer-stage: #141416;
  /* The stage's backdrop is a blurred copy of the same image (Louis
     2026-10-07) — the one exception to「没有毛玻璃」, and only behind the
     image: the image being judged is never filtered. */
  --cb-viewer-ambient-blur: 48px;
  --cb-viewer-ambient-opacity: 0.3;
  /* Behind the viewer the page stays in view, washed light and blurred
     (Louis 2026-10-07, after ElevenLabs) — not the dark scrim forms use:
     the viewer is a lighter, quicker look than a form. The second and last
     place glass is allowed; see DESIGN_SYSTEM §5. */
  --cb-viewer-backdrop: rgba(255, 255, 255, 0.55);
  --cb-viewer-backdrop-blur: 3px;
  /* The Recipe detail sheet (RecipeDetailSheet, 2026-10-08, Louis): the
     viewer's shell, but reading content rather than one image to see as
     large as possible. Width follows the window, narrower than the
     viewer's 90vw: 1152 at a 1440 window, 1536 at 1920 (a 1080 cap was
     tried the same day and was too narrow). Height follows the window too,
     87% of it like the viewer (an 800 cap was dropped the same day). A fixed info column; the showcase takes the rest. */
  --cb-sheet-width: 80vw;
  --cb-sheet-height: 87dvh;
  --cb-sheet-panel-width: 360px;
  /* Caps `<main>` content on ultrawide/4K displays so mouse travel stays
     reasonable — list rows and detail forms have no reason to stretch
     edge-to-edge past this. Starting value, not a locked-in number; the
     canvas page (`padded={false}`) is exempt on purpose, see AppShell. */
  --cb-auth-form-width: 380px;
  --cb-auth-provider-icon-size: 20px;
  --cb-content-max-width: 1280px;
  /* Long-form editorial reading; shrink to the available canvas on small screens. */
  --cb-reading-max-width: 720px;
  /* The workbench canvas is exempt from `--cb-content-max-width` on
     purpose (its card grids use `auto-fill` and want the extra columns),
     but "exempt" turned out to mean unbounded — on very wide displays the
     grids stop well short of the edge and leave a dead gap before the
     inspector instead of gaining columns. This is that page's own,
     wider cap: room for more of the batch grid than a list page, without
     the void. Also a starting value. */
  --cb-canvas-max-width: 1280px;

  /* ── Layers (2026-10-06) ──────────────────────────────────────────
     One ladder, bottom to top. Before this each overlay wrote its own
     number, and the tooltip (110) sat under the full-screen viewer (150):
     「重新创作」's hint rendered behind the picture and was never seen.
     A tooltip belongs to whatever it points at, so it is always on top. */
  --cb-z-popover: 80;          /* menus anchored to a control: account, notices */
  --cb-z-modal: 100;           /* forms; the viewer steps aside while one is open */
  --cb-z-viewer: 150;          /* the full-screen judging surface */
  --cb-z-viewer-chrome: 151;   /* the viewer's own floating controls */
  --cb-z-listbox: 155;         /* a dropdown's or a card's 「…」 menu — they open inside modals and the viewer */
  --cb-z-toast: 160;           /* a finished-generation notice, above everything you work in */
  --cb-z-tooltip: 200;         /* a hint always sits on top of what it explains */
  --bp-desktop: 1440px;
  --bp-tablet: 1024px;
  --bp-compact: 768px;
}
@keyframes cb-mark-drift {
  0% { transform: translate(0, 0); }
  25% { transform: translate(calc(-45px * var(--cb-mark-motion, 1)), calc(-20px * var(--cb-mark-motion, 1))); }
  50% { transform: translate(calc(-20px * var(--cb-mark-motion, 1)), calc(-38px * var(--cb-mark-motion, 1))); }
  75% { transform: translate(calc(30px * var(--cb-mark-motion, 1)), calc(-18px * var(--cb-mark-motion, 1))); }
  100% { transform: translate(0, 0); }
}
@keyframes cb-mark-breath {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(calc(1 + 0.14 * var(--cb-mark-motion, 1))); }
}
@keyframes cb-mark-color {
  0% { filter: hue-rotate(0deg) saturate(0.9); }
  50% { filter: hue-rotate(80deg) saturate(1.3); }
  100% { filter: hue-rotate(0deg) saturate(0.9); }
}
.cb-mark-drift {
  animation: cb-mark-drift 10s ease-in-out infinite;
  transform-box: fill-box;
  transform-origin: center center;
}
.cb-mark-breath {
  animation: cb-mark-breath 5s ease-in-out infinite,
    cb-mark-color 12s ease-in-out infinite;
  transform-box: fill-box;
  transform-origin: center center;
}
@media (prefers-reduced-motion: reduce) {
  .cb-mark-drift,
  .cb-mark-breath {
    animation: none;
  }
}
