/* ============================================================================
 * NEO PRODUCT UI -- Neo Genesis product-UI semantic layer, v2 (staged)
 * ============================================================================
 * Companion artifact to neo-product-ui.tokens.dtcg.json (same values, DTCG-
 * documented there; this file is what a real page actually <link>s).
 *
 * LOAD ORDER (required): this file loads AFTER Neo canon --
 *   1. dist/ops.light.css   (canon, defines :root                = light)
 *   2. dist/ops.dark.css    (canon, defines [data-theme="dark"]  = dark)
 *   3. neo-product-ui.css   (this file -- semantic layer)
 *
 * RULE 0 (G03): every value below is a var(--ng-*) reference to canon, or an
 * explicit color-mix()/rgba() derivation of one, with its provenance cited
 * inline. Zero new brand hex is authored here except the two NAMED
 * exceptions (--ng-ui-surround-neutral, G08; --ng-brand-key/--ng-brand-key-
 * on-light/--ng-brand-gradient, owner brand decision) -- see their comments.
 * No other literal hex appears in this file. Enforced by lint_design.py R1.
 *
 * THEME CONTRACT -- FIXED 2026-09-05, real-product regression (adoption test
 * on 6 products: 3 regressed 100% pixel diff on their canvas because bare
 * :root carried `color-scheme: dark`, which repaints the UA default canvas
 * background to black on any page with no explicit background -- merely
 * <link>ing this file was NOT a no-op). DARK IS NOW STRICTLY OPT-IN:
 *   - Bare :root defines ONLY the light-value variables. NO color-scheme, NO
 *     paint of any kind -- linking this file changes nothing on a page that
 *     sets neither data-theme nor data-ng-root.
 *   - :root[data-theme="dark"] -- explicit opt-in (e.g. a FOUC boot script
 *     that sets data-theme="dark"/"light" before first paint, see
 *     neo-starter.html). This is the PRIMARY, JS-driven opt-in path.
 *   - html[data-ng-root]:not([data-theme="light"]) inside
 *     @media (prefers-color-scheme: dark) -- a SECOND, CSS-only opt-in path
 *     for pages that mark themselves with a static data-ng-root attribute:
 *     these automatically follow the OS's dark preference even without any
 *     boot script, but ONLY because they explicitly declared themselves a
 *     Neo root. A page with neither attribute never matches either rule.
 *   These two dark rules carry IDENTICAL declarations (by construction) --
 *   see the "MUST STAY IN SYNC" comment at the media-query copy below. CSS
 *   has no way to share a declaration block across a plain selector and a
 *   conditional @media selector without a preprocessor, so the second copy
 *   is a deliberate, documented duplication, not an oversight.
 *
 * Gap items addressed: G03 G04 G05 G06 G07 G08 G09 G10 G12 G13.
 * Doctrine: NEO_DESIGN_DOCTRINE_v2.md. Lint: lint_design.py. Starter: neo-starter.html.
 * ========================================================================== */

/* ---------- 1. Semantic tokens -- LIGHT is the default posture (bare :root).
   No color-scheme, no paint -- see THEME CONTRACT above. ---------- */
:root {

  --ng-ui-bg: var(--ng-role-surface);
  --ng-ui-surface: var(--ng-role-surfacePanel);
  /* G04: light uses the canonical light primitive DIRECTLY -- no derivation.
     canon has no third light fill tier, so elevation here is carried by
     --ng-ui-shadow-1/2, not a fill-color shift. */
  --ng-ui-surface-raised: var(--ng-role-surfacePanel);
  --ng-ui-surface-raised-2: var(--ng-role-surfacePanel);

  --ng-ui-text: var(--ng-role-onSurface);                /* 16.64:1 vs bg -- exceeds 9:1 G13 target */
  --ng-ui-text-weak: var(--ng-role-onSurfaceMuted);       /* 4.67:1 vs bg -- exceeds 4.5:1 G13 target, smallest margin in this file */
  /* faint tier: same 80/20 formula, resolved against light's own canon values. measured #838B9B @3.21:1 */
  --ng-ui-text-faint: color-mix(in srgb, var(--ng-role-onSurfaceMuted) 80%, var(--ng-role-surface));

  --ng-ui-border: var(--ng-role-border);                  /* 1.33:1 -- decorative only */
  /* required-boundary tier needs a heavier mix in light (40% vs dark's 25%) to
     clear the same 3:1 floor. measured #888C91 @3.17:1 */
  --ng-ui-border-strong: color-mix(in srgb, var(--ng-role-onSurface) 40%, var(--ng-role-border));

  /* brand identity "네오 라임" -- same raw key/gradient values as dark (gradient stops
     are theme-independent), but --ng-ui-accent uses the darkened light-mode derivation:
     raw brand.key (#19E3B1) measures only 1.56:1 as text/icon on the light bg, below
     even the 3:1 non-text floor. brand.keyOnLight (#0E7D61, 55% brand-key/45% black)
     measures 4.78:1 -- see neo-product-ui.tokens.dtcg.json brand.keyOnLight for the
     full derivation history and two rejected weaker mixes. */
  --ng-brand-key: #19E3B1;
  --ng-brand-key-on-light: #0E7D61;
  --ng-brand-gradient: linear-gradient(135deg, #C6FF3D 0%, #19E3B1 50%, #2F6BFF 100%);
  --ng-ui-on-brand-gradient: var(--ng-color-panel);

  --ng-ui-accent: var(--ng-brand-key-on-light);            /* 4.78:1 vs bg */
  --ng-ui-accent-evidence: var(--ng-role-accentEvidence); /* rationed -- background-fill only, 1.82:1 as text, never used as text */

  --ng-ui-focus-ring-color: var(--ng-role-accentSystem);
  --ng-ui-focus-ring-inner: 2px;
  --ng-ui-focus-ring-outer: 2px;

  --ng-ui-surround-neutral: #323232;   /* same self-authored neutral, both themes -- see dark block comment */
  --ng-ui-thumb-bg: var(--ng-ui-surround-neutral);

  --ng-ui-overlay-scrim: color-mix(in srgb, black 48%, transparent);

  /* light mode relies primarily on shadow for elevation (no fill-color shift, see surface-raised above).
     tint derives from canon onSurface (ink) via color-mix, not a hardcoded rgb() triplet -- Rule 0. */
  --ng-ui-shadow-1: 0 1px 2px color-mix(in srgb, var(--ng-role-onSurface) 8%, transparent);
  --ng-ui-shadow-2: 0 8px 24px color-mix(in srgb, var(--ng-role-onSurface) 14%, transparent);

  --ng-ui-status-pass-bg: var(--ng-role-statePassBg);
  --ng-ui-status-pass-fg: var(--ng-role-statePassFg);
  --ng-ui-status-repair-bg: var(--ng-role-stateRepairBg);
  --ng-ui-status-repair-fg: var(--ng-role-stateRepairFg);
  --ng-ui-status-human-review-bg: var(--ng-role-stateHumanReviewBg);
  --ng-ui-status-human-review-fg: var(--ng-role-stateHumanReviewFg);
  --ng-ui-status-unknown-bg: var(--ng-role-stateUnknownBg);
  --ng-ui-status-unknown-fg: var(--ng-role-stateUnknownFg);
  --ng-ui-status-unverified-bg: var(--ng-role-stateUnverifiedBg);
  --ng-ui-status-unverified-fg: var(--ng-role-stateUnverifiedFg);

  --ng-ui-radius-table-cell: var(--ng-radius-sm);
  --ng-ui-radius-card: var(--ng-radius-md);
  --ng-ui-radius-panel: var(--ng-radius-md);
  --ng-ui-radius-cta: var(--ng-radius-md);
  --ng-ui-radius-row-action: var(--ng-radius-sm);
  --ng-ui-radius-chip: var(--ng-radius-sm);
  --ng-ui-radius-modal: var(--ng-radius-md);

  --ng-ui-space-4: var(--ng-spacing-xs);
  --ng-ui-space-8: var(--ng-spacing-sm);
  --ng-ui-space-12: 12px;
  --ng-ui-space-16: var(--ng-spacing-md);
  --ng-ui-space-24: var(--ng-spacing-lg);
  --ng-ui-space-32: var(--ng-spacing-xl);
  --ng-ui-space-40: 40px;
  --ng-ui-space-48: 48px;
  --ng-ui-space-64: 64px;

  --ng-ui-z-base: 0;
  --ng-ui-z-sticky: 10;
  --ng-ui-z-dropdown: 20;
  --ng-ui-z-overlay-backdrop: 30;
  --ng-ui-z-modal: 40;
  --ng-ui-z-toast: 50;
  --ng-ui-z-tooltip: 60;

  --ng-motion-instant: 100ms;
  --ng-motion-fast: 150ms;
  --ng-motion-base: 200ms;
  --ng-motion-moderate: 300ms;
  --ng-motion-slow: 450ms;
  --ng-motion-exit-ratio: 0.66;
  --ng-ease-enter: cubic-bezier(0, 0, 0.2, 1);
  --ng-ease-exit: cubic-bezier(0.4, 0, 1, 1);

  --ng-feedback-instant-ms: 100ms;
  --ng-feedback-loading-from-ms: 300ms;
  --ng-feedback-progress-required-ms: 10000ms;
  --ng-feedback-eta-from-ms: 60000ms;

  /* same owner-decided Pretendard override as the dark block above -- theme-independent */
  --ng-ui-font-family-base: Pretendard, "Pretendard Variable", -apple-system, BlinkMacSystemFont,
    "Apple SD Gothic Neo", "Malgun Gothic", system-ui, sans-serif;

  --ng-ui-font-body: 400 14px/21px var(--ng-ui-font-family-base);
  --ng-ui-font-label: 600 12px/1.2 var(--ng-ui-font-family-base);
  --ng-ui-font-table: 500 13px/1.4 var(--ng-ui-font-family-base);
  --ng-ui-font-caption: 400 12px/1.35 var(--ng-ui-font-family-base);
  --ng-ui-row-height: 36px;
  --ng-ui-density-gap: var(--ng-spacing-sm);
  --ng-ui-control-h: 36px;
}

/* ---------- 1a. Dark, explicit opt-in via data-theme="dark" ---------- */
:root[data-theme="dark"] {
  color-scheme: dark;

  /* surfaces -- canon aliases, no derivation */
  --ng-ui-bg: var(--ng-role-surface);
  --ng-ui-surface: var(--ng-role-surfacePanel);
  /* elevated fill: white-alpha overlay over dark panel (G04 dark rule). measured #242B2F */
  --ng-ui-surface-raised: color-mix(in srgb, white 6%, var(--ng-role-surfacePanel));
  --ng-ui-surface-raised-2: color-mix(in srgb, white 10%, var(--ng-role-surfacePanel));

  /* text ladder */
  --ng-ui-text: var(--ng-role-onSurface);               /* 17.35:1 vs bg -- exceeds 11:1 G13 target */
  --ng-ui-text-weak: var(--ng-role-onSurfaceMuted);      /* 7.12:1 vs bg -- exceeds 6:1 G13 target */
  /* faint tier: 80% onSurfaceMuted / 20% surface. measured #828586 @4.98:1.
     correction history (G13): a 60%/40% mix was tried first and measured
     3.01:1 dark / 2.29:1 light -- light dropped below the 3:1 non-text
     floor, so the ratio was raised to 80/20 here. do not revert to 60/40. */
  --ng-ui-text-faint: color-mix(in srgb, var(--ng-role-onSurfaceMuted) 80%, var(--ng-role-surface));

  /* border ladder */
  --ng-ui-border: var(--ng-role-border);                 /* 1.69:1 -- decorative hairline only, not a required UI boundary */
  /* required-boundary tier (WCAG 1.4.11, >=3:1): 25% onSurface / border. measured #696C6D @3.49:1 */
  --ng-ui-border-strong: color-mix(in srgb, var(--ng-role-onSurface) 25%, var(--ng-role-border));

  /* brand identity "네오 라임" (Neo Lime), owner decision 2026-09-04 20:0x candidate G.
     RATIONED (see neo-product-ui.tokens.dtcg.json brand.usage) -- topbar line, mark,
     one hero keyword via text-clip, primary CTA fill, PASS dot, focus ring ONLY.
     Never a background/large fill, never body text, never >1 per section (R9). */
  --ng-brand-key: #19E3B1;                                /* 11.15:1 vs dark bg -- fine as text/icon here */
  --ng-brand-gradient: linear-gradient(135deg, #C6FF3D 0%, #19E3B1 50%, #2F6BFF 100%);
  /* semantic alias so component rules (e.g. .ngui-btn--primary) never reference
     --ng-color-* directly (R2) -- canon.color.panel is #FFFFFF in both themes. */
  --ng-ui-on-brand-gradient: var(--ng-color-panel);

  /* accent -- now the brand key in dark mode (was accentSystem/teal). accentSystem
     remains available via var(--ng-role-accentSystem) for anything not brand-rationed. */
  --ng-ui-accent: var(--ng-brand-key);
  --ng-ui-accent-evidence: var(--ng-role-accentEvidence);

  /* focus ring: two-layer -- inner ring matches the surface behind the element
     (guarantees separation against any adjacent fill), outer ring is accentSystem
     (5.34:1 dark / 6.66:1 light vs bg -- accentEvidence is NOT used here, it fails
     as a thin stroke against light-mode bg at 1.82:1). */
  --ng-ui-focus-ring-color: var(--ng-role-accentSystem);
  --ng-ui-focus-ring-inner: 2px;
  --ng-ui-focus-ring-outer: 2px;

  /* judgment-surface neutral (G08): SELF-AUTHORED 0%-saturation neutral, CIELAB
     L*=20.79, inside the required L*18-25 band. Deliberately NOT Supercent's
     #333333 (this is #323232). SCOPE: media/evidence review canvases, comparison
     views, result-tile mattes ONLY -- never nav, dashboard, or buttons. Any
     brand-colour element keeps >=16px clearance from a surface painted with this
     token. This is the one documented Rule-0 hex exception in this file; see
     neo-product-ui.tokens.dtcg.json color.surroundNeutral for full provenance. */
  --ng-ui-surround-neutral: #323232;
  --ng-ui-thumb-bg: var(--ng-ui-surround-neutral);       /* small-thumbnail corollary, G08 */

  /* overlay scrim -- black-alpha, same overlay technique G04 sanctions for white */
  --ng-ui-overlay-scrim: color-mix(in srgb, black 64%, transparent);

  /* shadows -- dark mode relies primarily on the surface-raised fill shift above;
     shadow is a secondary, subtle cue here. */
  --ng-ui-shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4);
  --ng-ui-shadow-2: 0 8px 24px rgba(0, 0, 0, 0.5);

  /* status axis (G12) -- canon aliases, closed 5-tone set, never extended */
  --ng-ui-status-pass-bg: var(--ng-role-statePassBg);
  --ng-ui-status-pass-fg: var(--ng-role-statePassFg);
  --ng-ui-status-repair-bg: var(--ng-role-stateRepairBg);
  --ng-ui-status-repair-fg: var(--ng-role-stateRepairFg);
  --ng-ui-status-human-review-bg: var(--ng-role-stateHumanReviewBg);
  --ng-ui-status-human-review-fg: var(--ng-role-stateHumanReviewFg);
  --ng-ui-status-unknown-bg: var(--ng-role-stateUnknownBg);
  --ng-ui-status-unknown-fg: var(--ng-role-stateUnknownFg);
  --ng-ui-status-unverified-bg: var(--ng-role-stateUnverifiedBg);
  --ng-ui-status-unverified-fg: var(--ng-role-stateUnverifiedFg);

  /* radius -- component-bound (G10), canon aliases only */
  --ng-ui-radius-table-cell: var(--ng-radius-sm);
  --ng-ui-radius-card: var(--ng-radius-md);
  --ng-ui-radius-panel: var(--ng-radius-md);
  --ng-ui-radius-cta: var(--ng-radius-md);     /* NOT a pill -- see doctrine cta_shape_decision */
  --ng-ui-radius-row-action: var(--ng-radius-sm);
  --ng-ui-radius-chip: var(--ng-radius-sm);
  --ng-ui-radius-modal: var(--ng-radius-md);

  /* spacing -- canon aliases + extension steps (G09, report-mode only) */
  --ng-ui-space-4: var(--ng-spacing-xs);
  --ng-ui-space-8: var(--ng-spacing-sm);
  --ng-ui-space-12: 12px;
  --ng-ui-space-16: var(--ng-spacing-md);
  --ng-ui-space-24: var(--ng-spacing-lg);
  --ng-ui-space-32: var(--ng-spacing-xl);
  --ng-ui-space-40: 40px;
  --ng-ui-space-48: 48px;
  --ng-ui-space-64: 64px;

  /* z-index (G-adjacent layering scale, not color-bearing) */
  --ng-ui-z-base: 0;
  --ng-ui-z-sticky: 10;
  --ng-ui-z-dropdown: 20;
  --ng-ui-z-overlay-backdrop: 30;
  --ng-ui-z-modal: 40;
  --ng-ui-z-toast: 50;
  --ng-ui-z-tooltip: 60;

  /* motion (G06) -- 5-tier distance-proportional duration scale */
  --ng-motion-instant: 100ms;
  --ng-motion-fast: 150ms;
  --ng-motion-base: 200ms;
  --ng-motion-moderate: 300ms;
  --ng-motion-slow: 450ms;
  --ng-motion-exit-ratio: 0.66;
  --ng-ease-enter: cubic-bezier(0, 0, 0.2, 1);   /* decelerate */
  --ng-ease-exit: cubic-bezier(0.4, 0, 1, 1);    /* accelerate */

  /* feedback thresholds (G07) -- tokens-plus-prose, no component consumes these yet */
  --ng-feedback-instant-ms: 100ms;
  --ng-feedback-loading-from-ms: 300ms;
  --ng-feedback-progress-required-ms: 10000ms;
  --ng-feedback-eta-from-ms: 60000ms;

  /* font (owner decision 2026-09-04 19:3x, verbatim "폰트는 프리텐다드"): Pretendard,
     not canon's Inter (canonRef/--ng-typography-fontFamily-base still says Inter --
     canon dtcg itself is unchanged until l1.hq updates it; this override lives only
     at the product-UI layer). Pretendard is SIL OFL open-source, not a Supercent
     proprietary asset -- adopting it is not an R11/G03 violation. */
  --ng-ui-font-family-base: Pretendard, "Pretendard Variable", -apple-system, BlinkMacSystemFont,
    "Apple SD Gothic Neo", "Malgun Gothic", system-ui, sans-serif;

  /* density (G05) -- "regular" tier active by default; [data-density] switches it */
  --ng-ui-font-body: 400 14px/21px var(--ng-ui-font-family-base);
  --ng-ui-font-label: 600 12px/1.2 var(--ng-ui-font-family-base);
  --ng-ui-font-table: 500 13px/1.4 var(--ng-ui-font-family-base);
  --ng-ui-font-caption: 400 12px/1.35 var(--ng-ui-font-family-base);
  --ng-ui-row-height: 36px;
  --ng-ui-density-gap: var(--ng-spacing-sm);
  --ng-ui-control-h: 36px;   /* accessibility floor -- fixed across every density tier */
}

/* ---------- 1b. Dark, OS-preference opt-in -- ONLY for pages that mark
   themselves with a static data-ng-root attribute on <html>. MUST STAY IN
   SYNC with :root[data-theme="dark"] above (1a) -- same declarations,
   duplicated because CSS cannot share a block across a plain selector and a
   conditional @media selector without a preprocessor. If you change 1a,
   change this too. ---------- */
@media (prefers-color-scheme: dark) {
  html[data-ng-root]:not([data-theme="light"]) {
    color-scheme: dark;

  /* surfaces -- canon aliases, no derivation */
  --ng-ui-bg: var(--ng-role-surface);
  --ng-ui-surface: var(--ng-role-surfacePanel);
  /* elevated fill: white-alpha overlay over dark panel (G04 dark rule). measured #242B2F */
  --ng-ui-surface-raised: color-mix(in srgb, white 6%, var(--ng-role-surfacePanel));
  --ng-ui-surface-raised-2: color-mix(in srgb, white 10%, var(--ng-role-surfacePanel));

  /* text ladder */
  --ng-ui-text: var(--ng-role-onSurface);               /* 17.35:1 vs bg -- exceeds 11:1 G13 target */
  --ng-ui-text-weak: var(--ng-role-onSurfaceMuted);      /* 7.12:1 vs bg -- exceeds 6:1 G13 target */
  /* faint tier: 80% onSurfaceMuted / 20% surface. measured #828586 @4.98:1.
     correction history (G13): a 60%/40% mix was tried first and measured
     3.01:1 dark / 2.29:1 light -- light dropped below the 3:1 non-text
     floor, so the ratio was raised to 80/20 here. do not revert to 60/40. */
  --ng-ui-text-faint: color-mix(in srgb, var(--ng-role-onSurfaceMuted) 80%, var(--ng-role-surface));

  /* border ladder */
  --ng-ui-border: var(--ng-role-border);                 /* 1.69:1 -- decorative hairline only, not a required UI boundary */
  /* required-boundary tier (WCAG 1.4.11, >=3:1): 25% onSurface / border. measured #696C6D @3.49:1 */
  --ng-ui-border-strong: color-mix(in srgb, var(--ng-role-onSurface) 25%, var(--ng-role-border));

  /* brand identity "네오 라임" (Neo Lime), owner decision 2026-09-04 20:0x candidate G.
     RATIONED (see neo-product-ui.tokens.dtcg.json brand.usage) -- topbar line, mark,
     one hero keyword via text-clip, primary CTA fill, PASS dot, focus ring ONLY.
     Never a background/large fill, never body text, never >1 per section (R9). */
  --ng-brand-key: #19E3B1;                                /* 11.15:1 vs dark bg -- fine as text/icon here */
  --ng-brand-gradient: linear-gradient(135deg, #C6FF3D 0%, #19E3B1 50%, #2F6BFF 100%);
  /* semantic alias so component rules (e.g. .ngui-btn--primary) never reference
     --ng-color-* directly (R2) -- canon.color.panel is #FFFFFF in both themes. */
  --ng-ui-on-brand-gradient: var(--ng-color-panel);

  /* accent -- now the brand key in dark mode (was accentSystem/teal). accentSystem
     remains available via var(--ng-role-accentSystem) for anything not brand-rationed. */
  --ng-ui-accent: var(--ng-brand-key);
  --ng-ui-accent-evidence: var(--ng-role-accentEvidence);

  /* focus ring: two-layer -- inner ring matches the surface behind the element
     (guarantees separation against any adjacent fill), outer ring is accentSystem
     (5.34:1 dark / 6.66:1 light vs bg -- accentEvidence is NOT used here, it fails
     as a thin stroke against light-mode bg at 1.82:1). */
  --ng-ui-focus-ring-color: var(--ng-role-accentSystem);
  --ng-ui-focus-ring-inner: 2px;
  --ng-ui-focus-ring-outer: 2px;

  /* judgment-surface neutral (G08): SELF-AUTHORED 0%-saturation neutral, CIELAB
     L*=20.79, inside the required L*18-25 band. Deliberately NOT Supercent's
     #333333 (this is #323232). SCOPE: media/evidence review canvases, comparison
     views, result-tile mattes ONLY -- never nav, dashboard, or buttons. Any
     brand-colour element keeps >=16px clearance from a surface painted with this
     token. This is the one documented Rule-0 hex exception in this file; see
     neo-product-ui.tokens.dtcg.json color.surroundNeutral for full provenance. */
  --ng-ui-surround-neutral: #323232;
  --ng-ui-thumb-bg: var(--ng-ui-surround-neutral);       /* small-thumbnail corollary, G08 */

  /* overlay scrim -- black-alpha, same overlay technique G04 sanctions for white */
  --ng-ui-overlay-scrim: color-mix(in srgb, black 64%, transparent);

  /* shadows -- dark mode relies primarily on the surface-raised fill shift above;
     shadow is a secondary, subtle cue here. */
  --ng-ui-shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4);
  --ng-ui-shadow-2: 0 8px 24px rgba(0, 0, 0, 0.5);

  /* status axis (G12) -- canon aliases, closed 5-tone set, never extended */
  --ng-ui-status-pass-bg: var(--ng-role-statePassBg);
  --ng-ui-status-pass-fg: var(--ng-role-statePassFg);
  --ng-ui-status-repair-bg: var(--ng-role-stateRepairBg);
  --ng-ui-status-repair-fg: var(--ng-role-stateRepairFg);
  --ng-ui-status-human-review-bg: var(--ng-role-stateHumanReviewBg);
  --ng-ui-status-human-review-fg: var(--ng-role-stateHumanReviewFg);
  --ng-ui-status-unknown-bg: var(--ng-role-stateUnknownBg);
  --ng-ui-status-unknown-fg: var(--ng-role-stateUnknownFg);
  --ng-ui-status-unverified-bg: var(--ng-role-stateUnverifiedBg);
  --ng-ui-status-unverified-fg: var(--ng-role-stateUnverifiedFg);

  /* radius -- component-bound (G10), canon aliases only */
  --ng-ui-radius-table-cell: var(--ng-radius-sm);
  --ng-ui-radius-card: var(--ng-radius-md);
  --ng-ui-radius-panel: var(--ng-radius-md);
  --ng-ui-radius-cta: var(--ng-radius-md);     /* NOT a pill -- see doctrine cta_shape_decision */
  --ng-ui-radius-row-action: var(--ng-radius-sm);
  --ng-ui-radius-chip: var(--ng-radius-sm);
  --ng-ui-radius-modal: var(--ng-radius-md);

  /* spacing -- canon aliases + extension steps (G09, report-mode only) */
  --ng-ui-space-4: var(--ng-spacing-xs);
  --ng-ui-space-8: var(--ng-spacing-sm);
  --ng-ui-space-12: 12px;
  --ng-ui-space-16: var(--ng-spacing-md);
  --ng-ui-space-24: var(--ng-spacing-lg);
  --ng-ui-space-32: var(--ng-spacing-xl);
  --ng-ui-space-40: 40px;
  --ng-ui-space-48: 48px;
  --ng-ui-space-64: 64px;

  /* z-index (G-adjacent layering scale, not color-bearing) */
  --ng-ui-z-base: 0;
  --ng-ui-z-sticky: 10;
  --ng-ui-z-dropdown: 20;
  --ng-ui-z-overlay-backdrop: 30;
  --ng-ui-z-modal: 40;
  --ng-ui-z-toast: 50;
  --ng-ui-z-tooltip: 60;

  /* motion (G06) -- 5-tier distance-proportional duration scale */
  --ng-motion-instant: 100ms;
  --ng-motion-fast: 150ms;
  --ng-motion-base: 200ms;
  --ng-motion-moderate: 300ms;
  --ng-motion-slow: 450ms;
  --ng-motion-exit-ratio: 0.66;
  --ng-ease-enter: cubic-bezier(0, 0, 0.2, 1);   /* decelerate */
  --ng-ease-exit: cubic-bezier(0.4, 0, 1, 1);    /* accelerate */

  /* feedback thresholds (G07) -- tokens-plus-prose, no component consumes these yet */
  --ng-feedback-instant-ms: 100ms;
  --ng-feedback-loading-from-ms: 300ms;
  --ng-feedback-progress-required-ms: 10000ms;
  --ng-feedback-eta-from-ms: 60000ms;

  /* font (owner decision 2026-09-04 19:3x, verbatim "폰트는 프리텐다드"): Pretendard,
     not canon's Inter (canonRef/--ng-typography-fontFamily-base still says Inter --
     canon dtcg itself is unchanged until l1.hq updates it; this override lives only
     at the product-UI layer). Pretendard is SIL OFL open-source, not a Supercent
     proprietary asset -- adopting it is not an R11/G03 violation. */
  --ng-ui-font-family-base: Pretendard, "Pretendard Variable", -apple-system, BlinkMacSystemFont,
    "Apple SD Gothic Neo", "Malgun Gothic", system-ui, sans-serif;

  /* density (G05) -- "regular" tier active by default; [data-density] switches it */
  --ng-ui-font-body: 400 14px/21px var(--ng-ui-font-family-base);
  --ng-ui-font-label: 600 12px/1.2 var(--ng-ui-font-family-base);
  --ng-ui-font-table: 500 13px/1.4 var(--ng-ui-font-family-base);
  --ng-ui-font-caption: 400 12px/1.35 var(--ng-ui-font-family-base);
  --ng-ui-row-height: 36px;
  --ng-ui-density-gap: var(--ng-spacing-sm);
  --ng-ui-control-h: 36px;   /* accessibility floor -- fixed across every density tier */
  }
}

/* ---------- 2. Density profiles (G05) -- [data-density] switches the whole tier ---------- */
/* control-height stays 36px (or 40px comfortable) across every tier -- an accessibility
   floor, never a density knob. Only type size/line-height/row-height/gap vary. */
[data-density="compact"] {
  --ng-ui-font-body: 400 13px/18px var(--ng-ui-font-family-base);
  --ng-ui-font-label: 600 11px/1.2 var(--ng-ui-font-family-base);
  --ng-ui-font-table: 500 12px/1.3 var(--ng-ui-font-family-base);
  --ng-ui-font-caption: 400 11px/1.3 var(--ng-ui-font-family-base);
  --ng-ui-row-height: 28px;
  --ng-ui-density-gap: var(--ng-spacing-xs);
  --ng-ui-control-h: 36px;
}
[data-density="regular"] {
  --ng-ui-font-body: 400 14px/21px var(--ng-ui-font-family-base);
  --ng-ui-font-label: 600 12px/1.2 var(--ng-ui-font-family-base);
  --ng-ui-font-table: 500 13px/1.4 var(--ng-ui-font-family-base);
  --ng-ui-font-caption: 400 12px/1.35 var(--ng-ui-font-family-base);
  --ng-ui-row-height: 36px;
  --ng-ui-density-gap: var(--ng-spacing-sm);
  --ng-ui-control-h: 36px;
}
[data-density="comfortable"] {
  --ng-ui-font-body: 400 15px/23px var(--ng-ui-font-family-base);
  --ng-ui-font-label: 600 13px/1.3 var(--ng-ui-font-family-base);
  --ng-ui-font-table: 500 14px/1.5 var(--ng-ui-font-family-base);
  --ng-ui-font-caption: 400 12.5px/1.4 var(--ng-ui-font-family-base);
  --ng-ui-row-height: 44px;
  --ng-ui-density-gap: var(--ng-spacing-md);
  --ng-ui-control-h: 40px;
}

/* ---------- 3. Global rules ---------- */
.ngui,
.ngui * {
  box-sizing: border-box;
}
.ngui {
  background: var(--ng-ui-bg);
  color: var(--ng-ui-text);
  font: var(--ng-ui-font-body);
}
.ngui :focus-visible {
  outline: none;
  box-shadow:
    0 0 0 var(--ng-ui-focus-ring-inner) var(--ng-ui-bg),
    0 0 0 calc(var(--ng-ui-focus-ring-inner) + var(--ng-ui-focus-ring-outer)) var(--ng-ui-focus-ring-color);
  border-radius: var(--ng-radius-sm);
}
/* G06: reduced-motion is ONE global token swap. Every duration below becomes 0ms;
   any component using --ng-motion-* or --ng-ease-* inherits compliance automatically. */
@media (prefers-reduced-motion: reduce) {
  /* motion tokens are theme-independent, so this must match every selector that
     can declare them (bare :root/light-default, explicit dark opt-in, AND the
     data-ng-root OS-preference opt-in) at EQUAL specificity to each, so the
     cascade tie is broken by this rule's later source position -- matches the
     THEME CONTRACT selector set exactly (see file header). */
  :root,
  :root[data-theme="dark"],
  html[data-ng-root]:not([data-theme="light"]) {
    --ng-motion-instant: 0ms;
    --ng-motion-fast: 0ms;
    --ng-motion-base: 0ms;
    --ng-motion-moderate: 0ms;
    --ng-motion-slow: 0ms;
  }
  .ngui *,
  .ngui *::before,
  .ngui *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* ---------- 4. Panel / card ---------- */
.ngui-panel {
  background: var(--ng-ui-surface);
  border: 1px solid var(--ng-ui-border);
  border-radius: var(--ng-ui-radius-panel);
  box-shadow: var(--ng-ui-shadow-1);
  padding: var(--ng-ui-space-24);
}
.ngui-panel--raised {
  background: var(--ng-ui-surface-raised);
  box-shadow: var(--ng-ui-shadow-2);
}

/* ---------- 5. Status dot / chip (G12) -- closed 5-tone axis, no sixth tone,
   label always carries domain detail, chip text stays neutral (never tinted). ---------- */
.ng-status-dot {
  display: inline-block;
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
}
/* PASS dot uses the brand key (owner decision 2026-09-04 20:0x, 네오 라임) instead of
   canon's statePassBg -- the one deliberate brand-rationed exception inside the
   otherwise-closed G12 status axis; the other four dots stay on canon state colors. */
.ng-status-dot--pass { background: var(--ng-ui-accent); }
.ng-status-dot--repair { background: var(--ng-ui-status-repair-bg); }
.ng-status-dot--human-review { background: var(--ng-ui-status-human-review-bg); }
.ng-status-dot--unknown { background: var(--ng-ui-status-unknown-bg); }
.ng-status-dot--unverified { background: var(--ng-ui-status-unverified-bg); }

.ng-status-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--ng-ui-space-8);
  border: 1px solid var(--ng-ui-border);
  border-radius: var(--ng-ui-radius-chip);
  padding: 4px var(--ng-ui-space-12);
  font: var(--ng-ui-font-label);
  /* text is ALWAYS text-weak, neutral -- never tinted with the status color.
     the dot inside carries the color; the label text carries the domain detail. */
  color: var(--ng-ui-text-weak);
  background: var(--ng-ui-surface);
}

/* ---------- 6. Buttons -- CTA shape is radius-cta (8px), never a pill (G10) ---------- */
.ngui-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--ng-ui-space-8);
  min-height: var(--ng-ui-control-h);
  padding: 0 var(--ng-ui-space-16);
  border: 1px solid var(--ng-ui-border-strong);
  border-radius: var(--ng-ui-radius-cta);
  font: var(--ng-ui-font-label);
  background: transparent;
  color: var(--ng-ui-text);
  cursor: pointer;
  transition: background-color var(--ng-motion-fast) var(--ng-ease-enter),
              border-color var(--ng-motion-fast) var(--ng-ease-enter);
}
/* Primary CTA = brand gradient pill, owner decision 2026-09-04 20:0x (네오 라임).
   This SUPERSEDES G10's original CTA-shape decision (radius-cta/8px, explicitly
   "not a pill") FOR THE PRIMARY CTA ONLY -- see neo-product-ui.tokens.dtcg.json
   $extensions.app.neogenesis.cta_shape_decision for the full override note.
   Secondary/other buttons keep radius-cta (8px) via the base .ngui-btn rule above;
   only .ngui-btn--primary becomes a full pill. Rationed per brand.usage: at most
   one gradient-bearing element (this button) per section (R9). */
.ngui-btn--primary {
  border-color: transparent;
  border-radius: 999px;
  background: var(--ng-brand-gradient);
  /* "white text" per the owner spec -- --ng-ui-on-brand-gradient aliases canon's
     color.panel (#FFFFFF in both themes) so this rule still only ever touches a
     --ng-ui-* semantic token, never --ng-color-* or --ng-role-* directly (R2). */
  color: var(--ng-ui-on-brand-gradient);
}
.ngui-btn--secondary {
  background: var(--ng-ui-surface);
}

/* ---------- 7. Table (density-aware, G05) ---------- */
.ngui-table {
  width: 100%;
  border-collapse: collapse;
  font: var(--ng-ui-font-table);
}
.ngui-table th,
.ngui-table td {
  border-bottom: 1px solid var(--ng-ui-border);
  padding: 0 var(--ng-ui-space-12);
  height: var(--ng-ui-row-height);
  border-radius: var(--ng-ui-radius-table-cell);
  text-align: left;
}
.ngui-table th {
  color: var(--ng-ui-text-weak);
  font: var(--ng-ui-font-caption);
  position: sticky;
  top: 0;
  background: var(--ng-ui-bg);
  z-index: var(--ng-ui-z-sticky);
}

/* ---------- 8. Modal / overlay ---------- */
.ngui-overlay-backdrop {
  position: fixed;
  inset: 0;
  background: var(--ng-ui-overlay-scrim);
  z-index: var(--ng-ui-z-overlay-backdrop);
}
.ngui-modal {
  background: var(--ng-ui-surface-raised);
  border-radius: var(--ng-ui-radius-modal);
  box-shadow: var(--ng-ui-shadow-2);
  z-index: var(--ng-ui-z-modal);
  transition: transform var(--ng-motion-moderate) var(--ng-ease-enter),
              opacity var(--ng-motion-moderate) var(--ng-ease-enter);
}

/* ---------- 9. Judgment surface (G08) -- media/evidence review canvases only.
   Never applied to nav, dashboard, or buttons. Keep any brand-colour element
   >=16px clear of an element carrying this class. ---------- */
.ngui-judgment-surface {
  background: var(--ng-ui-surround-neutral);
}
.ngui-judgment-thumb {
  background: var(--ng-ui-thumb-bg);
  /* thumb-bg equals the surface it sits on by design (G08 -- same neutral
     treatment) so a real placeholder needs a hairline edge to read as a
     distinct tile at all; a real thumbnail image would not need this. */
  border: 1px solid var(--ng-ui-border-strong);
}

/* ---------- 10. Skeleton / staged loading (G07 -- tokens-plus-prose; no hook
   binds this yet, see neo-product-ui.tokens.dtcg.json feedback.$description) ---------- */
.ngui-skeleton {
  background: linear-gradient(
    90deg,
    var(--ng-ui-surface) 25%,
    var(--ng-ui-surface-raised) 37%,
    var(--ng-ui-surface) 63%
  );
  background-size: 400% 100%;
  border-radius: var(--ng-ui-radius-card);
  animation: ngui-skeleton-sweep var(--ng-motion-slow) ease-in-out infinite;
}
@keyframes ngui-skeleton-sweep {
  0% { background-position: 100% 50%; }
  100% { background-position: 0 50%; }
}

/* ---------- 11. Brand identity "네오 라임" (owner decision 2026-09-04 20:0x,
   candidate G) -- topbar line, mark, hero keyword text-clip. These two selectors
   (.ng-topbar, .ng-hero__message) are page chrome defined by the host page (see
   neo-starter.html), not a .ngui-* component; the rules below attach to whatever
   host page uses those class names. RATIONED (brand.usage in the token file):
   each is one of the six allowed brand-bearing surfaces, at most one per section
   (R9). ---------- */
.ng-topbar {
  position: relative; /* safe default; a host page's own sticky/relative positioning
                          (see neo-starter.html) takes precedence via source order */
}
.ng-topbar::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px; /* sits on top of the topbar's own 1px border-bottom */
  height: 3px;
  background: var(--ng-brand-gradient);
  pointer-events: none;
}
.ng-topbar__mark {
  background: var(--ng-brand-gradient); /* brand mark. NOTE: a host page that also
    declares its own .ng-topbar__mark rule (see neo-starter.html) must set the same
    gradient there too -- CSS source order means a later same-specificity host-page
    rule wins over this one, so this base rule alone does not guarantee the mark
    renders correctly if the host page still has an old --ng-ui-accent fill. */
}
.ng-hero__message em {
  font-style: normal; /* emphasis is carried by color, not italics */
  background: var(--ng-brand-gradient);
  background-clip: text;
  -webkit-background-clip: text;
  color: transparent;
  -webkit-text-fill-color: transparent;
}
