/* ! SKILLPRINT STYLESHEET

   Single source of truth for every prototype page.

   ARCHITECTURE - the file reads in layer order, and every section banner is
   numbered with the layer it belongs to. Source order is the cascade, so a
   later layer is expected to win over an earlier one.

     01  Reset, base elements and typography
     02  Design tokens          colour, space, radius, type, motion, aliases
     05  Layout primitives      container, grid, spans
     06  Utilities              effects, structural helpers, shared roles
     07  Shared components      buttons, badges, tags, fields, popups, icons,
                                navigation, code, charts, controls, inputs,
                                editors, dropdowns, avatars, progress,
                                sliders, video, traits
     08  Component variants     kept beside the component they modify, and
                                expressed as component custom properties
                                (--card-current-padding) rather than repeated
                                declaration blocks
     09  Unique compositions    one section per page, only for what genuinely
                                cannot be composed from the layers above
     10  Responsive rules       every breakpoint, grouped one block per query

   Layers 03 (base elements) and 04 (typography) live inside 01; there is no
   layer 11 (rare overrides) - nothing currently needs one.

   States use a single convention: is-active, is-open, is-selected,
   is-disabled, is-hidden, is-visible, is-current, plus aria-* where the
   attribute is the source of truth.

   Before adding a rule, reuse a shared class: sp-container, sp-grid /
   sp-span-*, sp-card / sp-panel, sp-nav__*, sp-side-nav__*, ui-badge, ui-tag,
   button, field, code-block, popup.
*/

/* ! SKILLPRINT FOUNDATION PRIMITIVES

   Extracted from skillprint(2).css and organised to mirror the
   design-system foundation structure in index(1).html.

   Includes only fonts and foundation-level tokens.
   Page-composition and code tokens are intentionally excluded.
   Approved base components, surfaces and navigation are included.
*/

/* ============================================================================
   02 · Design tokens · font files
   ========================================================================== */

@font-face {
  font-family: "Geist Local";
  src: url("../assets/fonts/geist/Geist-Variable.woff2") format("woff2"),
       url("../assets/fonts/geist/Geist-Variable.ttf") format("truetype");
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
}

@font-face {
  font-family: "Geist Local";
  src: url("../assets/fonts/geist/Geist-VariableItalic.woff2") format("woff2"),
       url("../assets/fonts/geist/Geist-VariableItalic.ttf") format("truetype");
  font-style: italic;
  font-weight: 100 900;
  font-display: swap;
}

@font-face {
  font-family: "Geist Mono Local";
  src: url("../assets/fonts/geist-mono/GeistMono-Variable.woff2") format("woff2"),
       url("../assets/fonts/geist-mono/GeistMono-Variable.ttf") format("truetype");
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
}

@font-face {
  font-family: "Geist Mono Local";
  src: url("../assets/fonts/geist-mono/GeistMono-VariableItalic.woff2") format("woff2"),
       url("../assets/fonts/geist-mono/GeistMono-VariableItalic.ttf") format("truetype");
  font-style: italic;
  font-weight: 100 900;
  font-display: swap;
}

@font-face {
  font-family: "Recoleta Local";
  src: url("../assets/fonts/recoleta/Recoleta-Regular.woff2") format("woff2"),
       url("../assets/fonts/recoleta/Recoleta-Regular.otf") format("opentype");
  font-style: normal;
  font-weight: var(--weight-regular);
  font-display: swap;
}

:root {
  /* --------------------------------------------------------------------------
     01 · TYPOGRAPHY
     -------------------------------------------------------------------------- */
  --skillprint-font-ui     : "Geist Local", "Geist", system-ui, sans-serif;
  --skillprint-font-mono   : "Geist Mono Local", "Geist Mono", ui-monospace, monospace;
  --skillprint-font-display: "Recoleta Local", Georgia, serif;

  --text-xxs-font-size      : 10px;
  --text-xxs-line-height    : 16px;
  --text-xs-font-size       : 12px;
  --text-xs-line-height     : 18px;
  --text-sm-font-size       : 14px;
  --text-sm-line-height     : 20px;
  --text-md-font-size       : 16px;
  --text-md-line-height     : 24px;
  --text-lg-font-size       : 18px;
  --text-lg-line-height     : 28px;
  --text-xl-font-size       : 20px;
  --text-xl-line-height     : 30px;
  --display-xs-font-size    : 24px;
  --display-xs-line-height  : 32px;
  --display-sm-font-size    : 30px;
  --display-sm-line-height  : 38px;
  --display-md-font-size    : 36px;
  --display-md-line-height  : 44px;
  --display-lg-font-size    : 48px;
  --display-lg-line-height  : 60px;
  --display-xl-font-size    : 60px;
  --display-xl-line-height  : 72px;
  --display-2xl-font-size   : 72px;
  --display-2xl-line-height : 90px;

  --weight-extralight      : 200;
  --weight-regular         : 400;
  --weight-medium          : 500;
  --weight-semibold        : 600;
  --weight-bold            : 700;

  --font-style-normal: normal;
  --font-style-italic: italic;

  /* --------------------------------------------------------------------------
     01a · TYPOGRAPHY · APP SCALE

     Product surfaces - the portal and anything app-shaped - use the same
     token names with `app-` in front, sized to what the product actually
     ships. The text tier matches the base ramp and the portal exactly:
     h1 = app-display-xs 24, h2 = app-text-lg 18, h3 = app-text-md 16,
     body = app-text-sm 14, labels = app-text-xs 12. The display tier is
     compressed: the portal builds nothing above 24, so display-sm and up
     take the size that fits an app - the base ramp's MOBILE tier
     (the even 4px ladder 24-28-32-36-40-44) on desktop and tablet, where
     there is room for it. The whole display tier steps down again at
     <=767px: measured on a 375px screen, 44px wraps a three-word phrase
     like "Session complete" onto two lines, so the practical ceiling is
     ~40px. Mobile values reuse pairs already present in the system, so
     nothing new is introduced; app-display-xs takes the text-xl pair,
     matching what the portal already does. The text tier never moves at
     any width. App headings are semibold at every level, with neutral
     tracking (0 for headings, -0.01em for the page title).
     -------------------------------------------------------------------------- */
  --app-text-xxs-font-size    : 10px;
  --app-text-xxs-line-height  : 16px;
  --app-text-xs-font-size     : 12px;
  --app-text-xs-line-height   : 18px;
  --app-text-sm-font-size     : 14px;
  --app-text-sm-line-height   : 20px;
  --app-text-md-font-size     : 16px;
  --app-text-md-line-height   : 24px;
  --app-text-lg-font-size     : 18px;
  --app-text-lg-line-height   : 28px;
  --app-text-xl-font-size     : 20px;
  --app-text-xl-line-height   : 30px;
  --app-display-xs-font-size  : 24px;
  --app-display-xs-line-height: 32px;
  --app-display-sm-font-size  : 28px;
  --app-display-sm-line-height: 36px;
  --app-display-md-font-size  : 32px;
  --app-display-md-line-height: 40px;
  --app-display-lg-font-size  : 36px;
  --app-display-lg-line-height: 44px;
  --app-display-xl-font-size  : 40px;
  --app-display-xl-line-height: 48px;
  --app-display-2xl-font-size : 44px;
  --app-display-2xl-line-height: 52px;

  --app-heading-weight        : var(--weight-semibold);
  --app-heading-tracking      : 0;
  --app-title-tracking        : -0.01em;

  /* ========================================================================
     02 · Design tokens · colour, space, radius, type, motion
     Neutral foundations followed by the canonical brand spectrum.
     ====================================================================== */

  /* --------------------------------------------------------------------------
     Deep Navy
     -------------------------------------------------------------------------- */
  --deep-navy-25 : #f8f9fc;
  --deep-navy-50 : #f0f2f8;
  --deep-navy-100: #e1e5f0;
  --deep-navy-200: #c5cbdb;
  --deep-navy-300: #a1aac1;
  --deep-navy-400: #7c87a4;
  --deep-navy-500: #5b6788;
  --deep-navy-600: #414d70;
  --deep-navy-700: #2e395b;
  --deep-navy-800: #1d2646;
  --deep-navy-900: #090d27;
  --deep-navy-950: #040616;

  /* --------------------------------------------------------------------------
     Panel Navy
     -------------------------------------------------------------------------- */
  --panel-navy-25 : #f7f7fb;
  --panel-navy-50 : #eceef5;
  --panel-navy-100: #d9dcea;
  --panel-navy-200: #bcc0d5;
  --panel-navy-300: #9da3c0;
  --panel-navy-400: #7c83a7;
  --panel-navy-500: #5f678d;
  --panel-navy-600: #4a527a;
  --panel-navy-700: #39416a;
  --panel-navy-800: #2b3159;
  --panel-navy-900: #212549;
  --panel-navy-950: #171a38;

  /* --------------------------------------------------------------------------
     Geistface Grey
     -------------------------------------------------------------------------- */
  --geistface-grey-25 : #fcfcfd;
  --geistface-grey-50 : #f7f8fa;
  --geistface-grey-100: #eceef2;
  --geistface-grey-200: #e3e5eb;
  --geistface-grey-300: #c9cdd6;
  --geistface-grey-400: #aab0bd;
  --geistface-grey-500: #8f96a7;
  --geistface-grey-600: #747c90;
  --geistface-grey-700: #5c647b;
  --geistface-grey-800: #444b61;
  --geistface-grey-900: #30364b;
  --geistface-grey-950: #1d2236;

  /* --------------------------------------------------------------------------
     Core Lime
     -------------------------------------------------------------------------- */
  --core-lime-25 : #f6faf3;
  --core-lime-50 : #f4f9ea;
  --core-lime-100: #eff8d7;
  --core-lime-200: #e8f6b7;
  --core-lime-300: #def48e;
  --core-lime-400: #d0f156;
  --core-lime-500: #c1ee16;
  --core-lime-600: #a7ce18;
  --core-lime-700: #8daf1b;
  --core-lime-800: #708b1d;
  --core-lime-900: #4f6221;
  --core-lime-950: #313e23;

  /* --------------------------------------------------------------------------
     Core Green
     -------------------------------------------------------------------------- */
  --core-green-25 : #eef9f8;
  --core-green-50 : #e5f8f3;
  --core-green-100: #d2f6eb;
  --core-green-200: #b0f2db;
  --core-green-300: #85eec8;
  --core-green-400: #4ce7ae;
  --core-green-500: #09e08f;
  --core-green-600: #09c280;
  --core-green-700: #09a572;
  --core-green-800: #098361;
  --core-green-900: #095d4f;
  --core-green-950: #093b3e;

  /* --------------------------------------------------------------------------
     Personality Mint
     -------------------------------------------------------------------------- */
  --personality-mint-25 : #f2fafb;
  --personality-mint-50 : #ebfafa;
  --personality-mint-100: #dffaf8;
  --personality-mint-200: #c9fbf4;
  --personality-mint-300: #adfbef;
  --personality-mint-400: #87fce9;
  --personality-mint-500: #5bfde1;
  --personality-mint-600: #50dbc7;
  --personality-mint-700: #44baad;
  --personality-mint-800: #37938f;
  --personality-mint-900: #28686e;
  --personality-mint-950: #1b4250;

  /* --------------------------------------------------------------------------
     Personality Blue
     -------------------------------------------------------------------------- */
  --personality-blue-25 : #f0f7fc;
  --personality-blue-50 : #e7f5fc;
  --personality-blue-100: #d7f0fc;
  --personality-blue-200: #b9e7fd;
  --personality-blue-300: #94dbfd;
  --personality-blue-400: #62ccfe;
  --personality-blue-500: #27baff;
  --personality-blue-600: #23a2e1;
  --personality-blue-700: #1f8ac3;
  --personality-blue-800: #1a6ea0;
  --personality-blue-900: #144f79;
  --personality-blue-950: #103357;

  /* --------------------------------------------------------------------------
     Mindset Violet
     -------------------------------------------------------------------------- */
  --mindset-violet-25 : #f2f2fb;
  --mindset-violet-50 : #ebebfb;
  --mindset-violet-100: #dedcf9;
  --mindset-violet-200: #c8c2f7;
  --mindset-violet-300: #aba0f4;
  --mindset-violet-400: #8473f0;
  --mindset-violet-500: #573eeb;
  --mindset-violet-600: #4c37d0;
  --mindset-violet-700: #4130b4;
  --mindset-violet-800: #352895;
  --mindset-violet-900: #272071;
  --mindset-violet-950: #1a1852;

  /* --------------------------------------------------------------------------
     Mindset Magenta
     -------------------------------------------------------------------------- */
  --mindset-magenta-25 : #f5f3fa;
  --mindset-magenta-50 : #f2ecf9;
  --mindset-magenta-100: #ecdef6;
  --mindset-magenta-200: #e2c6f0;
  --mindset-magenta-300: #d4a7e9;
  --mindset-magenta-400: #c37de0;
  --mindset-magenta-500: #ae4dd5;
  --mindset-magenta-600: #9744bd;
  --mindset-magenta-700: #803ba4;
  --mindset-magenta-800: #653188;
  --mindset-magenta-900: #482569;
  --mindset-magenta-950: #2d1b4d;

  /* --------------------------------------------------------------------------
     Skills Pink
     -------------------------------------------------------------------------- */
  --skills-pink-25 : #f8f3f8;
  --skills-pink-50 : #f9ecf3;
  --skills-pink-100: #f9dfea;
  --skills-pink-200: #fac7db;
  --skills-pink-300: #fba9c7;
  --skills-pink-400: #fd80ac;
  --skills-pink-500: #ff518d;
  --skills-pink-600: #dd477f;
  --skills-pink-700: #ba3e70;
  --skills-pink-800: #933360;
  --skills-pink-900: #66274e;
  --skills-pink-950: #3f1c3d;

  /* --------------------------------------------------------------------------
     Skills Orange
     -------------------------------------------------------------------------- */
  --skills-orange-25 : #f8f7f3;
  --skills-orange-50 : #f9f4ea;
  --skills-orange-100: #f9eed8;
  --skills-orange-200: #fae4b8;
  --skills-orange-300: #fbd790;
  --skills-orange-400: #fdc65a;
  --skills-orange-500: #ffb21b;
  --skills-orange-600: #dd9b1d;
  --skills-orange-700: #ba841e;
  --skills-orange-800: #936920;
  --skills-orange-900: #664c22;
  --skills-orange-950: #3f3124;

  /* --------------------------------------------------------------------------
     03 · SPACING PRIMITIVES
     -------------------------------------------------------------------------- */
  --space-0   : 0px;
  --space-2   : 2px;
--space-4   : 4px;
--space-6   : 6px;
--space-8   : 8px;
--space-10: 10px;
--space-12  : 12px;
--space-14: 14px;
  --space-16  : 16px;
  --space-18: 18px;
--space-20  : 20px;
  --space-22: 22px;
  --space-24  : 24px;
  --space-28: 28px;
  --space-30: 30px;
  --space-32  : 32px;
  --space-36  : 36px;
  --space-40  : 40px;
  --space-42  : 42px;
  --space-44: 44px;
  --space-48  : 48px;
  --space-64  : 64px;
  /* The section-rhythm steps. All multiples of 8, all already used by the
     compositions, and absent from the scale only because they were written as
     literals - which is what "one scale, no arbitrary values" is meant to stop.
     Naming them changes no rendering; it just gives those sizes a token. */
  --space-72  : 72px;
  --space-80  : 80px;
  --space-88  : 88px;
  --space-96  : 96px;
  --space-104 : 104px;
  --space-112 : 112px;
  --space-120 : 120px;
  --space-128 : 128px;
  --space-144 : 144px;
  --space-160 : 160px;
  --space-192 : 192px;
  --space-224 : 224px;
  --space-256 : 256px;
  --space-320 : 320px;
  --space-384 : 384px;
  --space-480 : 480px;
  --space-560 : 560px;
  --space-640 : 640px;
  --space-720 : 720px;
  --space-768 : 768px;
  --space-1024: 1024px;
  --space-1280: 1280px;
  --space-1440: 1440px;
  --space-1600: 1600px;
  --space-1920: 1920px;

  /* --------------------------------------------------------------------------
     04 · RESPONSIVE GRID
     -------------------------------------------------------------------------- */
  --grid-container-max: 1280px;
  --grid-content-max  : 1216px;
  --grid-readable-max : 720px;
  --grid-columns      : 12;
  --grid-gutter       : 32px;
  --grid-padding      : 32px;
  --grid-gap          : 16px;
  --grid-gap-wide     : 32px;

  /* --------------------------------------------------------------------------
     05 · BORDER COLOURS
     -------------------------------------------------------------------------- */
  --border-subtle          : color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
  --border-strong          : color-mix(in srgb, var(--deep-navy-25) 18%, transparent);
  --skillprint-border-hover: color-mix(in srgb, var(--deep-navy-25) 20%, transparent);

  /* --------------------------------------------------------------------------
     06 · RADIUS PRIMITIVES
     -------------------------------------------------------------------------- */
  --radius-none: 0px;
  --radius-xxs : 2px;
  --radius-xs  : 4px;
  --radius-sm  : 6px;
  --radius-md  : 8px;
  --radius-lg  : 10px;
  --radius-xl  : 12px;
  --radius-2xl : 16px;
  --radius-3xl : 20px;
  --radius-4xl : 24px;
  --radius-full: 9999px;

  /* --------------------------------------------------------------------------
     07 · SHADOWS
     -------------------------------------------------------------------------- */
  --shadow-xs: 0 1px 2px color-mix(in srgb, var(--deep-navy-950) 18%, transparent);
  --shadow-sm: 0 4px 12px color-mix(in srgb, var(--deep-navy-950) 16%, transparent);
  --shadow-md: 0 12px 32px color-mix(in srgb, var(--deep-navy-950) 20%, transparent);
  --shadow-xl: 0 24px 70px color-mix(in srgb, var(--deep-navy-950) 24%, transparent);

  /* --------------------------------------------------------------------------
     08 · BACKDROP BLUR AND GLASS
     -------------------------------------------------------------------------- */
  --backdrop-blur-sm          : 4px;
  --backdrop-blur-md          : 8px;
  --backdrop-blur-lg          : 12px;
  --backdrop-blur-xl          : 20px;
  --surface-glass-light       : color-mix(in srgb, var(--geistface-grey-25) 60%, transparent);
  --glass-surface-brand       : color-mix(in srgb, var(--deep-navy-900) 82%, transparent);
  --glass-surface-brand-strong: color-mix(in srgb, var(--deep-navy-900) 88%, transparent);

  /* --------------------------------------------------------------------------
     09 · FOCUS
     -------------------------------------------------------------------------- */
  --focus-ring-width : 2px;
  --focus-ring-offset: 4px;
  --focus-default    : var(--mindset-violet-400);
  --focus-soft       : var(--mindset-violet-200);
  --focus-danger     : var(--skills-pink-500);
  --focus-inner      : var(--panel-navy-950);

  /* --------------------------------------------------------------------------
     10 · MOTION
     -------------------------------------------------------------------------- */
  --duration-instant      : 80ms;
  --duration-fast         : 150ms;
  --duration-base         : 200ms;
  --duration-slow         : 280ms;
  --duration-slower       : 450ms;
  --duration-slowest      : 650ms;

  --pure-white            : #ffffff;
  --pure-black            : #000000;
  --ease-standard         : cubic-bezier(0.2, 0, 0, 1);
  --transition-interactive: color var(--duration-fast) var(--ease-standard),
    background-color var(--duration-fast) var(--ease-standard),
    border-color var(--duration-fast) var(--ease-standard),
    box-shadow var(--duration-fast) var(--ease-standard),
    transform var(--duration-fast) var(--ease-standard);

  /* --------------------------------------------------------------------------
     11 · CONTROL HEIGHTS
     -------------------------------------------------------------------------- */
  --control-height-sm: var(--space-36);
  --control-height-md: var(--space-42);
  --control-height-lg: var(--space-48);

  /* --------------------------------------------------------------------------
     Icon size and stroke primitives
     -------------------------------------------------------------------------- */
  --icon-size-xs     : 16px;
  --icon-size-sm     : 20px;
  --icon-size-md     : 24px;
  --icon-size-lg     : 32px;
  --icon-size-xl     : 48px;
  --icon-size-2xl    : 64px;
  --icon-size-display: 96px;
  --icon-size-inline : 20px;
  --icon-size-control: 24px;
  --icon-size-feature: 48px;
  --icon-stroke-sm   : 1.5px;
  --icon-stroke-md   : 2px;
  --icon-live-area   : 80%;


  /* --------------------------------------------------------------------------
     12 · BUTTON TOKENS
     -------------------------------------------------------------------------- */
  --button-font            : var(--skillprint-font-ui);
  --button-radius          : var(--radius-md);
  --button-primary         : var(--mindset-violet-500);
  --button-primary-hover   : var(--mindset-violet-600);
  --button-primary-active  : var(--mindset-violet-700);
  --button-primary-icon    : var(--mindset-violet-200);
  --button-focus           : var(--mindset-violet-400);
  --button-focus-inner     : var(--focus-inner);
  --button-text            : var(--geistface-grey-200);
  --button-text-strong     : var(--geistface-grey-25);
  --button-text-muted      : color-mix(in srgb, var(--geistface-grey-200) 72%, transparent);
  --button-text-disabled   : color-mix(in srgb, var(--geistface-grey-200) 42%, transparent);
  --button-surface         : var(--panel-navy-900);
  --button-surface-hover   : var(--panel-navy-800);
  --button-surface-disabled: color-mix(in srgb, var(--panel-navy-900) 62%, transparent);
  --button-border          : var(--border-strong);
  --button-border-disabled : var(--border-subtle);
  --button-danger          : var(--skills-pink-500);
  --button-danger-hover    : var(--skills-pink-600);
  --button-danger-text     : var(--deep-navy-900);
  --button-shadow          :
    0 1px 2px
      color-mix(in srgb, var(--deep-navy-950) 22%, transparent),
    inset 0 0 0 1px
      color-mix(in srgb, var(--geistface-grey-25) 6%, transparent),
    inset 0 -2px 0
      color-mix(in srgb, var(--deep-navy-950) 16%, transparent);


  /* --------------------------------------------------------------------------
     13 · BUTTON GROUP TOKENS
     -------------------------------------------------------------------------- */
  --button-group-surface    : var(--surface-box);
  --button-group-current    : var(--surface-raised);
  --button-group-text       : var(--surface-text);
  --button-group-disabled   : color-mix(in srgb, var(--surface-text) 42%, transparent);
  --button-group-border     : var(--surface-border-hover);
  --button-group-icon       : color-mix(in srgb, var(--surface-text) 62%, transparent);
  --button-group-icon-strong: var(--surface-text);
  --button-group-dot        : var(--core-green-500);
  --button-group-shadow     : var(--button-shadow);


  /* --------------------------------------------------------------------------
     14 · POPUP TOKENS
     -------------------------------------------------------------------------- */
  --popup-backdrop-light:
    color-mix(in srgb, var(--deep-navy-900) 28%, transparent);
  --popup-backdrop-dark:
    color-mix(in srgb, var(--deep-navy-950) 68%, transparent);
  --popup-light:
    color-mix(in srgb, var(--geistface-grey-25) 72%, transparent);
  --popup-dark:
    color-mix(in srgb, var(--deep-navy-900) 82%, transparent);


  /* --------------------------------------------------------------------------
     15 · NAVIGATION TOKENS
     -------------------------------------------------------------------------- */
  --nav-surface:
    color-mix(in srgb, var(--deep-navy-900) 95%, transparent);
  --nav-drawer-surface: var(--panel-navy-950);
  --nav-border: var(--border-subtle);
  --nav-text: var(--geistface-grey-200);
  --nav-muted:
    color-mix(in srgb, var(--geistface-grey-200) 72%, transparent);
  --nav-hover-surface: var(--panel-navy-900);
  --nav-hover-border: var(--border-subtle);
  /* The current route reads in the same violet the primary button is drawn in,
     so "this is the live one" is one colour across the system rather than a
     second accent. It is the button token itself, not a copy of its value, so
     the two can never drift apart. */
  --nav-current: var(--button-primary);
  --nav-current-border:
    color-mix(in srgb, var(--nav-current) 46%, transparent);
  --nav-current-surface:
    color-mix(in srgb, var(--nav-current) 16%, transparent);
  --nav-current-text: var(--geistface-grey-25);
  --nav-notification: var(--skills-pink-500);
  --nav-shadow: var(--shadow-xl);


  /* --------------------------------------------------------------------------
     16 · LEFT NAVIGATION TOKENS
     -------------------------------------------------------------------------- */
  --side-nav-width: var(--space-256);
  --side-nav-surface: var(--panel-navy-950);
  --side-nav-border: var(--border-subtle);
  --side-nav-text: var(--geistface-grey-200);
  --side-nav-muted:
    color-mix(in srgb, var(--geistface-grey-200) 72%, transparent);
  --side-nav-hover: var(--panel-navy-900);
  /* The left nav's current item is the same violet statement as the top bar's,
     drawn from the one nav token so both surfaces move together. */
  --side-nav-active: var(--nav-current-surface);
  --side-nav-active-border: var(--nav-current-border);
  --side-nav-accent: var(--mindset-violet-400);
  --side-nav-search: var(--panel-navy-900);
  --side-nav-scrim:
    color-mix(in srgb, var(--deep-navy-950) 68%, transparent);
  --side-nav-shadow: var(--shadow-xl);


  /* --------------------------------------------------------------------------
     17 · ICON ASSET TOKENS
     -------------------------------------------------------------------------- */
  --icon-foreground-dark : var(--geistface-grey-25);
  --icon-foreground-light: var(--panel-navy-900);
  --icon-foreground      : var(--icon-foreground-dark);


  /* --------------------------------------------------------------------------
     18 · PANEL SURFACE TOKENS
     -------------------------------------------------------------------------- */
  --surface-canvas      : var(--deep-navy-900);
  --surface-panel       : var(--panel-navy-950);
  --surface-box         : var(--panel-navy-900);
  --surface-raised      : var(--panel-navy-800);
  --surface-strong      : var(--panel-navy-700);
  --surface-text        : var(--geistface-grey-200);
  --surface-text-muted  : var(--geistface-grey-400);
  --surface-border      :
    color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
  --surface-border-hover:
    color-mix(in srgb, var(--deep-navy-25) 20%, transparent);


  /* --------------------------------------------------------------------------
     19 · CODE TOKENS
     -------------------------------------------------------------------------- */
  --code-bg           : var(--deep-navy-950);
  --code-surface      : var(--deep-navy-900);
  --code-raised       : var(--panel-navy-950);
  --code-hover        :
    color-mix(
      in srgb,
      var(--deep-navy-900) 82%,
      var(--panel-navy-950)
    );
  --code-border       :
    color-mix(
      in srgb,
      var(--geistface-grey-200) 12%,
      transparent
    );
  --code-border-strong:
    color-mix(
      in srgb,
      var(--geistface-grey-200) 20%,
      transparent
    );
  --code-border-hover :
    color-mix(
      in srgb,
      var(--geistface-grey-200) 36%,
      transparent
    );
  --code-text         : var(--panel-navy-100);
  --code-muted        : var(--panel-navy-400);
  --code-selection    :
    color-mix(
      in srgb,
      var(--mindset-violet-200) 24%,
      transparent
    );

  --syntax-comment    : var(--panel-navy-400);
  --syntax-keyword    : var(--mindset-violet-200);
  --syntax-string     : var(--skills-orange-500);
  --syntax-number     : var(--core-lime-500);
  --syntax-function   : var(--personality-mint-500);
  --syntax-property   : var(--personality-blue-500);
  --syntax-tag        : var(--skills-pink-500);
  --syntax-attribute  : var(--mindset-magenta-500);
  --syntax-constant   : var(--core-green-500);
  --syntax-operator   : var(--panel-navy-100);
  --syntax-punctuation: var(--panel-navy-300);
  --syntax-variable   : var(--geistface-grey-200);
  --syntax-inserted   : var(--core-green-500);
  --syntax-deleted    : var(--skills-pink-500);
}

html[data-surface="light"] {
  --icon-foreground: var(--icon-foreground-light);
  --focus-inner: var(--geistface-grey-25);
  --surface-canvas: var(--geistface-grey-50);
  --surface-panel:
    color-mix(
      in srgb,
      var(--panel-navy-50) 85%,
      var(--geistface-grey-25)
    );
  --surface-box: var(--geistface-grey-25);
  --surface-raised:
    color-mix(
      in srgb,
      var(--panel-navy-50) 75%,
      var(--panel-navy-100)
    );
  --surface-strong:
    color-mix(
      in srgb,
      var(--panel-navy-100) 85%,
      var(--panel-navy-50)
    );
  --surface-text: var(--geistface-grey-900);
  --surface-text-muted: var(--geistface-grey-600);
  --surface-border:
    color-mix(in srgb, var(--deep-navy-900) 10%, transparent);
  --surface-border-hover:
    color-mix(in srgb, var(--deep-navy-900) 18%, transparent);
}

/* ============================================================================
   01 · Reset, base elements and typography
   Applies the box model and type-scale tokens defined above to the
   underlying document. No new tokens are introduced here.
   ========================================================================== */

*,
*::before,
*::after {
  box-sizing: border-box;
}

/* The browser hides [hidden] with display:none, but any author display rule
   outranks it - a component that sets display:grid stays on screen while
   reporting itself hidden. This restores the attribute's meaning. */
.page [hidden] {
  display: none;
}

body {
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-md-font-size);
  line-height: var(--text-md-line-height);
}

/* ============================================================================
   Scrollbars: one component, two variants.

   A fully rounded housing carrying a shorter, fully rounded thumb. The housing
   stays visible past both ends of the thumb, so the control reads as a position
   within the content rather than as a handle that disappears into the edge.

   Geometry, radius, both engines and both axes are all driven by the custom
   properties below, so a surface picks a variant with a class and never writes
   a scrollbar rule of its own:

     (inherited)         violet  - the brand channel. Emphasis.
     .scrollbar-violet   violet  - opts a container back in inside a subtle page.
     .scrollbar-subtle   subtle  - muted grey-purple on an almost invisible track.
     .scrollbar-none     hidden  - no channel at all.

   Vertical and horizontal share every value, so a rail and a panel read as the
   same control turned through ninety degrees.
   ========================================================================== */

/* scrollbar-width does not inherit and scrollbar-color resolves its var()s on
   the element that declares it, so both are set on every element rather than
   on .page alone. Declaring them once at the root looked right on the page's
   own bar and left Firefox drawing the page's colours on every panel inside
   it - which is why components had started writing scrollbar-width themselves. */
.page,
.page * {
  scrollbar-width: thin;
  scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-housing);
}

.page {
  --scrollbar-size: 12px;
  --scrollbar-inset: 3px;
  /* Fully rounded, on the housing and on the thumb, at both ends of both axes.
     Far larger than half the track, so an end can never render square. */
  --scrollbar-radius: var(--radius-full);
}

/* --- variant · violet, the inherited default ------------------------------ */

/* Translucent, because the same channel sits over panels, code blocks and
   popups - an opaque colour would clash with one of them. */
.page,
.page .scrollbar-violet {
  --scrollbar-housing: color-mix(in srgb, var(--mindset-violet-200) 16%, transparent);
  --scrollbar-thumb: color-mix(in srgb, var(--mindset-violet-400) 72%, transparent);
  --scrollbar-thumb-hover: color-mix(in srgb, var(--mindset-violet-400) 88%, transparent);
  --scrollbar-thumb-active: var(--mindset-violet-400);
}

/* --- variant · subtle ----------------------------------------------------- */

/* A muted grey-purple thumb on a track that is barely there. No violet, no
   gradient, no glow: on Portal Home and the product screens the scrollbar is a
   hint about position, not one of the things the page is asking you to look at. */
.page.scrollbar-subtle,
.page .scrollbar-subtle {
  --scrollbar-housing: color-mix(in srgb, var(--panel-navy-200) 7%, transparent);
  --scrollbar-thumb: color-mix(in srgb, var(--panel-navy-400) 62%, transparent);
  --scrollbar-thumb-hover: color-mix(in srgb, var(--panel-navy-300) 74%, transparent);
  --scrollbar-thumb-active: color-mix(in srgb, var(--panel-navy-300) 88%, transparent);
}

/* --- light surfaces ------------------------------------------------------- */

/* Both variants step down the ramp so the channel keeps its weight against
   white. Each selector is repeated with the variant class so a light panel
   embedded in a dark page, and a variant container inside a light page, both
   land on the right ramp rather than inheriting the page's. */
:is(.page.light, html[data-surface="light"] > .page, html[data-theme="light"] > .page),
:is(.page.light, html[data-surface="light"] > .page, html[data-theme="light"] > .page) .scrollbar-violet,
.page :is(.panel-surface-demo--light, .surface-demo--light, .chart-card.light),
.page :is(.panel-surface-demo--light, .surface-demo--light, .chart-card.light) .scrollbar-violet {
  --scrollbar-housing: color-mix(in srgb, var(--mindset-violet-500) 13%, transparent);
  --scrollbar-thumb: color-mix(in srgb, var(--mindset-violet-500) 58%, transparent);
  --scrollbar-thumb-hover: color-mix(in srgb, var(--mindset-violet-500) 78%, transparent);
  --scrollbar-thumb-active: var(--mindset-violet-500);
}

:is(.page.light, html[data-surface="light"] > .page, html[data-theme="light"] > .page).scrollbar-subtle,
:is(.page.light, html[data-surface="light"] > .page, html[data-theme="light"] > .page) .scrollbar-subtle,
.page :is(.panel-surface-demo--light, .surface-demo--light, .chart-card.light).scrollbar-subtle,
.page :is(.panel-surface-demo--light, .surface-demo--light, .chart-card.light) .scrollbar-subtle {
  --scrollbar-housing: color-mix(in srgb, var(--panel-navy-900) 7%, transparent);
  --scrollbar-thumb: color-mix(in srgb, var(--panel-navy-500) 52%, transparent);
  --scrollbar-thumb-hover: color-mix(in srgb, var(--panel-navy-600) 68%, transparent);
  --scrollbar-thumb-active: color-mix(in srgb, var(--panel-navy-700) 82%, transparent);
}

/* --- the control ---------------------------------------------------------- */

.page::-webkit-scrollbar,
.page ::-webkit-scrollbar {
  width: var(--scrollbar-size);
  height: var(--scrollbar-size);
}

/* Chromium on Windows and Linux draws a stepper at each end unless it is asked
   not to. They are square, they cap the rounded housing, and they are why the
   bar read as cut off on those platforms while looking correct on a Mac. */
.page::-webkit-scrollbar-button,
.page ::-webkit-scrollbar-button {
  display: none;
  width: 0;
  height: 0;
}

.page::-webkit-scrollbar-track,
.page ::-webkit-scrollbar-track {
  border-radius: var(--scrollbar-radius);
  background: var(--scrollbar-housing);
}

/* The track piece paints over the track either side of the thumb and is square.
   Leaving it transparent is what lets the rounded housing show at both ends. */
.page::-webkit-scrollbar-track-piece,
.page ::-webkit-scrollbar-track-piece {
  background: transparent;
}

/* A transparent border plus content-box clipping keeps the thumb inside the
   housing, so the housing reads as a channel around it. The border is uniform,
   so the channel is the same 3px on the sides and at both ends. */
.page::-webkit-scrollbar-thumb,
.page ::-webkit-scrollbar-thumb {
  border: var(--scrollbar-inset) solid transparent;
  border-radius: var(--scrollbar-radius);
  background: var(--scrollbar-thumb);
  background-clip: content-box;
}

/* Below roughly two track widths the two rounded ends meet and the thumb stops
   reading as a pill. Very long content is exactly where that happens. */
.page::-webkit-scrollbar-thumb:vertical,
.page ::-webkit-scrollbar-thumb:vertical {
  min-height: calc(var(--scrollbar-size) * 2.5);
}

.page::-webkit-scrollbar-thumb:horizontal,
.page ::-webkit-scrollbar-thumb:horizontal {
  min-width: calc(var(--scrollbar-size) * 2.5);
}

.page::-webkit-scrollbar-thumb:hover,
.page ::-webkit-scrollbar-thumb:hover {
  background: var(--scrollbar-thumb-hover);
  background-clip: content-box;
}

.page::-webkit-scrollbar-thumb:active,
.page ::-webkit-scrollbar-thumb:active {
  background: var(--scrollbar-thumb-active);
  background-clip: content-box;
}

/* Where a vertical and a horizontal bar meet. Left clear so neither housing
   ends against a filled square. */
.page::-webkit-scrollbar-corner,
.page ::-webkit-scrollbar-corner {
  background: transparent;
}

/* --- variant · none ------------------------------------------------------- */

/* For a rail or a deck that carries its own affordance - a snap, a mask, a
   pager - and would only be noisier with a channel under it. */
.page.scrollbar-none,
.page .scrollbar-none {
  scrollbar-width: none;
}

.page.scrollbar-none::-webkit-scrollbar,
.page .scrollbar-none::-webkit-scrollbar {
  display: none;
}

h1 {
  font-family: var(--skillprint-font-ui);
  font-size: var(--display-lg-font-size);
  line-height: var(--display-lg-line-height);
  font-weight: var(--weight-regular);
}

h2 {
  font-family: var(--skillprint-font-ui);
  font-size: var(--display-sm-font-size);
  line-height: var(--display-sm-line-height);
  font-weight: var(--weight-regular);
}

h3 {
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-xl-font-size);
  line-height: var(--text-xl-line-height);
  font-weight: var(--weight-regular);
}

/* ============================================================================
   05 · Layout primitives · grid
   ========================================================================== */

.sp-container {
  width: min(100%, var(--grid-container-max));
  margin-inline: auto;
  padding-inline: var(--grid-padding);
}

.sp-grid {
  display: grid;
  grid-template-columns: repeat(var(--grid-columns), minmax(0, 1fr));
  gap: var(--grid-gutter);
}

.sp-readable {
  max-width: var(--grid-readable-max);
}

.sp-span-12 {
  grid-column: span 12;
}

.sp-span-8 {
  grid-column: span 8;
}

.sp-span-7 {
  grid-column: span 7;
}

.sp-span-6 {
  grid-column: span 6;
}

.sp-span-5 {
  grid-column: span 5;
}

.sp-span-4 {
  grid-column: span 4;
}

.sp-span-3 {
  grid-column: span 3;
}


/* ============================================================================
   06 · Utilities · effects
   ========================================================================== */


.shadow-xs {
  box-shadow: var(--shadow-xs);
}

.shadow-sm {
  box-shadow: var(--shadow-sm);
}

.shadow-md {
  box-shadow: var(--shadow-md);
}

.shadow-xl {
  box-shadow: var(--shadow-xl);
}

.backdrop-blur-sm {
  -webkit-backdrop-filter: blur(var(--backdrop-blur-sm));
  backdrop-filter: blur(var(--backdrop-blur-sm));
}

.backdrop-blur-md {
  -webkit-backdrop-filter: blur(var(--backdrop-blur-md));
  backdrop-filter: blur(var(--backdrop-blur-md));
}

.backdrop-blur-lg {
  -webkit-backdrop-filter: blur(var(--backdrop-blur-lg));
  backdrop-filter: blur(var(--backdrop-blur-lg));
}

.backdrop-blur-xl {
  -webkit-backdrop-filter: blur(var(--backdrop-blur-xl));
  backdrop-filter: blur(var(--backdrop-blur-xl));
}

.sp-glass {
  border: 1px solid
    color-mix(in srgb, var(--geistface-grey-25) 18%, transparent);
  box-shadow:
    inset 0 1px 0
      color-mix(in srgb, var(--geistface-grey-25) 10%, transparent),
    var(--shadow-md);
}

.sp-glass--light {
  background: var(--surface-glass-light);
  color: var(--panel-navy-950);
  border-color:
    color-mix(in srgb, var(--geistface-grey-25) 36%, transparent);
}

.sp-glass--brand {
  background: var(--glass-surface-brand);
  color: var(--geistface-grey-25);
}

/* The focus ring. Every control that re-asserts it at higher specificity
   than the :where() base shares this one declaration. */
:where(a,
button,
input,
select,
textarea,
[tabindex]):focus-visible,
.sp-nav__route:focus-visible,
.sp-side-nav__link:focus-visible,
.code-head button:focus-visible,
:is( .switch input:focus-visible + .switch__track,
.sp-toggle:focus-visible,
.sp-toggle.is-focus ) {
  outline: 0;
  box-shadow:
    0 0 0 var(--focus-ring-width) var(--focus-inner),
    0 0 0 calc(var(--focus-ring-width) * 2) var(--focus-default);
}

:where([aria-invalid="true"], [data-focus-tone="danger"]):focus-visible {
  box-shadow:
    0 0 0 var(--focus-ring-width) var(--focus-inner),
    0 0 0 calc(var(--focus-ring-width) * 2) var(--focus-danger);
}

@supports not (backdrop-filter: blur(1px)) {
  .sp-glass--light {
    background:
      color-mix(in srgb, var(--geistface-grey-25) 94%, transparent);
  }

  .sp-glass--brand {
    background: var(--deep-navy-900);
  }
}


/* ============================================================================
   07 · Shared component · buttons
   ========================================================================== */

.button {
  --button-height        : var(--space-40);
  --button-padding       : var(--space-10) var(--space-14);
  /* 8px, not 4. At 4 the icon and the first letter read as one glyph rather
     than as a mark and a label, which is worst on the buttons that lead with a
     product icon. The gap steps with the button: 6 on xs, 8 here, 10 on lg and
     xl, so the relationship holds at every size. */
  --button-gap           : var(--space-8);
  --button-font-size     : var(--text-sm-font-size);
  --button-line-height   : var(--text-sm-line-height);
  --button-icon-size     : var(--icon-size-sm);
  --button-icon-color    : currentColor;
  --button-current-shadow: none;

  min-height: var(--button-height);
  padding: var(--button-padding);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--button-gap);
  border: 1px solid transparent;
  border-radius: var(--button-radius);
  background: transparent;
  color: inherit;
  box-shadow: var(--button-current-shadow);
  font-family: var(--button-font);
  font-size: var(--button-font-size);
  line-height: var(--button-line-height);
  font-weight: var(--weight-semibold);
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: var(--transition-interactive);
}

.button svg,
.button__icon {
  width: var(--button-icon-size);
  height: var(--button-icon-size);
  flex: 0 0 var(--button-icon-size);
  color: var(--button-icon-color);
}

.button:focus-visible {
  outline: 0;
  box-shadow:
    var(--button-current-shadow),
    0 0 0 var(--focus-ring-width) var(--button-focus-inner),
    0 0 0 calc(var(--focus-ring-width) * 2) var(--button-focus);
}

.button:active:not(:disabled):not([aria-disabled="true"]) {
  transform: translateY(var(--space-2));
}

.button:disabled,
.button[aria-disabled="true"] {
  background: var(--button-surface-disabled);
  border-color: var(--button-border-disabled);
  color: var(--button-text-disabled);
  box-shadow: none;
  cursor: not-allowed;
  pointer-events: none;
}

/* Utility size. Figma draws xs at 28x28 for secondary and tertiary only, where
   it carries an icon rather than a label. */
.button--xs {
  --button-height     : var(--space-28);
  --button-padding    : 0 var(--space-8);
  --button-gap        : var(--space-6);
  --button-font-size  : var(--text-xxs-font-size);
  --button-line-height: var(--text-xxs-line-height);
  --button-icon-size  : var(--icon-size-xs);
}

.button--sm {
  --button-height : var(--space-36);
  --button-padding: var(--space-8) var(--space-12);
}

.button--md {
  --button-height : var(--space-40);
  --button-padding: var(--space-10) var(--space-14);
}

.button--lg {
  --button-height     : var(--space-44);
  --button-padding    : var(--space-10) var(--space-16);
  --button-gap        : var(--space-10);
  --button-font-size  : var(--text-md-font-size);
  --button-line-height: var(--text-md-line-height);
}

.button--xl {
  --button-height     : var(--space-48);
  --button-padding    : var(--space-12) var(--space-18);
  --button-gap        : var(--space-10);
  --button-font-size  : var(--text-md-font-size);
  --button-line-height: var(--text-md-line-height);
}

.button--icon-only {
  width: var(--button-height);
  min-width: var(--button-height);
  padding: 0;
}

.button--primary {
  --button-icon-color    : var(--button-primary-icon);
  --button-current-shadow: var(--button-shadow);
  background: var(--button-primary);
  color: var(--button-text-strong);
}

.button--primary:hover {
  background: var(--button-primary-hover);
}

.button--primary:active {
  background: var(--button-primary-active);
}

.button--secondary {
  --button-current-shadow: var(--button-shadow);
  background: var(--button-surface);
  border-color: var(--button-border);
  color: var(--button-text);
}

.button--secondary:hover {
  background: var(--button-surface-hover);
}

.button--tertiary {
  color: var(--button-text-muted);
}

.button--tertiary:hover {
  background: var(--button-surface-hover);
  color: var(--button-text);
}

.button--link,
.button--link-color,
.button--link-gray {
  --button-height : auto;
  --button-padding: 0;
  min-height: auto;
  border: 0;
  border-radius: var(--radius-xs);
  box-shadow: none;
}

/* The neutral text link: default text colour, no box. */
.button--link {
  color: var(--button-text);
}

.button--link:hover {
  color: var(--button-text-strong);
}

/* A link's colour is text, so it takes the mode-aware accent - not
   --button-focus, which is the focus ring. The two share a hue and nothing
   else: the ring is drawn on top of a surface and stays at the 400 step in
   both modes, while this is read as words and has to follow the ground. On a
   light page --button-focus left link text at violet-400, which is 3.50:1;
   the accent tier resolves to violet-500 there, which is 6.06:1. */
.button--link-color {
  color: var(--violet);
}

.button--link-gray {
  color: var(--button-text-muted);
}

.button--link-color:hover,
.button--link-gray:hover {
  text-decoration: underline;
  text-underline-offset: var(--space-2);
}

/* Destructive link. The danger hierarchy fills a box; combined with a link it
   keeps the colour and drops the box, matching the destructive Link variant. */
.button--link.button--danger,
.button--link-color.button--danger,
.button--link-gray.button--danger {
  background: transparent;
  color: var(--button-danger);
  box-shadow: none;
}

.button--danger {
  --button-current-shadow: var(--button-shadow);
  background: var(--button-danger);
  color: var(--button-danger-text);
}

.button--danger:hover {
  background: var(--button-danger-hover);
  color: var(--button-text-strong);
}

.button--danger:focus-visible {
  box-shadow:
    var(--button-current-shadow),
    0 0 0 var(--focus-ring-width) var(--button-focus-inner),
    0 0 0 calc(var(--focus-ring-width) * 2) var(--button-danger);
}

.button[aria-busy="true"] {
  pointer-events: none;
}

.button[aria-busy="true"]::before {
  content: "";
  width: var(--button-icon-size);
  height: var(--button-icon-size);
  flex: 0 0 var(--button-icon-size);
  border: var(--space-2) solid currentColor;
  border-right-color: transparent;
  border-radius: var(--radius-full);
  animation: button-spin var(--duration-slowest) linear infinite;
}

@keyframes button-spin {
  to {
    transform: rotate(360deg);
  }
}


/* ============================================================================
   07 · Shared component · accordion

   A list of disclosures built on <details>/<summary>, so it opens without any
   script and stays keyboard- and screen-reader-correct by default. The marker
   is suppressed and replaced with a chevron that rotates on open; the shared
   .sp-icon carries the glyph.
   ========================================================================== */

.accordion {
  display: grid;
  border-top: 1px solid var(--surface-border);
}

.accordion__item {
  border-bottom: 1px solid var(--surface-border);
}

.accordion__summary {
  list-style: none;
  width: 100%;
  min-height: var(--space-56);
  padding: var(--space-16) 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-16);
  color: var(--surface-text);
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-md-font-size);
  line-height: var(--text-md-line-height);
  font-weight: var(--weight-semibold);
  text-align: left;
  cursor: pointer;
}

.accordion__summary::-webkit-details-marker { display: none; }

.accordion__summary:hover { color: var(--surface-text-muted); }

.accordion__summary:focus-visible {
  outline: 0;
  box-shadow:
    0 0 0 var(--focus-ring-width) var(--focus-inner),
    0 0 0 calc(var(--focus-ring-width) * 2) var(--focus-default);
  border-radius: var(--radius-xs);
}

.accordion__icon {
  flex: 0 0 auto;
  color: var(--surface-text-muted);
  transition: transform var(--duration-fast) var(--ease-standard);
}

.accordion__item[open] .accordion__icon { transform: rotate(180deg); }

.accordion__panel {
  padding: 0 0 var(--space-20);
  max-width: var(--measure-md);
  color: var(--surface-text-muted);
  font-size: var(--text-md-font-size);
  line-height: var(--text-md-line-height);
}

.accordion__panel > :first-child { margin-top: 0; }
.accordion__panel > :last-child { margin-bottom: 0; }

@media (prefers-reduced-motion: reduce) {
  .accordion__icon { transition: none; }
}


/* ============================================================================
   06b · Shared component · navigation icon button
   ============================================================================

   The one square control every navigation uses for theme, home, settings,
   profile and the rest. It had no component: each page carried its own copy,
   so the same button was a bordered box on the portal and a bare glyph on the
   documentation pages, at three different sizes. Everything that defines it -
   the box, the size, the radius, the icon size, hover, focus and the current
   state - is here, and a page gets it by using the class.

   It sits after .button deliberately. The markup composes the two
   (.icon-button.button.button--tertiary.button--icon-only.button--md) and both
   selectors weigh the same, so the later one has to be this if the border is
   to survive .button--tertiary's borderless treatment. Written to also stand
   alone, since some pages carry a bare .icon-button and it must still be the
   same control.
   ========================================================================== */

.icon-button {
  --icon-button-size: var(--space-40);
  --icon-button-icon: var(--icon-size-sm);

  /* The class is used on a bare <button>, so it carries its own reset: there is
     no global one, and without this the control keeps the platform's chrome. */
  appearance: none;
  font: inherit;
  position: relative;
  width: var(--icon-button-size);
  min-width: var(--icon-button-size);
  height: var(--icon-button-size);
  min-height: var(--icon-button-size);
  padding: 0;
  display: inline-grid;
  place-items: center;
  flex: 0 0 auto;
  border: 1px solid var(--nav-border);
  border-radius: var(--button-radius);
  background: transparent;
  /* The full nav ink, not the muted one. Muted is for a label sitting in a row
     of other text, where the surrounding words carry the line; an icon-only
     control has nothing beside it, so the glyph is the whole message. At
     --nav-muted the mark came out at 3.8:1 on the light surface and read as
     disabled - on the theme toggle it was close to invisible. */
  color: var(--nav-text);
  cursor: pointer;
  transition: var(--transition-interactive);
}

.icon-button:hover {
  border-color: var(--nav-hover-border);
  background: var(--nav-hover-surface);
  color: var(--nav-text);
}

.icon-button:focus-visible {
  outline: 0;
  box-shadow:
    0 0 0 var(--focus-ring-width) var(--button-focus-inner),
    0 0 0 calc(var(--focus-ring-width) * 2) var(--button-focus);
}

/* The same press the rest of the buttons give. */
.icon-button:active:not(:disabled):not([aria-disabled="true"]) {
  transform: translateY(var(--space-2));
}

/* The icon is the whole control, so it never takes the pointer: a click has to
   land on the button itself or the toggle reads the wrong target. */
.icon-button svg,
.icon-button .sp-icon {
  width: var(--icon-button-icon);
  height: var(--icon-button-icon);
  flex: 0 0 var(--icon-button-icon);
  display: block;
  margin: 0;
  color: currentColor;
  pointer-events: none;
}

/* The current destination, in the navigation violet. `.active-app` is the
   portal's name for it and `aria-current` is the accessible one; all three
   name the same state and draw it the same way. */
.icon-button.is-current,
.icon-button.active-app,
.icon-button[aria-current="page"] {
  border-color: var(--nav-current-border);
  background: var(--nav-current-surface);
  color: var(--nav-current-text);
}

.icon-button.is-current:hover,
.icon-button.active-app:hover,
.icon-button[aria-current="page"]:hover {
  border-color: var(--nav-current-border);
  background: color-mix(in srgb, var(--nav-current) 24%, transparent);
  color: var(--nav-current-text);
}

.icon-button--sm {
  --icon-button-size: var(--space-36);
  --icon-button-icon: var(--icon-size-sm);
}

.icon-button--lg {
  --icon-button-size: var(--space-44);
  --icon-button-icon: var(--icon-size-md);
}

.icon-button:disabled,
.icon-button[aria-disabled="true"] {
  color: var(--button-text-disabled);
  cursor: not-allowed;
  pointer-events: none;
}

/* The count that rides on a navigation icon button. */
.icon-button .notification,
.notification {
  position: absolute;
  right: -4px;
  top: -5px;
  min-width: var(--space-18);
  height: var(--space-18);
  padding: 0 var(--space-4);
  display: grid;
  place-items: center;
  border: 2px solid var(--nav-surface);
  border-radius: var(--radius-full);
  background: var(--nav-notification);
  color: var(--geistface-grey-25);
  font: var(--weight-semibold) 9px/1 var(--skillprint-font-mono);
  pointer-events: none;
}


/* ============================================================================
   07 · Shared component · button groups
   ========================================================================== */

.button-group {
  min-height: var(--space-40);
  max-width: 100%;
  display: inline-flex;
  align-items: stretch;
  overflow-x: auto;
  isolation: isolate;
  border: 1px solid var(--button-group-border);
  border-radius: var(--button-radius);
  background: var(--button-group-surface);
  box-shadow: var(--button-group-shadow);
}

.button-group__item {
  position: relative;
  z-index: 0;
  min-width: 62px;
  min-height: 38px;
  padding: var(--space-8) var(--space-16);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  border: 0;
  border-right: 1px solid var(--button-group-border);
  border-radius: 0;
  background: var(--button-group-surface);
  color: var(--button-group-text);
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
  cursor: pointer;
  transition: var(--transition-interactive);
}

.button-group__item:first-child {
  border-radius: var(--radius-md) 0 0 var(--radius-md);
}

.button-group__item:last-child {
  border-right: 0;
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
}

.button-group__item:only-child {
  border-radius: var(--radius-md);
}

.button-group__item:hover,
.button-group__item[aria-pressed="true"],
.button-group__item.is-current {
  background: var(--button-group-current);
}

.button-group__item:focus-visible {
  z-index: 2;
  outline: 0;
  box-shadow:
    0 0 0 var(--focus-ring-width) var(--button-focus-inner),
    0 0 0 calc(var(--focus-ring-width) * 2) var(--button-focus);
}

.button-group__item:disabled,
.button-group__item[aria-disabled="true"] {
  color: var(--button-group-disabled);
  cursor: not-allowed;
  pointer-events: none;
}

.button-group__icon {
  width: var(--icon-size-sm);
  height: var(--icon-size-sm);
  flex: 0 0 var(--icon-size-sm);
  color: var(--button-group-icon);
}

.button-group__item:hover .button-group__icon,
.button-group__item[aria-pressed="true"] .button-group__icon,
.button-group__item.is-current .button-group__icon {
  color: var(--button-group-icon-strong);
}

.button-group__item--icon-only {
  width: var(--space-44);
  min-width: var(--space-44);
  padding: var(--space-8) var(--space-12);
}

.button-group__dot {
  width: var(--space-8);
  height: var(--space-8);
  flex: 0 0 var(--space-8);
  border-radius: var(--radius-full);
  background: var(--button-group-dot);
}


/* ============================================================================
   07 · Shared component · badges
   ========================================================================== */

.ui-badge {
  --badge-accent    : var(--panel-navy-400);
  --badge-bg        :
    color-mix(
      in srgb,
      var(--badge-accent) 13%,
      var(--surface-box)
    );
  --badge-border    :
    color-mix(
      in srgb,
      var(--badge-accent) 38%,
      var(--surface-border)
    );
  --badge-text      : var(--surface-text);
  --badge-height    : var(--space-22);
  --badge-font-size : var(--text-xs-font-size);
  --badge-line-height: var(--text-xs-line-height);

  height: var(--badge-height);
  padding: var(--space-2) var(--space-6);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0;
  border: 1px solid var(--badge-border);
  border-radius: var(--radius-sm);
  background: var(--badge-bg);
  color: var(--badge-text);
  font-family: var(--skillprint-font-ui);
  font-size: var(--badge-font-size);
  line-height: var(--badge-line-height);
  font-weight: var(--weight-medium);
  white-space: nowrap;
}

.ui-badge--pill {
  padding-inline: var(--space-8);
  border-radius: var(--radius-full);
}

.ui-badge--modern {
  background: var(--surface-box);
  border-color: var(--badge-border);
}

.ui-badge--md {
  --badge-height: var(--space-24);
  --badge-font-size: var(--text-sm-font-size);
  --badge-line-height: var(--text-sm-line-height);
  padding-inline: var(--space-8);
}

.ui-badge--lg {
  --badge-height: var(--space-28);
  --badge-font-size: var(--text-sm-font-size);
  --badge-line-height: var(--text-sm-line-height);
  padding: var(--space-4) var(--space-10);
  border-radius: var(--radius-md);
}

.ui-badge--leading {
  gap: var(--space-4);
  padding-inline-start: var(--space-4);
}

.ui-badge--pill.ui-badge--leading {
  padding-inline-start: var(--space-6);
}

.ui-badge__dot {
  width: var(--space-6);
  height: var(--space-6);
  flex: 0 0 var(--space-6);
  border-radius: var(--radius-full);
  background: var(--badge-accent);
}

.ui-badge__icon {
  width: var(--space-14);
  height: var(--space-14);
  fill: none;
  stroke: var(--badge-accent);
  stroke-width: var(--icon-stroke-sm);
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The remaining inner slots named by the badge contract: an avatar, a country
   marker and a trailing action. Each keeps the shell height and only claims the
   leading or trailing edge, so no combination needs a new component. */
.ui-badge__avatar {
  width: var(--space-16);
  height: var(--space-16);
  flex: 0 0 var(--space-16);
  border-radius: var(--radius-full);
  object-fit: cover;
  background: color-mix(in srgb, var(--badge-accent) 24%, transparent);
}

.ui-badge__flag {
  flex: 0 0 auto;
  font-size: var(--text-xs-font-size);
  line-height: 1;
}

.ui-badge__action {
  display: inline-flex;
  align-items: center;
  height: var(--space-14);
  padding-inline: var(--space-4);
  margin-inline-end: calc(var(--space-2) * -1);
  border: 0;
  border-radius: var(--radius-xs);
  background: none;
  color: var(--badge-accent);
  font: inherit;
  font-size: var(--text-xxs-font-size);
  font-weight: var(--weight-semibold);
  cursor: pointer;
}

.ui-badge__action:hover {
  background: color-mix(in srgb, var(--badge-accent) 18%, transparent);
}

.ui-badge.is-selected,
.ui-badge[aria-pressed="true"] {
  --badge-bg: var(--mindset-violet-500);
  --badge-border: var(--mindset-violet-500);
  --badge-text: var(--geistface-grey-25);
  --badge-accent: var(--geistface-grey-25);
}

[data-badge-tone="brand"] {
  --badge-accent: var(--violet);
}

[data-badge-tone="success"] {
  --badge-accent: var(--core-green-500);
}

[data-badge-tone="warning"] {
  --badge-accent: var(--skills-orange-500);
}

[data-badge-tone="error"] {
  --badge-accent: var(--skills-pink-500);
}

[data-badge-tone="mood"] {
  --badge-accent: var(--lime);
}

[data-badge-tone="cognition"] {
  --badge-accent: var(--mint);
}

[data-badge-tone="personality"] {
  --badge-accent: var(--blue);
}

/* ============================================================================
   07 · Shared component · tags
   ========================================================================== */

.ui-tag,
.chart-chip {
  min-height: var(--space-32);
  padding: var(--space-6) var(--space-12);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-full);
  background: var(--surface-panel);
  color:
    color-mix(
      in srgb,
      var(--surface-text) 72%,
      transparent
    );
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
  transition: var(--transition-interactive);
}

button.ui-tag:hover {
  border-color:
    color-mix(
      in srgb,
      var(--surface-text) 28%,
      var(--surface-border)
    );
  color: var(--surface-text);
}

.ui-tag.is-selected,
.ui-tag[aria-pressed="true"],
.chart-chip.is-active {
  background: var(--mindset-violet-500);
  border-color: var(--mindset-violet-500);
  color: var(--geistface-grey-25);
}

/* ============================================================================
   07 · Shared component · fields
   ========================================================================== */

/* The stacked field: label above control. The benchmark filter groups and the
   partner skill summary are the same stack, so they share the base. */
.field,
.page--partner-analysis .skill-summary {
  display: grid;
  gap: var(--space-8);
}

.field label {
  color: var(--surface-text);
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-semibold);
}

.field :is(input, select, textarea) {
  width: 100%;
  min-height: var(--space-44);
  padding: var(--space-10) var(--space-12);
  border: 1px solid var(--surface-border-hover);
  border-radius: var(--radius-md);
  background: var(--surface-box);
  color: var(--surface-text);
  font-family: var(--skillprint-font-ui);
  outline: 0;
  transition: var(--transition-interactive);
}

.field textarea {
  min-height: var(--space-96);
  resize: vertical;
}

.field ::placeholder {
  color: var(--surface-text-muted);
}

.field :is(input, select, textarea):focus-visible {
  border-color: var(--mindset-violet-200);
  box-shadow:
    0 0 0 var(--space-4)
      color-mix(
        in srgb,
        var(--mindset-violet-400) 24%,
        transparent
      );
}

.field [aria-invalid="true"] {
  border-color: var(--skills-pink-500);
}

.field [aria-invalid="true"]:focus-visible {
  box-shadow:
    0 0 0 var(--space-4)
      color-mix(
        in srgb,
        var(--skills-pink-500) 24%,
        transparent
      );
}

.field[data-state="success"] :is(input, select, textarea) {
  border-color: var(--core-green-500);
}

/* Disabled affordance, shared by the field component and the design system's
   is-disabled demo of it. */
.field :disabled,
.page--design-system .field.is-disabled input {
  opacity: 0.55;
  cursor: not-allowed;
}

.field :is(input, select, textarea):hover:not(:disabled):not([readonly]) {
  border-color:
    color-mix(
      in srgb,
      var(--surface-text) 28%,
      transparent
    );
}

.field[data-state="warning"] :is(input, select, textarea) {
  border-color: var(--skills-orange-500);
}

.field :is(input, textarea)[readonly] {
  background: var(--surface-raised);
  color: var(--surface-text-muted);
}

.field[data-size="sm"] :is(input, select) {
  min-height: var(--control-height-sm);
  padding-block: var(--space-8);
  font-size: var(--text-xs-font-size);
}

.field[data-size="lg"] :is(input, select) {
  min-height: var(--control-height-lg);
  padding-inline: var(--space-14);
  font-size: var(--text-md-font-size);
}

.field__control--leading :is(input, select, textarea),
.field__control--both :is(input, select, textarea) {
  padding-inline-start: var(--space-40);
}

.field__control--trailing :is(input, select, textarea),
.field__control--both :is(input, select, textarea) {
  padding-inline-end: var(--space-40);
}

/* ============================================================================
   07 · Shared component · popups
   ========================================================================== */

.popup-backdrop {
  position: fixed;
  inset: 0;
  z-index: 300;
  padding: var(--space-30);
  display: grid;
  place-items: center;
  background: var(--popup-backdrop-dark);
  -webkit-backdrop-filter: blur(var(--backdrop-blur-xl));
  backdrop-filter: blur(var(--backdrop-blur-xl));
}

.popup {
  width: min(var(--space-480), 100%);
  overflow: hidden;
  border-radius: var(--radius-4xl);
  box-shadow: var(--shadow-xl);
  -webkit-backdrop-filter: blur(var(--backdrop-blur-lg));
  backdrop-filter: blur(var(--backdrop-blur-lg));
}

.popup--light {
  color: var(--panel-navy-950);
  background: var(--popup-light);
  border: 1px solid
    color-mix(
      in srgb,
      var(--geistface-grey-25) 52%,
      transparent
    );
}

.popup--dark {
  color: var(--geistface-grey-200);
  background: var(--popup-dark);
  border: 1px solid
    color-mix(
      in srgb,
      var(--deep-navy-25) 18%,
      transparent
    );
}

.popup__close {
  --popup-close-accent: var(--panel-navy-300);

  width: var(--space-28);
  height: var(--space-28);
  padding: 0;
  display: grid;
  place-items: center;
  border: 1px solid
    color-mix(
      in srgb,
      var(--popup-close-accent) 34%,
      var(--border-subtle)
    );
  border-radius: var(--radius-sm);
  background: var(--panel-navy-900);
  color: var(--popup-close-accent);
  cursor: pointer;
  transition: var(--transition-interactive);
}

.popup__close:hover {
  background:
    color-mix(
      in srgb,
      var(--popup-close-accent) 14%,
      var(--panel-navy-900)
    );
}

.popup__close svg {
  width: var(--space-14);
  height: var(--space-14);
  fill: none;
  stroke: currentColor;
  stroke-width: var(--icon-stroke-sm);
  stroke-linecap: round;
  --icon-stroke: var(--icon-stroke-14);
}

.popup__content {
  padding: var(--space-32);
}

.popup__footer {
  padding: var(--space-20)
    var(--space-32)
    var(--space-24);
  display: flex;
  justify-content: flex-end;
  gap: var(--space-12);
  border-top: 1px solid currentColor;
}


/* ============================================================================
   07 · Shared component · icons
   Outline is the default utility style.
   Solid is reserved for selection or emphasis.
   Colour is fixed artwork and is never theme-recoloured.
   ========================================================================== */

.icon-asset {
  width: var(--icon-size, var(--icon-size-md));
  height: var(--icon-size, var(--icon-size-md));
  display: block;
  object-fit: contain;
  color: var(--icon-foreground);
}

.icon-asset--outline,
.icon-asset--solid {
  filter: brightness(0) invert(1);
}

html[data-surface="light"] .icon-asset--outline,
html[data-surface="light"] .icon-asset--solid {
  filter: none;
}

.icon-asset--colour {
  filter: none;
}

/* ============================================================================
   07 · Shared component · navigation
   One production structure for Marketing, Labs, Portal and developer surfaces.
   ========================================================================== */

.sp-nav {
  position: relative;
  z-index: 100;
  border-bottom: 1px solid var(--nav-border);
  background: var(--nav-surface);
  color: var(--nav-text);
  -webkit-backdrop-filter: blur(var(--backdrop-blur-lg));
  backdrop-filter: blur(var(--backdrop-blur-lg));
}

.sp-nav--sticky {
  position: sticky;
  top: 0;
}

.sp-nav--sticky {
  position: sticky;
  top: 0;
}

.sp-nav__inner {
  width: min(100%, var(--grid-container-max));
  min-height: var(--space-64);
  margin-inline: auto;
  padding-inline: var(--grid-padding);
  display: flex;
  align-items: center;
  gap: var(--space-16);
}

.sp-nav__brand {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
  text-decoration: none;
}

.sp-nav .brand-logo--light,
html[data-surface="light"] .sp-nav .brand-logo--dark {
  display: none;
}

html[data-surface="light"] .sp-nav .brand-logo--light {
  display: block;
}

.sp-nav__routes {
  height: var(--space-64);
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.sp-nav__route {
  min-height: var(--space-36);
  padding-inline: var(--space-10);
  display: inline-flex;
  align-items: center;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--nav-muted);
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-semibold);
  text-decoration: none;
  white-space: nowrap;
  transition: var(--transition-interactive);
}

.sp-nav__route:hover,
.sp-nav__route.is-hover {
  border-color: var(--nav-hover-border);
  background: var(--nav-hover-surface);
  color: var(--nav-text);
}

/* The current route carries the navigation violet, the one the primary button
   is drawn in. It used to be weight alone on a transparent box, which put the
   only signal on a half-step of boldness that disappears against a row of
   semibold labels. */
.sp-nav__route.is-active,
.sp-nav__route[aria-current="page"] {
  border-color: var(--nav-current-border);
  background: var(--nav-current-surface);
  color: var(--nav-current-text);
  font-weight: var(--weight-bold);
}

.sp-nav__route.is-active:hover,
.sp-nav__route.is-active.is-hover,
.sp-nav__route[aria-current="page"]:hover {
  border-color: var(--nav-current-border);
  background: color-mix(in srgb, var(--nav-current) 24%, transparent);
  color: var(--nav-current-text);
}

/* Inline action cluster: a row of controls or an icon beside a label. */
.sp-nav__actions,
.sp-nav__endcap,
.page--partner-analysis .analysis-head-actions,
.page--partner-analysis .pagination,
.page--partner-analysis .disclosure-title {
  display: flex;
  align-items: center;
  gap: var(--space-8);
}

.sp-nav__actions {
  margin-inline-start: auto;
}

html[data-surface="light"] .sp-nav {
  --nav-surface:
    color-mix(in srgb, var(--geistface-grey-25) 95%, transparent);
  --nav-drawer-surface: var(--geistface-grey-25);
  --nav-border:
    color-mix(in srgb, var(--deep-navy-900) 11%, transparent);
  --nav-text: var(--geistface-grey-800);
  --nav-muted:
    color-mix(in srgb, var(--geistface-grey-800) 72%, transparent);
  --nav-hover-surface: var(--panel-navy-50);
  --nav-hover-border:
    color-mix(in srgb, var(--deep-navy-900) 11%, transparent);
  --nav-shadow:
    0 var(--space-24) var(--space-64)
      color-mix(in srgb, var(--deep-navy-900) 12%, transparent);
}


.sp-side-nav {
  position: sticky;
  top: 0;
  height: 100vh;
  min-width: 0;
  display: flex;
  flex-direction: column;
  border-right: 1px solid var(--side-nav-border);
  background: var(--side-nav-surface);
  color: var(--side-nav-text);
}

.sp-side-nav .brand-logo--light,
html[data-surface="light"] .sp-side-nav .brand-logo--dark {
  display: none;
}

html[data-surface="light"] .sp-side-nav .brand-logo--light {
  display: block;
}

.sp-side-nav__scroll {
  min-height: 0;
  padding: var(--space-8) var(--space-12) var(--space-20);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
}

.sp-side-nav__group {
  display: grid;
  gap: var(--space-2);
}

.sp-side-nav__group + .sp-side-nav__group {
  margin-top: var(--space-20);
}

.sp-side-nav__title {
  margin: 0 var(--space-8) var(--space-6);
  color: var(--side-nav-muted);
  font-family: var(--skillprint-font-mono);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-bold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.sp-side-nav__link {
  width: 100%;
  min-height: var(--space-36);
  padding-inline: var(--space-10);
  display: flex;
  align-items: center;
  gap: var(--space-10);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  background: transparent;
  color: var(--side-nav-muted);
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-medium);
  text-decoration: none;
  transition: var(--transition-interactive);
}

.sp-side-nav__link:hover,
.sp-side-nav__link.is-hover {
  border-color: var(--side-nav-border);
  background: var(--side-nav-hover);
  color: var(--side-nav-text);
}

.sp-side-nav__link.is-active,
.sp-side-nav__link[aria-current="page"] {
  border-color: var(--side-nav-active-border, var(--side-nav-border));
  background: var(--side-nav-active);
  color: var(--nav-current-text);
  font-weight: var(--weight-semibold);
}

.sp-side-nav__link.is-active svg,
.sp-side-nav__link[aria-current="page"] svg {
  color: var(--side-nav-accent);
}

.sp-side-nav__link svg {
  width: var(--icon-size-sm);
  height: var(--icon-size-sm);
  flex: 0 0 var(--icon-size-sm);
  fill: none;
  stroke: currentColor;
  stroke-width: var(--icon-stroke-sm);
  stroke-linecap: round;
  stroke-linejoin: round;
  --icon-stroke: var(--icon-stroke-20);
}


.surface-panel {
  background: var(--surface-panel);
  color: var(--surface-text);
}

.surface-box {
  background: var(--surface-box);
  color: var(--surface-text);
}

.surface-raised {
  background: var(--surface-raised);
  color: var(--surface-text);
}

.sp-panel,
.sp-card {
  min-width: 0;
  border: 1px solid var(--surface-border);
  color: var(--surface-text);
}

.sp-panel {
  padding: var(--space-24);
  border-radius: var(--radius-4xl);
  background: var(--surface-panel);
}

.sp-card {
  padding: var(--card-current-padding, var(--space-16));
  border-radius: var(--card-current-radius, var(--radius-2xl));
  background: var(--surface-box);
}

.sp-card--raised {
  background: var(--surface-raised);
}

.sp-card--interactive {
  cursor: pointer;
  transition:
    border-color var(--duration-fast),
    box-shadow var(--duration-fast),
    transform var(--duration-fast);
}

.sp-card--interactive:hover,
.sp-card--interactive.is-hover,
.sp-card--interactive:active,
.sp-card--interactive.is-pressed,
.sp-card--interactive[aria-pressed="true"],
.sp-card--interactive[aria-expanded="true"] {
  border-color: var(--surface-border-hover);
}

.sp-card--interactive:focus-visible {
  outline: 0;
  border-color: var(--surface-border-hover);
  box-shadow:
    0 0 0 var(--focus-ring-width) var(--focus-inner),
    0 0 0 calc(var(--focus-ring-width) * 2) var(--focus-default);
}


/* ============================================================================
   07 · Shared component · code blocks
   Dark technical surface shared across dark and light interface modes.
   ========================================================================== */

.code-block {
  overflow: hidden;
  border: 1px solid var(--code-border);
  border-radius: var(--radius-2xl);
  background: var(--code-bg);
  color: var(--code-text);
  transition:
    border-color var(--duration-fast),
    box-shadow var(--duration-fast);
}

.code-block:hover {
  border-color: var(--code-border-hover);
}

.code-block:focus-within {
  border-color: var(--code-border-strong);
  box-shadow:
    0 0 0 var(--focus-ring-width) var(--focus-inner),
    0 0 0 calc(var(--focus-ring-width) * 2) var(--focus-default);
}

.code-block.is-error {
  border-color: var(--skills-pink-500);
}

.code-block[aria-disabled="true"] {
  opacity: 0.48;
  pointer-events: none;
}

.code-head {
  min-height: var(--space-44);
  padding: var(--space-10)
    var(--space-12)
    var(--space-10)
    var(--space-16);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-18);
  border-bottom: 1px solid var(--code-border);
  background: var(--code-surface);
  color: var(--code-muted);
  font-family: var(--skillprint-font-mono);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xxs-line-height);
}

.code-head button {
  padding: var(--space-6) var(--space-10);
  border: 1px solid var(--code-border-strong);
  border-radius: var(--radius-md);
  background: var(--code-raised);
  color: var(--code-text);
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xxs-line-height);
  font-weight: var(--weight-semibold);
  cursor: pointer;
  transition: var(--transition-interactive);
}

.code-head button:hover {
  border-color: var(--code-border-hover);
  background: var(--code-hover);
}

.code-block pre {
  margin: 0;
  padding: var(--space-22);
  overflow: auto;
  white-space: pre;
  tab-size: 2;
  color: var(--code-text);
  font-family: var(--skillprint-font-mono);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-sm-line-height);
}

.code-block--compact pre {
  padding: var(--space-18) var(--space-20);
}

.code-block--wrap pre {
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}

.inline-code {
  padding: var(--space-2) var(--space-6);
  border: 1px solid var(--code-border);
  border-radius: var(--radius-xs);
  background: var(--code-surface);
  color: var(--code-text);
  font-family: var(--skillprint-font-mono);
  font-size: 0.9em;
  line-height: 1.4;
}

.code-block ::selection {
  background: var(--code-selection);
}

.token.comment,
.token.prolog,
.token.doctype {
  color: var(--syntax-comment);
}

.token.keyword {
  color: var(--syntax-keyword);
}

.token.string,
.token.char,
.token.url {
  color: var(--syntax-string);
}

.token.number {
  color: var(--syntax-number);
}

.token.function,
.token.class-name {
  color: var(--syntax-function);
}

.token.property {
  color: var(--syntax-property);
}

.token.tag {
  color: var(--syntax-tag);
}

.token.attr-name {
  color: var(--syntax-attribute);
}

.token.boolean,
.token.constant {
  color: var(--syntax-constant);
}

.token.operator {
  color: var(--syntax-operator);
}

.token.punctuation {
  color: var(--syntax-punctuation);
}

.token.variable {
  color: var(--syntax-variable);
}

.token.inserted {
  color: var(--syntax-inserted);
}

.token.deleted {
  color: var(--syntax-deleted);
}

.hljs-comment,
.hljs-quote {
  color: var(--syntax-comment);
}

.hljs-keyword,
.hljs-selector-tag {
  color: var(--syntax-keyword);
}

.hljs-string,
.hljs-regexp {
  color: var(--syntax-string);
}

.hljs-number,
.hljs-literal {
  color: var(--syntax-number);
}

.hljs-title,
.hljs-function {
  color: var(--syntax-function);
}

.hljs-attr,
.hljs-attribute {
  color: var(--syntax-attribute);
}

.hljs-variable,
.hljs-template-variable {
  color: var(--syntax-variable);
}

/* ============================================================================
   07 · Shared component · charts
   Requires .sp-card on chart-card, stat and chart-insight elements.
   ========================================================================== */

.chart-pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-20);
}

.chart-card {
  --chart-panel: var(--surface-panel);
  --chart-muted: var(--surface-text-muted);
  --chart-grid: var(--surface-border);
  --chart-series: var(--violet);
  --chart-baseline: var(--surface-text);

  padding: 0;
  overflow: hidden;
}

.chart-card.dark {
  --surface-box: var(--panel-navy-950);
  --surface-text: var(--geistface-grey-200);
  --surface-text-muted:
    color-mix(
      in srgb,
      var(--geistface-grey-200) 72%,
      transparent
    );
  --surface-border:
    color-mix(
      in srgb,
      var(--geistface-grey-200) 12%,
      transparent
    );
  --chart-panel: var(--panel-navy-900);
}

.chart-card.light {
  --surface-box: var(--geistface-grey-25);
  --surface-text: var(--geistface-grey-800);
  --surface-text-muted:
    color-mix(
      in srgb,
      var(--geistface-grey-800) 86%,
      transparent
    );
  --surface-border:
    color-mix(
      in srgb,
      var(--deep-navy-900) 12%,
      transparent
    );
  --chart-panel: var(--panel-navy-50);
}

.chart-card-head,
.chart-actions,
.chart-key,
.key-item,
.chart-insight {
  display: flex;
}

.chart-card-head {
  padding: var(--space-24);
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-24);
  border-bottom: 1px solid var(--chart-grid);
}

.chart-card-title {
  min-width: 0;
}

.chart-card :where(
  .theme-label,
  .chart-card-title > span,
  .stat span,
  .chart-key,
  .chart-insight
) {
  color: var(--chart-muted);
}

.chart-card :where(
  .theme-label,
  .stat span,
  .stat small,
  .chart-key
) {
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
}

.chart-card :where(.theme-label, .stat span) {
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
}

.chart-card-title strong {
  display: block;
  margin-top: var(--space-6);
  color: var(--surface-text);
  font-size: var(--text-md-font-size);
  line-height: var(--text-md-line-height);
  font-weight: var(--weight-medium);
}

.chart-card-title > span {
  display: block;
  margin-top: var(--space-4);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

.chart-actions {
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-8);
}

.chart-card .chart-chip {
  background: var(--chart-panel);
  color: var(--chart-muted);
}

.chart-card .chart-chip.is-active {
  background: var(--mindset-violet-500);
  border-color: var(--mindset-violet-500);
  color: var(--geistface-grey-25);
}

.chart-stats {
  padding: var(--space-20) var(--space-24) 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-10);
}

.chart-card .stat {
  min-width: 0;
}

.chart-card :where(.stat span, .stat strong, .stat small) {
  display: block;
}

.chart-card .stat strong {
  margin-top: var(--space-6);
  color: var(--surface-text);
  font-family: var(--skillprint-font-mono);
  font-size: var(--space-24);
  line-height: var(--space-32);
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
}

.chart-card .stat small {
  margin-top: var(--space-2);
  color: var(--core-green-500);
  font-weight: var(--weight-medium);
}

.chart-frame {
  position: relative;
  min-height: var(--space-384);
  padding: var(--space-20) var(--space-18) var(--space-8);
}

.chart-frame svg {
  width: 100%;
  height: auto;
  display: block;
  overflow: visible;
}

.chart-frame text {
  font-family: var(--skillprint-font-ui);
  font-variant-numeric: tabular-nums;
}

.tone--blue,
.tone--cognition {
  --chart-tone: var(--blue);
}

.tone--green {
  --chart-tone: var(--core-green-500);
}

.tone--orange {
  --chart-tone: var(--skills-orange-500);
}

.tone--pink,
.tone--personality {
  --chart-tone: var(--skills-pink-500);
}

.tone--violet,
.tone--mindset {
  --chart-tone: var(--violet);
}

.tone--magenta {
  --chart-tone: var(--magenta);
}


.tone--text {
  --chart-tone: var(--geistface-grey-200);
}

.tone--deep {
  --chart-tone: var(--deep-navy-900);
}


.chart-frame.compact {
  min-height: var(--space-320);
}

.range-key {
  --chart-tone: var(--violet);
}

.range-marker.previous {
  background: var(--surface-box);
  border: 2px solid var(--chart-tone);
}

.range-marker.is-current {
  background: var(--chart-tone);
  box-shadow: 0 0 0 2px var(--surface-box);
}


.chart-key {
  padding: var(--space-4) var(--space-24) var(--space-20);
  flex-wrap: wrap;
  gap: var(--space-18);
}

.key-item {
  align-items: center;
  gap: var(--space-8);
}

.key-item i,
.insight-dot {
  width: var(--space-8);
  height: var(--space-8);
  flex: 0 0 var(--space-8);
  border-radius: var(--radius-full);
}

.key-item i {
  background: var(--chart-tone, var(--chart-key, currentColor));
}

.chart-insight {
  margin: 0 var(--space-24) var(--space-24);
  align-items: flex-start;
  gap: var(--space-12);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

.insight-dot {
  margin-top: var(--space-6);
  background: var(--mindset-violet-500);
}


/* ============================================================================
   07 · Shared component · trait semantics
   All layout and component styling comes from existing shared classes.
   ========================================================================== */

.trait-card {
  --trait-accent: var(--lime);

  display: flex;
  flex-direction: column;
}

.trait-card[data-pillar="cognition"] {
  --trait-accent: var(--mint);
}

.trait-card[data-pillar="personality"] {
  --trait-accent: var(--blue);
}

.trait-card .ui-badge {
  --badge-accent: var(--trait-accent);
}

.trait-card.is-hidden {
  display: none;
}

/* ============================================================================
   07 · Shared component · choice controls
   Radio and checkbox share layout, states and interaction behaviour.
   ========================================================================== */

:is(.radio-option, .check-option) {
  --choice-size: var(--icon-size-xs);
  --choice-mark: var(--space-6);

  position: relative;
  display: grid;
  grid-template-columns: var(--choice-size) minmax(0, 1fr);
  align-items: start;
  gap: var(--space-8);
  max-width: 100%;
  color: var(--surface-text);
  cursor: pointer;
}

:is(.radio-option--md, .check-option--md) {
  --choice-size: var(--icon-size-sm);
  --choice-mark: var(--space-8);

  gap: var(--space-12);
}

:is(.radio-input, .check-input) {
  position: absolute;
  z-index: 1;
  top: var(--space-2);
  left: 0;
  width: var(--choice-size);
  height: var(--choice-size);
  margin: 0;
  opacity: 0;
  cursor: inherit;
}

:is(.radio-control, .check-control) {
  position: relative;
  width: var(--choice-size);
  height: var(--choice-size);
  margin-top: var(--space-2);
  border: 1px solid var(--surface-border-hover);
  background: var(--surface-box);
  transition: var(--transition-interactive);
}

.radio-control {
  border-radius: var(--radius-full);
}

.check-control {
  border-radius: var(--radius-xs);
}

:is(.radio-control, .check-control)::after {
  content: "";
  position: absolute;
  inset: 50% auto auto 50%;
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.4);
  transition:
    opacity var(--duration-fast) var(--ease-standard),
    transform var(--duration-fast) var(--ease-standard);
}

.radio-control::after {
  width: var(--choice-mark);
  height: var(--choice-mark);
  border-radius: var(--radius-full);
  background: var(--geistface-grey-25);
}

.check-control::after {
  width: var(--space-10);
  height: var(--space-6);
  border-left: var(--space-2) solid var(--geistface-grey-25);
  border-bottom: var(--space-2) solid var(--geistface-grey-25);
  transform: translate(-50%, -62%) rotate(-45deg) scale(0.4);
}

:is(.radio-copy, .check-copy) {
  min-width: 0;
  display: grid;
}

:is(.radio-option--md .radio-copy, .check-option--md .check-copy) {
  gap: var(--space-2);
}

:is(
  .radio-option:hover,
  .radio-option.is-hover,
  .check-option:hover,
  .check-option.is-hover
) :is(.radio-control, .check-control) {
  border-color: var(--mindset-violet-400);
  background: var(--surface-raised);
}

:is(
  .radio-input:checked + .radio-control,
  .radio-option.is-checked .radio-control,
  .check-input:checked + .check-control,
  .check-option.is-checked .check-control
) {
  border-color: var(--mindset-violet-500);
  background: var(--mindset-violet-500);
}

:is(
  .radio-input:checked + .radio-control,
  .radio-option.is-checked .radio-control
)::after {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}

:is(
  .check-input:checked + .check-control,
  .check-option.is-checked .check-control
)::after {
  opacity: 1;
  transform: translate(-50%, -62%) rotate(-45deg) scale(1);
}

:is(.radio-input, .check-input):active
+ :is(.radio-control, .check-control) {
  transform: scale(0.94);
}

:is(
  .radio-input:focus-visible + .radio-control,
  .radio-option.is-focus .radio-control,
  .check-input:focus-visible + .check-control,
  .check-option.is-focus .check-control
) {
  box-shadow:
    0 0 0 var(--focus-ring-width) var(--focus-inner),
    0 0 0 calc(var(--focus-ring-width) * 2) var(--focus-default);
}

:is(
  .radio-input:disabled + .radio-control,
  .radio-option.is-disabled .radio-control,
  .check-input:disabled + .check-control,
  .check-option.is-disabled .check-control
) {
  border-color: var(--surface-border);
  background: var(--surface-raised);
}

:is(
  .radio-input:disabled ~ .radio-copy,
  .radio-option.is-disabled .radio-copy,
  .check-input:disabled ~ .check-copy,
  .check-option.is-disabled .check-copy
) {
  color: var(--surface-text-muted);
  opacity: 0.58;
}

:is(
  .radio-option:has(.radio-input:disabled),
  .radio-option.is-disabled,
  .check-option:has(.check-input:disabled),
  .check-option.is-disabled
) {
  cursor: not-allowed;
}

/* ============================================================================
   07 · Shared component · extended inputs
   Uses existing cards, icons, typography, buttons, grids and surface roles.
   ========================================================================== */

:is(.switch, .sp-toggle) {
  --toggle-width: var(--space-44);
  --toggle-height: var(--space-24);
  --toggle-thumb: var(--space-18);
  --toggle-offset: var(--space-2);
  --toggle-shift: var(--space-20);

  position: relative;
  width: var(--toggle-width);
  height: var(--toggle-height);
  flex: 0 0 var(--toggle-width);
}

:is(.switch__track, .sp-toggle) {
  border: 1px solid var(--surface-border-hover);
  border-radius: var(--radius-full);
  background: var(--surface-raised);
  transition: var(--transition-interactive);
}

.sp-toggle {
  padding: 0;
  display: inline-flex;
  border: 1px solid var(--surface-border-hover);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

:is(.switch__track::after, .sp-toggle__thumb) {
  content: "";
  position: absolute;
  top: var(--toggle-offset);
  left: var(--toggle-offset);
  width: var(--toggle-thumb);
  height: var(--toggle-thumb);
  border-radius: var(--radius-full);
  background: var(--surface-text-muted);
  box-shadow: var(--shadow-xs);
  transition:
    transform var(--duration-fast) var(--ease-standard),
    background-color var(--duration-fast) var(--ease-standard);
  pointer-events: none;
}

:is(
  .switch input:checked + .switch__track,
  .sp-toggle[aria-checked="true"]
) {
  border-color: var(--mindset-violet-500);
  background: var(--mindset-violet-500);
}

:is(
  .switch input:checked + .switch__track::after,
  .sp-toggle[aria-checked="true"] .sp-toggle__thumb
) {
  background: var(--geistface-grey-25);
  transform: translateX(var(--toggle-shift));
}

:is(
  .switch:hover .switch__track,
  .sp-toggle:hover,
  .sp-toggle.is-hover
):not(:disabled) {
  border-color: var(--mindset-violet-400);
}

.sp-toggle[aria-checked="true"]:is(:hover, .is-hover) {
  background: var(--mindset-violet-600);
}

.sp-toggle:active:not(:disabled) .sp-toggle__thumb {
  transform: translateX(var(--space-2)) scale(0.96);
}

.sp-toggle[aria-checked="true"]:active:not(:disabled)
.sp-toggle__thumb {
  transform:
    translateX(calc(var(--toggle-shift) - var(--space-2)))
    scale(0.96);
}

:is(
  .switch input:disabled + .switch__track,
  .sp-toggle:disabled
) {
  border-color: var(--surface-border);
  background: var(--surface-raised);
  opacity: 0.55;
  cursor: not-allowed;
}

:is(.switch--sm, .sp-toggle--sm) {
  --toggle-width: var(--space-36);
  --toggle-height: var(--space-20);
  --toggle-thumb: var(--space-16);
  --toggle-shift: var(--space-16);
}

:is(.switch--slim, .sp-toggle--slim) {
  --toggle-width: var(--space-40);
  --toggle-height: var(--space-20);
  --toggle-thumb: var(--space-18);
  --toggle-offset: 0;
  --toggle-shift: var(--space-20);
}

:is(
  .switch--slim .switch__track,
  .sp-toggle--slim
) {
  background: transparent;
}

:is(
  .switch--slim input:checked + .switch__track,
  .sp-toggle--slim[aria-checked="true"]
) {
  background: transparent;
}

:is(
  .switch--slim input:checked + .switch__track::after,
  .sp-toggle--slim[aria-checked="true"] .sp-toggle__thumb
) {
  background: var(--mindset-violet-500);
}


/* ============================================================================
   07 · Shared component · text editors
   Toolbars compose existing fields, button groups, buttons and icon assets.
   Editor frames compose the existing .sp-card surface.
   ========================================================================== */

.editor-shell {
  display: grid;
  gap: var(--space-12);
}

.editor-toolbar {
  min-width: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-8);
}

.editor-frame {
  padding: 0;
  overflow: hidden;
}

.editor-frame:focus-within {
  border-color: var(--mindset-violet-200);
  box-shadow:
    0 0 0 var(--space-4)
      color-mix(
        in srgb,
        var(--mindset-violet-400) 18%,
        transparent
      );
}

.editor-frame[data-state="error"] {
  border-color: var(--skills-pink-500);
}

.editor-frame:is(
  [data-state="disabled"],
  [aria-disabled="true"]
) {
  opacity: 0.55;
  cursor: not-allowed;
}

.editor-input {
  min-height: var(--space-320);
  max-height: var(--space-480);
  padding: var(--space-20);
  overflow: auto;
  outline: 0;
  color: var(--surface-text);
}

.editor-shell--sm .editor-input {
  min-height: var(--space-256);
}

.editor-input:empty::before {
  content: attr(data-placeholder);
  color: var(--surface-text-muted);
  pointer-events: none;
}

.editor-input > :first-child {
  margin-block-start: 0;
}

.editor-input > :last-child {
  margin-block-end: 0;
}

.editor-input :is(p, ul, ol) {
  margin-block: 0 var(--space-16);
}

.editor-input h2 {
  margin-block: 0 var(--space-12);
  font-size: var(--space-28);
  line-height: var(--space-36);
}

.editor-input h3 {
  margin-block: 0 var(--space-8);
  font-size: var(--space-20);
  line-height: var(--space-28);
}

.editor-input a {
  color: var(--mindset-violet-300);
}

.editor-input blockquote {
  margin: var(--space-16) 0;
  padding-inline-start: var(--space-16);
  border-inline-start: var(--space-4) solid var(--mindset-violet-400);
  color: var(--surface-text-muted);
}

.editor-footer {
  min-height: var(--space-20);
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-8) var(--space-16);
  color: var(--surface-text-muted);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

.editor-footer > :last-child {
  font-family: var(--skillprint-font-mono);
  font-size: var(--text-xs-font-size);
}

/* ============================================================================
   07 · Shared component · dropdowns
   Compose triggers with buttons, menus with cards, search with fields,
   shortcuts with badges, chips with tags and checks with choice controls.
   ========================================================================== */

.sp-dropdown {
  position: relative;
  width: 100%;
}

.sp-dropdown__trigger {
  width: 100%;
  justify-content: flex-start;
  text-align: left;
}

.sp-dropdown__trigger[aria-expanded="true"] {
  border-color: var(--mindset-violet-300);
  box-shadow:
    0 0 0 var(--focus-ring-width) var(--button-focus-inner),
    0 0 0 calc(var(--focus-ring-width) * 2) var(--button-focus);
}

.sp-dropdown__chevron {
  margin-inline-start: auto;
  transition:
    transform var(--duration-fast) var(--ease-standard);
}

.sp-dropdown__trigger[aria-expanded="true"]
.sp-dropdown__chevron {
  transform: rotate(180deg);
}

:is(
  .sp-dropdown__value,
  .sp-dropdown__item-main,
  .sp-dropdown__header
) {
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--space-8);
}

:is(
  .sp-dropdown__value,
  .sp-dropdown__item-main
) {
  flex: 1 1 auto;
}

:is(
  .sp-dropdown__copy,
  .sp-dropdown__header-copy
) {
  min-width: 0;
  flex: 1 1 auto;
}

:is(
  .sp-dropdown__copy,
  .sp-dropdown__header-copy
) > :is(strong, span) {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

:is(
  .sp-dropdown__copy,
  .sp-dropdown__header-copy
) > span {
  color: var(--surface-text-muted);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
}

.sp-dropdown__menu {
  position: absolute;
  z-index: 40;
  top: calc(100% + var(--space-8));
  left: 0;
  width: max(100%, var(--space-256));
  max-height: var(--space-320);
  padding: var(--space-6);
  overflow: auto;
  box-shadow: var(--shadow-xl);
  transform-origin: top;
}

.sp-dropdown__menu[hidden] {
  display: none;
}

.sp-dropdown__header {
  padding: var(--space-10);
  margin:
    calc(var(--space-6) * -1)
    calc(var(--space-6) * -1)
    var(--space-6);
  border-bottom: 1px solid var(--surface-border);
}

.sp-dropdown__group-label {
  padding: var(--space-8)
    var(--space-10)
    var(--space-4);
  color: var(--surface-text-muted);
  font-family: var(--skillprint-font-mono);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.sp-dropdown__item {
  width: 100%;
  justify-content: flex-start;
  text-align: left;
}

.sp-dropdown__item[aria-selected="true"] {
  background:
    color-mix(
      in srgb,
      var(--mindset-violet-500) 16%,
      var(--surface-box)
    );
}

.sp-dropdown__selected-mark {
  margin-inline-start: auto;
  color: var(--mindset-violet-300);
  opacity: 0;
}

.sp-dropdown__item[aria-selected="true"]
.sp-dropdown__selected-mark {
  opacity: 1;
}

.sp-dropdown__divider {
  height: 1px;
  margin:
    var(--space-6)
    calc(var(--space-6) * -1);
  background: var(--surface-border);
}

.sp-dropdown__empty {
  padding: var(--space-24) var(--space-16);
  color: var(--surface-text-muted);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  text-align: center;
}

.sp-dropdown--compact {
  width: var(--space-256);
}

.sp-dropdown--menu .sp-dropdown__trigger {
  width: auto;
}

.sp-dropdown--menu .sp-dropdown__menu {
  right: 0;
  left: auto;
}


/* ============================================================================
   07 · Shared component · avatars
   Labels use existing typography. Add controls use existing icon buttons.
   ========================================================================== */

.sp-avatar {
  --avatar-size: var(--space-40);
  --avatar-status-size: var(--space-10);

  position: relative;
  width: var(--avatar-size);
  height: var(--avatar-size);
  flex: 0 0 var(--avatar-size);
  display: inline-grid;
  place-items: center;
  overflow: visible;
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-full);
  background: var(--surface-raised);
  color: var(--surface-text-muted);
  font-weight: var(--weight-semibold);
  text-align: center;
}

.sp-avatar > :is(img, .sp-avatar__image) {
  width: 100%;
  height: 100%;
  display: block;
  border-radius: inherit;
  object-fit: cover;
}

.sp-avatar__placeholder {
  width: 60%;
  height: 60%;
  display: grid;
  place-items: center;
}

.sp-avatar__placeholder .icon-asset {
  width: 100%;
  height: 100%;
}

.sp-avatar[data-size="xs"] {
  --avatar-size: var(--space-24);
  --avatar-status-size: var(--space-6);
}

.sp-avatar[data-size="sm"] {
  --avatar-size: var(--space-32);
  --avatar-status-size: var(--space-8);
}

.sp-avatar[data-size="lg"] {
  --avatar-size: var(--space-48);
  --avatar-status-size: var(--space-12);
}

.sp-avatar[data-size="xl"] {
  --avatar-size: var(--space-56);
  --avatar-status-size: var(--space-14);
}

.sp-avatar[data-size="2xl"] {
  --avatar-size: var(--space-64);
  --avatar-status-size: var(--space-16);
}

.sp-avatar__status {
  position: absolute;
  right: 0;
  bottom: 0;
  z-index: 1;
  width: var(--avatar-status-size);
  height: var(--avatar-status-size);
  display: grid;
  place-items: center;
  border: var(--icon-stroke-sm) solid var(--surface-box);
  border-radius: var(--radius-full);
  background: var(--surface-text-muted);
  color: var(--geistface-grey-25);
}

.sp-avatar__status[data-status="online"] {
  background: var(--core-green-500);
}

.sp-avatar__status[data-status="verified"] {
  background: var(--personality-blue-500);
}

.sp-avatar__status[data-status="company"] {
  overflow: hidden;
  background: var(--surface-box);
}

.sp-avatar__status[data-status="company"] img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.sp-avatar-label {
  min-width: 0;
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
}

.sp-avatar-label__copy {
  min-width: 0;
  display: grid;
}

.sp-avatar-group {
  --avatar-overlap: var(--space-8);

  display: inline-flex;
  align-items: center;
}

.sp-avatar-group > .sp-avatar {
  box-shadow:
    0 0 0 var(--icon-stroke-sm) var(--surface-box);
}

.sp-avatar-group > .sp-avatar + .sp-avatar {
  margin-inline-start: calc(var(--avatar-overlap) * -1);
}

.sp-avatar-group > .sp-avatar:is(:hover, :focus-visible) {
  z-index: 2;
}

.sp-avatar-group[data-size="sm"] {
  --avatar-overlap: var(--space-12);
}

.sp-avatar-group[data-size="md"] {
  --avatar-overlap: var(--space-16);
}

.sp-avatar-add {
  border-style: dashed;
  border-radius: var(--radius-full);
}

/* ============================================================================
   07 · Shared component · progress
   Labels use existing text styles. Tooltips compose .ui-badge.
   ========================================================================== */

.sp-progress {
  /* Two ways in, and the second is the honest one wherever the reader is
     working towards a ceiling: give the count and the total, and the
     percentage is arithmetic rather than a third number somebody has to keep
     in step with the other two. Set --progress directly where the source
     really is a percentage and there is no total to name. */
  --progress-count: 0;
  --progress-total: 100;
  --progress: calc(var(--progress-count) / var(--progress-total) * 100%);
  --progress-colour: var(--violet);

  position: relative;
  width: 100%;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--space-12);
}

.sp-progress__track {
  position: relative;
  min-width: 0;
  height: var(--space-8);
  flex: 1 1 auto;
  overflow: hidden;
  border-radius: var(--radius-full);
  background: var(--surface-raised);
}

.sp-progress__fill {
  position: absolute;
  inset: 0 auto 0 0;
  width: clamp(0%, var(--progress), 100%);
  border-radius: inherit;
  background: var(--progress-colour);
  transition:
    width var(--duration-fast) var(--ease-standard);
}

.sp-progress__tooltip {
  position: absolute;
  left:
    clamp(
      var(--space-28),
      var(--progress),
      calc(100% - var(--space-28))
    );
  pointer-events: none;
  transform: translateX(-50%);
}

.sp-progress--tooltip-top .sp-progress__tooltip {
  bottom: calc(100% + var(--space-8));
}

/* The value against its ceiling. A bare percentage tells the reader how far
   along they are; this tells them what the journey is, which is the question
   anyone collecting points, sessions or badges is actually asking. Tabular
   figures so the label does not jitter as the count climbs. */
.sp-progress__count {
  flex: none;
  color: var(--surface-text-muted);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.sp-progress__count strong {
  color: var(--surface-text);
  font-weight: var(--weight-semibold);
}

:is(.sp-progress, .sp-progress-ring)[aria-disabled="true"] {
  opacity: 0.55;
}

.sp-progress-ring {
  --progress-count: 0;
  --progress-total: 100;
  --progress-value: calc(var(--progress-count) / var(--progress-total) * 100);
  --progress-colour: var(--violet);
  --progress-ring-size: var(--space-160);
  /* In the SVG's own units, not pixels. The dash that draws the value is
     measured in those units too, and the two have to agree - a stroke pinned
     to a pixel width while the dash scales is what turned this ring into a
     row of segments. Four of forty is a tenth of the ring at every size. */
  --progress-ring-stroke: 4;

  position: relative;
  width: var(--progress-ring-size);
  aspect-ratio: 1;
  display: grid;
  place-items: center;
}

.sp-progress-ring__svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
  transform: rotate(-90deg);
}

/* No non-scaling-stroke here. It pins the stroke to a pixel width by moving
   the whole calculation into the rendered coordinate space - and it takes
   stroke-dasharray with it, where the path is no longer 100 units long. The
   dash then repeats around the ring instead of drawing one arc. pathLength
   does not rescue it either; keeping the geometry in user units does. */
:is(
  .sp-progress-ring__track,
  .sp-progress-ring__value
) {
  fill: none;
  stroke-width: var(--progress-ring-stroke);
}

.sp-progress-ring__track {
  stroke: var(--surface-raised);
}

.sp-progress-ring__value {
  stroke: var(--progress-colour);
  stroke-dasharray: var(--progress-value) 100;
  transition:
    stroke-dasharray var(--duration-fast) var(--ease-standard);
}

.sp-progress-ring__copy {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  text-align: center;
  pointer-events: none;
}

/* The ceiling, set beside the score rather than under it. The arc already
   draws the proportion; the total only has to name what it is out of. */
.sp-progress-ring__total {
  color: var(--surface-text-muted);
  font-size: var(--text-sm-font-size);
  font-weight: var(--weight-regular);
}

.choice-card:has(:is(.radio-input, .check-input):checked),
.choice-card:is(.is-selected, [aria-checked="true"]) {
  border-color: var(--mindset-violet-400);
  box-shadow:
    inset 0 0 0 var(--focus-ring-width)
      var(--mindset-violet-400);
}

.choice-card:has(:is(.radio-input, .check-input):focus-visible),
.choice-card.is-focused {
  border-color: var(--mindset-violet-400);
  box-shadow:
    inset 0 0 0 var(--focus-ring-width)
      var(--mindset-violet-400),
    0 0 0 var(--focus-ring-width) var(--focus-inner),
    0 0 0 calc(var(--focus-ring-width) * 2) var(--focus-default);
}

.choice-card:has(:is(.radio-input, .check-input):disabled),
.choice-card:is(.is-disabled, [aria-disabled="true"]) {
  opacity: 0.58;
  cursor: not-allowed;
  pointer-events: none;
}

/* ============================================================================
   07 · Shared component · range sliders
   Value labels compose .ui-badge when a surface is required.
   ========================================================================== */

.sp-slider {
  --slider-start: 0%;
  --slider-end: 100%;
  --slider-position: var(--slider-start);
  --slider-handle-size: var(--icon-size-md);
  --slider-handle-radius: var(--space-12);

  position: relative;
  width: 100%;
  min-height: var(--slider-handle-size);
  touch-action: none;
}

:is(.sp-slider__track, .sp-slider__range) {
  position: absolute;
  top: var(--space-8);
  height: var(--space-8);
  border-radius: var(--radius-full);
}

.sp-slider__track {
  right: 0;
  left: 0;
  background: var(--surface-raised);
}

.sp-slider__range {
  right: calc(100% - var(--slider-end));
  left: var(--slider-start);
  background: var(--mindset-violet-500);
}

.sp-slider__handle {
  --slider-position: var(--slider-start);

  position: absolute;
  top: 0;
  left:
    clamp(
      0px,
      calc(var(--slider-position) - var(--slider-handle-radius)),
      calc(100% - var(--slider-handle-size))
    );
  z-index: 1;
  width: var(--slider-handle-size);
  height: var(--slider-handle-size);
  padding: 0;
  border: var(--space-2) solid var(--mindset-violet-500);
  border-radius: var(--radius-full);
  background: var(--surface-box);
  box-shadow: var(--shadow-sm);
  cursor: grab;
}

.sp-slider__handle--end,
.sp-slider__value--end {
  --slider-position: var(--slider-end);
}

.sp-slider__handle:is(:hover, .is-hover) {
  border-color: var(--mindset-violet-400);
  box-shadow: var(--shadow-md);
}

.sp-slider__handle:active {
  cursor: grabbing;
}

.sp-slider__handle:is(:focus-visible, .is-focused) {
  outline: 0;
  box-shadow:
    var(--shadow-xs),
    0 0 0 var(--focus-ring-width) var(--focus-inner),
    0 0 0 calc(var(--focus-ring-width) * 2) var(--focus-default);
}

.sp-slider__value {
  --slider-position: var(--slider-start);

  position: absolute;
  left:
    clamp(
      var(--slider-handle-radius),
      var(--slider-position),
      calc(100% - var(--slider-handle-radius))
    );
  display: none;
  pointer-events: none;
  transform: translateX(-50%);
}

.sp-slider[data-label="bottom"] {
  margin-bottom: var(--space-32);
}

.sp-slider[data-label="bottom"] .sp-slider__value {
  top: var(--space-32);
  display: inline-flex;
}

.sp-slider[data-label="tooltip"] {
  margin-top: var(--space-42);
}

.sp-slider[data-label="tooltip"] .sp-slider__value {
  top: calc(var(--space-42) * -1);
  display: inline-flex;
}

.sp-slider:is([aria-disabled="true"], .is-disabled) {
  opacity: 0.48;
  pointer-events: none;
}

/* ============================================================================
   07 · Shared component · video player
   Actions use icon buttons, timeline uses progress and volume uses slider.
   ========================================================================== */

.video-player {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  isolation: isolate;
  border-radius: var(--radius-xl);
  background: var(--deep-navy-950);
  color: var(--geistface-grey-25);
}

/* Media that fills its frame: player surfaces, the preview thumbnail and the
   deck's game art all crop the same way. */
.video-player__media,
.video-player__poster,
.video-preview__frame :is(img, video),
.page--seed-deck .game-art img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
}

.video-player :is(
  .icon-asset--outline,
  .icon-asset--solid
) {
  filter: brightness(0) invert(1);
}

.video-overlay {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: grid;
  place-items: center;
  background:
    color-mix(
      in srgb,
      var(--deep-navy-950) 10%,
      transparent
    );
  transition:
    opacity var(--duration-fast) var(--ease-standard),
    visibility var(--duration-fast) var(--ease-standard);
}

.video-player[data-playing="true"] .video-overlay {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.video-overlay__button {
  --button-height: var(--space-64);

  background:
    color-mix(
      in srgb,
      var(--geistface-grey-25) 30%,
      transparent
    );
  color: var(--geistface-grey-25);
  -webkit-backdrop-filter: blur(var(--backdrop-blur-md));
  backdrop-filter: blur(var(--backdrop-blur-md));
}

.video-overlay__button:hover {
  background:
    color-mix(
      in srgb,
      var(--geistface-grey-25) 40%,
      transparent
    );
}

.video-controls {
  position: absolute;
  inset: auto 0 0;
  z-index: 2;
  padding: var(--space-48)
    var(--space-32)
    var(--space-24);
  background:
    linear-gradient(
      180deg,
      transparent,
      color-mix(
        in srgb,
        var(--deep-navy-950) 42%,
        transparent
      )
    );
}

.video-controls__content,
.video-progress {
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--space-8);
}

.video-controls__content {
  gap: var(--space-4);
}

.video-player .button {
  color: var(--geistface-grey-25);
}

.video-player .button--tertiary:hover {
  background:
    color-mix(
      in srgb,
      var(--geistface-grey-25) 20%,
      transparent
    );
  color: var(--geistface-grey-25);
}

.video-progress {
  flex: 1 1 auto;
}

.video-progress__time {
  flex: 0 0 auto;
  font-family: var(--skillprint-font-mono);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.video-progress__buffer {
  position: absolute;
  inset: 0 auto 0 0;
  width: var(--video-buffer, 0%);
  border-radius: inherit;
  background:
    color-mix(
      in srgb,
      var(--geistface-grey-25) 30%,
      transparent
    );
}

.video-progress__track:is(:hover, :focus-visible)
.video-preview,
.video-preview[data-open="true"] {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, 0);
}


/* ============================================================================
   06 · Utilities · structural helpers
   Behaviour-only helpers with no equivalent design primitive.
   ========================================================================== */

.layout-grid {
  display: grid;
}

.layout-flex {
  display: flex;
}

.layout-inline-flex {
  display: inline-flex;
}

.layout-block {
  display: block;
}

.items-center {
  align-items: center;
}

.items-start {
  align-items: flex-start;
}

.justify-between {
  justify-content: space-between;
}

.place-center {
  place-items: center;
}

.flow-column {
  flex-direction: column;
}


.text-center {
  text-align: center;
}

/* ############################################################################
   LAYERS 02, 06 AND 09 CONTINUE BELOW
   Token aliases, the shared utility layer, then one composition section per
   page. Nothing here may redefine a shared component - extend it instead.
   ########################################################################## */

/* ==========================================================================
   02 - Design tokens · semantic tier
   The design system documents a two-tier scale: a semantic name, the
   primitive it maps to, and the value - see the Spacing section, which
   publishes --spacing-xs -> --space-4 -> 4px. These are that semantic tier,
   not legacy shims, and the markup and copy-ready samples reference them by
   name. A handful of real values are defined here too, marked below.
   ========================================================================== */

:root {
  --font-size-xs: var(--text-xxs-font-size);
  --font-size-sm: var(--text-xs-font-size);
  --font-size-md: var(--text-sm-font-size);
  --font-size-base: var(--text-md-font-size);
  --font-size-caption: var(--text-xxs-font-size);
  --line-height-sm: var(--text-xs-line-height);
  --line-height-md: var(--text-sm-line-height);
  --line-height-base: var(--text-md-line-height);
  --spacing-xs: var(--space-4);
  --spacing-sm: var(--space-6);
  --spacing-md: var(--space-8);
  --spacing-lg: var(--space-12);
  --spacing-xl: var(--space-16);
  --spacing-2xl: var(--space-20);
  --spacing-3xl: var(--space-24);
  --spacing-4xl: var(--space-32);
  --spacing-5xl: var(--space-40);
  --spacing-6xl: var(--space-48);
  --spacing-7xl: var(--space-64);
  --spacing-9xl: var(--space-96);
  /* Measure - the maximum line length for prose. The reference puts the ideal
     at about 66 characters and caps body copy at 720px; these are the three
     bands used across the surfaces. Anything wider is harder to read because
     of the distance the eye travels back to the next line. */
  --measure-sm: 620px;   /* ~55-70 characters, narrow columns and cards */
  --measure-md: 720px;   /* ~66 characters, the default for body copy      */
  --measure-lg: 860px;   /* ~85 characters, wide section intros only       */

  --radius-lg-system: var(--radius-lg);
  --radius-pill: var(--radius-full);
  --radius-control: var(--radius-md);
  --radius-compact: var(--radius-xl);
  --radius-card: var(--radius-2xl);
  --radius-panel: var(--radius-4xl);
  --space-56: 56px;
  /* Icon line weight, one token per icon size.

     stroke-width on an icon is measured in the symbol's own 24-unit grid, not
     in screen pixels, so the viewBox scale multiplies it: the same `2px` draws
     a 2px line at 24px and a 4px line at 48px. The ladder therefore has to run
     the other way to the sizes it serves. Each value below is chosen so the
     line lands on the weight in its comment once the scale is applied.

     Read left to right: a 16px icon carries a 1.5px line, and the weight rises
     gently to 2.5px at 48px, so an icon keeps its optical weight as it grows
     rather than thinning out or turning into a blob. */
  /* Two steps below Part A's --icon-size-xs, for the badge closes and the
     eyebrow dot that were sized from page rules rather than the component. */
  --icon-size-2xs: 12px;
  --icon-size-3xs: 10px;

  --icon-stroke-10: 3px;      /* draws 1.25px */
  --icon-stroke-12: 2.5px;    /* draws 1.25px */
  --icon-stroke-14: 2.57px;   /* draws 1.5px  */
  --icon-stroke-16: 2.25px;   /* draws 1.5px  */
  --icon-stroke-18: 2px;      /* draws 1.5px  */
  --icon-stroke-20: 2.1px;    /* draws 1.75px */
  --icon-stroke-22: 1.91px;   /* draws 1.75px */
  --icon-stroke-24: 2px;      /* draws 2px    */
  --icon-stroke-32: 1.6875px; /* draws 2.25px */
  --icon-stroke-40: 1.44px;   /* draws 2.4px  */
  --icon-stroke-48: 1.25px;   /* draws 2.5px  */
  /* Kept as the name the small-icon rules already use. */
  --icon-stroke-xs: var(--icon-stroke-16);
  --focus-ring-color: var(--focus-default);
  --skillprint-action-violet: var(--mindset-violet-500);
  --text-primary: var(--geistface-grey-200);
  --text-muted: color-mix(in srgb, var(--geistface-grey-200) 72%, transparent);
  --deep-navy: var(--deep-navy-900);
  --geistface-text: var(--geistface-grey-200);
  --skillprint-deep-navy: var(--deep-navy-900);
  --white: var(--deep-navy-25);
  --mood-500: var(--core-lime-500);
  --cognition-500: var(--personality-mint-500);
  --personality-500: var(--personality-blue-500);
  --focus-violet: var(--mindset-violet-400);
  --card-radius-sm: var(--radius-xl);
  --card-padding-sm: var(--space-16);
  /* The middle step. `.page--homepage :is(.dimension-card, .process-card)` sets
     --card-current-padding from it, so without a definition that whole
     declaration is invalid and those cards fall back to the 16px default. */
  --card-padding-md: var(--space-24);
  --card-padding-lg: var(--space-32);
  --card-min-height-tall: 210px;
  --code-surface-raised: var(--code-raised);
}

html[data-surface="light"] {
  --button-surface: var(--surface-box);
  --button-surface-hover: var(--surface-raised);
  --button-surface-disabled: color-mix(in srgb, var(--surface-raised) 62%, transparent);
  --button-border: var(--surface-border-hover);
  --button-border-disabled: var(--surface-border);
  --button-text: var(--surface-text);
  --button-text-muted: color-mix(in srgb, var(--surface-text) 72%, transparent);
  --button-text-disabled: color-mix(in srgb, var(--surface-text) 42%, transparent);

  /* Light drops the `inset 0 0 0 1px` layer the dark shadow carries. On a dark
     ground that layer is a 6% white bevel highlight drawn inside a light
     border, so it lifts the control. Translated to a light ground the ink has
     to invert, and at that point it lands as a second 1px band immediately
     inside --button-border - which is itself 18% navy - so two touching bands
     of identical ink read as a single 2px border. That is what made every
     light secondary look so much heavier than its dark counterpart. The
     border alone carries the edge here; the drop and the `inset 0 -2px 0`
     floor stay, so both modes keep the same three-part construction minus the
     one layer that has no job on this ground.

     This belongs on the surface block rather than on a page rule for two
     reasons. Every light page inherits it, instead of only the two that
     happened to declare their own. And it is the one selector read_tokens()
     harvests for the Tailwind light layer, so --shadow-button picks the
     correction up on the next `skillprint.py tailwind` run rather than
     drifting from the sheet it is generated from. */
  --button-shadow:
    0 1px 2px
      color-mix(in srgb, var(--deep-navy-950) 5%, transparent),
    inset 0 -2px 0
      color-mix(in srgb, var(--deep-navy-950) 5%, transparent);
}

html[data-surface="light"] .sp-side-nav {
  --side-nav-surface: var(--geistface-grey-25);
  --side-nav-border: color-mix(in srgb, var(--deep-navy-900) 11%, transparent);
  --side-nav-text: var(--geistface-grey-800);
  /* 80%, not 72%. This is the ink every unselected row in the portal's one
     navigation is set in - Home, Games, Skills, Profile, Settings, Theme, and
     the product line under the lockup - and at 72% it measured 4.03:1 on the
     rail's own ground, under the 4.5:1 those 14px labels are held to. 80%
     measures 4.93:1 and stays well clear of the 8.45:1 the current row is
     drawn at, so "not selected" still reads as quieter rather than as equal.

     It is corrected here rather than on the body because this declaration is
     the one that reaches the links: the portal's light ladder sets
     --side-nav-muted too, but on <body>, and a custom property resolves from
     the nearest ancestor that declares it - which for anything inside the rail
     is .sp-side-nav, not the page. Editing the body value alone changes
     nothing, which is the same substitution trap .popup--dark documents. */
  --side-nav-muted: color-mix(in srgb, var(--geistface-grey-800) 80%, transparent);
  --side-nav-hover: var(--panel-navy-50);
  --side-nav-active: var(--nav-current-surface);
  --side-nav-active-border: var(--nav-current-border);
  --side-nav-search: var(--panel-navy-50);
}


.button--unstyled {
  --button-height: auto;
  --button-padding: 0;
  --button-current-shadow: none;
  min-width: 0;
  min-height: 0;
  border-color: transparent;
  border-radius: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: inherit;
  white-space: normal;
}

.button--unstyled:active:not(:disabled):not([aria-disabled="true"]) {
  transform: none;
}

.page--design-system [data-demo-progress="24"] { --progress: 24%; }
.page--design-system [data-demo-progress="64"] { --progress: 64%; }
.page--design-system [data-demo-progress="88"] { --progress: 88%; }

/* The count specimens name both numbers and let the fill work itself out. */
.page--design-system [data-demo-count="84of120"] { --progress-count: 84; --progress-total: 120; }
.page--design-system [data-demo-ring="72of100"] { --progress-count: 72; --progress-total: 100; }
.page--design-system [data-demo-ring="34of40"] { --progress-count: 34; --progress-total: 40; }
.page--design-system [data-demo-slider="42"] { --slider-start: 0%; --slider-end: 42%; }
.page--design-system [data-demo-slider="range"] { --slider-start: 28%; --slider-end: 72%; }
.page--design-system .video-progress__buffer { --video-buffer: 46%; }

/* The miniature chart the popover specimen hangs off. Each bar is a real
   button so the popover has a real trigger to be anchored to and tabbed to;
   the height is the only thing that varies, and it comes from an attribute
   rather than a style attribute, the way the progress and slider demos do. */
.page--design-system .demo-chart {
  position: relative;
  height: var(--space-160);
  padding: var(--space-12);
  display: flex;
  align-items: flex-end;
  gap: var(--space-8);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-compact);
  background: var(--surface-panel);
}

.page--design-system .demo-chart__bar {
  --demo-bar: 50%;

  min-width: 0;
  width: 100%;
  height: var(--demo-bar);
  flex: 1 1 0;
  padding: 0;
  align-self: flex-end;
  border: 1px solid var(--mindset-violet-400);
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  background: color-mix(in srgb, var(--mindset-violet-500) 28%, transparent);
  cursor: pointer;
  transition: var(--transition-interactive);
}

.page--design-system .demo-chart__bar:is(:hover, [aria-expanded="true"]) {
  background: color-mix(in srgb, var(--mindset-violet-500) 68%, transparent);
}

/* The anchor has to be the bar, not the column: `bottom: 100%` is measured
   from the anchor, so an anchor stretched to the full plot height would park
   the bubble above the whole chart instead of above the mark it describes.
   The height therefore lives here and the bar fills it. */
.page--design-system .demo-chart .sp-chart-popover-anchor {
  min-width: 0;
  height: var(--demo-bar);
  flex: 1 1 0;
  align-self: flex-end;
}

.page--design-system .demo-chart .sp-chart-popover-anchor .demo-chart__bar { height: 100%; }

.page--design-system [data-demo-bar="34"] { --demo-bar: 34%; }
.page--design-system [data-demo-bar="52"] { --demo-bar: 52%; }
.page--design-system [data-demo-bar="71"] { --demo-bar: 71%; }
.page--design-system [data-demo-bar="88"] { --demo-bar: 88%; }
.page--design-system [data-demo-bar="63"] { --demo-bar: 63%; }

/* The confined-space specimen: a deliberately narrow frame, so the stacked
   form and the horizontal nudge can both be seen doing their job. Width is
   the constraint being shown; the frame does not clip vertically, because a
   popover a card has swallowed is not a demonstration of anything. */
.page--design-system .demo-chart--tight { width: min(var(--space-256), 100%); }

.page--design-system .base-component-panel .sp-dropdown__menu {
  position: static;
  width: 100%;
  margin-top: var(--space-8);
  max-height: none;
}

.sp-avatar__placeholder {
  font-family: var(--skillprint-font-ui);
  font-size: calc(var(--avatar-size) * 0.34);
  font-weight: var(--weight-semibold);
  line-height: 1;
}

.page--design-system .base-component-panel .video-player {
  max-width: var(--space-640);
}

.sp-slider__value.sp-slider__value--end {
  --slider-position: var(--slider-end);
}

/* ---------------------------------------------------------------------------
   Interface icons (Untitled UI — assets/icons/untitledui-icons.svg)

   `.sp-icon` is the stroke-icon contract: a 24x24 grid, no fill, stroke in
   currentColor. It is deliberately separate from the master's `.icon-asset`,
   which renders fixed artwork (logos, game art, brand marks) and is recoloured
   with filters rather than currentColor.

   Size comes from the master's --icon-size-* primitives, weight from
   --icon-stroke-*. Never set width/height on the <svg> directly.

   Weight travels as the custom property --icon-stroke, not as stroke-width.
   Icons are referenced with <use>, and a <symbol>'s own stroke-width
   presentation attribute beats a value inherited into that shadow tree - so
   the stroke-width below styles only the <svg> box and never reaches the
   paths. Custom properties do cross the boundary, and each symbol is drawn
   stroke-width="var(--icon-stroke, 2)" to pick this up. Set --icon-stroke to
   change weight; stroke-width alone is inert.
   -------------------------------------------------------------------------- */
.sp-icon {
  width: var(--icon-size, var(--icon-size-md));
  height: var(--icon-size, var(--icon-size-md));
  flex: 0 0 auto;
  display: inline-block;
  vertical-align: middle;
  fill: none;
  stroke: currentColor;
  stroke-width: var(--icon-stroke, var(--icon-stroke-md));
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Each size takes the weight token drawn for it. --md is the anchor: 24px is
   the grid the symbols are drawn on, so it needs no correction. */
.sp-icon--3xs { --icon-size: var(--icon-size-3xs); --icon-stroke: var(--icon-stroke-10); }
.sp-icon--2xs { --icon-size: var(--icon-size-2xs); --icon-stroke: var(--icon-stroke-12); }
.sp-icon--xs { --icon-size: var(--icon-size-xs); --icon-stroke: var(--icon-stroke-16); }
.sp-icon--sm { --icon-size: var(--icon-size-sm); --icon-stroke: var(--icon-stroke-20); }
.sp-icon--md { --icon-size: var(--icon-size-md); --icon-stroke: var(--icon-stroke-24); }
.sp-icon--lg { --icon-size: var(--icon-size-lg); --icon-stroke: var(--icon-stroke-32); }
.sp-icon--xl { --icon-size: var(--icon-size-xl); --icon-stroke: var(--icon-stroke-48); }

/* ---------------------------------------------------------------------------
   Inline icon sizes

   Icons drawn inside a component (a chevron in a link, a mark in a nav row)
   are sized by the component, not by .sp-icon. Those sizes were restated in
   thirty-odd places; one rule per step means an icon size is one edit.
   -------------------------------------------------------------------------- */

.page--design-system .trait-module .trait-chip .mini-icon svg {
  width: var(--space-12);
  flex-basis: auto;
  height: var(--space-12);
  --icon-stroke: var(--icon-stroke-12);
}

.page--design-system .homepage-mini-brand img,
.page--design-system .docs-mini-brand img,
.page--design-system .labs-mini-brand img,
.page--design-system .portal-library-embed .clear-search svg,
.page--portal .clear-search svg,
.page--design-system .portal-library-embed .duration svg,
.page--portal .duration svg,
.page--design-system .partner-mini-brand img,
.page--design-system .deck-mini-foot img {
  width: var(--space-14);
  flex-basis: auto;
  height: var(--space-14);
  --icon-stroke: var(--icon-stroke-14);
}

.page--design-system .portal-library-embed .featured-meta svg,
.page--portal-games .featured-meta svg,
.page--seed-deck .pipeline-step__icon svg,
.page--seed-deck .proof-card__icon svg {
  width: var(--space-16);
  flex-basis: auto;
  height: var(--space-16);
  --icon-stroke: var(--icon-stroke-16);
}

.page--design-system .trait-module .trait-card-top .icon-box svg,
.page--design-system .sp-popup-close svg,
.page--design-system .interface-preview-mark img,
.page--design-system .portal-library-embed .modal-close svg,
.page--portal .modal-close svg,
.page--seed-deck .thesis-card__icon svg {
  width: var(--space-18);
  flex-basis: auto;
  height: var(--space-18);
  --icon-stroke: var(--icon-stroke-18);
}

.page--documentation-landing .terminal-icon svg {
  width: var(--space-20);
  flex-basis: auto;
  height: var(--space-20);
  --icon-stroke: var(--icon-stroke-20);
}

.page--design-system .trait-module .icon-box svg {
  width: var(--space-22);
  flex-basis: auto;
  height: var(--space-22);
  --icon-stroke: var(--icon-stroke-22);
}

.page--design-system .trait-module .trait-chip .mini-icon,
.page--design-system .source-surface-icon img,
.page--game-session .game-result__icon svg,
.page--docs-v2 .brand-mark {
  width: var(--space-24);
  flex-basis: auto;
  height: var(--space-24);
  --icon-stroke: var(--icon-stroke-16);
}

.page--design-system .anatomy-item b,
.page--design-system .surface-identity-mark img,
.page--docs-v2 .quick-arrow {
  width: var(--space-28);
  flex-basis: auto;
  height: var(--space-28);
}

.page--design-system .trait-module .guidance-head b {
  width: var(--space-32);
  flex-basis: auto;
  height: var(--space-32);
}

/* Controls that size their own icons.

   `.button svg` takes its box from --button-icon-size, which overrides the
   size a .sp-icon--* modifier would set. Weight has to follow the box or the
   icon is drawn at the wrong optical weight, so the stroke is restated here
   against the same step the button uses: 20px at the default size, 16px at
   --xs. Written as a descendant selector so it outranks any size modifier
   left on the icon itself - inside a button the button owns both values.

   Without this an icon in a button inherits nothing and each symbol falls
   back to its own stroke-width="var(--icon-stroke, 2)", drawing every size
   at a flat 2px. */
.button svg,
.button__icon {
  --icon-stroke: var(--icon-stroke-20);
}

.button--xs svg,
.button--xs .button__icon {
  --icon-stroke: var(--icon-stroke-16);
}

/* The badge sizes its own icon the same way and needs the same pairing. */
.ui-badge--md .ui-badge__icon,
.ui-badge--lg .ui-badge__icon {
  --icon-stroke: var(--icon-stroke-14);
}

/* The separation between a badge's leading glyph and its label. Part A pairs
   them at 4px, and at this type size the two read as one word rather than as a
   marker and a word - the glyph needs to sit clear of the first letter. This
   adds 2px on top of whatever gap wins.

   It is a margin rather than a larger gap because gap here is contested and
   margin is not. Most badge markup carries the .gap-xs utility as well as
   .ui-badge--leading, and .gap-xs is declared later in this file at the same
   specificity, so it is the utility - not the component - that actually sets
   the 4px. Raising the component's gap would mean out-ordering or
   out-specifying every one of those, and any per-page gap written later would
   silently take the spacing back. Nothing in the system sets a margin on these
   slots, so +2px lands identically on all 30 of them, on every page, without
   touching Part A or contesting a single existing rule.

   :first-child holds it to the leading edge. The dismissible badges close with
   an icon too, and that one stays tight to its label. */
.ui-badge > :is(
    .ui-badge__icon,
    .ui-badge__dot,
    .ui-badge__avatar,
    .ui-badge__flag
  ):first-child {
  margin-inline-end: var(--space-2);
}

/* ---------------------------------------------------------------------------
   Icon frame

   A bordered box around an icon. The rule is that the corner radius tracks the
   box, not the icon: a 24px frame at the same radius as a 72px one reads as a
   different shape family. Each step pairs one icon size with the radius and
   padding that keep the optical inset even, so a frame is chosen by name
   rather than by hand-tuning three values.

       xs  24px box · 16px icon · radius-xs
       sm  32px box · 20px icon · radius-sm
       md  40px box · 24px icon · radius-md
       lg  48px box · 32px icon · radius-lg
       xl  64px box · 48px icon · radius-xl
   -------------------------------------------------------------------------- */
.sp-icon-frame {
  --icon-frame-size  : var(--space-40);
  --icon-frame-radius: var(--radius-md);

  width: var(--icon-frame-size);
  height: var(--icon-frame-size);
  flex: 0 0 var(--icon-frame-size);
  display: grid;
  place-items: center;
  border: 1px solid var(--surface-border);
  border-radius: var(--icon-frame-radius);
  background: var(--surface-box);
  color: var(--surface-text);
}

.sp-icon-frame--xs { --icon-frame-size: var(--space-24); --icon-frame-radius: var(--radius-xs); }
.sp-icon-frame--sm { --icon-frame-size: var(--space-32); --icon-frame-radius: var(--radius-sm); }
.sp-icon-frame--md { --icon-frame-size: var(--space-40); --icon-frame-radius: var(--radius-md); }
.sp-icon-frame--lg { --icon-frame-size: var(--space-48); --icon-frame-radius: var(--radius-lg); }
.sp-icon-frame--xl { --icon-frame-size: var(--space-64); --icon-frame-radius: var(--radius-xl); }

/* A round frame keeps its own shape at every size. */
.sp-icon-frame--round { --icon-frame-radius: var(--radius-full); }

/* Emphasis: the brand frame carries the accent, the plain one stays neutral. */
.sp-icon-frame--brand {
  border-color: color-mix(in srgb, var(--mindset-violet-500) 42%, transparent);
  background: color-mix(in srgb, var(--mindset-violet-500) 12%, transparent);
  color: var(--violet);
}

.sp-icon--muted { color: var(--surface-text-muted); }
.sp-icon--success { color: var(--core-green-500); }
.sp-icon--warning { color: var(--skills-orange-500); }
.sp-icon--danger { color: var(--skills-pink-500); }
.sp-icon--mood { color: var(--core-lime-500); }
.sp-icon--cognition { color: var(--personality-mint-500); }
.sp-icon--personality { color: var(--personality-blue-500); }

html:is([data-theme="light"], [data-surface="light"]) .sp-icon--brand { color: var(--mindset-violet-500); }
html:is([data-theme="light"], [data-surface="light"]) .sp-icon--success { color: var(--core-green-700); }
html:is([data-theme="light"], [data-surface="light"]) .sp-icon--warning { color: var(--skills-orange-600); }
html:is([data-theme="light"], [data-surface="light"]) .sp-icon--danger { color: var(--skills-pink-600); }
html:is([data-theme="light"], [data-surface="light"]) .sp-icon--mood { color: var(--core-lime-700); }
html:is([data-theme="light"], [data-surface="light"]) .sp-icon--cognition { color: var(--personality-mint-700); }
html:is([data-theme="light"], [data-surface="light"]) .sp-icon--personality { color: var(--personality-blue-600); }

.sp-tooltip {
  position: relative;
  display: inline-flex;
}

.sp-tooltip__bubble {
  position: absolute;
  left: 50%;
  z-index: 60;
  width: max-content;
  max-width: var(--space-256);
  padding: var(--space-6) var(--space-8);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-md);
  background: var(--surface-raised);
  color: var(--surface-text);
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  box-shadow: var(--shadow-md);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateX(-50%);
  transition: var(--transition-interactive);
}

.sp-tooltip[data-placement="top"] .sp-tooltip__bubble {
  bottom: calc(100% + var(--space-6));
}

.sp-tooltip[data-placement="bottom"] .sp-tooltip__bubble {
  top: calc(100% + var(--space-6));
}

.sp-tooltip:is(:hover, :focus-within) .sp-tooltip__bubble {
  opacity: 1;
  visibility: visible;
}

.sp-tooltip__bubble--data {
  min-width: 176px;
  padding: var(--space-10) var(--space-12);
  border-color: var(--surface-border-hover);
  border-radius: var(--radius-lg);
  background: var(--surface-box);
  text-align: left;
}

.sp-tooltip__bubble--data strong {
  display: block;
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
}

.sp-tooltip__bubble--data span {
  display: block;
  margin-top: var(--space-2);
  color: var(--surface-text-muted);
  font: var(--weight-medium) var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono);
}

.sp-sprite { position: absolute; overflow: hidden; }

.sp-table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

.sp-table :is(th, td) {
  padding: var(--space-12) var(--space-16);
  border-bottom: 1px solid var(--surface-border);
  text-align: left;
  vertical-align: top;
}

.sp-table th {
  color: var(--surface-text-muted);
  font-family: var(--skillprint-font-mono);
  font-size: var(--text-xs-font-size);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.sp-table td { color: var(--surface-text); }
.sp-table tr:last-child :is(th, td) { border-bottom: 0; }

/* The current item, for the two hooks Part A does not already list. Part A
   covers :hover, [aria-pressed="true"] and .is-current; the shared script
   toggles `is-active`, and some markup carries a bare `active`. All three
   named the same state and each had its own copy of this declaration in a
   different part of the sheet, so they are one rule now. */
.button-group__item.is-active,
.button-group__item.active {
  background: var(--button-group-current);
}

.popup-backdrop:not(.is-open):not([data-open="true"]) {
  display: none;
}

.popup-backdrop.is-open,
.popup-backdrop[data-open="true"] {
  display: grid;
}

/* ==========================================================================
   06 - Utilities · shared roles and variants
   ========================================================================== */

/* The hero layout family. The grid has three variants - benchmark, portal
   and seed-deck - each declaring the same two-column shape, and
   nothing declared what they share, so this is that base; .hero-inner is the
   single-column form and needs it for the same reason.

   A grid item's default minimum is min-content, not zero. The wide layouts
   sidestep it by writing minmax(0, 1fr), but every narrow override collapses
   to a plain `1fr`, which is minmax(auto, 1fr) and puts the floor back: the
   column then refuses to go below its longest unbreakable line and the hero
   pushes past the viewport. Clearing the minimum on the items themselves
   holds at every breakpoint, whichever track definition is in play. */
.hero-grid > *,
.hero-inner > * {
  min-width: 0;
}

/* Paired logo artwork: the markup carries both variants and the theme decides
   which one is drawn. Part A states this for .sp-nav and .sp-side-nav, and the
   design system states it again for its own specimens, but a surface that is
   neither - the documentation apps' mobile header - matched no rule and drew
   the dark and light lockups side by side. This is the component's default, so
   any surface carrying the pair gets it; the scoped rules above still win
   where a page needs something different. */
.brand-logo--light,
html:is([data-surface="light"], [data-theme="light"]) .brand-logo--dark {
  display: none;
}

html:is([data-surface="light"], [data-theme="light"]) .brand-logo--light {
  display: block;
}


@keyframes homepage-pulse {
  0%,
  to {
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--core-green-500) 28%, transparent);
  }
  50% {
  box-shadow: 0 0 0 7px transparent;
  }
}

@keyframes homepage-targetmove {
  0%,
  to {
  left: 16%;
  top: 22%;
  }
  35% {
  left: 73%;
  top: 28%;
  }
  68% {
  left: 60%;
  top: 75%;
  }
}

@keyframes homepage-scan {
  0%,
  to {
  top: 18%;
  }
  50% {
  top: 82%;
  }
}

@keyframes seed-deck-orbit {
  to {
  transform: rotate(360deg);
  }
}

@keyframes seed-deck-pulse {
  50% {
  transform: scale(1.1);
  opacity: 0.78;
  }
}

.page {
  --deep: var(--deep-navy);
  --panel-950: var(--panel-navy-950);
  --panel-900: var(--panel-navy-900);
  --panel-800: var(--panel-navy-800);
  --panel-700: var(--panel-navy-700);
  --panel-600: var(--panel-navy-600);
  --text: var(--text-primary);
  --text-strong: var(--deep-navy-25);
  --muted: var(--text-muted);
  --border: var(--border-subtle);
  --border-strong: rgb(248 250 252 / 18%);
  --border-hover: rgb(248 250 252 / 28%);
  /* The category accent tier. These are the shades a category wears when the
     colour is the FOREGROUND - an icon, a label, a chip, a trait indicator, a
     bar, a chart stroke. They are mode-specific by design: see the light block
     below, and section 06 of index.html, which documents the four defaults.

     Fills are deliberately NOT on this tier. A filled control puts white text
     on the colour, which runs the opposite way: white on violet-500 is 6.38:1
     but only 3.69:1 on violet-400. --button-primary keeps its own 500/600/700
     ladder for that reason, and so does --skillprint-action-violet, which is
     the brand mark and the focus ring. Re-pointing either at this tier would
     trade a legible icon for an illegible button. */
  --lime: var(--core-lime-500);
  --green: var(--core-green-500);
  --mint: var(--personality-mint-500);
  --blue: var(--personality-blue-500);
  --violet: var(--mindset-violet-400);
  --magenta: var(--mindset-magenta-400);
  --pink: var(--skills-pink-500);
  --orange: var(--skills-orange-500);

  /* The three trait pillars, restated here rather than chained from :root.
     :root declares them against the raw ramp, and a custom property is
     substituted where it is DECLARED - so `--mood-500: var(--lime)` on :root
     would resolve --lime on :root, where this tier does not exist, and come
     back empty. Restating them on .page is what lets Mood and Cognition follow
     the mode. The :root pair stays as the fallback for anything outside .page.
     The names keep their -500 suffix: it is the semantic step, not a promise
     about which rung of the ramp answers it. */
  --mood-500: var(--lime);
  --cognition-500: var(--mint);
  --personality-500: var(--blue);

  /* The current row's leading icon. Restated here for the same substitution
     reason as the pillars above: :root declares it against the raw ramp and
     cannot see this tier. It is a `color:` and nothing else, so it is a pure
     foreground and follows the mode. */
  --side-nav-accent: var(--violet);
  --font-ui: var(--skillprint-font-ui);
  --font-mono: var(--skillprint-font-mono);
  --font-brand: var(--skillprint-font-display);
  --surface-radius-md: var(--radius-card);
  --surface-radius-lg: var(--radius-panel);
}

/* The light half of the category accent tier.

   Measured on the two grounds each mode actually uses - deep navy #090d27 and
   panel #212549 for dark, page #f8f9fc and card #fcfcfd for light:

     violet   500 -> 3.00 on deep navy      400 -> 5.18   dark takes 400
     magenta  500 -> 4.39 on deep navy      400 -> 6.67   dark takes 400
     lime     500 -> 1.29 on light page     600 -> 1.74   light takes 600
     mint     500 -> 1.20 on light page     600 -> 1.62   light takes 600

   Lime and mint are the honest caveat. 600 is a real improvement and it is the
   agreed default, but neither step reaches 4.5:1 on a light ground - nothing
   below the 800 step does, because these two ramps are built bright. They are
   correct here for what this tier is FOR: fills, bars, chips, swatches and
   markers, where the shape carries the meaning. Where lime or mint has to be
   READ as text or as a hairline icon on a light ground, the system already has
   the answer one tier over: --accent-text and --positive-text resolve to the
   900 step in light mode. That split predates this change and still holds.

   The fourth selector matters as much as the first three: a light specimen
   island sitting inside a dark page (.panel-surface-demo--light, .chart-card
   .light) is a light ground too, and gets the light shades. */
.page.light,
html[data-surface="light"] > .page,
html[data-theme="light"] > .page,
.page :where(.panel-surface-demo--light, .surface-demo--light, .chart-card.light) {
  --lime: var(--core-lime-600);
  --mint: var(--personality-mint-600);
  --violet: var(--mindset-violet-500);
  --magenta: var(--mindset-magenta-500);
}

.page :where(.layout-grid) { display: grid; }
.page :where(.layout-flex) { display: flex; }
.page :where(.layout-inline-flex) { display: inline-flex; }

html:is([data-theme="light"], [data-surface="light"]) > .page {
  --deep: var(--deep-navy-25);
  --panel-950: var(--panel-navy-50);
  --panel-900: var(--geistface-grey-25);
  --panel-800: var(--geistface-grey-100);
  --panel-700: var(--panel-navy-100);
  --panel-600: var(--geistface-grey-300);
  --text: var(--geistface-grey-800);
  --text-strong: var(--deep-navy-900);
  --muted: rgb(68 75 97 / 82%);
  --border: color-mix(in srgb, var(--deep-navy-900) 11%, transparent);
  --border-strong: color-mix(in srgb, var(--deep-navy-900) 20%, transparent);
  --border-hover: color-mix(in srgb, var(--deep-navy-900) 28%, transparent);
}

[data-surface="dark"] {
  color-scheme: dark;
  --page: var(--deep-navy);
  --surface: var(--panel-navy-900);
  --surface-raised: var(--panel-navy-800);
  --text: var(--text-primary);
  --muted: var(--text-muted);
  --border: var(--border-subtle);
}

[data-surface="light"] {
  color-scheme: light;
  --page: var(--deep-navy-25);
  --surface: var(--geistface-grey-25);
  --surface-raised: var(--geistface-grey-100);
  --text: var(--geistface-grey-800);
  --muted: rgb(68 75 97 / 82%);
  --border: color-mix(in srgb, var(--deep-navy-900) 11%, transparent);
}

/* Type across these surfaces is authored in px, so a device's own text-size
   setting cannot scale it - that is a known limitation, not something this
   declaration fixes. What it does stop is the other direction: iOS Safari
   inflating body copy when the phone turns to landscape, which re-sizes text
   the layout was measured for. Stated outright rather than left to the
   viewport meta tag to imply. */
html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}
body { background: var(--page, var(--deep-navy)); color: var(--text, var(--text-primary)); }
img, svg { max-width: 100%; }
.margin-none { margin: 0; }
.space-top-2xl { margin-top: var(--spacing-2xl); }
.padding-2xl { padding: var(--spacing-2xl); }
.position-absolute { position: absolute; }
/* Four helpers the markup already asked for by name and never got. The missing
   one that mattered was .position-fixed: a tooltip with left/top set from the
   pointer but no positioning scheme keeps them as dead declarations and the
   panel opens wherever the flow puts it. */
.position-fixed { position: fixed; }
.items-end { align-items: flex-end; }
.items-stretch { align-items: stretch; }
.space-top-lg { margin-top: var(--spacing-lg); }
.justify-center { justify-content: center; }
.height-auto { height: auto; }
.push-block-end { margin-top: auto; }
.separator-bottom { border-bottom: 1px solid var(--ui-border); }
.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
.space-bottom-2xl { margin-bottom: var(--spacing-2xl); }
.padding-none { padding: 0; }
.padding-xl { padding: var(--spacing-xl); }
.space-bottom-3xl { margin-bottom: var(--spacing-3xl); }
.space-top-3xl { margin-top: var(--spacing-3xl); }
.border-none { border: 0; }
.text-left { text-align: left; }
/* .text-positive is not here: it takes --positive-text with the other success
   marks further down, which is the token that darkens to Core Green 900 in
   light mode. A `color: var(--green)` copy here only shadowed that. */
.separator-top { border-top: 1px solid var(--ui-border); }
.no-grow { flex: 0 0 auto; }
.padding-3xl { padding: var(--spacing-3xl); }
.text-deep { color: var(--deep-navy); }
.inset-none { inset: 0; }
.overflow-visible { overflow: visible; }
.text-subtle { color: var(--subtle); }
.opaque { opacity: 1; }
.font-md { font-size: var(--font-size-md); }
.gap-2xl { gap: var(--spacing-2xl); }
.radius-round { border-radius: 50%; }
.radius-full { border-radius: var(--radius-pill); }
.font-sm { font-size: var(--font-size-sm); }
.gap-3xl { gap: var(--spacing-3xl); }
.gap-md { gap: var(--spacing-md); }
.no-underline { text-decoration: none; }
.leading-sm { line-height: var(--line-height-sm); }
.text-muted { color: var(--ui-muted); }

.border-subtle { border: 1px solid var(--ui-border); }
.radius-control { border-radius: var(--radius-control); }
.radius-compact { border-radius: var(--radius-compact); }
.gap-lg { gap: var(--spacing-lg); }
.tracking-wide { letter-spacing: 0.1em; }

.font-xs { font-size: var(--font-size-xs); }
.radius-lg { border-radius: var(--radius-lg-system); }
.gap-xs { gap: var(--spacing-xs); }
.leading-compact { line-height: var(--text-xxs-line-height); }

.gap-xl { gap: var(--spacing-xl); }
.push-inline-end { margin-left: auto; }
.border-strong { border: 1px solid var(--ui-border-strong); }
.leading-md { line-height: var(--line-height-md); }
.text-default { color: var(--ui-text); }
.no-wrap { white-space: nowrap; }
.text-inverse { color: var(--geistface-grey-25); }
.gap-4xl { gap: var(--spacing-4xl); }
.font-mono { font-family: var(--skillprint-font-mono); }
.tracking-label { letter-spacing: 0.08em; }
.font-ui { font-family: var(--skillprint-font-ui); }
.font-base { font-size: var(--font-size-base); }
.leading-base { line-height: var(--line-height-base); }
.font-2xs { font-size: 9px; }
.surface-transparent { background: transparent; }
.radius-card { border-radius: var(--radius-card); }
.gap-sm { gap: var(--spacing-sm); }
.radius-panel { border-radius: var(--radius-panel); }
.leading-lg { line-height: 22px; }
.text-strong { color: var(--text-strong); }
.wrap { flex-wrap: wrap; }
.clip { overflow: hidden; }

/* Single-line truncated label. One rule for every place that clips a title to
   its column; sits with .clip and .no-wrap, the primitives it composes. */
.sp-avatar-label__copy > *,
.page--design-system .api-module .code-title span:last-child,
.page--docs-v2 .nav-endpoint span:last-child,
.page--docs-v2 .code-panel__title,
.page--docs-v2 .code-panel__route {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.position-relative { position: relative; }
.min-width-0 { min-width: 0; }
.full-width { width: 100%; }

.page :where(h1, h2, h3, h4) {
  margin: 0;
  color: var(--ui-text);
  font-family: var(--skillprint-font-ui);
  font-weight: var(--weight-regular);
  letter-spacing: -0.025em;
}

.page :where(h1) {
  font-size: clamp(44px, 6vw, 72px);
  line-height: 1.08;
  letter-spacing: -0.05em;
}

.page :where(h2) {
  font-size: clamp(32px, 4vw, 48px);
  line-height: 1.16;
  letter-spacing: -0.035em;
}

.page :where(h3) {
  font-size: var(--text-xl-font-size);
  line-height: 1.4;
  font-weight: var(--weight-regular);
}

.page :where(h4) {
  font-size: var(--font-size-base);
  line-height: var(--line-height-base);
  font-weight: var(--weight-regular);
}

/* Same type as the design system's .section-index, so an eyebrow reads the same
   on the marketing site and the prototypes as it does in the style guide. It
   was a step smaller and tighter here (10px/0.1em against 12px/0.14em), which
   is what made the website eyebrows read as captions rather than as section
   labels. .eyebrow--compact remains the deliberate small one. */
.page :where(.eyebrow) {
  width: max-content;
  max-width: 100%;
  margin: 0 0 var(--spacing-lg);
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-md);
  color: var(--ui-muted);
  font-family: var(--skillprint-font-ui);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-sm);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* The section eyebrow. One class for the small uppercase label that sits above
   a heading, so every surface uses the same contract instead of .eyebrow in
   some places and .card-kicker in others. It carries the structure .eyebrow
   used to supply; the type comes from the utilities the markup already has. */
.page--design-system .section-index {
  width: max-content;
  max-width: 100%;
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-md);
  margin: 0 0 var(--spacing-lg);
}

/* In the two-column section head the eyebrow sits beside the heading, spaced
   by the grid, so it must not add a margin of its own. */
.section-head--design-system .section-index { margin-bottom: 0; }

.page :where(.eyebrow--compact) {
  margin-bottom: var(--spacing-md);
  font-size: var(--font-size-xs);
  line-height: var(--text-xxs-line-height);
}

.page :where(.eyebrow--pill) {
  min-height: var(--space-30);
  padding: var(--spacing-xs) var(--spacing-md);
  border: 1px solid var(--ui-border-strong);
  border-radius: var(--radius-pill);
  background: var(--ui-surface);
}

.page :where(.grid) { display: grid; gap: var(--grid-gap); }
.page :where(.grid-2) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.page :where(.grid-3) { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.page :where(.grid-4) { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.page :where(.grid-6) { grid-template-columns: repeat(6, minmax(0, 1fr)); }
.page :where(.stack) { display: grid; gap: var(--spacing-xl); }

.page :where(.type--display-md) {
  font-size: clamp(24px, 3vw, var(--display-md-font-size));
  line-height: 1.22;
  letter-spacing: -0.03em;
}

.page :where(.cluster) {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--spacing-lg);
}

.page :where(input, select, textarea) {
  border: 1px solid var(--ui-border-strong);
  border-radius: var(--radius-control);
  background: var(--ui-surface);
  color: var(--ui-text);
  font-family: var(--skillprint-font-ui);
  transition: var(--transition-interactive);
}

.page :where(input, select, textarea):focus { border-color: var(--focus-ring-color); }

.page :where(.icon, .icon-box, .icon-button) {
  display: inline-grid;
  place-items: center;
  flex: 0 0 auto;
}

.page--design-system .master-subsection > .subsection-heading h2 {
  font-size: clamp(24px, 2.2vw, 28px);
  line-height: 1.25;
  letter-spacing: -0.02em;
  font-weight: var(--weight-semibold);
}

/* A section with no heading of its own is led by its first subsection, so those
   subsections carry the section scale instead. Keyed on the absence of the
   head rather than on a section name, so it corrects itself either way. */
.page--design-system .master-section:not(:has(> .section-inner > .section-head))
  .master-subsection > .subsection-heading h2 {
  font-size: clamp(32px, 4vw, 48px);
  line-height: 1.16;
  letter-spacing: -0.035em;
  font-weight: var(--weight-medium);
}

.page--design-system .master-section:not(:has(> .section-inner > .section-head))
  .master-subsection > .subsection-heading .card-kicker {
  font-size: var(--text-xs-font-size);
}


.page--design-system .copy-ready-block[data-clamped] { position: relative; }

.page--design-system .copy-ready-block[data-clamped="false"] pre { max-height: none; }

.page--design-system .copy-ready-actions {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  flex: 0 0 auto;
}

.page--design-system .section-inner > .copy-ready-section h3 {
  font-size: clamp(21px, 1.7vw, 24px);
  line-height: 1.3;
}

.page--design-system .master-subsection > .subsection-heading .card-kicker,
.page--design-system .section-inner > .copy-ready-section > * > .card-kicker,
.page--design-system .section-inner > .copy-ready-section .copy-ready-heading .card-kicker { font-size: var(--text-xs-font-size); }


.page--design-system .ontology-interface-mini {
  min-height: 410px;
  padding: var(--space-24);
  background: var(--deep-navy-950);
}

.page--design-system .ontology-interface-mini svg { max-width: 300px; }

.page :where(code, kbd, samp) { font-family: var(--skillprint-font-mono); }

.page--design-system .study-card :is(h4, p) { color: inherit; }

.page--design-system :is(.state-matrix, .token-table-wrap) {
  min-width: 0;
  overflow-x: auto;
}

/* The colour scale is documented on two pages now - the design system itself
   and the Tailwind handoff - so its layout is one contract shared by both
   rather than a rule that belongs to whichever page was written first. */
:is(.page--design-system, .page--tailwind-handoff) :is(.scale-group, .scale-row, .scale-head) { min-width: 0; }
:is(.page--design-system, .page--tailwind-handoff) .scale-head > * { min-width: 0; }

/* A single-handle slider is a range whose start is pinned to zero, so the one
   thing that moves is --slider-end. Part A points the handle at --slider-start,
   which is right for the first handle of a real range and wrong for both the
   handle and its value label here - without this the badge parks at 0% while
   the handle it is meant to be reporting sits somewhere else on the track. */
.sp-slider[data-single-value] :is(.sp-slider__handle, .sp-slider__value) {
  --slider-position: var(--slider-end);
}

.button--primary.is-hover-demo { background: var(--button-primary-hover); }
.button--secondary.is-hover-demo { background: var(--button-surface-hover); }

.button--tertiary.is-hover-demo {
  background: var(--button-surface-hover);
  color: var(--button-text);
}

.button--link-color.is-hover-demo,
.button--link-gray.is-hover-demo {
  text-decoration: underline;
  text-underline-offset: var(--space-2);
}

.button--danger.is-hover-demo {
  background: var(--button-danger-hover);
  color: var(--button-text-strong);
}

.button.is-focus-demo {
  outline: 0;
  box-shadow:
    var(--button-current-shadow),
    0 0 0 var(--focus-ring-width) var(--button-focus-inner),
    0 0 0 calc(var(--focus-ring-width) * 2) var(--button-focus);
}

.button--danger.is-focus-demo {
  box-shadow:
    var(--button-current-shadow),
    0 0 0 var(--focus-ring-width) var(--button-focus-inner),
    0 0 0 calc(var(--focus-ring-width) * 2) var(--button-danger);
}

.button--primary.is-active-demo { background: var(--button-primary-active); }
.button--secondary.is-active-demo,
.button--tertiary.is-active-demo { background: var(--button-surface-hover); }
.button.is-active-demo { transform: translateY(var(--space-2)); }
.button--unstyled.is-active-demo { transform: none; }

html[data-surface="light"] > .page--design-system .integrated-module .page,
html[data-theme="light"] > .page--design-system .integrated-module .page {
  --deep: inherit;
  --panel-950: inherit;
  --panel-900: inherit;
  --panel-800: inherit;
  --panel-700: inherit;
  --panel-600: inherit;
  --text: inherit;
  --text-strong: inherit;
  --muted: inherit;
  --border: inherit;
  --border-strong: inherit;
  --border-hover: inherit;
}

/* Reading colour for a success mark, and for the mint accent that carries a
   measured value. Both brand tints are drawn for a dark surface and fall to
   about 1.1:1 on a light one, so each has a light-mode counterpart at the dark
   end of its own ramp. Use these rather than --green or --mint wherever the
   colour is on text or on an icon that has to be read. */
.page {
  --positive-text: var(--green);
  --accent-text: var(--mint);
}

html[data-surface="light"] > .page,
html[data-theme="light"] > .page {
  --positive-text: var(--core-green-900);
  --accent-text: var(--personality-mint-900);
}

.chart-card.light,
.page :where(.logo-stage--light, .panel-surface-demo--light, .surface-demo--light) {
  --positive-text: var(--core-green-900);
  --accent-text: var(--personality-mint-900);
}

.field[data-state="success"] .field__help,
.field__status[data-tone="success"],
.chart-card .stat small,
.chart-positive,
.dropzone__files,
.sp-icon--success,
.page--design-system .labs-mini-stats i,
.page--design-system .deck-mini-kicker,
.page--design-system .deck-mini-accent {
  color: var(--positive-text);
}

.text-positive,
.page--design-system .field.success .help,
.page--partner-analysis .flow-insight .sp-icon,
.page--partner-analysis .processed,
.page--partner-analysis .pipeline-stat strong.success {
  color: var(--positive-text);
}

.editor-toolbar {
  padding: var(--space-10) var(--space-20);
  border-bottom: 1px solid var(--ui-border);
}

.editor-footer {
  align-items: center;
  padding: var(--space-12) var(--space-20);
  border-top: 1px solid var(--ui-border);
}

.editor-shell--sm .editor-input { padding: var(--space-16); }

.editor-shell--sm :is(.editor-toolbar, .editor-footer) {
  padding: var(--space-10) var(--space-16);
}

.page :where(.code-block, .code-card) {
  overflow: hidden;
  border: 1px solid var(--ui-border);
  border-radius: var(--radius-card);
  background: var(--code-bg);
  color: var(--code-text);
  font-family: var(--skillprint-font-mono);
}

.page :where(.code-block pre, .code-card pre) {
  margin: 0;
  overflow: auto;
  font: inherit;
}

.page :focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

.page .card--compact {
  --card-current-padding: var(--card-padding-sm);
  --card-current-radius: var(--card-radius-sm);
}

.page .card--spacious { --card-current-padding: var(--card-padding-lg); }
.page .card--flush { --card-current-padding: 0px; }
.page .card--tall { --card-current-min-height: var(--card-min-height-tall); }
.page .palette-surface { background: var(--palette-surface); color: var(--palette-text); }
.page .surface--panel-950 { --palette-surface: var(--panel-navy-950); }
.page .surface--panel-900 { --palette-surface: var(--panel-navy-900); }
.page .surface--panel-800 { --palette-surface: var(--panel-navy-800); }
.page .surface--lime { --palette-surface: var(--core-lime-500); }
.page .surface--lime-soft { --palette-surface: var(--core-lime-100); }
.page .surface--green { --palette-surface: var(--core-green-500); }
.page .surface--mint { --palette-surface: var(--personality-mint-500); }
.page .surface--mint-soft { --palette-surface: var(--personality-mint-100); }
.page .surface--blue { --palette-surface: var(--personality-blue-500); }
.page .surface--blue-soft { --palette-surface: var(--personality-blue-100); }
.page .surface--violet { --palette-surface: var(--mindset-violet-500); }
.page .surface--violet-soft { --palette-surface: var(--mindset-violet-100); }
.page .surface--magenta { --palette-surface: var(--mindset-magenta-600); }
.page .surface--pink { --palette-surface: var(--skills-pink-500); }
.page .surface--pink-soft { --palette-surface: var(--skills-pink-100); }
.page .surface--orange { --palette-surface: var(--skills-orange-500); }
.page .surface--deep { --palette-surface: var(--deep-navy); }
.page .surface--light { --palette-surface: var(--deep-navy-25); }
/* The literal ramp step, not the --text-primary alias. A palette card's ground
   is a fixed brand colour, so the pairing it demonstrates has to hold still in
   both modes. --text-primary flips to geistface-grey-900 on a light surface,
   which put dark navy on violet and magenta at 1.88:1 in light mode and made
   three of the twenty-four cards show a pairing the system never specifies.
   This is the same reason .popup--dark restates its tokens as literals. */
.page .text--base { --palette-text: var(--geistface-grey-200); }
.page .text--deep { --palette-text: var(--deep-navy); }
.page .text--panel { --palette-text: var(--panel-navy-900); }
.page .text--lime { --palette-text: var(--core-lime-500); }
.page .text--mint { --palette-text: var(--personality-mint-500); }
.page .text--blue { --palette-text: var(--personality-blue-500); }
.page .text--violet { --palette-text: var(--mindset-violet-500); }
.page .text--violet-soft { --palette-text: var(--mindset-violet-100); }
.page .text--pink { --palette-text: var(--skills-pink-500); }
.page .text--orange { --palette-text: var(--skills-orange-500); }
.page .tone--lime { --tone: var(--core-lime-500); }
.page .tone--green { --tone: var(--core-green-500); }
.page .tone--mint { --tone: var(--personality-mint-500); }
.page .tone--blue { --tone: var(--personality-blue-500); }
.page .tone--violet, .page .tone--violet-soft { --tone: var(--mindset-violet-500); }
.page .tone--violet-light { --tone: var(--mindset-violet-100); }
.page .tone--magenta { --tone: var(--mindset-magenta-500); }
.page .tone--pink { --tone: var(--skills-pink-500); }
.page .tone--orange { --tone: var(--skills-orange-500); }
.page .tone--deep { --tone: var(--deep-navy); }
.page .tone--text { --tone: var(--ui-text); }
.page .tone--color-mood-500 { --tone: var(--mood-500); }
.page .tone--color-cognition-500 { --tone: var(--cognition-500); }
.page .tone--color-personality-500 { --tone: var(--personality-500); }

.page .tone {
  --accent: var(--tone);
  --badge-accent: var(--tone);
  --card-accent: var(--tone);
  --chip: var(--tone);
  --dot: var(--tone);
  --item: var(--tone);
  --key: var(--tone);
  --legend: var(--tone);
  --milestone: var(--tone);
  --point: var(--tone);
  --proof: var(--tone);
  --provider: var(--tone);
  --shape: var(--tone);
  --step: var(--tone);
  --team: var(--tone);
  --use: var(--tone);
}

.page .tag--pink-soft {
  color: var(--pink, var(--skills-pink-500));
  background: color-mix( in srgb, var(--pink, var(--skills-pink-500)) 12%, transparent );
}

.page .text--caption-muted { font-size: var(--font-size-caption); }
.page .metric-stack { gap: var(--line-height-sm); padding: var(--spacing-lg) 0 var(--spacing-md); }
.page .metric-fill--mood { width: 74%; background: var(--mood-500); }
.page .metric-fill--personality { width: 68%; background: var(--personality-500); }
.page .swatch { background: var(--swatch-color); color: var(--swatch-contrast, var(--deep-navy)); }
.page .swatch--deep-navy-900 { --swatch-color: var(--deep-navy-900); }
.page .swatch--panel-navy-800 { --swatch-color: var(--panel-navy-800); }
.page .swatch--panel-navy-900 { --swatch-color: var(--panel-navy-900); }
.page .swatch--panel-navy-950 { --swatch-color: var(--panel-navy-950); }
.page .swatch--geistface-grey-200 { --swatch-color: var(--geistface-grey-200); }
.page .swatch--core-lime-100 { --swatch-color: var(--core-lime-100); }
.page .swatch--core-lime-500 { --swatch-color: var(--core-lime-500); }
.page .swatch--core-green-500 { --swatch-color: var(--core-green-500); }
.page .swatch--personality-mint-100 { --swatch-color: var(--personality-mint-100); }
.page .swatch--personality-mint-500 { --swatch-color: var(--personality-mint-500); }
.page .swatch--personality-blue-100 { --swatch-color: var(--personality-blue-100); }
.page .swatch--personality-blue-500 { --swatch-color: var(--personality-blue-500); }
.page .swatch--mindset-violet-100 { --swatch-color: var(--mindset-violet-100); }
.page .swatch--mindset-violet-500 { --swatch-color: var(--mindset-violet-500); }
.page .swatch--mindset-magenta-500 { --swatch-color: var(--mindset-magenta-500); }
.page .swatch--mindset-magenta-700 { --swatch-color: var(--mindset-magenta-700); }
.page .swatch--skills-pink-100 { --swatch-color: var(--skills-pink-100); }
.page .swatch--skills-pink-500 { --swatch-color: var(--skills-pink-500); }
.page .swatch--surface-canvas { --swatch-color: var(--surface-canvas); }
.page .swatch--surface-panel { --swatch-color: var(--surface-panel); }
.page .swatch--surface-box { --swatch-color: var(--surface-box); }
.page .swatch--surface-raised { --swatch-color: var(--surface-raised); }
.page .swatch--surface-strong { --swatch-color: var(--surface-strong); }
.page .swatch--code-bg { --swatch-color: var(--code-bg); }
.page .swatch--code-surface { --swatch-color: var(--code-surface); }
.page .swatch--code-raised { --swatch-color: var(--code-raised); }
.page .swatch--skills-orange-500 { --swatch-color: var(--skills-orange-500); }
.page .on-color--core-lime-500 { --swatch-contrast: var(--core-lime-500); }
.page .on-color--geistface-grey-200 { --swatch-contrast: var(--geistface-grey-200); }
.page .on-color--mindset-violet-100 { --swatch-contrast: var(--mindset-violet-100); }
.page .on-color--mindset-violet-500 { --swatch-contrast: var(--mindset-violet-500); }
.page .on-color--panel-navy-900 { --swatch-contrast: var(--panel-navy-900); }
.page .on-color--personality-blue-500 { --swatch-contrast: var(--personality-blue-500); }
.page .on-color--personality-mint-500 { --swatch-contrast: var(--personality-mint-500); }
.page .on-color--skills-orange-500 { --swatch-contrast: var(--skills-orange-500); }
.page .on-color--skills-pink-500 { --swatch-contrast: var(--skills-pink-500); }

.hero-copy--portal { max-width: var(--space-720); margin-bottom: 0; }


@supports not (
  (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))
) {
  .page--design-system .sp-popup-dialog--light { background: color-mix(in srgb, var(--geistface-grey-25) 96%, transparent); }
  .page--design-system .sp-popup-dialog--dark { background: var(--deep-navy-900); }

  .page--design-system .sp-popup-live, .page--design-system .sp-popup-stage-overlay {
    background: color-mix(in srgb, var(--deep-navy-950) 88%, transparent);
  }
}


@page {
  size: 16in 9in;
  margin: 0;
}


.page {
  --skillprint-shared-border: color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

.page :where(a, button, input, select, textarea):focus-visible {
  outline: 2px solid var(--skillprint-action-violet);
  outline-offset: 3px;
}

.page .hero-actions { margin-top: var(--space-32); }

.page .eyebrow--pill {
  margin-inline: auto;
  min-height: var(--space-30);
  padding: var(--space-4) var(--space-10) var(--space-4) var(--space-6);
  box-shadow: var(--shadow-xs);
}

.page .toast {
  position: fixed;
  left: 50%;
  bottom: var(--space-24);
  z-index: 400;
  transform: translate(-50%, 18px);
  padding: var(--space-10) var(--space-14);
  border: 1px solid var(--border-strong, var(--border-interactive, var(--border)));
  background: var( --surface-box, var(--surface-card, var(--panel-900, var(--panel-navy-900))) );
  color: var(--text-strong, var(--text, var(--geistface-grey-200)));
  box-shadow: var(--shadow-md, var(--shadow, 0 16px 36px color-mix(in srgb, var(--pure-black) 18%, transparent)));
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--duration-base) ease, transform var(--duration-base) ease;
}

.page .toast.is-visible { opacity: 1; transform: translate(-50%, 0); }

html[data-surface="light"], html[data-theme="light"] {
  color-scheme: light;
  --skillprint-deep-navy: var(--deep-navy-25);
  --skillprint-border-hover: color-mix(in srgb, var(--deep-navy-900) 24%, transparent);

  /* The current-route violet is the same hue in both modes; only the weight of
     the tint and the label move, because the same 16% wash that reads as a
     statement on near-black barely registers on white and the white label on
     it fails contrast. Set here rather than per navigation so the top bar, the
     left nav and the portal's app buttons all take the one light-mode value. */
  --nav-current-surface:
    color-mix(in srgb, var(--nav-current) 10%, transparent);
  --nav-current-border:
    color-mix(in srgb, var(--nav-current) 38%, transparent);
  --nav-current-text: var(--mindset-violet-900);

  /* The navigation's own surface and interaction tokens. These lived only
     inside `html[data-surface="light"] .sp-nav`, so a control built from the
     navigation - an .icon-button in a sidebar footer, a docs rail, the partner
     dashboard - kept the dark values on a white page and hovered to near-black.
     The bars still override these for themselves; this is the floor. */
  --nav-surface:
    color-mix(in srgb, var(--geistface-grey-25) 95%, transparent);
  --nav-border: color-mix(in srgb, var(--deep-navy-900) 11%, transparent);
  --nav-text: var(--geistface-grey-800);
  --nav-muted: color-mix(in srgb, var(--geistface-grey-800) 72%, transparent);
  --nav-hover-surface: var(--panel-navy-50);
  --nav-hover-border: color-mix(in srgb, var(--deep-navy-900) 11%, transparent);

  /* Light code theme. Same hue per token as the dark theme, stepped down each
     brand ramp to the first value that clears 4.5:1 on --code-bg - the 500s
     that read well on near-black sit at 1.2-2.9:1 on a light surface. */
  --code-bg           : var(--deep-navy-25);
  --code-surface      : var(--deep-navy-50);
  --code-raised       : var(--geistface-grey-25);
  --code-hover        : color-mix(in srgb, var(--deep-navy-50) 62%, var(--geistface-grey-25));
  --code-border       : color-mix(in srgb, var(--deep-navy-900) 12%, transparent);
  --code-border-strong: color-mix(in srgb, var(--deep-navy-900) 20%, transparent);
  --code-border-hover : color-mix(in srgb, var(--deep-navy-900) 32%, transparent);
  --code-text         : var(--deep-navy-800);
  --code-muted        : var(--deep-navy-500);
  --code-selection    : color-mix(in srgb, var(--mindset-violet-300) 42%, transparent);

  --syntax-comment    : var(--panel-navy-500);
  --syntax-keyword    : var(--mindset-violet-600);
  --syntax-string     : var(--skills-orange-900);
  --syntax-number     : var(--core-lime-900);
  --syntax-function   : var(--personality-mint-900);
  --syntax-property   : var(--personality-blue-800);
  --syntax-tag        : var(--skills-pink-700);
  --syntax-attribute  : var(--mindset-magenta-600);
  --syntax-constant   : var(--core-green-900);
  --syntax-operator   : var(--deep-navy-700);
  --syntax-punctuation: var(--deep-navy-500);
  --syntax-variable   : var(--geistface-grey-800);
  --syntax-inserted   : var(--core-green-900);
  --syntax-deleted    : var(--skills-pink-800);
}

html[data-surface="dark"], html[data-theme="dark"] { color-scheme: dark; }

.page {
  --ui-page: var(--page, var(--deep, var(--deep-navy)));
  --ui-surface: var( --surface-card, var(--surface-box, var(--surface, var(--panel-900, var(--panel-navy-900)))) );
  --ui-surface-raised: var( --surface-raised, var(--panel-800, var(--panel-navy-800)) );
  --ui-text: var(--text-strong, var(--text, var(--text-primary)));
  --ui-muted: var(--muted-text, var(--muted, var(--text-muted)));
  --ui-border: var(--border, var(--border-subtle));
  --ui-border-strong: var( --border-interactive, var(--border-strong, rgb(248 250 252 / 18%)) );
  --ui-border-hover: var( --border-hover, var(--skillprint-border-hover, rgb(248 250 252 / 28%)) );
  --nav-surface: color-mix(in srgb, var(--ui-page) 82%, transparent);
  --nav-border: var(--ui-border);
  --card-surface: var(--ui-surface);
  --card-surface-raised: var(--ui-surface-raised);
  --card-text: var(--ui-text);
  --card-border: var(--ui-border);
  --card-border-hover: var(--ui-border-hover);
  --button-secondary: var(--ui-surface);
  --button-secondary-border: var(--ui-border-strong);
  --button-secondary-hover: var(--ui-surface-raised);
  --button-ghost-text: var(--ui-muted);
  --button-focus-shadow: 0 0 0 var(--focus-ring-width) var(--ui-page), 0 0 0 calc(var(--focus-ring-width) * 2) var(--focus-ring-color);
  --icon-bg: var(--ui-surface-raised);
  --icon-fg: var(--ui-text);
  --pill: var(--ui-muted);
}

.page .border-tone--lime { border-color: color-mix(in srgb, var(--lime) 40%, var(--border)); }
.page .border-tone--mint { border-color: color-mix(in srgb, var(--mint) 40%, var(--border)); }
.page .border-tone--blue { border-color: color-mix(in srgb, var(--blue) 40%, var(--border)); }

.page .border-tone--violet {
  border-color: color-mix(in srgb, var(--violet-400) 40%, var(--border));
}

.page .text--danger { color: var(--pink); }
.page .text--warning { color: var(--orange); }
.page .spacer--6xl { height: var(--space-48); }
.page .space-bottom--4xl { margin-bottom: var(--space-28); }
.page .spacer--6xl { height: 54px; }
.page .surface-token--panel-950 { --token-color: var(--panel-navy-950); }
.page .surface-token--panel-900 { --token-color: var(--panel-navy-900); }
.page .surface-token--panel-800 { --token-color: var(--panel-navy-800); }
.page .surface-token--panel-700 { --token-color: var(--panel-navy-700); }
.page .surface-token--light-panel { --token-color: var(--panel-navy-50); }
.page .surface-token--light-box { --token-color: var(--geistface-grey-25); }
.page .surface-token--light-raised { --token-color: var(--geistface-grey-100); }
.page .surface-token--light-strong { --token-color: var(--panel-navy-100); }


.page .u-background-f8fafc { background: var(--deep-navy-25); }
.page .u-background-090d27 { background: var(--deep-navy-900); }
.page .u-background-ffffff { background: var(--geistface-grey-25); }
.page .measure--xs { --measure: 4px; }
.page .measure--md { --measure: 8px; }
.page .measure--lg { --measure: 12px; }
.page .measure--xl { --measure: 16px; }
.page .measure--2xl { --measure: 20px; }
.page .measure--3xl { --measure: 24px; }
.page .measure--4xl { --measure: 32px; }
.page .measure--6xl { --measure: 48px; }
.page .measure--7xl { --measure: 64px; }
.page .measure--9xl { --measure: 96px; }
.page .radius-swatch--none { --r: 0px; }
.page .radius-swatch--xxs { --r: 2px; }
.page .radius-swatch--xs { --r: 4px; }
.page .radius-swatch--sm { --r: 6px; }
.page .radius-swatch--control { --r: 8px; }
.page .radius-swatch--lg { --r: 10px; }
.page .radius-swatch--compact { --r: 12px; }
.page .radius-swatch--card { --r: 16px; }
.page .radius-swatch--3xl { --r: 20px; }
.page .radius-swatch--panel { --r: 24px; }
.page .radius-swatch--full { --r: 9999px; }
.page .syntax--keyword { --syntax: var(--syntax-keyword); }
.page .syntax--string { --syntax: var(--syntax-string); }
.page .syntax--function { --syntax: var(--syntax-function); }
.page .syntax--property { --syntax: var(--syntax-property); }
.page .syntax--tag { --syntax: var(--syntax-tag); }
.page .syntax--attribute { --syntax: var(--syntax-attribute); }
.page .syntax--number { --syntax: var(--syntax-number); }
.page .syntax--constant { --syntax: var(--syntax-constant); }
.page .syntax--comment { --syntax: var(--syntax-comment); }
.page .syntax--operator { --syntax: var(--syntax-operator); }
.page .syntax--punctuation { --syntax: var(--syntax-punctuation); }
.page .syntax--variable { --syntax: var(--syntax-variable); }
.page .fill--mood { background: var(--mood-500); }
.page .fill--cognition { background: var(--cognition-500); }
.page .fill--personality { background: var(--personality-500); }
.page .metric-fill--mood { width: 82%; }
.page .metric-fill--personality { width: 68%; }
.page .spacer--2xl { height: var(--space-18); }
.page .space-top--4xl { margin-top: var(--space-28); }
.page .fill--lime { background: var(--core-lime-500); }
.page .fill--mint { background: var(--personality-mint-500); }
.page .fill--blue { background: var(--personality-blue-500); }
.page .fill--panel-700 { background: var(--panel-700); }
.page .fill--violet-soft { background: var(--mindset-violet-200); }
.page .space-top--none { margin-top: 0; }


/* ---------------------------------------------------------------------------
   Website utilities, folded in from the generated marketing stylesheet

   These four were the only genuinely missing primitives. The first three
   are page-agnostic; the fourth repairs a Part A component.
   --------------------------------------------------------------------------- */

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  border: 0;
  clip-path: inset(50%);
  white-space: nowrap;
}

.page { -webkit-font-smoothing: antialiased; }


/* ==========================================================================
   07 - Shared component · adjustment banner

   The live signal the engine sends mid-session. It names the parameter that
   just changed, the value it moved to and, when the session has one, the mood
   being targeted. It sits over the game canvas, announces itself and leaves
   on its own; nothing in it is interactive, so it never takes pointer events.

   Values follow the engine's own formatting: a speed or modifier reads as
   "1.5x", a duration reads as "30s", anything else prints as given.
   ========================================================================== */

.sp-adjustment {
  --adjustment-offset: var(--space-12);

  position: fixed;
  top: var(--space-16);
  left: 50%;
  z-index: 320;
  min-width: min(var(--space-320), calc(100vw - var(--space-32)));
  padding: var(--space-8) var(--space-20) var(--space-8) var(--space-8);
  display: flex;
  align-items: center;
  gap: var(--space-12);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-full);
  background: var(--glass-surface-brand-strong);
  color: var(--geistface-grey-25);
  box-shadow: var(--shadow-xl);
  -webkit-backdrop-filter: blur(var(--backdrop-blur-md));
  backdrop-filter: blur(var(--backdrop-blur-md));
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translate(-50%, calc(var(--adjustment-offset) * -1));
  transition:
    opacity var(--duration-slower) var(--ease-standard),
    transform var(--duration-slower) var(--ease-standard),
    visibility var(--duration-slower) var(--ease-standard);
}

.sp-adjustment.is-visible {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, 0);
}

.sp-adjustment__icon {
  width: var(--space-32);
  height: var(--space-32);
  flex: 0 0 var(--space-32);
  display: grid;
  place-items: center;
  border-radius: var(--radius-full);
  background:
    linear-gradient(
      135deg,
      var(--mindset-violet-500),
      var(--mindset-magenta-500)
    );
  color: var(--geistface-grey-25);
  box-shadow: var(--shadow-sm);
}

.sp-adjustment.is-visible .sp-adjustment__icon {
  animation: sp-adjustment-pulse var(--duration-slowest) var(--ease-standard) infinite alternate;
}

@keyframes sp-adjustment-pulse {
  to {
    opacity: 0.55;
  }
}

.sp-adjustment__icon svg {
  width: var(--space-16);
  height: var(--space-16);
  fill: none;
  stroke: currentColor;
  stroke-width: var(--icon-stroke-md);
  stroke-linecap: round;
  stroke-linejoin: round;
  --icon-stroke: var(--icon-stroke-16);
}

.sp-adjustment__body {
  min-width: 0;
  display: grid;
  gap: var(--space-2);
}

/* Card head row: title on the left, action on the right. */
.sp-adjustment__head,
.page--design-system .trait-module .trait-card-top,
.page--partner-analysis .update-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-12);
}

.sp-adjustment__label {
  color: var(--geistface-grey-400);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-medium);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.sp-adjustment__target {
  --badge-accent: var(--violet);
  --badge-bg: color-mix(in srgb, var(--geistface-grey-25) 10%, transparent);
  --badge-border: transparent;
  --badge-text: var(--geistface-grey-25);
  --badge-height: var(--space-20);
  --badge-font-size: var(--text-xxs-font-size);
  --badge-line-height: var(--text-xxs-line-height);

  gap: var(--space-6);
}

.sp-adjustment__target b {
  color: var(--mindset-violet-200);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.sp-adjustment__readout {
  display: flex;
  align-items: baseline;
  gap: var(--space-8);
}

.sp-adjustment__parameter {
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.01em;
}

.sp-adjustment__value {
  color: var(--mindset-violet-300);
  font-family: var(--skillprint-font-mono);
  font-size: var(--text-md-font-size);
  line-height: var(--text-md-line-height);
  font-weight: var(--weight-semibold);
}


/* ==========================================================================
   07 - Shared component · page footers

   Every surface used to end in a footer written for that surface alone. The
   marketing site had a four column block; the games portal and the benchmark
   each had a one row bar, written twice under two sets of class names; the
   documentation landing had a third bar; this reference had a fourth. Three
   shapes, five vocabularies, and no way to change one without finding five.

   One component now, and the arrangement is the only decision a surface makes:

     .sp-footer--full    brand block, link columns, legal rule
     .sp-footer--bar     one row: identity on the left, links on the right
     .sp-footer--pager   previous and next, inside a documentation article

   Padding, rules, colour, hover, focus and the order things collapse in all
   belong to the component. A surface supplies content and, where it needs a
   different ground, one tone modifier. It never restates the geometry.

   Decks and single-purpose visuals are deliberately outside this: a slide foot
   and a diagram caption are the bottom of a canvas, not the end of a page, and
   they keep their own rules.
   ========================================================================== */

.sp-footer {
  --footer-surface   : transparent;
  --footer-border    : var(--ui-border);
  --footer-text      : var(--ui-muted);
  --footer-strong    : var(--ui-text);
  --footer-link      : var(--ui-muted);
  --footer-link-hover: var(--ui-text);
  --footer-block     : var(--space-48);
  --footer-row       : var(--space-88);
  --footer-gap       : var(--space-24);

  border-top: 1px solid var(--footer-border);
  background: var(--footer-surface);
  color: var(--footer-text);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

/* The ground. Product chrome lifts the bar off the page so it reads as part of
   the application shell; the marketing site and this reference leave it flush
   with the last section above it. */
.sp-footer--panel { --footer-surface: var(--surface-panel); }

/* The inner element owns only the stack. Width and gutters stay with
   .sp-container so the footer lines up with the content above it instead of
   measuring itself. */
.sp-footer__inner { padding-block: var(--footer-block); }


/* --- Full ----------------------------------------------------------------
   The public site. A brand block that says what Skillprint is, up to three
   link columns, then a rule and the legal line. The brand column is wider
   than the link columns because it carries a sentence, not a list. */

.sp-footer--full .sp-footer__inner { padding-block: var(--space-64) var(--space-30); }

.sp-footer__top {
  display: grid;
  grid-template-columns: 1.2fr repeat(3, 0.6fr);
  align-items: start;
  gap: var(--space-48);
  padding-bottom: var(--space-48);
}

.sp-footer__brand { min-width: 0; }

/* The wordmark is 494x100 artwork. Height has to be released explicitly or the
   box keeps the intrinsic 100px and letterboxes the logo inside it, which
   reads as a gap under the mark that no margin can close. */
.sp-footer__logo {
  width: 170px;
  max-width: 100%;
  height: auto;
}

/* Block, so the mark does not sit on a text baseline and open a second gap
   beneath itself. Held at zero specificity on purpose: a surface that swaps a
   dark and a light wordmark hides one of them with .brand-logo--light, and a
   plain .sp-footer__logo rule would tie with that and win on source order,
   leaving both marks stacked on top of each other. */
:where(.sp-footer__logo) { display: block; }

.sp-footer__statement {
  margin: var(--space-12) 0 0;
  max-width: 34ch;
  font-size: var(--text-md-font-size);
  line-height: var(--text-md-line-height);
}

.sp-footer__col {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.sp-footer__col-title {
  margin-bottom: var(--space-10);
  color: var(--footer-strong);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
  font-weight: var(--weight-semibold);
}

.sp-footer__legal {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--footer-gap);
  padding-top: var(--space-24);
  border-top: 1px solid var(--footer-border);
  color: var(--quiet, var(--footer-text));
}


/* --- Bar -----------------------------------------------------------------
   One row: who this is on the left, where to go or what to know on the right.
   Every product surface ends in this — the portal, the benchmark, the
   documentation landing and this reference. The height is the component's, not
   the copy's, so two surfaces open side by side end at the same place. */

.sp-footer--bar .sp-footer__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--footer-gap);
  min-height: var(--footer-row);
  padding-block: var(--space-20);
}

.sp-footer__identity {
  display: flex;
  align-items: center;
  gap: var(--space-10);
  min-width: 0;
}

.sp-footer__mark {
  flex: 0 0 auto;
  width: var(--space-24);
  height: var(--space-24);
}

.sp-footer__strong { color: var(--footer-strong); font-weight: var(--weight-semibold); }

.sp-footer__note { min-width: 0; }

.sp-footer__nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-20);
}


/* --- Pager ---------------------------------------------------------------
   Documentation is the exception. An article does not end in site chrome, it
   ends in the next article, so the pager is the foot of the column rather than
   of the page: no ground, no container, no mark. It takes the measure of the
   prose it follows and stays a step quieter than it. */

.sp-footer--pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-22);
  padding-top: var(--space-24);
  font-size: var(--font-size-xs);
  line-height: var(--text-xs-line-height);
}

.sp-footer__step--next { margin-inline-start: auto; }


/* --- Links ---------------------------------------------------------------
   One link treatment for all three arrangements. Colour is the only thing that
   moves on hover; nothing underlines, nothing reflows, and the external mark
   rides the text rather than pushing the row taller. */

.sp-footer :is(.sp-footer__link, .sp-footer__step) {
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
  width: fit-content;
  border-radius: var(--radius-control);
  color: var(--footer-link);
  text-decoration: none;
  transition: color 0.15s ease;
}

.sp-footer__link {
  padding-block: var(--space-6);
  font-size: var(--text-md-font-size);
  line-height: var(--text-md-line-height);
}

/* Inside a bar or a legal line the links are chrome, not content, so they take
   the size of the row they sit in. */
.sp-footer :is(.sp-footer__nav, .sp-footer__legal) .sp-footer__link {
  padding-block: 0;
  font-size: inherit;
  line-height: inherit;
}

.sp-footer :is(.sp-footer__link, .sp-footer__step, .sp-footer__legal a):hover {
  color: var(--footer-link-hover);
}

.sp-footer__legal a { color: inherit; text-decoration: none; }

.sp-footer :is(.sp-footer__link, .sp-footer__step) .sp-icon {
  --icon-size: var(--icon-size-xs);
  flex: 0 0 auto;
  opacity: 0.6;
}

.sp-footer :is(.sp-footer__link, .sp-footer__step):hover .sp-icon { opacity: 1; }


/* --- Collapse ------------------------------------------------------------
   Meaning survives the width. The link columns pair up before they stack, and
   every row becomes a column rather than losing an item. */

@media (max-width: 900px) {
  .sp-footer__top {
    grid-template-columns: 1fr 1fr;
    gap: var(--space-32);
  }
}

@media (max-width: 650px) {
  .sp-footer__top { grid-template-columns: 1fr; }

  .sp-footer__legal,
  .sp-footer--pager,
  .sp-footer--bar .sp-footer__inner {
    align-items: flex-start;
    flex-direction: column;
  }

  .sp-footer--bar .sp-footer__inner {
    justify-content: center;
    padding-block: var(--space-24);
  }

  .sp-footer__step--next { margin-inline-start: 0; }
}


/* ==========================================================================
   07 - Shared component · status alerts

   The message the interface owes the reader once something has happened: a
   limit approaching, an import that failed, a session saved. It sits in the
   flow of the page rather than over it - an alert that covers content is a
   popup, and one the reader has to dismiss before continuing is a dialog.

   Tone travels as data-alert-tone, following the [data-badge-tone] precedent,
   and it only ever tints the border, the surface wash and the icon. The status
   word itself belongs in the title, because colour is never the sole carrier
   of meaning: the four tones are identical to a screen reader, and to a reader
   with a red-green deficiency success and danger are the same alert twice.
   ========================================================================== */

.sp-alert {
  /* Info is the base tone; the three others re-point this one property. */
  --alert-accent: var(--personality-blue-500);
  --alert-wash: 8%;

  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--space-12);
  padding: var(--space-14) var(--space-16);
  border: 1px solid color-mix(in srgb, var(--alert-accent) 34%, var(--surface-border));
  border-radius: var(--radius-compact);
  background: color-mix(in srgb, var(--alert-accent) var(--alert-wash), var(--surface-box));
  color: var(--surface-text);
  font-family: var(--skillprint-font-ui);
}

/* display:grid outranks the [hidden] UA rule, so dismissal needs this. */
.sp-alert[hidden] { display: none; }

.sp-alert__icon {
  margin-block-start: var(--space-2);
  color: var(--alert-accent);
}

.sp-alert__body {
  min-width: 0;
  display: grid;
  gap: var(--space-2);
}

.sp-alert__title {
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
  font-weight: var(--weight-semibold);
}

.sp-alert__text {
  margin: 0;
  color: var(--surface-text-muted);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

.sp-alert__actions {
  margin-block-start: var(--space-10);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-8);
}

/* Pulled into the padding so the glyph optically aligns with the title. */
.sp-alert__dismiss {
  margin-block-start: calc(var(--space-4) * -1);
  margin-inline-end: calc(var(--space-8) * -1);
}

.sp-alert[data-alert-tone="success"] { --alert-accent: var(--core-green-500); }
.sp-alert[data-alert-tone="warning"] { --alert-accent: var(--skills-orange-500); }
.sp-alert[data-alert-tone="danger"] { --alert-accent: var(--skills-pink-500); }

/* Light steps one rung down each ramp, the rule the sheet applies to
   .sp-icon--* and to the light code theme: keep the hue, drop to the first
   value that clears 4.5:1 on a bright surface. The wash drops with it,
   because the same percentage of a darker ink reads much heavier. */
html:is([data-theme="light"], [data-surface="light"]) .sp-alert {
  --alert-accent: var(--personality-blue-600);
  --alert-wash: 6%;
}

html:is([data-theme="light"], [data-surface="light"]) .sp-alert[data-alert-tone="success"] { --alert-accent: var(--core-green-700); }
html:is([data-theme="light"], [data-surface="light"]) .sp-alert[data-alert-tone="warning"] { --alert-accent: var(--skills-orange-600); }
html:is([data-theme="light"], [data-surface="light"]) .sp-alert[data-alert-tone="danger"] { --alert-accent: var(--skills-pink-600); }

.sp-alert--compact {
  gap: var(--space-10);
  padding: var(--space-10) var(--space-12);
}

.sp-alert--compact .sp-alert__title,
.sp-alert--compact .sp-alert__text {
  font-size: var(--font-size-xs);
  line-height: var(--text-xs-line-height);
}

/* The page-level notice: maintenance, a trial ending, a model degraded. It is
   the same alert with the two things that make it a card taken away - the side
   borders and the corners - so it spans the surface it is laid on instead of
   sitting in a column. It keeps the top and bottom rule, which is what still
   separates it from the navigation above and the content below.

   One class, not a compound: .sp-alert[hidden] is display:none at 0-2-0, and a
   modifier written .sp-alert.sp-alert--banner would tie it and win on order,
   leaving a dismissed banner on screen. */
.sp-alert--banner {
  border-inline: 0;
  border-radius: 0;
  padding-inline: var(--space-24);
}


/* ==========================================================================
   07 - Shared component · tutorial coachmarks

   The onboarding step that points at the control it is describing. It is not
   a .popup and deliberately not modal: a coachmark that hides the interface
   behind a backdrop cannot teach the interface. The reader keeps working, the
   page keeps reading, and Tab walks out of the bubble into the rest of the
   document rather than being trapped inside it.

   One anchor holds one bubble; the steps are panels inside that bubble, so a
   tour never moves focus between DOM subtrees mid-sequence.
   ========================================================================== */

.sp-tour-anchor {
  position: relative;
  display: inline-flex;
}

/* The target ring, so the reader can see what the current step is about. */
.sp-tour-anchor[data-tour-active="true"] > :first-child {
  border-radius: var(--radius-control);
  box-shadow: 0 0 0 var(--space-4) color-mix(in srgb, var(--mindset-violet-400) 28%, transparent);
}

.sp-tour {
  --tour-gap: var(--space-12);
  --tour-arrow: var(--space-10);
  --tour-shift: 0px;

  position: absolute;
  left: 50%;
  /* Above .sp-tooltip (60), below .popup-backdrop (300). */
  z-index: 70;
  width: max-content;
  max-width: min(var(--space-320), calc(100vw - var(--space-32)));
  padding: var(--space-16);
  border: 1px solid var(--surface-border-hover);
  border-radius: var(--radius-card);
  background: var(--surface-box);
  color: var(--surface-text);
  font-family: var(--skillprint-font-ui);
  text-align: left;
  box-shadow: var(--shadow-md);
  opacity: 0;
  visibility: hidden;
  transform: translateX(calc(-50% + var(--tour-shift))) translateY(var(--space-4));
  transition:
    opacity var(--duration-base) var(--ease-standard),
    transform var(--duration-base) var(--ease-standard),
    visibility var(--duration-base) var(--ease-standard);
}

.sp-tour[data-placement="top"] { bottom: calc(100% + var(--tour-gap)); }
.sp-tour[data-placement="bottom"] { top: calc(100% + var(--tour-gap)); }

.sp-tour[data-open="true"] {
  opacity: 1;
  visibility: visible;
  transform: translateX(calc(-50% + var(--tour-shift))) translateY(0);
}

/* The arrow is a rotated square with two borders removed, so it inherits the
   bubble's border and surface instead of needing a second colour pair. */
.sp-tour__arrow {
  position: absolute;
  left: calc(50% - var(--tour-shift));
  width: var(--tour-arrow);
  height: var(--tour-arrow);
  border: 1px solid var(--surface-border-hover);
  background: var(--surface-box);
  transform: translateX(-50%) rotate(45deg);
}

.sp-tour[data-placement="bottom"] .sp-tour__arrow {
  top: calc(var(--tour-arrow) / -2);
  border-inline-end: 0;
  border-block-end: 0;
}

.sp-tour[data-placement="top"] .sp-tour__arrow {
  bottom: calc(var(--tour-arrow) / -2);
  border-inline-start: 0;
  border-block-start: 0;
}

.sp-tour__step[hidden] { display: none; }

.sp-tour__eyebrow {
  display: block;
  color: var(--mindset-violet-300);
  font-family: var(--skillprint-font-mono);
  font-size: var(--text-xxs-font-size);
  line-height: var(--text-xxs-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.sp-tour__title {
  margin: var(--space-6) 0 0;
  font-size: var(--text-md-font-size);
  line-height: var(--text-md-line-height);
  font-weight: var(--weight-semibold);
}

.sp-tour__text {
  margin: var(--space-4) 0 0;
  color: var(--surface-text-muted);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

.sp-tour__footer {
  margin-block-start: var(--space-16);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-12);
}

.sp-tour__dots {
  display: flex;
  align-items: center;
  gap: var(--space-6);
}

.sp-tour__dot {
  width: var(--space-6);
  height: var(--space-6);
  border-radius: var(--radius-full);
  background: var(--surface-border-hover);
  transition: var(--transition-interactive);
}

/* The current dot stretches rather than recolouring alone, so the position in
   the sequence survives a monochrome rendering. */
.sp-tour__dot[data-current="true"] {
  width: var(--space-16);
  background: var(--mindset-violet-400);
}

.sp-tour__actions {
  display: flex;
  align-items: center;
  gap: var(--space-8);
}

html:is([data-theme="light"], [data-surface="light"]) .sp-tour__eyebrow { color: var(--mindset-violet-500); }
html:is([data-theme="light"], [data-surface="light"]) .sp-tour__dot[data-current="true"] { background: var(--mindset-violet-500); }

/* Opacity alone is sufficient; the entrance offset is the part that needs to
   go, matching the rule the popup section states. */
@media (prefers-reduced-motion: reduce) {
  .sp-tour,
  .sp-tour[data-open="true"] {
    transform: translateX(calc(-50% + var(--tour-shift)));
  }
}


/* ==========================================================================
   07 - Shared component · actionable chart popovers

   A chart tooltip that carries somewhere to go. Both existing chart tooltips
   set pointer-events: none, which is correct for a bubble that only reports a
   value and fatal for one holding links - the pointer crosses the gap, the
   bubble is not there, the link cannot be clicked.

   So this one is interactive from the moment it is visible, and it bridges
   the gap under the mark with its own hit area. Hovering the mark opens it,
   moving into it keeps it open, and the actions are one uninterrupted
   journey rather than something the reader has to know to unlock first.

   Clicking still pins, because pinning is the state that survives the
   pointer leaving altogether: it is what holds the bubble open while the
   reader reads something else, and it is the whole story on a touch screen,
   where hover never fires. There is no focus trap: the popover follows its
   trigger in the DOM, so Tab runs trigger -> actions -> rest of the page.

   Confined space is the normal case, not the exception. Chart cards clip
   their overflow and the narrowest is around 338px, so the bubble is capped
   against its own container, its copy truncates rather than widening the
   card, its actions wrap and then stack, and JS nudges it back inside the
   frame instead of letting it disappear past the edge.
   ========================================================================== */

.sp-chart-popover-anchor {
  position: relative;
  display: inline-flex;
}

.sp-chart-popover {
  --popover-gap: var(--space-10);
  --popover-arrow: var(--space-10);
  --popover-shift: 0px;

  position: absolute;
  left: 50%;
  z-index: 70;
  width: max-content;
  min-width: var(--space-160);
  /* Never wider than the frame it floats inside. A percentage would be no
     use here - the containing block is the anchor, which for a chart mark is
     a single bar a few pixels wide - so the frame's real width arrives from
     the script as --popover-room, and the viewport is the fallback. */
  max-width: min(var(--space-320), var(--popover-room, calc(100vw - var(--space-32))));
  padding: var(--space-12);
  border: 1px solid var(--surface-border-hover);
  border-radius: var(--radius-card);
  background: var(--surface-box);
  color: var(--surface-text);
  font-family: var(--skillprint-font-ui);
  text-align: left;
  box-shadow: var(--shadow-md);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateX(calc(-50% + var(--popover-shift)));
  /* Deliberately not --transition-interactive: that animates transform, and
     transform is what holds the position. A popover re-clamping on resize
     should already be inside the frame, not sliding towards it. */
  transition:
    opacity var(--duration-fast) var(--ease-standard),
    visibility var(--duration-fast) var(--ease-standard),
    border-color var(--duration-fast) var(--ease-standard);
}

.sp-chart-popover[data-placement="top"] { bottom: calc(100% + var(--popover-gap)); }
.sp-chart-popover[data-placement="bottom"] { top: calc(100% + var(--popover-gap)); }

/* Visible is interactive. The bubble sits inside the anchor, so hovering the
   bubble already keeps the anchor hovered and the state open - what stopped
   the reader reaching the links was never the selector, it was the bubble
   refusing the pointer and the dead gap below. */
.sp-chart-popover-anchor:is(:hover, :focus-within) .sp-chart-popover,
.sp-chart-popover[data-open="true"] {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

/* The gap under the mark is dead space the pointer has to cross, and a hover
   that ends halfway across it is a bubble nobody can click. The bubble
   extends its own hit area back over that gap so the two regions touch and
   the journey from mark to action is unbroken. Padding for the pointer, not
   for the layout: nothing moves, and it only exists while the bubble is
   already visible. */
.sp-chart-popover::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  height: var(--popover-gap);
}

.sp-chart-popover[data-placement="top"]::before { top: 100%; }
.sp-chart-popover[data-placement="bottom"]::before { bottom: 100%; }

/* Pinned is the state that survives the pointer leaving altogether: the only
   one that shows a close, and the whole story on touch, where hover never
   fires at all. */
.sp-chart-popover[data-open="true"] {
  border-color: var(--mindset-violet-400);
}

.sp-chart-popover__arrow {
  position: absolute;
  left: calc(50% - var(--popover-shift));
  width: var(--popover-arrow);
  height: var(--popover-arrow);
  border: 1px solid var(--surface-border-hover);
  background: var(--surface-box);
  transform: translateX(-50%) rotate(45deg);
}

.sp-chart-popover[data-placement="bottom"] .sp-chart-popover__arrow {
  top: calc(var(--popover-arrow) / -2);
  border-inline-end: 0;
  border-block-end: 0;
}

.sp-chart-popover[data-placement="top"] .sp-chart-popover__arrow {
  bottom: calc(var(--popover-arrow) / -2);
  border-inline-start: 0;
  border-block-start: 0;
}

.sp-chart-popover[data-open="true"] .sp-chart-popover__arrow { border-color: var(--mindset-violet-400); }

.sp-chart-popover__head { min-width: 0; }

/* Truncate rather than widen: a long series name must not blow out the card. */
.sp-chart-popover__title {
  display: block;
  overflow: hidden;
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
  font-weight: var(--weight-semibold);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sp-chart-popover__meta {
  display: block;
  overflow: hidden;
  margin-block-start: var(--space-2);
  color: var(--surface-text-muted);
  font: var(--weight-medium) var(--text-xs-font-size)/var(--text-xs-line-height) var(--skillprint-font-ui);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sp-chart-popover__value {
  display: block;
  margin-block-start: var(--space-6);
  font-family: var(--skillprint-font-mono);
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
  font-weight: var(--weight-semibold);
}

/* Two or three of these. A fourth is a menu, and a menu is a dropdown. */
.sp-chart-popover__actions {
  margin-block-start: var(--space-12);
  padding-block-start: var(--space-10);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-8);
  border-block-start: 1px solid var(--surface-border);
}

.sp-chart-popover__actions > * { flex: 1 1 auto; }

/* The close is only present while pinned, and display rather than opacity
   because an invisible button is still a tab stop. */
.sp-chart-popover__close {
  position: absolute;
  top: var(--space-6);
  inset-inline-end: var(--space-6);
  display: none;
}

.sp-chart-popover[data-open="true"] .sp-chart-popover__close { display: inline-flex; }
.sp-chart-popover[data-open="true"] .sp-chart-popover__head { padding-inline-end: var(--space-24); }

/* The confined form: one column, full-width actions, no arrow to overhang. */
.sp-chart-popover--stacked { min-width: var(--space-160); }
.sp-chart-popover--stacked .sp-chart-popover__actions { flex-direction: column; }
.sp-chart-popover--stacked .sp-chart-popover__actions > * { width: 100%; }
.sp-chart-popover--stacked .sp-chart-popover__arrow { display: none; }


/* ==========================================================================
   07 - Shared component · skeleton loaders

   The shape of the content that has not arrived. A spinner reports that
   something is happening; a skeleton reports what is coming and where it will
   sit, so the layout is already right when the data lands and nothing below it
   jumps. That only holds while the placeholder keeps the real block's
   geometry, which is why every shape here is sized from the same space and
   radius tokens the real component uses rather than from round numbers that
   happened to look right in one panel.

   Use it where the page can predict its own shape - a card grid, a list, a
   profile header. Where it cannot, or where the wait belongs to one action
   rather than to a screen, the button spinner and the progress bar are the
   honest answers: a skeleton drawn for content nobody can predict is an
   invented layout that the real response then contradicts.

   The shapes carry no text and mean nothing read aloud. They stay out of the
   accessibility tree, and .sp-skeleton-region announces the wait once through
   aria-busy and a single .sr-only line rather than letting a screen reader
   walk a wall of empty boxes.
   ========================================================================== */

.sp-skeleton {
  --skeleton-base: var(--surface-raised);
  --skeleton-sheen: color-mix(in srgb, var(--geistface-grey-25) 14%, transparent);
  --skeleton-radius: var(--radius-xs);
  --skeleton-height: var(--space-12);
  --skeleton-width: 100%;
  --skeleton-speed: 1200ms;

  position: relative;
  display: block;
  width: var(--skeleton-width);
  height: var(--skeleton-height);
  overflow: hidden;
  border-radius: var(--skeleton-radius);
  background: var(--skeleton-base);
}

/* The sweep is a pseudo-element moved with transform, not an animated
   background-position: it is the one form of this effect the compositor can
   run without repainting every placeholder on the screen each frame, and a
   loading state is exactly when the main thread is already busy. */
.sp-skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(90deg, transparent, var(--skeleton-sheen), transparent);
  transform: translateX(-100%);
  animation: sp-skeleton-sweep var(--skeleton-speed) var(--ease-standard) infinite;
}

@keyframes sp-skeleton-sweep {
  to { transform: translateX(100%); }
}

/* Light needs both halves restated. The raised step that separates a
   placeholder from a panel on dark is only a few units on light, so the base
   moves down to the strong surface to keep the same presence, and the sheen
   is white over a pale field rather than over Panel Navy. */
html:is([data-theme="light"], [data-surface="light"]) .sp-skeleton {
  --skeleton-base: var(--surface-strong);
  --skeleton-sheen: color-mix(in srgb, var(--geistface-grey-25) 55%, transparent);
}

/* The shapes. Height and radius are what separate them; width is a separate
   axis, because one line stands for a title on this row and a caption on the
   next. */
.sp-skeleton--text { --skeleton-height: var(--space-12); }

.sp-skeleton--heading {
  --skeleton-height: var(--space-20);
  --skeleton-radius: var(--radius-sm);
}

/* Both of these stand in for something with a fixed size - a tag, a button -
   so they keep it in a row instead of being squeezed by the text beside them. */
.sp-skeleton--pill {
  --skeleton-height: var(--space-24);
  --skeleton-width: var(--space-72);
  --skeleton-radius: var(--radius-full);

  flex: none;
}

.sp-skeleton--control {
  --skeleton-height: var(--space-40);
  --skeleton-width: var(--space-120);
  --skeleton-radius: var(--radius-control);

  flex: none;
}

/* Media holds its ratio rather than a height, so the placeholder reserves the
   space the image will take at every column width it is dropped into. */
.sp-skeleton--block {
  --skeleton-height: auto;
  --skeleton-radius: var(--radius-compact);
  aspect-ratio: var(--skeleton-ratio, 16 / 9);
}

/* Sizes match .sp-avatar step for step, so the circle standing in for an
   avatar is the avatar's size and the row does not resize on load. */
.sp-skeleton--circle {
  --skeleton-size: var(--space-40);
  --skeleton-width: var(--skeleton-size);
  --skeleton-height: var(--skeleton-size);
  --skeleton-radius: var(--radius-full);

  flex: 0 0 var(--skeleton-size);
}

.sp-skeleton--circle[data-size="xs"] { --skeleton-size: var(--space-24); }
.sp-skeleton--circle[data-size="sm"] { --skeleton-size: var(--space-32); }
.sp-skeleton--circle[data-size="lg"] { --skeleton-size: var(--space-48); }
.sp-skeleton--circle[data-size="xl"] { --skeleton-size: var(--space-56); }
.sp-skeleton--circle[data-size="2xl"] { --skeleton-size: var(--space-64); }

/* Width is the one thing a placeholder cannot derive - it stands in for a
   string nobody has read yet. The ladder is coarse on purpose: a placeholder
   measured to the real text is a promise the response may not keep. */
.sp-skeleton[data-width="quarter"] { --skeleton-width: 25%; }
.sp-skeleton[data-width="third"] { --skeleton-width: 33%; }
.sp-skeleton[data-width="half"] { --skeleton-width: 50%; }
.sp-skeleton[data-width="two-thirds"] { --skeleton-width: 66%; }
.sp-skeleton[data-width="three-quarters"] { --skeleton-width: 75%; }

/* A paragraph is a stack, not a set of authored widths. The ragged right edge
   comes from position, so a stack of any length still ends short, and the
   :where() keeps every derived width weaker than an explicit data-width. */
.sp-skeleton-text {
  flex: 1 1 auto;
  min-width: 0;
  display: grid;
  gap: var(--space-10);
}

.sp-skeleton-text > :where(.sp-skeleton:nth-child(3n + 2)) { --skeleton-width: 92%; }
.sp-skeleton-text > :where(.sp-skeleton:nth-child(4n + 3)) { --skeleton-width: 96%; }
.sp-skeleton-text > :where(.sp-skeleton:last-child) { --skeleton-width: 62%; }

/* Pulse instead of sweep on large areas: a highlight travelling across half a
   page reads as something moving on the screen rather than as something
   loading. */
.sp-skeleton--pulse {
  animation:
    sp-skeleton-pulse calc(var(--skeleton-speed) * 1.5) var(--ease-standard) infinite;
}

.sp-skeleton--pulse::after { content: none; }

@keyframes sp-skeleton-pulse {
  50% { opacity: 0.5; }
}

/* The still form. It is what reduced motion resolves to, and it is also the
   right choice on its own once a placeholder may be on screen for a long
   time - an animation that never ends stops reading as progress. */
.sp-skeleton--static { animation: none; }
.sp-skeleton--static::after { content: none; }

/* One region, one announcement, one switch. aria-busy carries the state the
   way aria-checked carries a toggle's, so nothing has to remember to add and
   remove a class on both halves. */
.sp-skeleton-region[aria-busy="true"] > .sp-skeleton-region__content { display: none; }

.sp-skeleton-region:not([aria-busy="true"])
  > :is(.sp-skeleton-region__shell, .sr-only) { display: none; }

@media (prefers-reduced-motion: reduce) {
  .sp-skeleton { animation: none; }
  .sp-skeleton::after { content: none; }
}


/* ==========================================================================
   07 - Shared component · breadcrumbs

   Where you are, and every level you can step back to. It answers a question
   the page title cannot: not "what is this" but "what is this inside of".

   Three surfaces had grown their own trail before this one existed - the two
   documentation topbars and the portal's game detail route line. They agreed
   on everything that matters (muted ancestors, one strong leaf, a chevron
   between) and disagreed on everything else, including whether the wrapper
   was a <nav>. This is that agreement written once.

   The trail is an ordered list inside a labelled <nav>, because it is a list
   of places in order and a screen reader should be able to skip it as one
   landmark rather than reading it as loose text in the header.
   ========================================================================== */

.sp-breadcrumb {
  --breadcrumb-gap: var(--space-10);
  --breadcrumb-chevron: var(--space-6);
  --breadcrumb-max: var(--space-160);

  min-width: 0;
  color: var(--surface-text-muted);
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

.sp-breadcrumb__list {
  margin: 0;
  padding: 0;
  min-width: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--breadcrumb-gap);
  list-style: none;
}

.sp-breadcrumb__item {
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--breadcrumb-gap);
}

/* The separator is drawn, not written. It is decoration: it carries no
   meaning a reader needs, so it must not be a node anything can reach or
   read, and generating it means no page can forget one or leave one inside a
   link. A rotated square with two borders removed inherits the trail's own
   colour - the same construction .sp-tour__arrow uses for its pointer. */
.sp-breadcrumb__item + .sp-breadcrumb__item::before {
  content: "";
  width: var(--breadcrumb-chevron);
  height: var(--breadcrumb-chevron);
  flex: none;
  border: var(--icon-stroke-xs) solid currentColor;
  border-block-end: 0;
  border-inline-start: 0;
  rotate: 45deg;
}

.sp-breadcrumb__link,
.sp-breadcrumb__current {
  display: block;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.sp-breadcrumb__link {
  max-width: var(--breadcrumb-max);
  border-radius: var(--radius-xs);
  color: inherit;
  text-decoration: none;
}

.sp-breadcrumb__link:hover {
  color: var(--surface-text);
  text-decoration: underline;
  text-underline-offset: 3px;
}

.sp-breadcrumb__link:focus-visible {
  outline: 0;
  box-shadow:
    0 0 0 var(--focus-ring-width) var(--focus-inner),
    0 0 0 calc(var(--focus-ring-width) * 2) var(--focus-violet);
}

/* The root crumb may be a mark rather than a word. It stops truncating,
   because an icon has nothing to truncate. */
.sp-breadcrumb__link--root {
  display: inline-flex;
  align-items: center;
  gap: var(--space-6);
  max-width: none;
  overflow: visible;
}

/* The end of the trail is where you already are: the one item at full
   strength, and the one that is not a link. */
.sp-breadcrumb__current {
  color: var(--surface-text);
  font-weight: var(--weight-semibold);
}

/* Narrow screens keep the last two rungs - the parent you would step back to
   and the page you are on. The levels above that are reachable from the nav,
   so dropping them costs a shortcut rather than a route. */
@media (max-width: 650px) {
  .sp-breadcrumb__item:not(:nth-last-child(-n + 2)) { display: none; }
  .sp-breadcrumb__item:nth-last-child(2)::before { content: none; }
}


/* ==========================================================================
   07 - Shared component · steppers

   A sequence with a position in it: what is finished, what is in front of
   you, and how much is left. A breadcrumb says where you are in a hierarchy;
   a stepper says where you are in a process, which is why one is a <nav> of
   links and this is an ordered list of states.

   State is an attribute - data-state is done, current or todo - because the
   position moves and a class pair that has to be added on one node and
   removed from another is the thing that goes out of sync. It reads the way
   aria-selected drives the calendar and aria-busy drives the skeleton.

   Numbers are counted rather than typed, so reordering the steps cannot
   leave a 3 sitting in second place, and the finished mark swaps its number
   for a tick in the same grid cell so nothing resizes as a flow advances.

   Deliberately still: the pulse on the game interstitial's step list says
   "this is happening now, wait", which is true of a loading screen and false
   of a form the reader is filling in. A stepper that pulses in a wizard is
   reporting work nobody is doing.
   ========================================================================== */

.sp-stepper {
  --stepper-mark: var(--space-28);
  --stepper-gap: var(--space-12);
  --stepper-line: 2px;
  --stepper-done: var(--core-green-500);
  --stepper-current: var(--violet);

  counter-reset: sp-step;
  margin: 0;
  padding: 0;
  display: flex;
  align-items: center;
  gap: var(--stepper-gap);
  list-style: none;
}

.sp-stepper__item {
  counter-increment: sp-step;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--stepper-gap);
}

/* The connector grows so the row divides itself between however many steps
   it is given; the last item does not grow, which is what keeps the trailing
   label hard against the end instead of floating in leftover space. */
.sp-stepper__item:not(:last-child) { flex: 1 1 auto; }

.sp-stepper__item:not(:last-child)::after {
  content: "";
  flex: 1 1 var(--space-24);
  height: var(--stepper-line);
  border-radius: var(--radius-full);
  background: var(--surface-raised);
}

/* The line behind a finished step is finished too, so progress reads along
   the whole row rather than only at the marks. */
.sp-stepper__item[data-state="done"]::after { background: var(--stepper-done); }

.sp-stepper__mark {
  width: var(--stepper-mark);
  height: var(--stepper-mark);
  flex: 0 0 var(--stepper-mark);
  display: grid;
  place-items: center;
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-full);
  background: var(--surface-raised);
  color: var(--surface-text-muted);
  font-family: var(--skillprint-font-mono);
  font-size: var(--text-xxs-font-size);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

.sp-stepper__mark::before { content: counter(sp-step); }

/* Number and tick occupy the same cell; only one is ever drawn. */
.sp-stepper__mark .sp-icon { display: none; }
.sp-stepper__item[data-state="done"] .sp-stepper__mark::before { content: none; }
.sp-stepper__item[data-state="done"] .sp-stepper__mark .sp-icon { display: block; }

.sp-stepper__item[data-state="done"] .sp-stepper__mark {
  border-color: transparent;
  background: var(--stepper-done);
  color: var(--deep-navy-900);
}

.sp-stepper__item[data-state="current"] .sp-stepper__mark {
  border-color: var(--stepper-current);
  background: color-mix(in srgb, var(--stepper-current) 22%, transparent);
  color: var(--surface-text);
}

.sp-stepper__label {
  min-width: 0;
  display: grid;
  gap: var(--space-2);
  color: var(--surface-text-muted);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

.sp-stepper__item[data-state="done"] .sp-stepper__label { color: var(--surface-text); }

.sp-stepper__item[data-state="current"] .sp-stepper__label {
  color: var(--surface-text);
  font-weight: var(--weight-semibold);
}

/* The second line is always secondary, including under the current step,
   which is bold. */
.sp-stepper__hint {
  color: var(--surface-text-muted);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-regular);
}

/* A finished step is a way back; a step nobody has reached is not a place to
   go. That is the entire interaction rule, so only the reachable steps are
   buttons and the stylesheet never has to invent a disabled look for the
   rest. */
.sp-stepper__action {
  padding: 0;
  display: flex;
  align-items: center;
  gap: inherit;
  border: 0;
  border-radius: var(--radius-control);
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: inherit;
  cursor: pointer;
}

.sp-stepper__action:hover .sp-stepper__label { color: var(--surface-text); }

.sp-stepper__action:focus-visible {
  outline: 0;
  box-shadow:
    0 0 0 var(--focus-ring-width) var(--focus-inner),
    0 0 0 calc(var(--focus-ring-width) * 2) var(--focus-violet);
}

/* Vertical. The connector leaves the row and runs down the gutter under the
   mark, so it has to be positioned rather than flexed - it is spanning the
   gap between two items instead of sitting inside one. */
.sp-stepper--vertical {
  flex-direction: column;
  align-items: stretch;
  /* Each item carries its own trailing space here, because the connector has
     to run through it. A list gap would put that space outside every item and
     leave the line with nothing to span. */
  gap: 0;
}

.sp-stepper--vertical .sp-stepper__item {
  position: relative;
  flex: none;
  align-items: flex-start;
  padding-block-end: var(--space-20);
}

.sp-stepper--vertical .sp-stepper__item:last-child { padding-block-end: 0; }

.sp-stepper--vertical .sp-stepper__item:not(:last-child)::after {
  position: absolute;
  inset-block: var(--stepper-mark) var(--space-4);
  inset-inline-start: calc((var(--stepper-mark) - var(--stepper-line)) / 2);
  flex: none;
  width: var(--stepper-line);
  height: auto;
}

/* The compact form: position without labels, for a rail beside a form or a
   sequence inside a card. The label is hidden the way .sr-only hides one -
   clipped, not removed - so the sequence still reads as named steps. */
.sp-stepper--dots {
  --stepper-mark: var(--space-10);

  gap: var(--space-8);
}

.sp-stepper--dots .sp-stepper__item { flex: none; }
.sp-stepper--dots .sp-stepper__item::after { content: none; }
.sp-stepper--dots .sp-stepper__mark::before { content: none; }
.sp-stepper--dots .sp-stepper__mark .sp-icon { display: none; }

.sp-stepper--dots .sp-stepper__label {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* A horizontal stepper cannot carry every label on a phone. It keeps the one
   the reader is on and the marks either side of it, which is the sentence
   "step three of five" drawn rather than written. */
@media (max-width: 650px) {
  .sp-stepper:not(.sp-stepper--vertical)
    .sp-stepper__item:not([data-state="current"]) .sp-stepper__label {
    display: none;
  }
}


/* ==========================================================================
   07 - Shared component · progress button

   A primary button that carries its own progress. Not a bar with a button
   next to it: the fill is the button's background, so the control that says
   "go and see this" is also the thing reporting how far along it is, and a
   card ends in one object instead of three.

   The two-tone is a value swap on one hue, and the direction is deliberate.
   The base is Mindset Violet 800 and the fill is Mindset Violet 500 - which
   is --button-primary itself - so a reading of 100% draws exactly the primary
   button every other surface uses, and anything less is that button with the
   remainder sunk a few steps deeper. The control never stops looking primary
   while it is filling up.

   Doing it the other way round - a quiet base with a brighter fill - is what
   the mockup for this showed, and it cannot survive the light surface: the
   quiet base there is near-white, the fill is violet, and no single label
   colour is readable across both. Two shades of the same dark violet are
   readable in both themes at once, which is why the ramp is walked downward.
   Near-white on the base is 10.8:1 and on the fill 6.2:1; the two tones sit
   1.74:1 apart, which is a boundary you can see without measuring.

   The length is data, not design: [data-meter] on the button, --meter out,
   set by the meter module in js/skillprint.js. There is no second number to
   keep in step - the label reads the same value the fill is drawn from.
   ========================================================================== */

.sp-progress-button {
  --progress-button-base: var(--mindset-violet-800);
  --progress-button-fill: var(--button-primary);

  position: relative;
  width: 100%;
  /* The fill is a square-cornered block; the button is not. */
  overflow: hidden;
  /* .button centres its content. This one has a left cluster and a right
     cluster, so it opens out and the value pushes itself over. */
  justify-content: flex-start;
  background: var(--progress-button-base);
}

.sp-progress-button::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  z-index: 0;
  width: clamp(0%, var(--meter, 0%), 100%);
  background: var(--progress-button-fill);
  transition: width var(--duration-slow, 320ms) var(--ease-standard);
}

/* .button's children are static, and a static child paints under a positioned
   pseudo-element however the source is ordered. Lifting them is what keeps the
   label on top of its own fill rather than behind it. */
.sp-progress-button > * {
  position: relative;
  z-index: 1;
}

.sp-progress-button__value {
  margin-inline-start: auto;
  font-variant-numeric: tabular-nums;
}

/* --- the compact form ------------------------------------------------------
   The full composition - mark, label, value, chevron - measures 234px, and a
   three-across card column is 208. Something has to give, and the mark is the
   part that was never carrying anything: the fill behind it is already the
   progress graphic, so a chart glyph in front of it says the same thing twice.
   The label, the value and the chevron all mean something distinct and all
   three stay. Dropping the mark and closing the spacing brings it to 198.

   It is a modifier rather than a media query because what is short of room is
   the column, not the screen: the same page draws this button at full width on
   a phone, where the card is a single column and the mark fits comfortably. */
.sp-progress-button--compact {
  --button-padding: var(--space-10) var(--space-12);
  --button-gap: var(--space-6);
}

.sp-progress-button--compact .sp-progress-button__icon {
  display: none;
}

/* Both tones deepen together, so the boundary between them is as legible on
   hover as at rest - matching .button--primary, which also darkens rather
   than lightens on hover. */
.sp-progress-button:hover {
  --progress-button-base: var(--mindset-violet-900);
  --progress-button-fill: var(--button-primary-hover);
  background: var(--progress-button-base);
}

.sp-progress-button:active {
  --progress-button-fill: var(--button-primary-active);
  background: var(--progress-button-base);
}

/* .button already greys the surface and the label when disabled, but it cannot
   know about the fill, which would otherwise stay at full strength over a
   muted ground. */
.sp-progress-button:disabled::before,
.sp-progress-button[aria-disabled="true"]::before {
  opacity: 0.32;
}

/* A reading of zero still has to look like a control rather than a slab, so
   the empty state keeps a hairline of the fill colour at the leading edge -
   the same trick the track uses to stay visible at 0%. */
.sp-progress-button[data-meter="0"]::before {
  width: var(--space-2);
}

@media (prefers-reduced-motion: reduce) {
  .sp-progress-button::before { transition: none; }
}


/* ==========================================================================
   09 - Unique composition · Design System Reference
   ========================================================================== */

.grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.grid-1 { grid-template-columns: 1fr; }
.leading-xs { line-height: 14px; }

.scale--deep-navy {
  --scale-25: var(--deep-navy-25);
  --scale-50: var(--deep-navy-50);
  --scale-100: var(--deep-navy-100);
  --scale-200: var(--deep-navy-200);
  --scale-300: var(--deep-navy-300);
  --scale-400: var(--deep-navy-400);
  --scale-500: var(--deep-navy-500);
  --scale-600: var(--deep-navy-600);
  --scale-700: var(--deep-navy-700);
  --scale-800: var(--deep-navy-800);
  --scale-900: var(--deep-navy-900);
  --scale-950: var(--deep-navy-950);
}

.scale--panel-navy {
  --scale-25: var(--panel-navy-25);
  --scale-50: var(--panel-navy-50);
  --scale-100: var(--panel-navy-100);
  --scale-200: var(--panel-navy-200);
  --scale-300: var(--panel-navy-300);
  --scale-400: var(--panel-navy-400);
  --scale-500: var(--panel-navy-500);
  --scale-600: var(--panel-navy-600);
  --scale-700: var(--panel-navy-700);
  --scale-800: var(--panel-navy-800);
  --scale-900: var(--panel-navy-900);
  --scale-950: var(--panel-navy-950);
}

.scale--geistface-grey {
  --scale-25: var(--geistface-grey-25);
  --scale-50: var(--geistface-grey-50);
  --scale-100: var(--geistface-grey-100);
  --scale-200: var(--geistface-grey-200);
  --scale-300: var(--geistface-grey-300);
  --scale-400: var(--geistface-grey-400);
  --scale-500: var(--geistface-grey-500);
  --scale-600: var(--geistface-grey-600);
  --scale-700: var(--geistface-grey-700);
  --scale-800: var(--geistface-grey-800);
  --scale-900: var(--geistface-grey-900);
  --scale-950: var(--geistface-grey-950);
}

.scale--core-lime {
  --scale-25: var(--core-lime-25);
  --scale-50: var(--core-lime-50);
  --scale-100: var(--core-lime-100);
  --scale-200: var(--core-lime-200);
  --scale-300: var(--core-lime-300);
  --scale-400: var(--core-lime-400);
  --scale-500: var(--core-lime-500);
  --scale-600: var(--core-lime-600);
  --scale-700: var(--core-lime-700);
  --scale-800: var(--core-lime-800);
  --scale-900: var(--core-lime-900);
  --scale-950: var(--core-lime-950);
}

.scale--core-green {
  --scale-25: var(--core-green-25);
  --scale-50: var(--core-green-50);
  --scale-100: var(--core-green-100);
  --scale-200: var(--core-green-200);
  --scale-300: var(--core-green-300);
  --scale-400: var(--core-green-400);
  --scale-500: var(--core-green-500);
  --scale-600: var(--core-green-600);
  --scale-700: var(--core-green-700);
  --scale-800: var(--core-green-800);
  --scale-900: var(--core-green-900);
  --scale-950: var(--core-green-950);
}

.scale--personality-mint {
  --scale-25: var(--personality-mint-25);
  --scale-50: var(--personality-mint-50);
  --scale-100: var(--personality-mint-100);
  --scale-200: var(--personality-mint-200);
  --scale-300: var(--personality-mint-300);
  --scale-400: var(--personality-mint-400);
  --scale-500: var(--personality-mint-500);
  --scale-600: var(--personality-mint-600);
  --scale-700: var(--personality-mint-700);
  --scale-800: var(--personality-mint-800);
  --scale-900: var(--personality-mint-900);
  --scale-950: var(--personality-mint-950);
}

.scale--personality-blue {
  --scale-25: var(--personality-blue-25);
  --scale-50: var(--personality-blue-50);
  --scale-100: var(--personality-blue-100);
  --scale-200: var(--personality-blue-200);
  --scale-300: var(--personality-blue-300);
  --scale-400: var(--personality-blue-400);
  --scale-500: var(--personality-blue-500);
  --scale-600: var(--personality-blue-600);
  --scale-700: var(--personality-blue-700);
  --scale-800: var(--personality-blue-800);
  --scale-900: var(--personality-blue-900);
  --scale-950: var(--personality-blue-950);
}

.scale--mindset-violet {
  --scale-25: var(--mindset-violet-25);
  --scale-50: var(--mindset-violet-50);
  --scale-100: var(--mindset-violet-100);
  --scale-200: var(--mindset-violet-200);
  --scale-300: var(--mindset-violet-300);
  --scale-400: var(--mindset-violet-400);
  --scale-500: var(--mindset-violet-500);
  --scale-600: var(--mindset-violet-600);
  --scale-700: var(--mindset-violet-700);
  --scale-800: var(--mindset-violet-800);
  --scale-900: var(--mindset-violet-900);
  --scale-950: var(--mindset-violet-950);
}

.scale--mindset-magenta {
  --scale-25: var(--mindset-magenta-25);
  --scale-50: var(--mindset-magenta-50);
  --scale-100: var(--mindset-magenta-100);
  --scale-200: var(--mindset-magenta-200);
  --scale-300: var(--mindset-magenta-300);
  --scale-400: var(--mindset-magenta-400);
  --scale-500: var(--mindset-magenta-500);
  --scale-600: var(--mindset-magenta-600);
  --scale-700: var(--mindset-magenta-700);
  --scale-800: var(--mindset-magenta-800);
  --scale-900: var(--mindset-magenta-900);
  --scale-950: var(--mindset-magenta-950);
}

.scale--skills-pink {
  --scale-25: var(--skills-pink-25);
  --scale-50: var(--skills-pink-50);
  --scale-100: var(--skills-pink-100);
  --scale-200: var(--skills-pink-200);
  --scale-300: var(--skills-pink-300);
  --scale-400: var(--skills-pink-400);
  --scale-500: var(--skills-pink-500);
  --scale-600: var(--skills-pink-600);
  --scale-700: var(--skills-pink-700);
  --scale-800: var(--skills-pink-800);
  --scale-900: var(--skills-pink-900);
  --scale-950: var(--skills-pink-950);
}

.scale--skills-orange {
  --scale-25: var(--skills-orange-25);
  --scale-50: var(--skills-orange-50);
  --scale-100: var(--skills-orange-100);
  --scale-200: var(--skills-orange-200);
  --scale-300: var(--skills-orange-300);
  --scale-400: var(--skills-orange-400);
  --scale-500: var(--skills-orange-500);
  --scale-600: var(--skills-orange-600);
  --scale-700: var(--skills-orange-700);
  --scale-800: var(--skills-orange-800);
  --scale-900: var(--skills-orange-900);
  --scale-950: var(--skills-orange-950);
}

.scale-row > .shade:nth-child(1) { --swatch-color: var(--scale-25); }
.scale-row > .shade:nth-child(2) { --swatch-color: var(--scale-50); }
.scale-row > .shade:nth-child(3) { --swatch-color: var(--scale-100); }
.scale-row > .shade:nth-child(4) { --swatch-color: var(--scale-200); }
.scale-row > .shade:nth-child(5) { --swatch-color: var(--scale-300); }
.scale-row > .shade:nth-child(6) { --swatch-color: var(--scale-400); }
.scale-row > .shade:nth-child(7) { --swatch-color: var(--scale-500); }
.scale-row > .shade:nth-child(8) { --swatch-color: var(--scale-600); }
.scale-row > .shade:nth-child(9) { --swatch-color: var(--scale-700); }
.scale-row > .shade:nth-child(10) { --swatch-color: var(--scale-800); }
.scale-row > .shade:nth-child(11) { --swatch-color: var(--scale-900); }
.scale-row > .shade:nth-child(12) { --swatch-color: var(--scale-950); }

.page--design-system {
  --text-base: var(--geistface-grey-200);
  --text: var(--text-base);
  --content: 1240px;
  --nav: 272px;
  background: var(--deep);
  --shadow-color: 10 13 18;
  --shadow-lg: 0 12px 16px -4px rgb(var(--shadow-color) / 8%), 0 4px 6px -2px rgb(var(--shadow-color) / 3%), 0 2px 2px -1px rgb(var(--shadow-color) / 4%);
  --shadow: var(--shadow-lg);
  --surface-brand: var(--violet);
}

html:has(> .page--design-system) { scroll-behavior: smooth; scroll-padding-top: 24px; }
.page--design-system .shell { min-height: 100vh; }

.page--design-system .sidebar {
  position: fixed;
  inset: 0 auto 0 0;
  width: var(--nav);
  padding: var(--space-28) var(--space-18) var(--space-22);
  border-right: 1px solid var(--border);
  z-index: 50;
}

.page--design-system .brand-lockup { padding: var(--space-4) var(--space-8); }
.page--design-system .brand-logo { width: 158px; filter: brightness(0) invert(1); }

.page--design-system .nav-list { overflow: auto; padding-right: var(--space-4); }

.page--design-system .nav-list a {
  gap: var(--spacing-md);
  padding: var(--spacing-md) var(--spacing-lg);
  border: 1px solid transparent;
  border-radius: var(--radius-control);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-sm);
  font-weight: var(--weight-medium);
}

.page--design-system .nav-list a::before {
  content: "";
  width: var(--space-4);
  height: var(--space-4);
  border-radius: 50%;
  background: color-mix(in srgb, var(--geistface-grey-400) 36%, transparent);
}

.page--design-system .nav-list a:hover {
  background: color-mix(in srgb, var(--deep-navy-25) 6%, transparent);
  color: var(--text);
}

/* The section you are reading takes the navigation violet, the same statement
   every other current route in the system makes. Hover and current used to
   share this one wash, so the only thing separating where you are from where
   the pointer is was a four-pixel dot. */
.page--design-system .nav-list a.is-active {
  border-color: var(--nav-current-border);
  background: var(--nav-current-surface);
  color: var(--nav-current-text);
  box-shadow: none;
}
.page--design-system .nav-list a.is-active::before { background: var(--surface-brand, var(--violet)); }
.page--design-system .sidebar-footer { padding: 0 var(--space-8); }

.page--design-system .mobile-bar,
.page--design-system[data-chart-view="dark"] #charts .chart-card.light,
.page--design-system[data-chart-view="light"] #charts .chart-card.dark {
  display: none;
}

.page--design-system main { margin-left: var(--nav); }
.hero--design-system { background: var(--deep); }
.page--design-system .hero-inner { padding: var(--space-104) 0 var(--space-88); }


.page--design-system h1, .page--design-system h2, .page--design-system h3, .page--design-system p {
  margin-top: 0;
}

.section-head--homepage h2, .page--design-system h1 { max-width: 760px; }

.hero-copy--design-system, .page--design-system .lead {
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
  max-width: var(--measure-md);
}

.section-head--design-system {
  grid-template-columns: 220px 1fr;
  gap: var(--space-56);
  margin-bottom: var(--space-48);
}

.page--design-system .section-head-copy { max-width: 860px; }

.page--design-system .base-component-panel, .page--design-system .guidance-card {
  border-radius: var(--radius-card);
  padding: var(--spacing-3xl, var(--space-24));
}

.page--design-system .principle-num {
  width: var(--space-32);
  height: var(--space-32);
  margin-bottom: var(--space-30);
}

.page--design-system .mode-preview { height: 84px; padding: var(--space-12); }
.page--design-system .mode-preview.dark { background: var(--deep); }
.page--design-system .mode-preview.bright { background: var(--deep-navy-25); }
.page--design-system .mode-preview.dark i:first-child { background: var(--panel-900); }
.page--design-system .mode-preview.dark i:last-child { background: var(--violet); }
.page--design-system .mode-preview.bright i:first-child { background: var(--geistface-grey-100); }
.page--design-system .mode-preview.bright i:last-child { background: var(--violet); }
.page--design-system .logo-stage { min-height: 340px; padding: var(--space-64); }
.page--design-system .logo-stage.light { background: var(--deep-navy-25); }
.page--design-system .logo-stage.dark { background: var(--panel-navy-950); }
.page--design-system .logo-asset { width: min(78%, 460px); }
.page--design-system .logo-stage.dark img { filter: brightness(0) invert(1); }
.page--design-system .asset-label { left: var(--space-28); }
.page--design-system .logo-stage.light .asset-label { color: var(--geistface-grey-700); }

.page--design-system .clear-space {
  border: 1px dashed color-mix(in srgb, var(--mindset-violet-500) 55%, transparent);
  border-radius: var(--radius-2xl);
}

.page--design-system .guidance-title { margin-bottom: var(--space-16); }

/* The marker always sits on a bright green or pink chip, so its ink belongs to
   the chip rather than the page. It read var(--deep), which flips with the
   surface, so on the dark page the tick drew near-white on Core Green: 1.38:1.
   The chip never changes colour, so neither does the ink on it. */
.page--design-system .guidance-icon {
  width: 34px;
  height: 34px;
  --icon-color: var(--deep-navy-900);
  color: var(--deep-navy-900);
}

.page--design-system .guidance-card.use .guidance-icon { background: var(--green); }
.page--design-system .guidance-card.avoid .guidance-icon { background: var(--pink); }
.page--design-system .checklist { list-style: none; }
.page--design-system .checklist li { grid-template-columns: 10px 1fr; }

.page--design-system .checklist li::before {
  content: "";
  width: var(--space-4);
  height: var(--space-4);
  border-radius: 50%;
  background: color-mix(in srgb, var(--geistface-grey-400) 58%, transparent);
  margin-top: var(--space-8);
}

.page--design-system .token-swatch {
  height: 132px;
  border-radius: var(--radius-compact);
}

.page--design-system .copy-chip {
  right: var(--space-10);
  top: var(--space-10);
  border: 1px solid currentColor;
  padding: var(--space-4) var(--space-8);
  background: color-mix(in srgb, var(--geistface-grey-25) 18%, transparent);
  color: inherit;
}

.page--design-system .token-title { margin-bottom: var(--space-2); }
.page--design-system .token-usage { margin-top: var(--space-8); }

.page--design-system .spectrum-band {
  grid-template-columns: repeat(8, minmax(0, 1fr));
  border-radius: var(--surface-radius-md);
}

.page--design-system .spectrum-segment { min-height: 190px; padding: var(--space-18) var(--space-14); }

.page--design-system .guideline-card,
.page--design-system .implementation-note,
.page--design-system .rule-card,
.page--design-system .story-card {
  padding: var(--spacing-2xl);
}

.page--design-system .story-card span { margin-bottom: var(--space-10); }
.page--design-system .story-card p { margin-bottom: 0; }
:is(.page--design-system, .page--tailwind-handoff) .scale-head { align-items: end; margin-bottom: var(--space-14); }
:is(.page--design-system, .page--tailwind-handoff) .scale-row { grid-template-columns: repeat(12, minmax(0, 1fr)); }
:is(.page--design-system, .page--tailwind-handoff) .shade { min-height: 86px; padding: var(--space-10) var(--space-6); }

:is(.page--design-system, .page--tailwind-handoff) .shade code {
  color: inherit;
  opacity: 0.82;
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  align-self: flex-end;
}

.page--design-system .canonical { padding: var(--space-2) var(--space-4); border: 1px solid currentColor; }

/* The mode-default marker on a ramp step. .canonical says "this is the 500, the
   colour's own identity"; this says "this is the step that identity RESOLVES to
   in one mode". Both can sit on the same ramp without contradiction, which is
   the point: the palette did not change, only which rung the semantic token
   reaches for. The filled pill distinguishes it from .canonical's outline. */
.page--design-system .mode-default {
  /* inline-block, not the inherited inline: the label below is a block-level
     ::after, and an inline box cannot contain one - the border wrapped in two
     pieces and the pill measured 57px tall against .canonical's 22px. */
  display: inline-block;
  padding: var(--space-2) var(--space-6);
  border: 1px solid currentColor;
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, currentColor 16%, transparent);
  text-align: center;
}

/* The label sits UNDER the step, not beside it. A ramp cell is only about 60px
   of usable width at the narrow end of the grid, and the inline form measured
   71-79px, so every pill overflowed its swatch and the longest one wrapped. */
.page--design-system .mode-default::after {
  content: "dark";
  display: block;
  margin-top: var(--space-2);
  /* 9px matches .font-2xs, which is what the hex code in this same cell uses,
     so the pill's second line sits at the swatch's caption size rather than at
     the step number's. The earlier draft named two tokens that do not exist
     (--text-2xs-font-size, --tracking-label), which silently resolved to
     nothing and left this line inheriting the number's 12px. */
  font-size: 9px;
  font-family: var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  line-height: 1;
  opacity: 0.75;
}

.page--design-system .mode-default[data-mode="light"]::after { content: "light"; }
.page--design-system .mode-default[data-mode="both"]::after { content: "both"; }

/* Where a ramp has a mode-specific rule, BOTH ends carry a label - the 500 says
   which mode it still answers, the 400 or 600 says the other. An unlabelled 500
   beside a labelled 600 read as though only one of them had a mode, which is
   the opposite of the point. On a ramp with no mode rule the 500 keeps the bare
   .canonical outline and says nothing, because there is nothing to say. */
.page--design-system .canonical.mode-default { padding: var(--space-2) var(--space-6); }

.page--design-system .alias {
  padding: 1px var(--space-4);
  border: 1px solid currentColor;
  font-size: 8px;
  line-height: 12px;
}

.page--design-system .pair-sample { font-size: 42px; line-height: 50px; }
.page--design-system .pair-card span { opacity: 0.78; }

/* Background pattern library. One pattern per row, every colour in that row, so
   patterns compare down the page and colours compare across it. The row lays
   itself out with auto-flow, which keeps it a single line whatever the colour
   count is; the floor makes a long row scroll rather than crush its swatches.
   Nothing here paints colour — every colour on show ships inside the asset. */
.page--design-system .bg-library {
  display: grid;
  gap: var(--space-32);
}

/* A grid item's min-width is its content by default, so on a phone the row
   was propping the whole page open instead of scrolling. Let the pattern
   shrink and the row's own overflow-x take over, as the note above intends. */
.page--design-system .bg-library .bg-pattern { min-width: 0; }

.page--design-system .bg-pattern__head {
  padding-bottom: var(--space-10);
  margin-bottom: var(--space-14);
}

.page--design-system .bg-swatch-row {
  grid-template-columns: none;
  grid-auto-flow: column;
  grid-auto-columns: minmax(76px, 1fr);
  gap: var(--space-8);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  padding-bottom: var(--space-4);
}

/* <figure> ships a 40px inline margin by default, which inside a ~93px grid
   column leaves 14px of swatch. */
.page--design-system .bg-swatch { margin: 0; }

.page--design-system .bg-swatch__art {
  width: 100%;
  height: auto;
  aspect-ratio: 380 / 210;
}

.page--design-system .bg-swatch__name {
  padding-top: var(--space-6);
  text-align: center;
}

.page--design-system .study-card {
  min-height: 300px;
  padding: var(--space-22);
  border-radius: var(--radius-2xl);
  isolation: isolate;
}

.page--design-system .study-top { z-index: 2; }

.page--design-system .study-label {
  font-size: var(--text-xxs-font-size);
  line-height: var(--text-xxs-line-height);
  letter-spacing: 0.12em;
}

.page--design-system .study-number { font-weight: var(--weight-bold); opacity: 0.7; }

.page--design-system .study-card h4 {
  margin: auto 0 0;
  font-size: var(--display-md-font-size);
  line-height: 0.9;
  letter-spacing: -0.055em;
  font-weight: var(--weight-semibold);
  z-index: 2;
}

.page--design-system .study-card p {
  margin: var(--space-32) 0 0;
  max-width: 220px;
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xxs-line-height);
  z-index: 2;
}

.page--design-system .study-motif {
  right: -16px;
  bottom: -28px;
  width: 130px;
  height: 130px;
  color: var(--accent);
  opacity: 0.25;
  z-index: 1;
}

.page--design-system .study-motif.ring { border: 20px solid var(--accent); }

.page--design-system .study-motif.diamond {
  border: 18px solid var(--accent);
  border-radius: var(--radius-3xl);
  transform: rotate(22deg);
}

.page--design-system .study-motif.arch {
  border: 18px solid var(--accent);
  border-bottom: 0;
  border-radius: 90px 90px 0 0;
}

.page--design-system .study-motif.orbit,
.page--design-system .study-motif.orbit b,
.page--design-system .study-motif.target {
  border: 4px solid var(--accent);
}

.page--design-system .study-motif.orbit b { inset: var(--space-44); background: var(--accent); }

.page--design-system .study-motif.orbit i, .page--design-system .study-motif.target i {
  border: 4px solid var(--accent);
  inset: var(--space-20);
}

.page--design-system .study-motif.target i { inset: var(--space-40); }

.page--design-system .study-motif.target::after, .page--design-system .study-motif.target::before {
  content: "";
  position: absolute;
  background: var(--accent);
  left: 50%;
  top: -15px;
  width: var(--space-4);
  height: var(--space-160);
}

.page--design-system .study-motif.target::after { transform: rotate(90deg); }
.page--design-system .study-motif.bars { align-items: flex-end; gap: var(--space-8); }

.page--design-system .study-motif.bars i {
  flex: 1;
  background: var(--accent);
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
}

.page--design-system .study-motif.bars i:nth-child(1) { height: 35%; }
.page--design-system .study-motif.bars i:nth-child(2) { height: 54%; }
.page--design-system .study-motif.bars i:nth-child(3) { height: 73%; }
.page--design-system .study-motif.bars i:nth-child(4) { height: 95%; }

.page--design-system .study-motif.sun {
  border: 5px solid var(--accent);
  box-shadow: 0 0 0 14px color-mix(in srgb, var(--accent) 55%, transparent), 0 0 0 28px color-mix(in srgb, var(--accent) 25%, transparent);
}

.page--design-system .study-motif.wave, .page--design-system .study-motif.waves {
  align-content: center;
}

.page--design-system .study-motif.wave i, .page--design-system .study-motif.waves i {
  height: var(--space-12);
  border-top: 4px solid var(--accent);
}

.page--design-system .study-motif.frame {
  border: 18px solid var(--accent);
  border-radius: var(--radius-2xl);
  transform: rotate(16deg);
}

.page--design-system .study-motif.chevrons { gap: var(--space-2); }

.page--design-system .study-motif.chevrons i {
  width: var(--space-42);
  height: var(--space-42);
  border-top: 11px solid var(--accent);
  border-right: 11px solid var(--accent);
  transform: rotate(45deg);
}

.page--design-system .study-motif.fingerprint {
  border: 5px solid var(--accent);
  border-radius: 50% 50% 42% 42%;
}

.page--design-system .study-motif.fingerprint b, .page--design-system .study-motif.fingerprint i {
  border: 4px solid var(--accent);
  border-radius: 50% 50% 42% 42%;
  inset: var(--space-36);
}

.page--design-system .study-motif.fingerprint i { inset: var(--space-18); }

.page--design-system .study-motif.halo {
  border: 18px solid var(--accent);
  box-shadow: 0 0 0 18px color-mix(in srgb, var(--accent) 30%, transparent);
}

.page--design-system .study-motif.rotate { font-size: 108px; line-height: 1; }

.page--design-system .study-motif.cube {
  width: 94px;
  height: 94px;
  border: 6px solid var(--accent);
  transform: rotate(30deg);
  bottom: -8px;
  right: var(--space-12);
}

.page--design-system .study-motif.cube::after, .page--design-system .study-motif.cube::before {
  content: "";
  position: absolute;
  background: var(--accent);
}

.page--design-system .study-motif.cube::before {
  width: var(--space-6);
  height: 140%;
  left: 48%;
  top: -20%;
  transform: rotate(30deg);
}

.page--design-system .study-motif.cube::after {
  height: var(--space-6);
  width: 140%;
  top: 48%;
  left: -20%;
  transform: rotate(-30deg);
}

.page--design-system .study-motif.nodes i {
  width: var(--space-40);
  height: var(--space-40);
  background: var(--accent);
}

.page--design-system .study-motif.nodes i:nth-child(1) { left: var(--space-12); top: 52px; }
.page--design-system .study-motif.nodes i:nth-child(2) { left: 46px; top: var(--space-10); }
.page--design-system .study-motif.nodes i:nth-child(3) { right: var(--space-8); top: 58px; }

.page--design-system .study-motif.nodes::before {
  content: "";
  position: absolute;
  inset: var(--space-30) var(--space-14);
  border: 5px solid var(--accent);
  transform: rotate(8deg);
}

.page--design-system .study-motif.bolt {
  background: var(--accent);
  clip-path: polygon(52%0, 20% 53%, 48% 53%, 33% 100%, 83% 39%, 56% 39%);
}

.page--design-system .study-motif.cursor {
  background: var(--accent);
  clip-path: polygon( 9%0, 82% 66%, 50% 70%, 73% 100%, 58% 100%, 36% 73%, 9% 100% );
}

.page--design-system .study-motif.dotgrid {
  background: var(--accent);
  clip-path: polygon( 0 0, 20%0, 20% 20%, 40% 20%, 40%0, 60%0, 60% 20%, 80% 20%, 80%0, 100%0, 100% 20%, 80% 20%, 80% 40%, 100% 40%, 100% 60%, 80% 60%, 80% 80%, 100% 80%, 100% 100%, 80% 100%, 80% 80%, 60% 80%, 60% 100%, 40% 100%, 40% 80%, 20% 80%, 20% 100%, 0 100%, 0 80%, 20% 80%, 20% 60%, 0 60%, 0 40%, 20% 40%, 20% 20%, 0 20% );
}

.page--design-system .study-motif.flower {
  background: var(--accent);
  clip-path: polygon( 50%0, 63% 29%, 91% 9%, 71% 38%, 100% 50%, 71% 62%, 91% 91%, 62% 71%, 50% 100%, 38% 71%, 9% 91%, 29% 62%, 0 50%, 29% 38%, 9% 9%, 38% 29% );
}

.page--design-system .table-wrap, .page--design-system .type-table-wrap {
  overflow: auto;
  border-radius: var(--surface-radius-md);
}

.page--design-system table { border-collapse: collapse; min-width: 760px; }
.page--design-system .font-lockup { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.page--design-system .font-card { padding: var(--space-28); min-height: 230px; }

.page--design-system .font-card .alphabet {
  font-size: var(--display-lg-font-size);
  line-height: var(--display-lg-line-height);
  letter-spacing: -0.04em;
  margin: var(--space-28) 0 var(--space-12);
}


.page--design-system .component-stage { padding: var(--space-28); }
.page--design-system .state-row { grid-template-columns: 150px repeat(5, minmax(0, 1fr)); }
.page--design-system .state-row + .state-row { margin-top: var(--space-12); }

.page--design-system .field input, .page--design-system .field textarea {
  width: 100%;
  border-color: color-mix(in srgb, var(--geistface-grey-400) 28%, transparent);
}

/* The demo has to paint the ring the component actually paints: Part A's
   .field :is(input, ...):focus-visible recipe, not a lighter 3px approximation. */
.page--design-system .field.focus input {
  border-color: var(--mindset-violet-200);
  box-shadow:
    0 0 0 var(--space-4)
      color-mix(
        in srgb,
        var(--mindset-violet-400) 24%,
        transparent
      );
}

.page--design-system .field.error input { border-color: var(--pink); }
.page--design-system .field.error .help { color: var(--pink); }
.page--design-system .field.success input { border-color: var(--green); }

.page--design-system .rule-card strong { margin-bottom: var(--space-8); }

.page--design-system .badge-card img, .page--design-system .achievement-anatomy-example img {
  aspect-ratio: 1;
  object-fit: contain;
}

.page--design-system .badge-card figcaption { gap: var(--space-2); padding: var(--space-10) var(--space-4) var(--space-4); }
.page--design-system .anatomy-item { grid-template-columns: 28px 1fr; }

.page--design-system .guideline-card strong { margin-bottom: var(--space-8); }

.page--design-system pre {
  padding: var(--space-22);
  overflow: auto;
  color: var(--code-text);
  line-height: 1.7;
}

.page--design-system .implementation-grid { grid-template-columns: 1.2fr 0.8fr; }


.page--design-system .roadmap {
  gap: 1px;
  background: var(--border);
  border-radius: var(--surface-radius-md);
}

.page--design-system .phase {
  grid-template-columns: 180px 1fr;
  gap: var(--space-24);
  padding: var(--space-24);
}

.hero--design-system { min-height: 620px; }
.page--design-system .hero-inner { margin-right: auto; }
.page--design-system .hero-inner > div:first-child { max-width: 900px; }
.page--design-system .clear-space { inset: var(--space-36) var(--space-36) 76px; }
.page--design-system .asset-label { bottom: var(--space-24); }


html[data-surface="light"] > .page--design-system {
  --skillprint-shared-border: color-mix(in srgb, var(--deep-navy-900) 11%, transparent);
  --text-base: var(--geistface-grey-800);
  --text: var(--text-base);
  background: var(--deep-navy-25);
  color: var(--deep-navy-900);
  --shadow: var(--shadow-lg);
  --focus-inner: var(--geistface-grey-25);
  --button-focus-inner: var(--geistface-grey-25);
  --button-surface: var(--geistface-grey-25);
  /* --button-shadow and --button-group-shadow are deliberately absent. They
     used to be restated here, carrying an `inset 0 0 0 1px` ring that doubled
     --button-border; the corrected value now lives once on
     html[data-surface="light"] and every light page inherits it. */
  --button-group-surface: var(--geistface-grey-25);
  --button-group-current: var(--deep-navy-25);
  --button-group-text: var(--geistface-grey-800);
  --button-group-disabled: var(--deep-navy-300);
  --button-group-border: var(--panel-navy-100);
  --button-group-icon: var(--deep-navy-300);
  --button-group-icon-strong: var(--geistface-grey-600);
}

html[data-surface="light"] > .page--design-system .sidebar { background: var(--panel-950); }
html[data-surface="light"] > .page--design-system .brand-logo { filter: none; }

html[data-surface="light"] > .page--design-system .nav-list a:hover {
  background: color-mix(in srgb, var(--deep-navy-900) 6%, transparent);
}


html[data-surface="light"] > .page--design-system .logo-stage.dark .asset-label {
  color: var(--geistface-grey-200);
}

html[data-surface="light"] > .page--design-system .mobile-bar { background: var(--panel-950); }

.page--design-system .source-surface-row {
  min-height: 108px;
  padding: var(--space-24) var(--space-32);
  border-radius: var(--radius-3xl);
  transition: background var(--duration-fast), border-color var(--duration-fast), transform var(--duration-fast), box-shadow var(--duration-fast);
}

.page--design-system .source-surface-row:hover {
  background: var(--panel-900);
  border-color: color-mix(in srgb, var(--geistface-grey-400) 34%, transparent);
  transform: translateY(-1px);
  box-shadow: var(--shadow-md);
}


.page--design-system .source-surface-url {
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
  overflow-wrap: anywhere;
}


/* The row is the link; the action is the button component sitting inside it.
   Hovering anywhere on the row reproduces the tertiary hover state through the
   component's own tokens rather than a colour this section invents. */
.page--design-system .source-surface-row:hover .source-surface-action {
  background: var(--button-surface-hover);
  color: var(--button-text);
}

html[data-surface="light"] > .page--design-system .source-surface-row {
  background: var(--panel-900);
  border-color: color-mix(in srgb, var(--deep-navy-900) 14%, transparent);
  box-shadow: var(--shadow-md);
}

html[data-surface="light"] > .page--design-system .source-surface-row:hover {
  background: var(--panel-950);
  border-color: color-mix(in srgb, var(--deep-navy-900) 24%, transparent);
}

.page--design-system .chart-section-head { margin-bottom: var(--space-56); }
.page--design-system .chart-view-toolbar { margin-top: var(--space-28); }


.page--design-system .chart-view-switch {
  grid-template-columns: repeat(3, 1fr);
  padding: var(--space-4);
  background: color-mix(in srgb, var(--deep-navy-25) 4%, transparent);
}

.page--design-system .chart-view-switch button { min-width: 78px; padding: var(--space-8) var(--space-10); }
.page--design-system .chart-view-switch button:hover { color: var(--text); }

.page--design-system .chart-view-switch button.is-active {
  box-shadow: var(--shadow-sm);
}

html[data-surface="light"] > .page--design-system .chart-view-switch {
  background: color-mix(in srgb, var(--deep-navy-900) 4%, transparent);
}

html[data-surface="light"] > .page--design-system .chart-view-switch button.is-active {
  background: var(--panel-900);
  box-shadow: var(--shadow-sm);
}

.page--design-system .chart-study + .chart-study { margin-top: var(--space-96); }

.page--design-system .study-title,
.page--design-system .badge-reference-head,
.page--design-system .game-art-head,
.page--design-system .asset-icon-guide__head {
  align-items: end;
  margin-bottom: var(--space-22);
}

.page--design-system .study-note { max-width: 520px; text-align: right; }


.page--design-system .chart-card.dark {
  --card-bg: var(--panel-navy-950);
  --card-panel: var(--panel-navy-900);
  --card-text: var(--geistface-grey-200);
  --card-muted: color-mix(in srgb, var(--geistface-grey-200) 72%, transparent);
  --card-grid: color-mix(in srgb, var(--geistface-grey-200) 9%, transparent);
  --card-border: color-mix(in srgb, var(--geistface-grey-200) 12%, transparent);
  --card-shadow: color-mix(in srgb, var(--deep-navy-950) 22%, transparent);
}

.page--design-system .chart-card.light {
  --card-bg: var(--geistface-grey-25);
  --card-panel: var(--panel-navy-50);
  --card-text: var(--geistface-grey-800);
  --card-muted: color-mix(in srgb, var(--geistface-grey-800) 72%, transparent);
  --card-grid: color-mix(in srgb, var(--deep-navy-900) 11%, transparent);
  --card-border: color-mix(in srgb, var(--deep-navy-900) 12%, transparent);
  --card-shadow: color-mix(in srgb, var(--deep-navy-900) 9%, transparent);
}

.page--design-system .theme-label { color: var(--card-muted); }

.page--design-system .chart-card-title strong, .page--design-system .stat strong {
  margin-top: var(--space-6);
  font-size: var(--text-xl-font-size);
  line-height: var(--text-xl-line-height);
  letter-spacing: -0.015em;
}

.page--design-system .stat strong {
  font-size: var(--display-xs-font-size);
  line-height: var(--display-xs-line-height);
  letter-spacing: -0.025em;
}

.page--design-system .chart-card-title span { margin-top: var(--space-4); color: var(--card-muted); }
.page--design-system .stat { border: 1px solid var(--card-border); background: var(--card-panel); }
.page--design-system .stat span { color: var(--card-muted); }
.page--design-system .stat small { margin-top: var(--space-2); font-variant-numeric: tabular-nums; }

.page--design-system .chart-insight {
  margin: 0 var(--space-28) var(--space-28);
  padding: var(--space-16) var(--space-18);
  border: 1px solid var(--card-border);
  background: var(--card-panel);
  color: var(--card-muted);
}

.page--design-system .chart-key { padding: var(--space-4) var(--space-28) var(--space-22); color: var(--card-muted); }
.page--design-system .range-marker { background: var(--violet) !important; }

.page--design-system .range-marker.is-current {
  border: 2px solid var(--card-bg);
  box-shadow: 0 0 0 1px var(--violet);
}

.page--design-system .chart-rules-block { margin-top: var(--space-104); padding-top: var(--space-48); }

.page--design-system .rule, .page--design-system .scale-family-demo article {
  border-radius: var(--surface-radius-md);
}

.page--design-system .rule { min-height: 196px; }

.page--design-system .rule b {
  width: 34px;
  height: 34px;
  margin-bottom: var(--space-30);
}

.page--design-system[data-chart-view="dark"] #charts .chart-pair,
.page--design-system[data-chart-view="light"] #charts .chart-pair {
  grid-template-columns: minmax(0, 760px);
}

.page--design-system .badge-reference-head p,
.page--design-system .game-art-head p,
.page--design-system .type-subsection-head p,
.page--design-system .badge-system-subhead p,
.page--design-system .nav-spec-board__head p,
.page--design-system .nav-spec-stateboard__head p,
.page--design-system .asset-icon-guide__head p {
  max-width: var(--space-560);
  text-align: right;
}

.page--design-system .badge-card img { padding: var(--space-4); }
.page--design-system .badge-subsection,
.page--design-system .game-art-subsection { margin-top: var(--space-80); }
.page--design-system .badge-section-head,
.page--design-system .game-art-section-head { margin-bottom: var(--space-32); }

.page--design-system .font-card.mono .alphabet,
.page--design-system .stat strong,
.page--design-system code {
  font-variant-numeric: tabular-nums;
}


.page--design-system .study-number {
  font-weight: var(--weight-bold);
  font-variant-numeric: tabular-nums;
  opacity: 0.7;
}

.page--design-system .study-card.title-recoleta h4 {
  font-family: var(--font-brand);
  font-weight: var(--weight-medium);
  line-height: 0.96;
  letter-spacing: -0.045em;
  text-transform: none;
}


.page--design-system .poster-type-rule {
  min-height: 126px;
  border-radius: var(--surface-radius-md);
  grid-template-columns: 74px 1fr;
}

.page--design-system .poster-key-number {
  font-size: var(--display-sm-font-size);
  line-height: var(--display-sm-line-height);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

.page--design-system .poster-key-title {
  font-family: var(--font-brand);
  font-size: var(--display-xs-font-size);
  line-height: var(--text-xl-line-height);
  font-weight: var(--weight-medium);
  letter-spacing: -0.03em;
}


.page--design-system .poster-type-rule small { margin-top: var(--space-4); }
.page--design-system .type-subsection-head { align-items: end; margin-top: var(--space-72); }

.page--design-system .shared-scale-callout {
  grid-template-columns: minmax(250px, 0.8fr) minmax(0, 1.2fr);
  border-radius: var(--surface-radius-lg);
}

.page--design-system .scale-family-demo { margin-top: var(--space-16); }

/* ---------------------------------------------------------------------------
   Typography specimens

   The tables give the numbers; these render them. Because the page can only
   be one width at a time, each sample carries its tier's size INLINE rather
   than reading the responsive token - that is the only way to show desktop,
   tablet and mobile side by side on one screen. A token whose size is the
   same at every tier collapses to a single row labelled "All devices", so
   the boards also show at a glance which steps of the ramp actually move.
   --------------------------------------------------------------------------- */

.page--design-system .type-specimens {
  display: grid;
  gap: var(--space-4);
  margin-top: var(--space-16);
}

.page--design-system .type-specimen {
  display: grid;
  gap: var(--space-20);
  padding: var(--space-28) var(--space-32);
  background: var(--surface-box);
  border: 1px solid var(--surface-border);
}

.page--design-system .type-specimen__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-8) var(--space-24);
}

.page--design-system .type-specimen__rows { display: grid; gap: var(--space-20); }

.page--design-system .type-specimen__row {
  display: grid;
  /* Wide enough that the longest label - "DESKTOP & TABLET · 30 / 38" at the
     uppercase mono size with 0.08em tracking - stays on one line. */
  grid-template-columns: 232px minmax(0, 1fr);
  align-items: baseline;
  gap: var(--space-24);
}

.page--design-system .type-specimen__tier {
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--ui-muted);
}

/* The sample is the point of the block, so it is allowed to break mid-word
   rather than push a 72px line out of the panel on a narrow screen. */
.page--design-system .type-specimen__sample {
  display: block;
  min-width: 0;
  font-family: var(--skillprint-font-ui);
  font-weight: var(--weight-medium);
  letter-spacing: -0.02em;
  color: var(--ui-text);
  overflow-wrap: anywhere;
}

/* App samples are drawn the way the app draws headings: semibold, neutral
   tracking - so the board matches what a product screen actually renders. */
.page--design-system .type-specimen--app .type-specimen__sample {
  font-weight: var(--app-heading-weight);
  letter-spacing: var(--app-heading-tracking);
}

@media (max-width: 767px) {
  .page--design-system .type-specimen { padding: var(--space-20); }
  .page--design-system .type-specimen__row { grid-template-columns: minmax(0, 1fr); gap: var(--space-6); }
}
.page--design-system .scale-demo-meta { margin-bottom: var(--space-22); }

.page--design-system .scale-family-demo strong {
  font-size: var(--display-sm-font-size);
  line-height: var(--display-sm-line-height);
  font-weight: var(--weight-medium);
  letter-spacing: -0.03em;
  overflow-wrap: anywhere;
}

.page--design-system .scale-family-demo .font-mono {
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.page--design-system .scale-family-demo small { margin-top: var(--space-12); }


.page--design-system .font-card.mono .alphabet {
  font-size: 42px;
  line-height: 54px;
  letter-spacing: -0.025em;
}

.page--design-system .font-meta { gap: var(--space-2); padding-top: var(--space-22); }
.page--design-system .type-combo { min-height: 390px; border-radius: var(--surface-radius-lg); }
.page--design-system .combo-top { margin-bottom: var(--space-42); }

.page--design-system .combo-number {
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

.page--design-system .type-combo p { margin: var(--space-28) 0 0; font-weight: var(--weight-regular); }
.page--design-system .combo-spec { gap: var(--space-2); padding-top: var(--space-30); }


.page--design-system .product-value {
  margin-bottom: var(--space-8);
  font-size: var(--display-xl-font-size);
  line-height: 68px;
  font-weight: var(--weight-medium);
  letter-spacing: -0.045em;
  font-variant-numeric: tabular-nums;
}

.page--design-system .weight-heading { margin-top: var(--space-72); }
.page--design-system .weight-table { margin-top: 0; }
.page--design-system .weight-grid { margin-top: var(--space-16); }
.page--design-system .weight-card-head { margin-bottom: var(--space-28); }


.page--design-system .weight-sample {
  font-size: var(--text-xl-font-size);
  line-height: var(--text-xl-line-height);
  letter-spacing: -0.025em;
}

.page--design-system .weight-mono { margin-top: var(--space-8); font-variant-numeric: tabular-nums; }


.page--design-system .integration-rule { min-height: 170px; border-radius: var(--surface-radius-md); }

.page--design-system .integration-rule b, .page--design-system .implementation-overview__grid b {
  width: var(--space-30);
  height: var(--space-30);
}

.page--design-system .integration-rule strong { margin-bottom: var(--space-6); }
.page--design-system .pattern-note { margin-top: var(--space-16); padding: var(--space-16) var(--space-18); }

.page--design-system .pattern-note b {
  flex: 0 0 26px;
  width: 26px;
  height: 26px;
  /* fill with light text on it: keeps the 500 ladder, not the accent tier */
  background: var(--button-primary);
}

.page--design-system .integrated-module { margin-top: var(--space-40); isolation: isolate; }
.page--design-system .integrated-module + .pattern-note { margin-top: var(--space-22); }

.page--design-system .integrated-module .mobile-bar,
.page--design-system .integrated-module .sidebar,
.page--design-system .integrated-module nav {
  display: none !important;
}

.page--design-system .integrated-module img { max-width: 100%; }

.page--design-system .integrated-module article[id],
.page--design-system .integrated-module header[id],
.page--design-system .integrated-module section[id] {
  scroll-margin-top: 28px;
}

.page--design-system .trait-module {
  color-scheme: dark;
  /* 48% composited to 3.85:1 on the panel - below AA for the 10px labels using it. */
  --faint: color-mix(in srgb, var(--geistface-grey-200) 62%, transparent);
  --shadow: var(--shadow-md);
  --action: var(--mindset-violet-500);
  --action-hover: var(--mindset-violet-600);
  --action-active: var(--mindset-violet-700);
  --focus: var(--mindset-violet-200);
  --success: var(--core-green-500);
  --danger: var(--skills-pink-500);
  --mood-500: var(--lime);
  --cognition-500: var(--mint);
  --personality-500: var(--blue);
  --trait-on-color: var(--deep-navy-900);
  --content: 1260px;
  --sidebar: 264px;
  box-sizing: border-box;
}

html[data-surface="light"] > .page--design-system .trait-module {
  color-scheme: light;
  --faint: color-mix(in srgb, var(--geistface-grey-900) 82%, transparent);
  --shadow: var(--shadow-md);
}

/* The ground is painted from --page, not --deep-navy, for the reason the
   api-module already records: every token and every inner surface inside this
   specimen follows the page's mode, but --deep-navy is pinned to deep-navy-900
   in this scope and never does. Painting from --deep-navy left the module dark
   in light mode while its own cards went light, so the headings - which take
   the light-mode text colour - landed on the dark ground at 1.00:1 and could
   not be read. --page is deep-navy-900 in dark and deep-navy-25 in light, so
   this is the same value in dark mode and the correct one in light. */
.page--design-system .trait-module {
  scroll-behavior: smooth;
  scroll-padding-top: 28px;
  background: var(--page);
  -webkit-font-smoothing: antialiased;
}

.page--design-system .trait-module main { margin-left: var(--sidebar); }

.page--design-system .trait-module .hero-inner {
  min-height: 620px;
  padding: var(--space-88) 0;
  grid-template-columns: 1.05fr 0.95fr;
  gap: var(--space-64);
}

.page--design-system .brand-meta,
.page--design-system .card-kicker, .page--design-system .section-index,
.page--design-system .source-surface-name,
.page--design-system .chart-number, .page--design-system .theme-label,
.page--design-system .trait-module .card-kicker, .page--design-system .trait-module .section-index { letter-spacing: 0.14em; }

.page--design-system .library-module h1,
.page--design-system .library-module h2,
.page--design-system .library-module h3,
.page--design-system .library-module p,
.page--design-system .trait-module h1,
.page--design-system .trait-module h2,
.page--design-system .trait-module h3,
.page--design-system .trait-module p {
  margin-top: 0;
}

/* The trait module opens with its own hero, but the document already has the
   page h1 - this heading is an h2 for structure and keeps the h1 scale. */
.page--design-system .hero--trait h2 {
  font-size: clamp(44px, 6vw, 72px);
  line-height: 1.08;
  letter-spacing: -0.05em;
}

.page--design-system .trait-module .hero-copy--trait, .page--design-system .trait-module .lead {
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
  max-width: var(--measure-md);
}

.page--design-system .trait-module .hero-system, .page--design-system .nav-spec-board {
  border-radius: var(--surface-radius-lg);
  box-shadow: var(--shadow);
}

.page--design-system .trait-module .hero-system-head { padding-bottom: var(--space-18); }


.page--design-system .trait-module .pillar-stack { padding-top: var(--space-18); }

.page--design-system .trait-module .pillar-preview {
  --pillar: var(--mood-500);
  grid-template-columns: 44px 1fr auto;
  padding: var(--space-12);
}

.page--design-system .trait-module .pillar-preview[data-pillar="cognition"] {
  --pillar: var(--cognition-500);
}

.page--design-system .trait-module .pillar-preview[data-pillar="personality"] {
  --pillar: var(--personality-500);
}

.page--design-system .trait-module .pillar-preview .icon-box {
  width: var(--space-42);
  height: var(--space-42);
  background: var(--pillar);
}


.section-head--trait {
  grid-template-columns: 210px 1fr;
  gap: var(--space-56);
  margin-bottom: var(--space-44);
}

.page--design-system .trait-module .section-head-copy { max-width: 850px; }

.page--design-system .trait-module .principle-number {
  width: 34px;
  height: 34px;
  margin-bottom: var(--space-32);
  font: 600 11px/1 var(--font-mono);
}

.page--design-system .trait-module .pillar-card::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
}

.page--design-system .trait-module .pillar-card[data-pillar="mood"] { --pillar: var(--mood-500); }

.page--design-system .trait-module .pillar-card[data-pillar="cognition"] {
  --pillar: var(--cognition-500);
}

.page--design-system .trait-module .pillar-card[data-pillar="personality"] {
  --pillar: var(--personality-500);
}

.page--design-system .trait-module .pillar-card .icon-box {
  width: 50px;
  height: 50px;
  background: var(--pillar);
}


.page--design-system .trait-module .swatch-strip { margin-top: var(--space-22); }

.page--design-system .trait-module .swatch-strip i {
  height: var(--space-64);
  border: 1px solid color-mix(in srgb, var(--geistface-grey-25) 8%, transparent);
}

html[data-surface="light"] > .page--design-system .trait-module .swatch-strip i {
  border-color: color-mix(in srgb, var(--deep-navy-900) 8%, transparent);
}

.page--design-system .trait-module .token-row { padding: var(--space-10) 0; }
.page--design-system .trait-module .token-row:first-of-type { margin-top: var(--space-18); }
.page--design-system .trait-module .hero-system-head span,
.page--design-system .trait-module .pillar-count,
.page--design-system .trait-module .token-row code { font: 11px/16px var(--font-mono); }

.page--design-system .trait-module .rule-banner {
  grid-template-columns: 42px 1fr;
  border-radius: var(--radius-xl);
}

.page--design-system .trait-module .rule-banner b {
  width: var(--space-32);
  height: var(--space-32);
  background: var(--success);
  font: 700 12px/1 var(--font-mono);
}

.page--design-system .trait-module .library-toolbar { margin-bottom: var(--space-22); }
.page--design-system .trait-module .search-field { width: min(360px, 100%); }

.page--design-system .trait-module .search-field svg,
.page--design-system .portal-library-embed .search-field > svg,
.page--portal-games .search-field > svg {
  left: var(--space-12);
  top: 50%;
  width: var(--space-16);
  height: var(--space-16);
  transform: translateY(-50%);
  --icon-stroke: var(--icon-stroke-16);
}

.page--design-system .trait-module .search-field input { height: var(--space-44); padding-block: 0; padding-inline-end: var(--space-14); }

.page--design-system .trait-module .trait-card {
  --pillar: var(--mood-500);
  display: flex;
  flex-direction: column;
}

.page--design-system .trait-module .trait-card[data-pillar="cognition"] {
  --pillar: var(--cognition-500);
}

.page--design-system .trait-module .trait-card[data-pillar="personality"] {
  --pillar: var(--personality-500);
}

.page--design-system .trait-module .trait-card.is-hidden { display: none; }

/* ---------------------------------------------------------------------------
   Trait module, restored from the approved design system.

   Consolidation dropped the base boxes: the 28 trait cards lost their padding,
   border, radius and surface and rendered as bare text blocks, and the icon
   boxes ended up filled with the pillar colour. The module's own principle is
   "colour identifies, not fills" - the box is a dark surface, the pillar colour
   is the glyph and a tinted border.
   -------------------------------------------------------------------------- */
.page--design-system .trait-module .trait-card {
  min-height: 214px;
  padding: var(--space-18);
  display: flex;
  flex-direction: column;
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-card);
  background: var(--panel-900);
}

.page--design-system .trait-module .trait-card:hover { border-color: var(--pillar); }

.page--design-system .trait-module .pillar-card {
  padding: var(--space-24);
  box-shadow: var(--shadow-sm);
}

.page--design-system .trait-module .trait-card h3 {
  margin: var(--space-18) 0 var(--space-2);
  font-size: var(--text-lg-font-size);
  line-height: var(--text-md-line-height);
}

.page--design-system .trait-module .icon-box {
  width: 46px;
  height: 46px;
  color: var(--trait-on-color);
  background: var(--pillar);
}

.page--design-system .trait-module .trait-pillar {
  display: inline-flex;
  align-items: center;
  background: var(--pillar);
  font: 600 10px/15px var(--font-mono);
}

.page--design-system .trait-module .trait-key {
  color: var(--muted);
  font: var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono);
  overflow-wrap: anywhere;
}

.page--design-system .trait-module .trait-description {
  margin: var(--space-12) 0 0;
  color: var(--muted);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
}

.page--design-system .trait-module .trait-card-footer {
  margin-top: auto;
  padding-top: var(--space-16);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-10);
}

.page--design-system .trait-module .usage-chip {
  padding: var(--space-2) var(--space-8);
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  color: var(--muted);
  background: var(--panel-950);
  font-size: var(--text-xxs-font-size);
  line-height: var(--text-xxs-line-height);
}

.page--design-system .trait-module .icon-id {
  color: var(--faint);
  font: var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono);
}

.page--design-system .trait-module .empty-state { display: none; }
.page--design-system .trait-module .demo-head { padding-bottom: var(--space-20); }
.page--design-system .trait-module .demo-stack { padding-top: var(--space-24); }
.page--design-system .trait-module .demo-row { grid-template-columns: 180px 1fr; }

.page--design-system .trait-module .pillar-tab, .page--design-system .trait-module .trait-chip {
  --pillar: var(--mood-500);
  min-height: var(--space-42);
  gap: var(--space-8);
  padding: 0 var(--space-14);
}

.page--design-system .trait-module .trait-chip { min-height: var(--space-40); padding: 0 var(--space-12) 0 var(--space-8); }

.page--design-system .trait-module .pillar-tab[data-pillar="cognition"] {
  --pillar: var(--cognition-500);
}

.page--design-system .trait-module .pillar-tab[data-pillar="personality"] {
  --pillar: var(--personality-500);
}

.page--design-system .trait-module .pillar-tab .dot {
  width: var(--space-8);
  height: var(--space-8);
  background: var(--pillar);
}

.page--design-system .trait-module .pillar-tab:hover {
  color: var(--text);
  border-color: var(--pillar);
}

.page--design-system .trait-module .pillar-tab.is-selected,
.page--design-system .trait-module .trait-chip.is-selected {
  background: var(--pillar);
  border-color: var(--pillar);
  color: var(--trait-on-color);
}

.page--design-system .trait-module .pillar-tab.is-selected .dot { background: var(--trait-on-color); }

.page--design-system .trait-module .trait-chip[data-pillar="cognition"] {
  --pillar: var(--cognition-500);
}

.page--design-system .trait-module .trait-chip[data-pillar="personality"] {
  --pillar: var(--personality-500);
}

.page--design-system .trait-module .trait-chip .mini-icon {
  color: var(--trait-on-color);
  background: var(--pillar);
}

.page--design-system .trait-module .trait-chip:hover { border-color: var(--pillar); }
.page--design-system .trait-module .trait-chip.is-disabled { opacity: 0.42; cursor: not-allowed; }

.page--design-system .trait-module .metric-card {
  --pillar: var(--mood-500);
  width: min(350px, 100%);
}

.page--design-system .trait-module .metric-card[data-pillar="cognition"] {
  --pillar: var(--cognition-500);
}

.page--design-system .trait-module .metric-card[data-pillar="personality"] {
  --pillar: var(--personality-500);
}

.page--design-system .trait-module .metric-card-head span { font: var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono); }
.page--design-system .trait-module .metric-value { margin-top: var(--space-22); letter-spacing: -0.04em; }
.page--design-system .trait-module .metric-note { margin-top: var(--space-4); }

.page--design-system .trait-module .metric-bar i {
  width: 74%;
  height: 100%;
  border-radius: inherit;
  background: var(--pillar);
}

.page--design-system .trait-module .state-sample, .page--design-system .interface-window__chrome {
  min-height: 38px;
  padding: 0 var(--space-12);
}

.page--design-system .trait-module .state-sample.is-disabled { opacity: 0.42; }

.page--design-system .trait-module .icon-anatomy {
  min-height: 360px;
  padding: var(--space-30);
  border-radius: var(--surface-radius-lg);
}

.page--design-system .trait-module .anatomy-icon {
  width: 172px;
  height: 172px;
  color: var(--trait-on-color);
  background: var(--mood-500);
  border-radius: 42px;
}

.page--design-system .trait-module .anatomy-icon svg { width: 84px; height: 84px; }
.page--design-system .trait-module .pillar-preview code,
.page--design-system .trait-module .measure { font: 10px/15px var(--font-mono); }

.page--design-system .trait-module .measure.top {
  top: var(--space-24);
  left: 50%;
  transform: translateX(-50%);
}

.page--design-system .trait-module .measure.left {
  left: var(--space-24);
  top: 50%;
  transform: translateY(-50%) rotate(-90deg);
}

.page--design-system .trait-module .measure.bottom {
  bottom: var(--space-24);
  left: 50%;
  transform: translateX(-50%);
}

.page--design-system .trait-module .spec-item { grid-template-columns: 34px 1fr; }

.page--design-system .trait-module .spec-item b {
  width: var(--space-32);
  height: var(--space-32);
  font: 600 11px/1 var(--font-mono);
}

.page--design-system .trait-module .guidance-head { gap: var(--space-10); margin-bottom: var(--space-14); }
.page--design-system .trait-module .guidance.good .guidance-head b { background: var(--success); }
.page--design-system .trait-module .guidance.bad .guidance-head b { background: var(--danger); }
.page--design-system .trait-module .guidance ul { padding-left: var(--space-18); }
.page--design-system .trait-module .guidance li + li { margin-top: var(--space-8); }
.page--design-system .trait-module .chart-example { border-radius: var(--surface-radius-lg); }

.page--design-system .trait-module .legend i {
  --legend-color: var(--text);
  width: var(--space-18);
  height: 0;
  border-radius: 0;
  border-top: 3px solid var(--legend-color);
}

.page--design-system .trait-module .legend i.series-dashed { border-top-style: dashed; }
.page--design-system .trait-module .legend i.series-dotted { border-top-style: dotted; }

.page--design-system .trait-module .legend i.pillar-swatch {
  width: var(--space-10);
  height: var(--space-10);
  background: var(--legend-color);
}

.page--design-system .trait-module .chart-canvas { height: 260px; grid-template-columns: 48px 1fr; }

.page--design-system .trait-module .axis-y {
  color: var(--faint);
  font: 10px/1 var(--font-mono);
  text-align: right;
  padding: var(--space-2) 0 var(--space-18);
}

.page--design-system .trait-module .plot { border-left: 1px solid var(--border); }

.page--design-system .trait-module .grid-line {
  left: 0;
  right: 0;
  height: 1px;
  background: var(--border);
}

.page--design-system .trait-module .grid-line:nth-child(1) { top: 0; }
.page--design-system .trait-module .grid-line:nth-child(2) { top: 25%; }
.page--design-system .trait-module .grid-line:nth-child(3) { top: 50%; }
.page--design-system .trait-module .grid-line:nth-child(4) { top: 75%; }
.page--design-system .trait-module .plot svg { height: 100%; }

.page--design-system .trait-module .axis-x {
  margin: var(--space-8) 0 0 var(--space-56);
  color: var(--faint);
  font: 10px/1 var(--font-mono);
}

.page--design-system .trait-module .small-swatch {
  display: inline-block;
  width: var(--space-18);
  height: var(--space-18);
  margin-right: var(--space-8);
  vertical-align: -4px;
  border-radius: var(--radius-xs);
}

.page--design-system .trait-module .code-head {
  padding: var(--space-10) var(--space-14);
  color: color-mix(in srgb, var(--geistface-grey-200) 72%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
  font: 11px/16px var(--font-mono);
}

.page--design-system .trait-module pre {
  padding: var(--space-22);
  overflow: auto;
  color: var(--code-text);
  font: 12px/1.7 var(--font-mono);
}

.page--design-system .trait-module .implementation-grid { grid-template-columns: 1.15fr 0.85fr; }
.page--design-system .implementation-note strong,
.page--design-system .trait-module .implementation-note strong { margin-bottom: var(--space-6); }
.page--design-system .trait-module .demo-head { border-bottom-color: var(--panel-700); }

.page--design-system .trait-module .demo-head h3,
.page--design-system .trait-module .demo-label strong {
  color: var(--deep-navy-25);
}

.page--design-system .trait-module .demo-head p, .page--design-system .trait-module .demo-label span {
  color: var(--panel-navy-200);
}

.page--design-system .trait-module .demo-content { align-items: stretch; }

.page--design-system .trait-module .demo-row:last-child .demo-content {
  width: 100%;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-12);
}

.page--design-system .trait-module .metric-card .icon-box {
  background: var(--pillar);
  border: 1px solid color-mix(in srgb, var(--deep-navy-900) 16%, transparent);
  box-shadow: var(--shadow-sm);
}

.page--design-system .trait-module .metric-card .icon-box svg { stroke-width: 2.1; }
.page--design-system .trait-module .metric-card-head strong { color: var(--deep-navy-25); font-size: var(--text-sm-font-size); }
.page--design-system .trait-module .metric-card-head span { color: var(--panel-navy-200); }

.page--design-system .trait-module .metric-value {
  color: var(--deep-navy-25);
  font-size: 42px;
  line-height: 48px;
  font-variant-numeric: tabular-nums;
}

.page--design-system .trait-module .metric-bar {
  height: var(--space-8);
  background: var(--panel-600);
  box-shadow: inset 0 1px 2px color-mix(in srgb, var(--deep-navy-950) 25%, transparent);
}

.page--design-system .trait-module .metric-bar i { box-shadow: 0 0 0 1px color-mix(in srgb, var(--deep-navy-900) 12%, transparent); }

.page--design-system .trait-module .pillar-tab, .page--design-system .trait-module .trait-chip {
  border-color: var(--panel-600);
}

.page--design-system .trait-module .pillar-tab:hover,
.page--design-system .trait-module .trait-chip:hover {
  background: var(--panel-700);
}

html[data-surface="light"] > .page--design-system .trait-module .demo-head {
  border-bottom-color: var(--panel-navy-200);
}

html[data-surface="light"] > .page--design-system .trait-module .demo-head h3,
html[data-surface="light"] > .page--design-system .trait-module .demo-label strong,
html[data-surface="light"] > .page--design-system .trait-module .metric-card-head strong,
html[data-surface="light"] > .page--design-system .trait-module .metric-value {
  color: var(--deep-navy-900);
}

html[data-surface="light"] > .page--design-system .trait-module .demo-head p,
html[data-surface="light"] > .page--design-system .trait-module .demo-label span,
html[data-surface="light"] > .page--design-system .trait-module .metric-card-head span,
html[data-surface="light"] > .page--design-system .trait-module .metric-note {
  color: var(--panel-navy-600);
}

html[data-surface="light"] > .page--design-system .trait-module .metric-bar {
  background: var(--panel-navy-100);
  box-shadow: inset 0 1px 2px color-mix(in srgb, var(--deep-navy-900) 12%, transparent);
}

html[data-surface="light"] > .page--design-system .trait-module .pillar-tab,
html[data-surface="light"] > .page--design-system .trait-module .trait-chip {
  background: var(--panel-navy-25);
  border-color: var(--panel-navy-200);
}

html[data-surface="light"] > .page--design-system .trait-module .pillar-tab:hover,
html[data-surface="light"] > .page--design-system .trait-module .trait-chip:hover {
  background: var(--panel-navy-50);
}


.page--design-system .trait-module .metric-card-head .icon-box {
  width: var(--space-40);
  height: var(--space-40);
  --icon-stroke: var(--icon-stroke-40);
}


.page--design-system .library-module {
  --deep: var(--deep-navy-900);
  --subtle: color-mix(in srgb, var(--geistface-grey-200) 54%, transparent);
  --page: var(--deep);
  --surface: var(--panel-950);
  --surface-soft: var(--panel-900);
  --surface-raised: var(--panel-800);
  --shadow: var(--shadow-xl);
  --shadow-soft: var(--shadow-md);
  --content: 1540px;
  --font: "Geist", ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  box-sizing: border-box;
}

html[data-surface="light"] > .page--design-system .library-module {
  --surface: var(--geistface-grey-25);
  --shadow: var(--shadow-xl);
  --shadow-soft: var(--shadow-md);
}

.page--design-system .library-module {
  scroll-behavior: smooth;
  font-family: var(--font);
  -webkit-font-smoothing: antialiased;
  transition: background var(--duration-base) ease, color var(--duration-base) ease;
}


.page--design-system .library-module .clear-search {
  right: var(--space-6);
  top: var(--space-6);
  width: var(--space-28);
  height: var(--space-28);
  font-size: var(--text-lg-font-size);
  line-height: 1;
}

.page--design-system .library-module .clear-search:hover,
.page--design-system .portal-library-embed .clear-search:hover,
.page--portal-games .clear-search:hover {
  background: var(--surface-raised);
  color: var(--text);
}

.page--design-system .library-module h1 { max-width: var(--measure-md); }
.page--design-system .library-module .games-section { padding: var(--space-44) 0 var(--space-88); }
.page--design-system .library-module .section-heading { align-items: end; }

.page--design-system .library-module .game-media {
  aspect-ratio: 16/8.8;
  background: var(--surface-soft);
}

.page--design-system .library-module .game-media img { height: 100%; }

.page--design-system .library-module .modal-media img,
.page--design-system .portal-library-embed .modal-media img,
.page--portal .modal-media img {
  height: 100%;
  object-fit: cover;
}

.page--design-system .library-module .game-body { flex: 1; padding: var(--space-22); }

.page--design-system .library-module .game-description { min-height: var(--space-48); margin: var(--space-10) 0 var(--space-20); }
.page--design-system .library-module .card-footer { padding-top: var(--space-22); }

.page--design-system .library-module .modal-close {
  right: var(--space-18);
  top: var(--space-18);
  z-index: 3;
  width: var(--space-40);
  height: var(--space-40);
  border: 1px solid color-mix(in srgb, var(--geistface-grey-25) 18%, transparent);
  background: color-mix(in srgb, var(--deep-navy-900) 78%, transparent);
  font-size: var(--display-xs-font-size);
  line-height: 1;
}

.page--design-system .library-module .modal-close:hover { background: var(--deep-navy-900); }

.page--design-system .library-module .modal-media {
  min-height: var(--space-640);
  background: var(--surface-soft);
}

.page--design-system .library-module .modal-media::after { display: none; }

.page--design-system .library-module .modal-media-label {
  left: var(--space-24);
  bottom: var(--space-24);
  max-width: calc(100% - 48px);
  padding: var(--space-12) var(--space-14);
  border: 1px solid color-mix(in srgb, var(--geistface-grey-25) 22%, transparent);
  background: color-mix(in srgb, var(--deep-navy-900) 78%, transparent);
  backdrop-filter: blur(12px);
}

.page--design-system .library-module .modal-media-label span,
.page--design-system .portal-library-embed .modal-media-label span,
.page--portal .modal-media-label span {
  letter-spacing: 0.11em;
  opacity: 0.72;
}

.page--design-system .library-module .modal-media-label strong {
  margin-top: var(--space-2);
  font-size: var(--text-lg-font-size);
  line-height: 26px;
}

.page--design-system .library-module .modal-content { overflow: auto; }
.page--design-system .library-module .modal-inner { padding: var(--space-44) var(--space-42) var(--space-36); }

.page--design-system .library-module .modal-category,
.page--design-system .nav-spec-sidelink.is-active svg {
  color: var(--mindset-violet-200);
}

.page--design-system .library-module .modal-title {
  margin: var(--space-8) var(--space-56) var(--space-12) 0;
  font-size: 42px;
  line-height: 48px;
  letter-spacing: -0.04em;
}


.page--design-system .library-module .modal-meta { margin: var(--space-24) 0; }
.page--design-system .library-module .meta-card strong { margin-top: var(--space-2); font-size: var(--text-sm-font-size); }
.page--design-system .library-module .modal-section { padding: var(--space-22) 0; }
.text-uppercase,
.page--design-system .library-module .modal-section h3 { text-transform: uppercase; }
.page--design-system .library-module .modal-description,
.page--design-system .library-module .modal-section p { line-height: var(--text-md-line-height); }

.page--design-system .library-module .insight-box {
  border: 1px solid color-mix(in srgb, var(--green) 40%, var(--border));
  background: color-mix(in srgb, var(--green) 9%, var(--surface));
}

.page--design-system .library-module .modal-actions {
  position: sticky;
  bottom: 0;
  padding: var(--space-18) var(--space-42) var(--space-28);
  background: color-mix(in srgb, var(--surface) 94%, transparent);
  backdrop-filter: blur(14px);
}

html[data-surface="light"] > .page--design-system .library-module .modal-category {
  color: var(--violet);
}

.page--design-system .api-module {
  color-scheme: dark;
  --deep: var(--deep-navy-900);
  --page: var(--deep-navy-900);
  --sidebar: var(--panel-navy-950);
  --panel: var(--panel-navy-900);
  --panel-raised: var(--panel-navy-800);
  --subtle: color-mix(in srgb, var(--geistface-grey-200) 48%, transparent);
  --violet-soft: color-mix(in srgb, var(--mindset-violet-500) 16%, transparent);
  --shadow: 0 20px 50px color-mix(in srgb, var(--deep-navy-950) 20%, transparent);
  --sidebar-w: 292px;
  --topbar-h: 64px;
  /* The one surface radius that is deliberately not the shared pair. This
     module is a reduced-scale preview of the API reference, so its panels
     step down one notch - 12px and 20px against the 16px and 24px every
     full-size surface uses - to keep the corner reading the same at size.
     Every other module restated the shared values verbatim; those copies
     are gone. */
  --surface-radius-md: var(--radius-xl);
  --surface-radius-lg: var(--radius-3xl);
  --content: 1360px;
  box-sizing: border-box;
}

html[data-surface="light"] > .page--design-system .api-module {
  color-scheme: light;
  --page: var(--deep-navy-25);
  --sidebar: var(--panel-navy-50);
  --panel: var(--geistface-grey-25);
  --panel-raised: var(--geistface-grey-100);
  --subtle: color-mix(in srgb, var(--geistface-grey-800) 50%, transparent);
  --violet-soft: color-mix(in srgb, var(--mindset-violet-500) 10%, transparent);
  --shadow: 0 20px 50px color-mix(in srgb, var(--deep-navy-900) 9%, transparent);
}

.page--design-system .api-module {
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--topbar-h) + 24px);
  min-width: var(--space-320);
  background: var(--page);
  font-size: var(--text-md-font-size);
  -webkit-font-smoothing: antialiased;
}

.page--design-system .api-module .endpoint-grid, .page--design-system .api-module .intro-grid {
  grid-template-columns: minmax(0, 1fr) minmax(390px, 0.92fr);
}

.page--design-system .api-module .intro-grid { padding-bottom: var(--space-64); }

.page--design-system .api-module .eyebrow-badge {
  min-height: var(--space-22);
  padding: 0 var(--space-6);
  background: var(--panel);
}

.page--design-system .api-module h1,
.page--design-system .api-module h2,
.page--design-system .api-module h3,
.page--design-system .api-module p,
.page--documentation-landing h1,
.page--documentation-landing h2,
.page--documentation-landing p {
  margin-top: 0;
}

.page--design-system .api-module .lead { max-width: 680px; font-size: var(--text-sm-font-size); }

.page--design-system .api-module .text-link {
  text-underline-offset: 4px;
  text-decoration-color: var(--violet);
}

.page--design-system .api-module .text-link:hover { color: var(--mindset-violet-200); }

.page--design-system .api-module .setup-row {
  grid-template-columns: 122px 1fr;
  padding: var(--space-12) var(--space-14);
}

.page--design-system .api-module .setup-row:last-child { border-bottom: 0; }
.page--design-system .api-module .setup-value { overflow-wrap: anywhere; }
.page--design-system .api-module .section-block { padding: var(--space-56) 0; }

.page--design-system .api-module .operations-card,
.page--design-system .api-module .section-heading-grid {
  grid-template-columns: minmax(0, 1fr) minmax(390px, 0.92fr);
  gap: var(--space-56);
}

.page--design-system .api-module .operations-list-title { margin-bottom: var(--space-8); }
.page--design-system .api-module .operation-line { grid-template-columns: 38px 1fr; }
.page--design-system .api-module .method { color: var(--blue); }
.page--design-system .api-module .operation-path { overflow-wrap: anywhere; }
.page--design-system .api-module .endpoint { padding: var(--space-56) 0; }
.page--design-system .api-module .endpoint:first-of-type { border-top: 0; }
.page--design-system .api-module .endpoint-meta { margin-bottom: var(--space-14); }

.page--design-system .api-module .endpoint-path {
  margin-bottom: var(--space-10);
  font-size: var(--text-xl-font-size);
  line-height: var(--text-xl-line-height);
  letter-spacing: -0.025em;
  overflow-wrap: anywhere;
}

.page--design-system .api-module .endpoint-description { max-width: 620px; }
.page--design-system .api-module .response-title { margin-top: var(--space-30); margin-bottom: var(--space-8); }
.page--design-system .api-module .response-row { padding: var(--space-10) 0; }
.page--design-system .api-module .response-row span { color: var(--muted); }
.page--design-system .api-module .code-card { position: sticky; top: calc(var(--topbar-h) + 24px); }

.page--design-system .api-module .code-area { min-height: 300px; }

.page--design-system .api-module .response-panel {
  border-top: 1px solid var(--border);
}

.page--design-system .api-module .response-head { padding: var(--space-10) var(--space-12); }

.page--design-system .api-module .response-empty {
  min-height: 70px;
  color: var(--code-muted, var(--subtle));
}

.page--design-system .api-module .callout { grid-template-columns: 32px 1fr; margin-top: var(--space-22); }

.page--design-system .api-module .callout-icon {
  width: var(--space-32);
  height: var(--space-32);
  border-radius: var(--radius-md);
  background: var(--button-primary); /* fill with light text on it: keeps the 500 ladder, not the accent tier */
}

.page--design-system .api-module .callout strong { margin-bottom: var(--space-2); }

.page--design-system .component-stage,
.page--design-system .font-card,
.page--design-system .pair-card,
.page--design-system .story-card,
.page--design-system .study-card,
.page--design-system .token-card,
.page--design-system .weight-card {
  box-shadow: var(--shadow-sm);
}

.page--design-system .weight-mono.italic, .page--design-system .weight-sample.italic {
  font-style: italic;
}

.page--design-system .weight-sample.regular-italic {
  font-weight: var(--weight-regular);
  font-style: italic;
}

.page--design-system .weight-sample.medium-italic {
  font-weight: var(--weight-medium);
  font-style: italic;
}

.page--design-system .weight-sample.semibold-italic {
  font-weight: var(--weight-semibold);
  font-style: italic;
}

.page--design-system .study-card p,
.page--design-system .poster-key-body,
.page--design-system .regular,
.page--design-system .weight-mono.regular, .page--design-system .weight-sample.regular { font-weight: var(--weight-regular); }

.weight-medium,
.page .weight--medium,
.page--design-system .font-card .alphabet,
.page--design-system .medium,
.page--design-system .weight-medium,
.page--design-system .weight-mono.medium, .page--design-system .weight-sample.medium { font-weight: var(--weight-medium); }

.weight-semibold,
.page .weight--semibold,
.page--design-system .poster-type-rule strong,
.page--design-system .product-label,
.page--design-system .weight-card-head span,
.page--design-system .semibold,
.page--design-system .weight-semibold,
.page--design-system .weight-mono.semibold, .page--design-system .weight-sample.semibold { font-weight: var(--weight-semibold); }


.page--design-system .type-weight-note { margin-top: var(--space-16); }

.page--design-system .trait-module .trait-card-top .icon-box {
  width: var(--space-40);
  height: var(--space-40);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs);
}

.page--design-system .trait-module .trait-pillar {
  background: color-mix(in srgb, var(--pillar) 18%, var(--panel-800));
  border: 1px solid color-mix(in srgb, var(--pillar) 38%, var(--border-strong));
}

.page--design-system .trait-module .metric-card-head .icon-box svg {
  width: var(--space-18);
  height: var(--space-18);
  display: block;
  --icon-stroke: var(--icon-stroke-18);
}

.page--design-system .trait-module .metric-card-head .icon-box[data-icon="focus"] svg,
.page--design-system .trait-module .metric-card-head .icon-box[data-icon="openness"] svg,
.page--design-system .trait-module .metric-card-head .icon-box[data-icon="pattern"] svg {
  transform: translate(0, 0);
}

.page--design-system .trait-module .pillar-preview .icon-box { box-shadow: var(--shadow-xs); }


.page--design-system .nav-list { gap: var(--space-2); }
.page--design-system .nav-group-title { margin: var(--space-16) var(--space-10) var(--space-4); letter-spacing: 0.12em; }
.page--design-system .nav-group-title:first-child { margin-top: 0; }
.page--design-system .base-component-grid { margin: var(--space-28) 0; }
.page--design-system .base-demo-row { margin-top: var(--space-22); }

/* Section 43's specimens. A footer measures itself against the page it ends,
   so a stage the width of a panel has to restate the page: its own ground, its
   own gutters and a container that fills the stage rather than keeping the
   reference's 1240px and overflowing the panel it sits in. */
.page--design-system .footer-specimen {
  --grid-container-max: 100%;
  --content: 100%;
  --grid-padding: var(--space-24);

  background: var(--deep);
  border-radius: var(--radius-compact);
}

/* The stage already draws the edge a full or bar footer's top border would
   repeat. The pager keeps its rule: there it separates two articles rather
   than the page from its chrome, which is the whole point of the part. */
.page--design-system .footer-specimen .sp-footer:not(.sp-footer--pager) { border-top: 0; }

/* Two pagers in one stage read as a single article's foot unless they are
   spaced and given the gutters a reading column would have. */
.page--design-system .footer-specimen--column {
  display: grid;
  gap: var(--space-36);
  padding: var(--space-28) var(--space-24);
}

/* The page whites out and resizes every .brand-logo for the sidebar mark. The
   footer sizes its own wordmark, and the customer artwork carries Core Green,
   so neither applies inside one. */
.page--design-system .sp-footer__logo { width: 170px; filter: none; }

.page--design-system .api-module .code-card, .page--design-system .code-block {
  border-color: var(--border-strong);
}

.page--design-system .copy-ready-block .copy-ready-head {
  background: var(--code-surface-raised);
  color: var(--code-muted);
}

.page--design-system .copy-ready-block pre { background: var(--code-surface); }

html[data-surface="light"] > .page--design-system .copy-ready-block {
  background: var(--code-surface);
  border-color: color-mix(in srgb, var(--deep-navy-900) 16%, transparent);
  box-shadow: var(--shadow-sm);
}

html[data-surface="light"] > .page--design-system .api-module .code-head,
html[data-surface="light"] > .page--design-system .code-block .code-head {
  color: var(--panel-navy-600);
}

html[data-surface="light"] > .page--design-system .copy-ready-block .copy-ready-head {
  background: var(--code-surface-raised);
  color: var(--panel-navy-600);
  border-bottom-color: color-mix(in srgb, var(--deep-navy-900) 13%, transparent);
}

.weight-bold,
.page .weight--bold,
.page--design-system .study-label,
.page--design-system .bold,
.page--design-system .weight-bold,
.page--design-system .weight-mono.bold, .page--design-system .weight-sample.bold,
.page--design-system .api-module .tok-import { font-weight: var(--weight-bold); }


.page--design-system .library-module { margin-top: 0 !important; background: var(--page); }

html[data-surface="light"] > .page--design-system .library-module {
  --page: var(--deep-navy-25);
  --surface: var(--geistface-grey-25);
  --surface-soft: var(--panel-navy-50);
  --surface-raised: var(--geistface-grey-100);
  --subtle: color-mix(in srgb, var(--geistface-grey-800) 54%, transparent);
  color: var(--text);
  background: var(--page);
}

html[data-surface="light"] > .page--design-system .library-module .games-section,
html[data-surface="light"] > .page--design-system .library-module .hero--portal {
  background: var(--page);
}

html[data-surface="light"] > .page--design-system .library-module .game-card--portal {
  background: var(--surface);
  color: var(--text);
}


.page--design-system .library-module .game-media { isolation: isolate; }

.page--design-system .library-module .game-media img {
  inset: -1px;
  width: calc(100% + 2px);
  height: calc(100% + 2px);
  max-width: none;
  object-fit: cover;
  object-position: center;
  transform: scale(1.005);
}

.page--design-system .library-module .trait-group + .trait-group { margin-top: var(--space-16); }

/* The reference's own footer is the last thing in <main>, so its row lines up
   with the sections above it rather than with .sp-container's own measure. The
   gutters are the width these rules publish, so the container's padding stands
   down and the footer joins them at every breakpoint. */
.page--design-system main > .sp-footer > .sp-footer__inner { --grid-padding: 0px; }

/* The three of them run the width the page runs. The --content cap used to
   hold this column at 1240px and centre it inside a main that is already
   offset by the 272px rail, so past ~1590px the reference drifted inward and
   left a matching band of dead page on the right. Only the 40px gutter insets
   it now. --content still governs the specimen modules below, which are
   miniature pages and do want a cap of their own. */
.page--design-system .hero-inner,
.page--design-system .section-inner,
.page--design-system main > .sp-footer > .sp-footer__inner {
  width: calc(100% - var(--space-80));
  margin: 0 auto;
}

.page--design-system .section-inner {
  padding-top: var(--spacing-9xl);
  padding-bottom: var(--spacing-9xl);
}

.page--design-system .hero-inner {
  padding: var(--space-96) 0 var(--space-80);
}

.section-head--design-system { gap: var(--spacing-6xl); margin-bottom: var(--spacing-6xl); }

.page--design-system .badge-card, .page--design-system .chart-card, .page--design-system .logo-stage {
  border-radius: var(--radius-panel);
}

.page--design-system .form-state { padding: var(--spacing-3xl); }

.page--design-system .chart-view-switch,
.page--design-system .chart-view-switch button {
  border-radius: var(--radius-control);
}

.page--design-system .master-subsection {
  margin-top: var(--spacing-9xl);
  scroll-margin-top: var(--spacing-4xl);
}

.page--design-system .master-subsection:first-of-type { margin-top: 0; }

.page--design-system .subsection-heading {
  grid-template-columns: minmax(300px, 0.8fr) minmax(360px, 1.2fr);
  gap: var(--spacing-6xl);
  align-items: end;
  margin-bottom: var(--spacing-4xl);
}

.page--design-system .master-principles { margin-bottom: var(--spacing-7xl); }
.page--design-system .master-table-wrap { margin-top: var(--spacing-3xl); }
.page--design-system .spacing-token-grid { grid-template-columns: repeat(5, minmax(0, 1fr)); }

.page--design-system .spacing-token {
  grid-template-columns: 1fr auto;
  grid-template-rows: auto 104px;
  gap: var(--space-8) var(--space-12);
  align-items: end;
  padding: var(--spacing-xl);
  border-radius: var(--radius-compact);
}

.page--design-system .spacing-token code { overflow-wrap: anywhere; }


.page--design-system .spacing-token i {
  grid-column: 1/-1;
  width: max(4px, var(--measure));
  height: var(--space-12);
  max-width: 100%;
  border-radius: var(--radius-full);
  background: var(--violet);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--geistface-grey-25) 8%, transparent);
}

.page--design-system .grid-preview {
  height: 132px;
  padding: var(--space-12);
  border-radius: var(--radius-compact);
}

.page--design-system .grid-preview-12 { grid-template-columns: repeat(12, 1fr); }
.page--design-system .grid-preview-6 { grid-template-columns: repeat(6, 1fr); }
.page--design-system .grid-preview-4 { grid-template-columns: repeat(4, 1fr); }

.page--design-system .grid-preview i {
  border-radius: var(--radius-xs);
  background: color-mix(in srgb, var(--violet) 42%, var(--panel-800));
}

.page--design-system .button-size-grid dl, .page--design-system .grid-system-card dl {
  grid-template-columns: repeat(3, 1fr);
}

.page--design-system .button-size-grid dl div, .page--design-system .grid-system-card dl div {
  gap: var(--space-2);
}

.page--design-system .button-size-grid dd, .page--design-system .grid-system-card dd {
  font: 600 12px/18px var(--font-mono);
}

.page--design-system .span-composition { margin-top: var(--spacing-xl); }
.page--design-system .span-composition-title { margin-bottom: var(--spacing-xl); }
.page--design-system .span-grid { grid-template-columns: repeat(12, 1fr); }

.page--design-system .span-item {
  min-height: var(--space-96);
  padding: var(--spacing-xl);
  align-items: flex-end;
  border-radius: var(--radius-compact);
}

.page--design-system .span-7 { grid-column: span 7; }
.page--design-system .span-5 { grid-column: span 5; }
.page--design-system .span-4 { grid-column: span 4; }

.page--design-system .radius-scale {
  grid-template-columns: repeat(11, minmax(0, 1fr));
  gap: var(--spacing-md);
}

.page--design-system .radius-scale article > div { height: 86px; border-radius: var(--r); }
.page--design-system .role-grid { margin-top: var(--spacing-3xl); }
.page--design-system .shadow-system-grid { align-items: stretch; }
.page--design-system .shadow-sample { padding: var(--spacing-3xl); }

.page--design-system .shadow-sample > div {
  height: 92px;
  margin-bottom: var(--space-12);
  border-radius: var(--radius-compact);
}

.page--design-system .shadow-xs > div { box-shadow: var(--shadow-xs); }
.page--design-system .shadow-sm > div { box-shadow: var(--shadow-sm); }
.page--design-system .shadow-md > div { box-shadow: var(--shadow-md); }
.page--design-system .shadow-xl > div { box-shadow: var(--shadow-xl); }

.page--design-system .blur-scene {
  height: 168px;
  margin-bottom: var(--spacing-2xl);
  border-radius: var(--radius-compact);
  background: linear-gradient(color-mix(in srgb, var(--deep-navy-900) 9%, transparent) 1px, transparent 1px), linear-gradient(90deg, color-mix(in srgb, var(--deep-navy-900) 9%, transparent) 1px, transparent 1px), var(--panel-navy-25);
  background-size: 18px 18px;
  isolation: isolate;
}

.page--design-system .blur-orb { filter: saturate(1.1); }

.page--design-system .orb-a {
  width: 106px;
  height: 106px;
  left: var(--space-16);
  top: var(--space-18);
  background: linear-gradient(135deg, var(--pink), var(--orange));
}

.page--design-system .orb-b {
  width: 130px;
  height: 130px;
  right: var(--space-4);
  bottom: -34px;
  background: linear-gradient(135deg, var(--blue), var(--mint));
}

.page--design-system .blur-panel {
  inset: var(--space-28) var(--space-24);
  z-index: 2;
  align-items: flex-end;
  padding: var(--spacing-xl);
}

.page--design-system .blur-panel span { font: 600 11px/16px var(--font-mono); opacity: 0.72; }

.page--design-system .backdrop-surface-light {
  background: var(--surface-glass-light);
  color: var(--geistface-grey-950);
  border: 1px solid color-mix(in srgb, var(--pure-white) 36%, transparent);
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--pure-white) 56%, transparent), 0 14px 34px color-mix(in srgb, var(--deep-navy-900) 12%, transparent);
}

.page--design-system .backdrop-surface-brand {
  background: var(--glass-surface-brand-strong);
  border: 1px solid color-mix(in srgb, var(--pure-white) 18%, transparent);
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--pure-white) 10%, transparent), 0 14px 34px color-mix(in srgb, var(--pure-black) 24%, transparent);
}

.page--design-system .glass-surface-grid { margin-top: var(--spacing-xl); }

.page--design-system .glass-surface-card::after, .page--design-system .glass-surface-card::before {
  content: "";
  position: absolute;
}

.page--design-system .glass-surface-card::before {
  width: 220px;
  height: 220px;
  left: -40px;
  top: -50px;
}

.page--design-system .glass-surface-card::after {
  width: 240px;
  height: 240px;
  right: -60px;
  bottom: -90px;
}

.page--design-system .glass-demo-content {
  z-index: 2;
  min-height: 140px;
  align-content: end;
  gap: var(--space-4);
  padding: var(--spacing-3xl);
}

.page--design-system .glass-demo-content code { opacity: 0.76; }

.page--design-system .glass-surface-card > p {
  z-index: 2;
  margin: var(--space-18) 0 0;
  color: var(--geistface-grey-900);
  max-width: 520px;
}

.page--design-system .glass-brand-demo > p { color: var(--geistface-grey-900); }
.page--design-system .focus-demo { place-items: start; gap: var(--spacing-xl); }
.page--design-system .focus-demo strong { margin-top: var(--space-8); }

/* Short enough that the specimen always overflows and shows its scrollbar. */
.page--design-system .scroll-demo {
  display: grid;
  gap: var(--space-10);
  height: var(--space-128);
  padding: var(--space-12);
  overflow-y: auto;
}

/* The same panel turned through ninety degrees, so the two axes can be read
   against each other rather than one after the other. */
.page--design-system .scroll-demo--inline {
  grid-auto-flow: column;
  grid-auto-columns: 84%;
  height: auto;
  overflow-x: auto;
  overflow-y: hidden;
}

.page--design-system .scroll-demo.panel-surface-demo--light {
  border-color: var(--demo-border);
  background: var(--demo-box);
  color: var(--demo-text);
}

.page--design-system .scroll-demo.panel-surface-demo--light p { color: var(--demo-muted); }

.page--design-system .button-demo-head { margin-bottom: var(--spacing-3xl); }
.page--design-system .button-demo-grid > div { justify-items: start; }

.page--design-system .button-size-grid, .page--design-system .button-state-table {
  margin-top: var(--spacing-xl);
}

.page--design-system .button-size-grid article { align-content: start; gap: var(--spacing-2xl); }
.page--design-system .button-group { overflow: auto; background: var(--button-surface); }
.page--design-system .badge-variant-row > div:last-child { border-right: 0; }

.page--design-system .button-group__item:hover {
  z-index: 1;
  background: color-mix(in srgb, var(--text) 6%, transparent);
}

.page--design-system .button-group__item[aria-pressed="true"] {
  background: var(--panel-800);
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--geistface-grey-25) 5%, transparent);
}

.page--design-system .button-group--wrap .button-group__item { flex: 1 1 auto; }
.page--design-system .status-dot { background: var(--green); }

.page--design-system .button-group-showcase article {
  align-content: start;
  gap: var(--spacing-xl);
}

.page--design-system .field input, .page--design-system .field textarea {
  min-height: var(--space-44);
  padding: var(--space-10) var(--space-12);
}

.page--design-system .icon {
  width: var(--icon-size-control);
  height: var(--icon-size-control);
  display: inline-grid;
  color: var(--icon-color, currentColor);
  line-height: 1;
}

.page--design-system .icon svg { height: 100%; }

/* Equal specificity to the rule above, so it has to follow it rather than
   join the shared inline icon sizes. */
.page--design-system .sp-popup-icon svg { width: var(--space-24); height: var(--space-24);
  --icon-stroke: var(--icon-stroke-24);
}

.page--design-system .icon-stage {
  min-height: 190px;
  place-items: center;
  margin-bottom: var(--spacing-2xl);
}

.page--design-system .icon-geometry-grid { margin-top: var(--spacing-xl); }
.page--design-system .code-head strong { color: var(--code-text); }

.page--design-system .code-head button {
  min-width: 58px;
  min-height: var(--space-28);
  padding: var(--space-4) var(--space-8);
  border: 1px solid var(--code-border-strong) !important;
  border-radius: var(--radius-control) !important;
  background: var(--code-raised) !important;
  color: var(--code-text) !important;
  font: 600 10px/16px var(--font-ui);
}

.page--design-system .code-block pre {
  padding: var(--space-22) !important;
  overflow: auto !important;
  white-space: pre !important;
  color: var(--code-text) !important;
  font: 12px/1.7 var(--font-mono) !important;
  tab-size: 2;
}

.page--design-system .code-block--compact { border-radius: var(--radius-compact) !important; }

.page--design-system .inline-code {
  padding: var(--space-2) var(--space-6);
  border: 1px solid var(--code-border);
  border-radius: var(--radius-sm);
  background: var(--code-surface);
  color: var(--code-text);
  font: 12px/18px var(--font-mono);
}

.page--design-system .code-system-layout {
  grid-template-columns: minmax(0, 1.35fr) minmax(280px, 0.65fr);
  gap: var(--spacing-xl);
}

.page--design-system .syntax-token-grid article {
  min-height: 58px;
  padding: var(--space-10) var(--space-12);
  grid-template-columns: 12px 1fr;
  border-radius: var(--radius-compact);
}

.page--design-system .syntax-token-grid i {
  width: var(--space-12);
  height: var(--space-12);
  background: var(--syntax);
}

.page--design-system .code-variant-grid {
  margin-top: var(--spacing-xl);
}

.page--design-system .code-principles { margin-bottom: var(--spacing-xl); }
.page--design-system .button--icon-only[aria-busy="true"] > * { display: none; }

.page--design-system .button-demo-grid {
  grid-template-columns: repeat(auto-fit, minmax(145px, 1fr));
}

.page--design-system .sp-icon-grid {
  grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
  gap: var(--spacing-md);
}

.page--design-system .sp-icon-cell {
  gap: var(--spacing-md);
  padding: var(--spacing-lg) var(--spacing-md);
  text-align: center;
  overflow-wrap: anywhere;
}

/* Applied spacing spec: a real dialog with its vertical rhythm measured.

   Each gap is a grid row whose height is the token, and the bracket beside it
   sits in the same row of the same grid. A measurement therefore cannot drift
   from the spacing it describes - change the token and the label's bracket
   changes with it, because they are the same row. The card outline is one more
   grid item spanning every row rather than a wrapper, so the content rows and
   the annotation rows stay in one coordinate system. */
.page--design-system .spacing-spec {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--space-72);
  column-gap: var(--space-20);
  align-items: center;
  max-width: var(--space-560);
}

.page--design-system .spacing-spec > * {
  grid-column: 1;
  position: relative;
  z-index: 1;
}

/* The card the rows sit in. It is drawn behind the stack rather than as a grid
   item spanning it: every row here is implicit, and `grid-row: 1 / -1` only
   reaches the explicit grid, so a spanning item collapses onto the first row. */
.page--design-system .spacing-spec::before {
  content: "";
  position: absolute;
  inset-block: 0;
  left: 0;
  right: calc(var(--space-72) + var(--space-20));
  border: 1px solid var(--ui-border);
  border-radius: var(--radius-card);
  background: var(--surface-panel);
}

.page--design-system .spacing-spec__row {
  padding-inline: var(--space-24);
  text-align: center;
}

/* text-align centres the copy rows; a frame is its own grid box, so it needs
   the inline axis centring too. */
.page--design-system .spacing-spec__row--center {
  display: grid;
  justify-items: center;
}

/* A field keeps its own left-aligned label; only the display rows above it are
   centred. */
.page--design-system .spacing-spec__row--field { text-align: left; }

.page--design-system .spacing-spec__gap--4 { height: var(--space-4); }
.page--design-system .spacing-spec__gap--20 { height: var(--space-20); }
.page--design-system .spacing-spec__gap--24 { height: var(--space-24); }
.page--design-system .spacing-spec__gap--32 { height: var(--space-32); }

/* The measurement itself. Skills Pink is the one brand colour not carrying a
   meaning in the components below, so it reads as annotation rather than as
   part of the dialog being annotated. */
.page--design-system .spacing-spec__mark {
  position: relative;
  grid-column: 2;
  align-self: stretch;
  /* The row's height must come from the gap it measures and from nothing else,
     so the label is taken out of flow below. A 4px row cannot be 4px tall if
     the 4px label sitting in it is asking for sixteen. */
  min-height: 0;
  color: var(--skills-pink-500);
  font-family: var(--font-mono);
  font-size: var(--text-xxs-font-size);
  line-height: 1;
}

.page--design-system .spacing-spec__mark span {
  position: absolute;
  top: 50%;
  left: var(--space-20);
  transform: translateY(-50%);
  white-space: nowrap;
}

/* Dimension bracket: a cap at each end of the span, joined down the middle. */
.page--design-system .spacing-spec__mark i {
  position: absolute;
  inset-block: 0;
  left: 0;
  width: var(--space-10);
  border-top: 1px solid currentColor;
  border-bottom: 1px solid currentColor;
}

.page--design-system .spacing-spec__mark i::after {
  content: "";
  position: absolute;
  inset-block: 0;
  left: 50%;
  border-left: 1px solid currentColor;
}

/* The horizontal inset.

   This one is an overlay, not a row. Measuring the other axis, it has no gap
   of its own to share a row with, and sitting in the stack it took a row's
   worth of height and a second 24px bracket for the space it had just added -
   the dialog then read 24 + band + 24 before its icon, which is not the inset
   the copy describes. Laid over the top inset band instead, the vertical
   rhythm below is the dialog's own.

   Each bracket is drawn one inset wide, so the span the reader sees is the
   24px being reported. Previously a single rule ran the full content width
   with a 24px label at either end, which measured the content and named the
   padding. */
.page--design-system .spacing-spec__inset {
  position: absolute;
  top: 0;
  left: 0;
  right: calc(var(--space-72) + var(--space-20));
  height: var(--space-24);
  z-index: 2;
  display: grid;
  grid-template-columns: var(--space-24) minmax(0, 1fr) var(--space-24);
  align-items: center;
  color: var(--skills-pink-500);
  font-family: var(--font-mono);
  font-size: var(--text-xxs-font-size);
  line-height: 1;
}

.page--design-system .spacing-spec__inset i {
  position: relative;
  height: var(--space-10);
  border-left: 1px solid currentColor;
  border-right: 1px solid currentColor;
}

.page--design-system .spacing-spec__inset i::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  top: 50%;
  border-top: 1px solid currentColor;
}

/* Each label sits just inside the bracket it belongs to. */
.page--design-system .spacing-spec__inset-labels {
  display: flex;
  justify-content: space-between;
  padding-inline: var(--space-8);
}

/* Stroke weight specimen. The bar is the drawn line at 1:1, so the reader can
   hold it against the icon beside it and see the same thickness. These are the
   only place in the sheet that states the drawn value rather than the grid
   value, because showing it is the whole point of the specimen. */
.page--design-system .icon-weight {
  align-content: start;
  gap: var(--spacing-lg);
}

.page--design-system .icon-weight__stage {
  min-height: var(--space-64);
  place-items: center;
}

.page--design-system .icon-weight__bar {
  width: 100%;
  max-width: var(--space-64);
  border-radius: var(--radius-full);
  background: currentColor;
}

.page--design-system .icon-weight__bar--10 { height: 1.25px; }
.page--design-system .icon-weight__bar--12 { height: 1.25px; }
.page--design-system .icon-weight__bar--14 { height: 1.5px; }
.page--design-system .icon-weight__bar--16 { height: 1.5px; }
.page--design-system .icon-weight__bar--18 { height: 1.5px; }
.page--design-system .icon-weight__bar--20 { height: 1.75px; }
.page--design-system .icon-weight__bar--22 { height: 1.75px; }
.page--design-system .icon-weight__bar--24 { height: 2px; }
.page--design-system .icon-weight__bar--32 { height: 2.25px; }
.page--design-system .icon-weight__bar--40 { height: 2.4px; }
.page--design-system .icon-weight__bar--48 { height: 2.5px; }

/* The four steps with no named modifier still have to appear at their real
   size in the specimen, or the row would show 14px as 16px and claim a weight
   the reader could not check. Demo only - product markup uses the component
   modifier, or the component rule that owns that context. */
.page--design-system .icon-weight__demo--14 { --icon-size: 14px; --icon-stroke: var(--icon-stroke-14); }
.page--design-system .icon-weight__demo--18 { --icon-size: 18px; --icon-stroke: var(--icon-stroke-18); }
.page--design-system .icon-weight__demo--22 { --icon-size: 22px; --icon-stroke: var(--icon-stroke-22); }
.page--design-system .icon-weight__demo--40 { --icon-size: 40px; --icon-stroke: var(--icon-stroke-40); }

/* The specimen row runs eleven steps now, so it wraps rather than scrolls. */
.page--design-system .icon-weight { row-gap: var(--space-32); }

.page--design-system .button-group__item {
  z-index: 0;
  min-width: 62px;
  min-height: 38px;
  padding: var(--space-8) var(--space-16);
  border-right: 1px solid var(--button-group-border);
  border-radius: 0;
  background: var(--button-group-surface);
  color: var(--button-group-text);
  font: 600 14px/20px var(--font-ui);
  transition: background-color var(--duration-fast) ease, color var(--duration-fast) ease, box-shadow var(--duration-fast) ease;
}

.page--design-system .button-group__item.is-current,
.page--design-system .button-group__item:hover,
.page--design-system .button-group__item[aria-pressed="true"] {
  background: var(--button-group-current);
  color: var(--button-group-text);
}

.page--design-system .button-group__item.is-disabled {
  color: var(--button-group-disabled);
  cursor: not-allowed;
  pointer-events: none;
}

.page--design-system .status-dot {
  width: var(--space-8);
  height: var(--space-8);
  flex: 0 0 8px;
  border-radius: 50%;
  background: var(--button-group-dot);
}

.page--design-system .copy-ready-section {
  margin-top: var(--spacing-7xl);
  scroll-margin-top: var(--spacing-4xl);
}

.page--design-system .copy-ready-heading {
  align-items: flex-end;
  margin-bottom: var(--spacing-3xl);
}

.page--design-system .copy-ready-heading p { max-width: 660px; text-align: right; }

.page--design-system .copy-ready-grid {
  grid-template-columns: minmax(0, 1.35fr) minmax(240px, 0.65fr);
}

.page--design-system .copy-ready-block {
  min-width: 0;
  overflow: hidden;
  background: var(--code-bg);
  color: var(--code-text);
  border: 1px solid var(--code-border);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-sm);
}

.page--design-system .copy-ready-head {
  min-height: var(--space-48);
  align-items: center;
  justify-content: space-between;
  gap: var(--space-18);
  padding: var(--space-8) var(--space-12) var(--space-8) var(--space-16);
  background: var(--code-surface);
  border-bottom: 1px solid var(--code-border);
}

.page--design-system .copy-ready-head > div { display: grid; gap: 1px; }

.page--design-system .copy-ready-head strong {
  font: 600 11px/17px var(--font-mono);
  color: var(--code-text);
}

.page--design-system .copy-ready-head span {
  font: 10px/15px var(--font-mono);
  color: var(--code-muted);
}

.page--design-system .copy-ready-head button {
  min-height: var(--space-30);
  padding: var(--space-4) var(--space-8);
  border: 1px solid var(--code-border-strong);
  border-radius: var(--radius-control);
  background: var(--code-raised);
  color: var(--code-text);
  font: 600 10px/15px var(--font-ui);
}

.page--design-system .copy-ready-head button:hover {
  border-color: var(--code-border-hover);
  background: var(--code-hover);
}

.page--design-system .copy-ready-block pre {
  max-height: 620px;
  margin: 0;
  padding: var(--space-22);
  overflow: auto;
  white-space: pre;
  tab-size: 2;
  color: var(--code-text);
  font: 12px/1.72 var(--font-mono);
}

.page--design-system .copy-ready-block code { font: inherit; color: inherit; }
.page--design-system .copy-ready-note { padding: var(--space-16) var(--space-18); }

.page--design-system .contract-card strong, .page--design-system .copy-ready-note strong {
  margin-bottom: var(--space-4);
}

.page--design-system .contract-grid { margin-bottom: var(--spacing-3xl); }

.page--design-system .asset-contract { margin-top: var(--spacing-3xl); }

.page--design-system .code-state-grid {
  grid-template-columns: repeat(5, minmax(0, 1fr));
  margin-top: var(--spacing-3xl);
}

.page--design-system .code-state-card strong { margin-bottom: var(--space-6); }
.page--design-system .code-state-card code { color: var(--code-muted); }
.page--design-system .code-state-card.is-disabled { opacity: 0.48; }

.page--design-system .sp-popup-preview-head {
  min-height: 72px;
  padding: var(--spacing-xl) var(--spacing-2xl);
  gap: var(--spacing-xl);
}

.page--design-system .sp-popup-preview-head > div { gap: var(--space-2); }

.page--design-system .sp-popup-preview-stage { min-height: var(--space-560); isolation: isolate; }
.page--design-system .sp-popup-preview-stage--light { color: var(--geistface-grey-950); background: var(--panel-navy-50); }

.page--design-system .sp-popup-preview-stage--dark {
  color: var(--deep-navy-25);
  background: var(--deep-navy-900);
}

.page--design-system .sp-popup-scene { grid-template-rows: auto auto 1fr; }

.page--design-system .sp-popup-scene-bar, .page--design-system .sp-popup-scene-card {
  border-radius: var(--radius-compact);
}

/* The light stage restyled the ground of these cards but not their ink, so they
   kept the dark stage's near-white text on a near-white card - 1.2:1. A stage
   that declares its own ground declares the ink that goes on it. */
.page--design-system .sp-popup-preview-stage--light .sp-popup-scene-bar,
.page--design-system .sp-popup-preview-stage--light .sp-popup-scene-card {
  border: 1px solid color-mix(in srgb, var(--deep-navy-900) 11%, transparent);
  background: color-mix(in srgb, var(--geistface-grey-25) 82%, transparent);
  color: var(--geistface-grey-950);
}

.page--design-system .sp-popup-preview-stage--dark .sp-popup-scene-bar,
.page--design-system .sp-popup-preview-stage--dark .sp-popup-scene-card {
  border: 1px solid color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
}

.page--design-system .sp-popup-scene-bar { min-height: var(--space-48); padding: var(--space-12) var(--space-14); }
.page--design-system .sp-popup-scene-brand { gap: var(--space-8); }

.page--design-system .sp-popup-scene-dot {
  width: var(--space-8);
  height: var(--space-8);
  background: var(--violet);
  box-shadow: 12px 0 0 var(--mint), 24px 0 0 var(--pink);
}

.page--design-system .sp-popup-scene-lines i {
  width: var(--space-48);
  height: var(--space-6);
  border-radius: var(--radius-full);
  background: currentColor;
  opacity: 0.13;
}

.page--design-system .sp-popup-scene-summary { grid-template-columns: 1.25fr 0.75fr; }

.page--design-system .sp-popup-scene-card span {
  color: currentColor;
  opacity: 0.52;
  font: 600 9px/14px var(--font-mono);
}

.page--design-system .sp-popup-scene-card strong {
  margin-top: var(--space-8);
  font: 500 25px/32px var(--font-mono);
}

.page--design-system .sp-popup-scene-chart::before {
  content: "";
  position: absolute;
  inset: 34px var(--space-18) var(--space-30);
  border-left: 1px solid currentColor;
  border-bottom: 1px solid currentColor;
  opacity: 0.12;
}

.page--design-system .sp-popup-scene-chart i {
  left: var(--space-44);
  right: 26px;
  height: var(--space-2);
  border-radius: var(--radius-full);
  transform-origin: left center;
}

.page--design-system .sp-popup-scene-chart i:nth-child(1) {
  top: 132px;
  background: var(--violet);
  transform: rotate(-10deg);
}

.page--design-system .sp-popup-scene-chart i:nth-child(2) {
  top: 170px;
  background: var(--mint);
  transform: rotate(-20deg);
}

.page--design-system .sp-popup-scene-chart i:nth-child(3) {
  top: 105px;
  background: var(--pink);
  transform: rotate(8deg);
}

.page--design-system .sp-popup-stage-overlay {
  z-index: 3;
  padding: var(--space-28);
  -webkit-backdrop-filter: blur(var(--backdrop-blur-xl));
  backdrop-filter: blur(var(--backdrop-blur-xl));
}

.page--design-system .sp-popup-preview-stage--light .sp-popup-stage-overlay {
  background: color-mix(in srgb, var(--deep-navy-900) 28%, transparent);
}

.page--design-system .sp-popup-preview-stage--dark .sp-popup-stage-overlay {
  background: color-mix(in srgb, var(--deep-navy-950) 68%, transparent);
}

.page--design-system .sp-popup-dialog { width: min(440px, 100%); box-shadow: var(--shadow-xl); }

.page--design-system .sp-popup-dialog--dark, .page--design-system .sp-popup-dialog--light {
  -webkit-backdrop-filter: blur(var(--backdrop-blur-lg));
  backdrop-filter: blur(var(--backdrop-blur-lg));
}

/* Headings do not read `color`; .page publishes --ui-text and h1-h4 take that,
   so on the dark page this light dialog drew its title in near-white on
   near-white. A surface that declares its own ground declares the heading ink
   with it - the same pairing .popup--dark needs in the other direction. */
.page--design-system .sp-popup-dialog--light {
  color: var(--geistface-grey-950);
  --ui-text: var(--geistface-grey-950);
  --ui-muted: var(--geistface-grey-600);
  background: color-mix(in srgb, var(--geistface-grey-25) 72%, transparent);
  border: 1px solid color-mix(in srgb, var(--geistface-grey-25) 52%, transparent);
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--geistface-grey-25) 64%, transparent), var(--shadow-xl);
}

.page--design-system .sp-popup-dialog--dark {
  color: var(--deep-navy-25);
  background: color-mix(in srgb, var(--deep-navy-900) 82%, transparent);
  border: 1px solid color-mix(in srgb, var(--deep-navy-25) 18%, transparent);
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--geistface-grey-25) 10%, transparent), var(--shadow-xl);
}

.page--design-system .sp-popup-content { padding: var(--space-28); }

/* The green "live/complete" status pill, shared by the design system popup
   and the game result dialog. */
.page--design-system .sp-popup-status,
.page--game-session .game-result__status {
  min-height: 26px;
  padding: 0 var(--space-8);
  border: 1px solid color-mix(in srgb, var(--green) 38%, transparent);
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--green) 12%, transparent);
}

/* The status dot itself. */
.page--design-system .sp-popup-status i,
.page--design-system .interface-live-badge i,
.page--game-session .game-result__status i,
.page--seed-deck .proof-note i,
.page--seed-deck .timeline-note i {
  width: var(--space-6);
  height: var(--space-6);
  background: var(--green);
}

/* Dialog close button, shared by the popup and the game result. */
.page--design-system .sp-popup-close,
.page--game-session .game-result__close {
  width: var(--space-40);
  height: var(--space-40);
  flex: 0 0 var(--space-40);
  border: 1px solid currentColor;
  color: inherit;
  opacity: 0.72;
}

.page--design-system .sp-popup-close:hover,
.page--game-session .game-result__close:hover {
  opacity: 1;
  background: color-mix(in srgb, currentColor 8%, transparent);
}

/* Dialog icon plate. The popup centres it with a margin, the game result
   holds its place in a flex row; everything else is one contract. */
.page--design-system .sp-popup-icon,
.page--game-session .game-result__icon {
  width: 52px;
  height: 52px;
  border-radius: var(--radius-compact);
  background: var(--button-primary); /* fill with light text on it: keeps the 500 ladder, not the accent tier */
  box-shadow: var(--shadow-sm);
}

.page--design-system .sp-popup-icon { margin: var(--space-22) 0 var(--space-18); }

.page--design-system .sp-popup-dialog--light .sp-popup-description {
  color: color-mix(in srgb, var(--geistface-grey-900) 76%, transparent);
}

.page--design-system .sp-popup-dialog--dark .sp-popup-description {
  color: color-mix(in srgb, var(--geistface-grey-200) 72%, transparent);
}

.page--design-system .sp-popup-summary { margin-top: var(--space-22); }

.page--design-system .sp-popup-dialog--light .sp-popup-summary {
  border: 1px solid color-mix(in srgb, var(--deep-navy-900) 12%, transparent);
  background: color-mix(in srgb, var(--geistface-grey-25) 42%, transparent);
}

.page--design-system .sp-popup-dialog--dark .sp-popup-summary {
  border: 1px solid color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
  background: color-mix(in srgb, var(--panel-navy-900) 60%, transparent);
}

.page--design-system .sp-popup-summary span { margin-bottom: var(--space-4); opacity: 0.56; }

.page--design-system .sp-popup-summary strong {
  font: 500 11px/17px var(--font-mono);
  overflow-wrap: anywhere;
}

.page--design-system .sp-popup-footer {
  padding: var(--space-18) var(--space-28) var(--space-22);
  justify-content: flex-end;
  border-top: 1px solid;
}

.page--design-system .sp-popup-dialog--light .sp-popup-footer {
  border-color: color-mix(in srgb, var(--deep-navy-900) 12%, transparent);
  background: color-mix(in srgb, var(--geistface-grey-25) 24%, transparent);
}

.page--design-system .sp-popup-dialog--dark .sp-popup-footer {
  border-color: color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
  background: color-mix(in srgb, var(--deep-navy-900) 26%, transparent);
}

.page--design-system .sp-popup-dialog--light .sp-popup-secondary {
  color: var(--geistface-grey-800);
  background: color-mix(in srgb, var(--geistface-grey-25) 44%, transparent);
  border-color: color-mix(in srgb, var(--deep-navy-900) 20%, transparent);
}

.page--design-system .sp-popup-dialog--dark .sp-popup-secondary {
  color: var(--deep-navy-25);
  background: color-mix(in srgb, var(--panel-navy-900) 64%, transparent);
  border-color: color-mix(in srgb, var(--deep-navy-25) 18%, transparent);
}

.page--design-system .sp-popup-token-note { padding: var(--space-18) var(--space-20); grid-template-columns: 28px 1fr; }

.page--design-system .sp-popup-token-note b {
  width: var(--space-28);
  height: var(--space-28);
  font: 600 10px/1 var(--font-mono);
}

.page--design-system .sp-popup-token-note p { margin: var(--space-2) 0 0; }
.page--design-system .sp-popup-rules { margin-top: var(--spacing-3xl); }
.page--design-system .sp-popup-live[hidden] { display: none; }

.page--design-system .sp-popup-live {
  position: fixed;
  z-index: 300;
  padding: var(--space-30);
  -webkit-backdrop-filter: blur(var(--backdrop-blur-xl));
  backdrop-filter: blur(var(--backdrop-blur-xl));
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--duration-base) ease, visibility var(--duration-base) ease;
}

.page--design-system .sp-popup-live[data-popup-theme="light"] { background: color-mix(in srgb, var(--deep-navy-900) 28%, transparent); }
.page--design-system .sp-popup-live[data-popup-theme="dark"] { background: color-mix(in srgb, var(--deep-navy-950) 68%, transparent); }

.page--design-system .sp-popup-live.is-open,
.page--design-system .nav-spec-doc[data-open-panel="sidebar"] .nav-spec-scrim {
  opacity: 1;
  visibility: visible;
}

.page--design-system .sp-popup-live .sp-popup-dialog {
  transform: translateY(12px) scale(0.985);
  transition: transform var(--duration-base) ease;
}

.page--design-system .sp-popup-live.is-open .sp-popup-dialog { transform: translateY(0) scale(1); }

/* The entrance transform is the thing reduced motion is asking us to drop, and
   the guide says so on the page itself - opacity alone carries the arrival.
   The global * block only shortens the duration to 0.01ms, which snaps the
   translate rather than removing it, so the rule has to be stated here the way
   .sp-tour already states it. */
@media (prefers-reduced-motion: reduce) {
  .page--design-system .sp-popup-live .sp-popup-dialog {
    transform: none;
    transition: none;
  }
}


/* ==========================================================================
   09 - Unique composition - the multi-step tutorial popup

   A popup that teaches rather than confirms. It differs from every other
   modal here in one way that changes its whole construction: the content is
   paged, so the dialog has to hold a stable frame around a body that changes
   height between steps, and it has to report where the reader is in the
   sequence. The report is .sp-stepper, unmodified - a tutorial is a sequence
   with a known length, which is the exact thing the stepper exists to say.

   It is wider than the 440px confirmation dialog because a stepper across the
   head needs room for its labels, and it caps its own height so a long step
   scrolls its body instead of pushing the footer off the screen. That recipe
   is the one the game result dialog already uses.
   ========================================================================== */

.page--design-system .sp-popup-dialog--tutorial {
  width: min(520px, 100%);
  max-height: calc(100dvh - var(--space-48));
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
}

/* Only the paged body scrolls. The stepper and the footer are the fixed frame
   the reader navigates from, so they stay put while the step under them moves. */
.page--design-system .sp-popup-dialog--tutorial .sp-popup-content {
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
}

.page--design-system .sp-popup-steps {
  padding: var(--space-24) var(--space-32) 0;
}

/* A step panel is a plain block, so the UA [hidden] rule is enough on its own.
   It is restated because the alert next door needed the same line for the
   opposite reason, and a reader who finds one and not the other will assume
   the omission is deliberate. */
.page--design-system [data-tutorial-step][hidden] { display: none; }

/* The step counter sits opposite the actions and reads as supporting text, not
   as a control. It repeats the stepper's position in words for the case the
   stepper cannot serve: the counter is inside the live region, the stepper is
   not, so a reader who never sees the marks still hears "Step 2 of 3". */
.page--design-system .sp-popup-progress {
  margin-inline-end: auto;
  color: var(--surface-text-muted);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
}

/* .sp-popup-secondary re-themes the secondary button per dialog surface, and it
   sets colour, background and border three classes deep. The shared disabled
   treatment on .button[aria-disabled="true"] is only two deep, so it loses all
   three declarations and keeps nothing but the cursor: Back on the first step
   is inert but reads as entirely enabled. No other secondary in the system is
   ever disabled, which is why this has not surfaced before. Restate the
   disabled tokens at matching depth rather than weakening the theme rule. */
.page--design-system .sp-popup-dialog--light .sp-popup-secondary[aria-disabled="true"],
.page--design-system .sp-popup-dialog--dark .sp-popup-secondary[aria-disabled="true"] {
  background: var(--button-surface-disabled);
  border-color: var(--button-border-disabled);
  color: var(--button-text-disabled);
}

.page--design-system .badge-contract-grid { margin-bottom: var(--spacing-5xl); }

.page--design-system .badge-system-board {
  padding: var(--spacing-4xl);
  box-shadow: var(--shadow-sm);
}

.page--design-system .badge-system-head, .page--design-system .badge-system-subhead {
  align-items: flex-end;
  gap: var(--spacing-4xl);
}

.page--design-system .badge-system-head { padding-bottom: var(--spacing-3xl); }
.page--design-system .badge-size-grid { padding: var(--spacing-4xl) 0; }

.page--design-system .badge-size-demo {
  min-height: 100px;
  margin: var(--spacing-xl) 0;
  border-radius: var(--radius-compact);
}

.page--design-system .badge-system-subhead { padding: var(--spacing-4xl) 0 var(--spacing-xl); }

.page--design-system .ui-badge {
  --badge-accent: var(--panel-navy-400);
  --badge-border: color-mix(in srgb, var(--badge-accent) 38%, var(--border));
  --badge-text: var(--text);
  --badge-height: 22px;
  height: var(--badge-height);
  vertical-align: middle;
  transition: background-color var(--duration-fast), border-color var(--duration-fast), color var(--duration-fast), transform var(--duration-fast), box-shadow var(--duration-fast);
}

.page--design-system button.ui-badge:hover { filter: brightness(0.985); }
.page--design-system button.ui-badge:active { transform: translateY(1px); }

.page--design-system .ui-badge--modern {
  border-color: var(--badge-border);
  box-shadow: var(--shadow-xs);
}

.page--design-system .ui-badge--sm { --badge-height: 22px; }
.page--design-system .ui-badge--lg.ui-badge--pill { padding-inline: var(--space-12); }
.page--design-system .ui-badge__label { text-overflow: ellipsis; }
.page--design-system .ui-badge--dismissible { gap: var(--space-2); padding-inline-end: 3px; }

.page--design-system .ui-badge--lg .ui-badge__dot, .page--design-system .ui-badge--md .ui-badge__dot {
  width: var(--space-8);
  height: var(--space-8);
  flex-basis: var(--space-8);
}

.page--design-system .ui-badge__icon {
  width: var(--space-12);
  height: var(--space-12);
  flex: 0 0 12px;
  fill: none;
  stroke: var(--badge-accent);
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.page--design-system .ui-badge--lg .ui-badge__icon,
.page--design-system .ui-badge--md .ui-badge__icon {
  width: var(--space-14);
  height: var(--space-14);
  flex-basis: var(--space-14);
}

.page--design-system .ui-badge__remove {
  width: var(--space-16);
  height: var(--space-16);
  flex: 0 0 16px;
  color: var(--badge-accent);
}

.page--design-system .ui-badge__remove:hover {
  background: color-mix(in srgb, var(--badge-accent) 14%, transparent);
  color: var(--badge-text);
}

.page--design-system .ui-badge__remove svg {
  width: var(--space-12);
  height: var(--space-12);
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  --icon-stroke: var(--icon-stroke-12);
}

.page--design-system .ui-badge.is-selected, .page--design-system .ui-badge[aria-pressed="true"] {
  --badge-border: var(--violet);
  --badge-text: var(--geistface-grey-25);
  --badge-accent: var(--geistface-grey-25);
  box-shadow: var(--shadow-xs);
}

.page--design-system [data-badge-tone="neutral"] { --badge-accent: var(--panel-navy-400); }
.page--design-system .badge-variant-matrix { overflow: auto; }

.page--design-system .badge-variant-row {
  min-width: 1040px;
  grid-template-columns: 160px repeat(5, minmax(150px, 1fr));
}

.page--design-system .badge-variant-row:last-child { border-bottom: 0; }

.page--design-system .badge-variant-row > div {
  min-height: 76px;
  border-right: 1px solid var(--border);
}

.page--design-system .badge-variant-row--head > div {
  min-height: var(--space-44);
  font: 600 9px/15px var(--font-mono);
}

.page--design-system .badge-variant-label { align-content: center; }
.page--design-system .badge-tone-grid { margin-top: var(--spacing-xl); }

.page--design-system .badge-tone-grid article {
  padding: var(--spacing-xl);
  border-radius: var(--radius-compact);
}

.page--design-system .badge-tone-grid code { overflow-wrap: anywhere; }
.page--design-system .badge-detail-grid { margin-top: var(--spacing-5xl); }
.page--design-system .badge-group-stack { margin-top: var(--spacing-2xl); }

.page--design-system .ui-badge-group {
  --badge-accent: var(--panel-navy-400);
  min-height: var(--space-36);
  max-width: 100%;
  padding: var(--space-4) var(--space-6);
  border: 1px solid color-mix(in srgb, var(--badge-accent) 34%, var(--border));
  border-radius: var(--radius-full);
}

/* The narrowest phones: the specimen group is a few pixels wider than a 320px
   viewport minus the gutters, and the datepicker's 36px cells are the same
   story. Wrapping the one and stepping the other's cell token keeps both
   whole with the page still pinned to its width. */
@media (max-width: 360px) {
  .page--design-system .ui-badge-group { flex-wrap: wrap; }
  .page--design-system .sp-datepicker { --datepicker-cell: var(--space-32); }
}

.page--design-system .ui-badge-group__message { text-overflow: ellipsis; }

.page--design-system .ui-badge-group__action {
  width: var(--space-22);
  height: var(--space-22);
  flex: 0 0 22px;
  color: var(--badge-accent);
}

.page--design-system .ui-badge-close:hover, .page--design-system .ui-badge-group__action:hover {
  background: color-mix(in srgb, var(--badge-accent) 14%, transparent);
}

.page--design-system .ui-badge-group__action svg {
  width: var(--space-12);
  height: var(--space-12);
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  --icon-stroke: var(--icon-stroke-12);
}

.page--design-system .badge-anatomy-list span, .page--design-system .badge-close-row {
  border-radius: var(--radius-compact);
}

.page--design-system .badge-close-row {
  --badge-accent: var(--violet);
  min-height: var(--space-96);
  margin-top: var(--spacing-2xl);
}

.page--design-system .ui-badge-close {
  width: var(--space-28);
  height: var(--space-28);
  border-radius: var(--radius-sm);
  color: var(--badge-accent);
}

.page--design-system .ui-badge-close--circle, .page--design-system .ui-badge-close--square {
  border: 1px solid color-mix(in srgb, var(--badge-accent) 34%, var(--border));
}

.page--design-system .nav-spec-icon svg, .page--design-system .ui-badge-close svg {
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
}

.page--design-system .ui-badge-close svg {
  width: var(--space-12);
  height: var(--space-12);
  stroke-width: 1.5;
  --icon-stroke: var(--icon-stroke-12);
}

.page--design-system .badge-anatomy-list { margin-top: var(--spacing-xl); }
.page--design-system .badge-anatomy-list span { gap: var(--space-8); padding: var(--space-10); }

.page--design-system .badge-anatomy-list b {
  width: var(--space-24);
  height: var(--space-24);
  flex: 0 0 24px;
  font: 600 9px/1 var(--font-mono);
}

.page--design-system .tag-component-grid { margin-bottom: 0; }
.page--design-system .nav-spec { margin-top: var(--space-22); }
.page--design-system .nav-spec-board + .nav-spec-board { margin-top: var(--space-22); }
.page--design-system .nav-spec-board__head { gap: var(--space-28); padding: var(--space-22) var(--space-24); }

.page--design-system .nav-spec-toolbar { min-height: 58px; padding: var(--space-10) var(--space-12); }

.page--design-system .nav-spec-switch {
  padding: var(--space-4);
  background: color-mix(in srgb, var(--deep) 45%, transparent);
}

.page--design-system .nav-spec-switch span { padding: 0 var(--space-4); font: 600 9px/14px var(--font-mono); }

.page--design-system .nav-spec-switch button {
  min-height: var(--space-32);
  padding: 0 var(--space-10);
  border: 1px solid transparent;
}

.page--design-system .nav-spec-switch button:hover {
  background: var(--panel-900);
  color: var(--text);
}

.page--design-system .nav-spec-switch button.is-active {
  border-color: var(--border-strong);
  box-shadow: var(--shadow-sm);
}

.page--design-system .nav-spec-help { font: 500 9px/14px var(--font-mono); letter-spacing: 0.05em; }
.page--design-system .nav-spec-stage { padding: var(--space-22); overflow: auto; }

.page--design-system .nav-spec-frame {
  margin: 0 auto;
  border-radius: var(--radius-2xl);
  background: var(--deep);
  box-shadow: var(--shadow-md);
  transition: width var(--duration-base) ease;
  zoom: 0.7;
}

.page--design-system .nav-spec-frame[data-size="desktop"] { width: 940px; }
.page--design-system .nav-spec-frame[data-size="tablet"] { width: 760px; }
.page--design-system .nav-spec-frame[data-size="mobile"] { width: 390px; }

.page--design-system .nav-spec-viewport {
  right: var(--space-8);
  bottom: var(--space-8);
  z-index: 40;
  padding: var(--space-2) var(--space-6);
  background: color-mix(in srgb, var(--panel-950) 92%, transparent);
  font: 600 8px/13px var(--font-mono);
  pointer-events: none;
}

.page--design-system .nav-spec-logo .brand-logo-set,
.page--design-system .nav-spec-sidebar__brand .brand-logo-set,
.page--design-system .mobile-bar .brand-logo-set {
  width: 132px;
  height: var(--space-28);
}

.page--design-system .nav-spec-logo .brand-logo-set .brand-logo { width: 132px; }

.page--design-system .nav-spec-icon {
  width: 38px;
  height: 38px;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-xs);
}

.page--design-system .nav-spec-icon.is-hover, .page--design-system .nav-spec-icon:hover {
  border-color: color-mix(in srgb, var(--text) 30%, transparent);
  color: var(--text);
}

.page--design-system .nav-spec-icon svg {
  width: var(--space-18);
  height: var(--space-18);
  stroke-width: 1.8;
  stroke-linejoin: round;
  --icon-stroke: var(--icon-stroke-18);
}

/* The specimen draws the real state, from the same tokens the live control
   uses, so the page cannot document a colour the system no longer ships. */
.page--design-system .nav-spec-icon.is-current {
  background: var(--nav-current-surface);
  border-color: var(--nav-current-border);
  color: var(--nav-current-text);
}

.page--design-system .nav-spec-icon.is-current:hover {
  background: color-mix(in srgb, var(--nav-current) 24%, transparent);
  border-color: var(--nav-current-border);
  color: var(--nav-current-text);
}

.page--design-system .nav-spec-notification {
  right: -5px;
  top: -6px;
  min-width: var(--space-18);
  height: var(--space-18);
  padding: 0 var(--space-4);
  border: 2px solid var(--deep);
  background: var(--pink);
  font: 600 9px/1 var(--font-mono);
  box-shadow: var(--shadow-xs);
}

.page--design-system .nav-spec-top { min-height: 350px; background: var(--deep); }

.page--design-system .nav-spec-topbar {
  height: 72px;
  padding: 0 var(--space-22);
  background: color-mix(in srgb, var(--deep) 95%, transparent);
}

.page--design-system .nav-spec-product { gap: var(--space-10); font: 600 11px/18px var(--font-mono); }

.page--design-system .nav-spec-product::before {
  content: "";
  width: 1px;
  height: var(--space-22);
  background: var(--border-strong);
}

.page--design-system .nav-spec-routes { height: 72px; gap: var(--space-2); }

.page--design-system .nav-spec-route {
  min-height: var(--space-36);
  padding: 0 var(--space-10);
  border: 1px solid transparent;
  transition: background var(--duration-fast), border-color var(--duration-fast), color var(--duration-fast);
}

.page--design-system .nav-spec-route.is-hover, .page--design-system .nav-spec-route:hover {
  background: var(--panel-900);
  border-color: var(--border);
  color: var(--text);
}

.page--design-system .nav-spec-route.is-active { border-color: transparent; }
.page--design-system .nav-spec-endcap[hidden] { display: none; }

.page--design-system .nav-spec-action {
  min-height: 38px;
  padding: 0 var(--space-12);
  border: 1px solid transparent;
}

.page--design-system .nav-spec-action--secondary { border-color: var(--border-strong); }

.page--design-system .nav-spec-action--secondary:hover {
  border-color: color-mix(in srgb, var(--text) 28%, transparent);
}

.page--design-system .nav-spec-action--primary {
  background: var(--button-primary); /* fill with light text on it: keeps the 500 ladder, not the accent tier */
  border-color: var(--button-primary);
}

.page--design-system .nav-spec-action--primary:hover {
  background: var(--mindset-violet-600);
  border-color: var(--mindset-violet-600);
}

.page--design-system .nav-spec-avatar { padding-left: var(--space-6); }

.page--design-system .nav-spec-avatar i {
  width: 26px;
  height: 26px;
  color: var(--mindset-violet-200);
  font: 700 9px/1 var(--font-mono);
}

.page--design-system .nav-spec-live {
  min-height: 34px;
  padding: 0 var(--space-12);
  gap: var(--space-8);
  border: 1px solid color-mix(in srgb, var(--green) 56%, var(--border));
  background: color-mix(in srgb, var(--green) 9%, var(--panel-900));
  font: 600 11px/18px var(--font-mono);
}

.page--design-system .nav-spec-live i {
  width: var(--space-8);
  height: var(--space-8);
  background: var(--green);
  box-shadow: 0 0 0 5px color-mix(in srgb, var(--green) 15%, transparent);
}

.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-routes,
.page--design-system .nav-spec-frame[data-size="tablet"] .nav-spec-endcap[data-endcap="labs"] .nav-spec-live,
.page--design-system .nav-spec-frame[data-size="tablet"] .nav-spec-endcap[data-endcap="portal"] .nav-spec-icon[data-app="home"],
.page--design-system .nav-spec-frame[data-size="tablet"] .nav-spec-endcap[data-endcap="standard"] .nav-spec-action--secondary,
.page--design-system .nav-spec-frame[data-size="tablet"] .nav-spec-routes,
.page--design-system .nav-spec-menu-button {
  display: none;
}

.page--design-system .nav-spec-drawer {
  top: var(--space-64);
  right: var(--space-12);
  z-index: 35;
  width: 300px;
  padding: var(--space-10);
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-md);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-5px);
  transition: opacity var(--duration-fast), transform var(--duration-fast), visibility var(--duration-fast);
}

.page--design-system .nav-spec-drawer.is-open,
.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-doc[data-open-panel="local"] .nav-spec-localpopover,
.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-doc[data-open-panel="toc"] .nav-spec-toc,
.page--design-system .nav-spec-frame[data-size="tablet"] .nav-spec-doc[data-open-panel="toc"] .nav-spec-toc {
  opacity: 1;
  visibility: visible;
  transform: none;
}

.page--design-system .nav-spec-drawer nav,
.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-menu-button,
.page--design-system .nav-spec-frame[data-size="tablet"] .nav-spec-menu-button {
  display: grid;
}

.page--design-system .nav-spec-drawer .nav-spec-route { min-height: var(--space-40); padding: 0 var(--space-12); }

.page--design-system .nav-spec-drawer__divider {
  height: 1px;
  margin: var(--space-8) 0;
  background: var(--border);
}

.page--design-system .nav-spec-drawer__utilities { display: none; justify-content: flex-end; }

.page--design-system .nav-spec-frame[data-active-endcap="labs"] .nav-spec-drawer__utilities[data-drawer-endcap="labs"],
.page--design-system .nav-spec-frame[data-active-endcap="portal"] .nav-spec-drawer__utilities[data-drawer-endcap="portal"],
.page--design-system .nav-spec-frame[data-active-endcap="standard"] .nav-spec-drawer__utilities[data-drawer-endcap="standard"] {
  display: flex;
}

.page--design-system .nav-spec-topbody { padding: var(--space-44) var(--space-32); }
.page--design-system .nav-spec-topbody p { max-width: 650px; }
.page--design-system .nav-spec-skeleton { grid-template-columns: 1.15fr 0.85fr; }
.page--design-system .nav-spec-skeleton i { min-height: 92px; }
.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-product { display: none; }

.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-topbar {
  padding: 0 var(--space-14);
  gap: var(--space-10);
}

.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-logo .brand-logo-set,
.page--design-system .nav-spec-mobilebar .brand-logo-set {
  width: 116px;
  height: var(--space-24);
}

.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-logo .brand-logo-set .brand-logo {
  width: 116px;
}

.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-endcap[data-endcap="standard"] {
  display: none !important;
}

.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-endcap[data-endcap="labs"] .nav-spec-live,
.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-endcap[data-endcap="portal"] .nav-spec-icon[data-app="account"],
.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-endcap[data-endcap="portal"] .nav-spec-icon[data-app="home"] {
  display: none;
}

.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-endcaps { gap: var(--space-6); }
.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-topbody { padding: var(--space-36) var(--space-22); }

.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-skeleton {
  grid-template-columns: 1fr;
}

.page--design-system .nav-spec-doc { min-height: 520px; background: var(--deep); }

.page--design-system .nav-spec-mobilebar {
  display: none;
  height: 60px;
  padding: 0 var(--space-14);
}

.page--design-system .nav-spec-mobilebar .brand-logo-set .brand-logo { width: 116px; }

.page--design-system .nav-spec-mobilebar .nav-spec-icon:last-child { margin-left: auto; }

.page--design-system .nav-spec-doclayout {
  grid-template-columns: 240px minmax(0, 1fr) 170px;
  min-height: 520px;
}

.page--design-system .nav-spec-sidebar { border-right: 1px solid var(--border); }
.page--design-system .nav-spec-sidebar__brand { padding: var(--space-20) var(--space-18) var(--space-16); }
.page--design-system .nav-spec-sidebar__brand .brand-logo-set .brand-logo { width: 132px; }

.page--design-system .nav-spec-surface {
  margin-top: var(--space-6);
  font: 600 9px/14px var(--font-mono);
  letter-spacing: 0.11em;
}

.page--design-system .nav-spec-search { margin: var(--space-14) var(--space-12) var(--space-8); }

.page--design-system .nav-spec-search svg {
  left: var(--space-10);
  top: 50%;
  width: var(--space-14);
  height: var(--space-14);
  transform: translateY(-50%);
  fill: none;
  stroke: var(--muted);
  stroke-width: 1.8;
  --icon-stroke: var(--icon-stroke-14);
}

.page--design-system .nav-spec-search input { height: var(--space-40); padding: 0 var(--space-12) 0 var(--space-32); }
.page--design-system .nav-spec-side-scroll { padding: var(--space-6) var(--space-10) var(--space-18); overflow: auto; }
.page--design-system .nav-spec-sidegroup + .nav-spec-sidegroup { margin-top: var(--space-16); }

.page--design-system .nav-spec-sidetitle {
  margin: 0 var(--space-8) var(--space-6);
  color: color-mix(in srgb, var(--muted) 70%, transparent);
  font: 600 8px/14px var(--font-mono);
  letter-spacing: 0.12em;
}

.page--design-system .nav-spec-sidelink {
  min-height: var(--space-36);
  padding: 0 var(--space-10);
  gap: var(--space-8);
  border: 1px solid transparent;
}

.page--design-system .nav-spec-sidelink svg {
  width: var(--space-16);
  height: var(--space-16);
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  --icon-stroke: var(--icon-stroke-16);
}

.page--design-system .nav-spec-sidelink[hidden] { display: none !important; }

.page--design-system .nav-spec-sidelink.is-hover, .page--design-system .nav-spec-sidelink:hover {
  background: color-mix(in srgb, var(--text) 5%, transparent);
  color: var(--text);
}

.page--design-system .nav-spec-sidelink.is-active {
  border-color: var(--border-strong);
  box-shadow: var(--shadow-xs);
}

.page--design-system .nav-spec-sidefooter { padding: var(--space-12); font: 500 8px/14px var(--font-mono); }

.page--design-system .nav-spec-localbar {
  height: var(--space-64);
  padding: 0 var(--space-18);
  background: color-mix(in srgb, var(--deep) 93%, transparent);
}

.page--design-system .nav-spec-localtitle { margin-right: var(--space-8); }

.page--design-system .nav-spec-localtitle::after {
  content: "";
  display: inline-block;
  width: 1px;
  height: var(--space-18);
  margin-left: var(--space-12);
  vertical-align: middle;
  background: var(--border-strong);
}

.page--design-system .nav-spec-localbar .nav-spec-action { min-height: var(--space-36); }
.page--design-system .nav-spec-localtoggle { display: none; }
.page--design-system .nav-spec-doccontent { padding: var(--space-44) var(--space-32); }
.page--design-system .nav-spec-doccontent p { max-width: 610px; }
.page--design-system .nav-spec-contentrow { grid-template-columns: 1fr 0.72fr; }
.page--design-system .nav-spec-contentrow i { min-height: 105px; }
.page--design-system .nav-spec-contentrow i:last-child { background: var(--deep-navy-950); }

.page--design-system .nav-spec-toc {
  padding: var(--space-72) var(--space-14) var(--space-22);
  border-left: 1px solid var(--border);
}

.page--design-system .nav-spec-toctitle { margin-bottom: var(--space-10); }
.page--design-system .nav-spec-toclink { padding: var(--space-4) 0 var(--space-4) var(--space-12); }

.page--design-system .nav-spec-toclink::before {
  content: "";
  position: absolute;
  left: 0;
  top: var(--space-10);
  width: var(--space-2);
  height: var(--space-2);
  border-radius: 50%;
  background: color-mix(in srgb, var(--muted) 70%, transparent);
}

.page--design-system .nav-spec-toclink.is-hover, .page--design-system .nav-spec-toclink:hover {
  color: var(--text);
}

.page--design-system .nav-spec-toclink.is-active::before {
  background: var(--mindset-violet-200);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--mindset-violet-200) 14%, transparent);
}

.page--design-system .nav-spec-localpopover {
  right: var(--space-12);
  top: 70px;
  z-index: 31;
  width: 190px;
  padding: var(--space-8);
  box-shadow: var(--shadow-md);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-5px);
  transition: opacity var(--duration-fast), visibility var(--duration-fast), transform var(--duration-fast);
}

.page--design-system .nav-spec-localpopover .nav-spec-route { min-height: 38px; padding: 0 var(--space-10); }

.page--design-system .nav-spec-scrim {
  inset: 60px 0 0;
  z-index: 20;
  background: color-mix(in srgb, var(--deep-navy-950) 55%, transparent);
  backdrop-filter: blur(2px);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--duration-fast), visibility var(--duration-fast);
}

.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-mobilebar,
.page--design-system .nav-spec-frame[data-size="tablet"] .nav-spec-mobilebar {
  display: flex;
}

.page--design-system .nav-spec-frame[data-size="tablet"] .nav-spec-doclayout {
  grid-template-columns: minmax(0, 1fr);
}

.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-sidebar,
.page--design-system .nav-spec-frame[data-size="tablet"] .nav-spec-sidebar {
  position: absolute;
  left: 0;
  top: 60px;
  bottom: 0;
  z-index: 30;
  width: 250px;
  transform: translateX(-104%);
  transition: transform var(--duration-base) ease;
  box-shadow: 18px 0 40px color-mix(in srgb, var(--deep-navy-950) 24%, transparent);
}

.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-doc[data-open-panel="sidebar"] .nav-spec-sidebar,
.page--design-system .nav-spec-frame[data-size="tablet"] .nav-spec-doc[data-open-panel="sidebar"] .nav-spec-sidebar {
  transform: none;
}

.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-toc,
.page--design-system .nav-spec-frame[data-size="tablet"] .nav-spec-toc {
  position: absolute;
  right: var(--space-10);
  top: 68px;
  z-index: 31;
  width: 210px;
  padding: var(--space-18);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-xl);
  background: var(--panel-950);
  box-shadow: var(--shadow-md);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-5px);
  transition: opacity var(--duration-fast), visibility var(--duration-fast), transform var(--duration-fast);
}

.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-doclayout {
  grid-template-columns: minmax(0, 1fr);
}

.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-localbar > .nav-spec-route,
.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-localtitle::after,
.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-mobiletitle {
  display: none;
}

.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-localbar { padding: 0 var(--space-12); }

.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-localtoggle {
  display: inline-flex;
  margin-left: auto;
}

.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-localbar .nav-spec-action--primary {
  margin-left: var(--space-6);
  padding: 0 var(--space-10);
}

.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-doccontent {
  padding: var(--space-32) var(--space-22);
}

.page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-contentrow {
  grid-template-columns: 1fr;
}

.page--design-system .nav-spec-responsive { padding: 0 var(--space-22) var(--space-22); }

.page--design-system .nav-spec-responsive span {
  margin-bottom: var(--space-4);
  color: var(--mindset-violet-200);
  font: 600 9px/14px var(--font-mono);
}

.page--design-system .nav-spec-responsive p { margin: var(--space-4) 0 0; }

.page--design-system .nav-spec-stateboard {
  margin-top: var(--space-22);
  padding: var(--space-22);
  border-radius: var(--surface-radius-lg);
}

.page--design-system .nav-spec-stateboard__head { align-items: flex-end; margin-bottom: var(--space-16); }

.page--design-system .nav-spec-state > span:first-child {
  display: block;
  margin-bottom: var(--space-12);
  color: var(--muted);
  font: 600 8px/13px var(--font-mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.page--design-system .nav-spec-stageline { min-height: 58px; }
.page--design-system .nav-spec-state small { margin-top: var(--space-10); }
.page--design-system .nav-spec-sidebar-state { max-width: 180px; }
.page--design-system .nav-spec-rulegrid { margin-top: var(--space-22); }
.page--design-system .nav-spec-rule { min-height: 172px; border-radius: var(--surface-radius-md); }

.page--design-system .nav-spec-rule b {
  width: var(--space-30);
  height: var(--space-30);
  color: var(--mindset-violet-200);
  font: 600 9px/1 var(--font-mono);
}

.page--design-system .nav-spec-rule strong { margin-bottom: var(--space-6); }
.page--design-system .nav-spec-empty { display: none; padding: var(--space-8) var(--space-10); }
.page--design-system .nav-spec-empty.is-visible { display: block; }

html[data-surface="light"] > .page--design-system .nav-spec-frame {
  box-shadow: var(--shadow-md);
}

html[data-surface="light"] > .page--design-system .nav-spec-drawer,
html[data-surface="light"] > .page--design-system .nav-spec-frame[data-size="mobile"] .nav-spec-toc,
html[data-surface="light"] > .page--design-system .nav-spec-frame[data-size="tablet"] .nav-spec-toc {
  box-shadow: var(--shadow-md);
}

html[data-surface="light"] > .page--design-system .nav-spec-scrim {
  background: color-mix(in srgb, var(--deep-navy-900) 20%, transparent);
}

.page--design-system .api-module .code-head {
  min-height: var(--space-44);
  padding: var(--space-8) var(--space-12) var(--space-8) var(--space-16);
  background: var(--code-surface, var(--deep-navy-900));
  border-bottom: 1px solid var(--code-border, color-mix(in srgb, var(--geistface-grey-200) 12%, transparent));
  color: var(--code-muted, var(--panel-navy-400));
  font: var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono);
}

.page--design-system .api-module .copy-code {
  padding: var(--space-4) var(--space-8);
  border: 1px solid var(--code-border-strong, color-mix(in srgb, var(--geistface-grey-200) 20%, transparent));
  border-radius: var(--radius-control, var(--radius-lg));
  background: var(--code-raised, var(--panel-navy-950));
  color: var(--code-text, var(--panel-navy-100));
  font: 600 10px/15px var(--font-ui);
}

.page--design-system .api-module .tok-key {
  color: var(--syntax-keyword, var(--mindset-violet-200));
}

.page--design-system .api-module .tok-string {
  color: var(--syntax-string, var(--skills-orange-500));
}

.page--design-system .api-module .tok-fn {
  color: var(--syntax-function, var(--personality-mint-500));
}

.page--design-system .api-module .tok-method {
  color: var(--syntax-property, var(--personality-blue-500));
}

.page--design-system .api-module .tok-import {
  color: var(--syntax-attribute, var(--mindset-magenta-500));
}

.page--design-system #code-blocks .code-state-grid { margin-bottom: var(--spacing-4xl, var(--space-48)); }
.page--design-system .trait-module .trait-pillar { --pillar: var(--mood-500); }

.page--design-system .trait-module .trait-card[data-pillar="cognition"] .trait-pillar {
  --pillar: var(--cognition-500);
}

.page--design-system .trait-module .trait-card[data-pillar="personality"] .trait-pillar {
  --pillar: var(--personality-500);
}

.page--design-system .library-module .modal-category {letter-spacing: 0; text-transform: none;}


.page--design-system .asset-icon-matrix { border-radius: var(--surface-radius-md); }

.page--design-system .asset-icon-row {
  display: grid;
  grid-template-columns: minmax(180px, 1.15fr) repeat(3, minmax(0, 1fr));
}

.page--design-system .asset-icon-row:last-child {
  border-bottom: 0;
}

.page--design-system .asset-icon-row--header { min-height: var(--space-42); }
.page--design-system .asset-icon-row--header > div { padding: var(--space-12) var(--space-16); }
.page--design-system .asset-icon-name { gap: var(--space-2); border-right: 1px solid var(--border); }
.page--design-system .asset-icon-cell { padding: var(--space-12); border-right: 1px solid var(--border); }
.page--design-system .asset-icon-cell:last-child { border-right: 0; }
.page--design-system .asset-icon-stage { height: 112px; }

.page--design-system .asset-icon {
  width: 72px;
  height: 72px;
  object-fit: contain;
}

.page--design-system .asset-icon--mono { filter: brightness(0) invert(1); }

.page--design-system .asset-icon--colour,
html[data-surface="light"] > .page--design-system .asset-icon--mono {
  filter: none;
}

.page--design-system .asset-icon-cell figcaption { padding-top: var(--space-8); }

.page--design-system .trait-module .mini-icon {
  color: var(--pillar, var(--mood-500)) !important;
  border: 1px solid color-mix(in srgb, var(--pillar, var(--mood-500)) 42%, var(--border-strong)) !important;
  box-shadow: none !important;
}

.page--design-system .trait-module .metric-card .icon-box,
.page--design-system .trait-module .pillar-card .icon-box,
.page--design-system .trait-module .pillar-preview .icon-box,
.page--design-system .trait-module .trait-chip.is-selected .mini-icon {
  color: var(--pillar) !important;
}

.page--design-system .trait-module .icon-box--solid {
  border-color: color-mix( in srgb, var(--pillar) 58%, var(--border-strong) ) !important;
}

.page--design-system .trait-module .pillar-card::before { height: 1px; }

.page--design-system .trait-module .pillar-tab.is-selected,
.page--design-system .trait-module .trait-chip.is-selected {
  border-color: color-mix( in srgb, var(--pillar) 64%, var(--border-strong) ) !important;
}

.page--design-system .trait-module .anatomy-icon { --pillar: var(--mood-500); }


.page--design-system .brand-logo-set { width: 158px; height: var(--space-32); }

.page--design-system .brand-logo-set .brand-logo {
  inset: 0 auto auto 0;
  width: 158px;
  filter: none;
}

.page--design-system .brand-logo--light,
html[data-surface="light"] > .page--design-system .brand-logo--dark {
  display: none;
}

html[data-surface="light"] > .page--design-system .brand-logo--light { display: block; }
.page--design-system .mobile-bar .brand-logo-set .brand-logo { width: 132px; }
.page--design-system .hero-inner { gap: var(--space-48); }
.page--design-system .implementation-overview { margin-top: var(--space-8); padding-top: var(--space-36); }
.page--design-system .implementation-overview__head { align-items: end; }
.page--design-system .implementation-overview__head p { max-width: 620px; text-align: right; }

.page--design-system .implementation-overview__grid article {
  min-height: 176px;
  border-radius: var(--surface-radius-md);
}

.page--design-system .implementation-overview__grid strong { margin-bottom: var(--space-6); }
.page--design-system .implementation-overview__grid code { color: var(--text); font-size: var(--text-xxs-font-size); }

.page--design-system .api-module .code-card,
.page--design-system .code-block,
.page--design-system .copy-ready-block {
  background: var(--code-bg);
  border-color: color-mix(in srgb, var(--geistface-grey-200) 16%, transparent);
  box-shadow: none;
}

.page--design-system .api-module .code-head,
.page--design-system .code-block .code-head,
html[data-surface="light"] > .page--design-system .api-module .code-head,
html[data-surface="light"] > .page--design-system .code-block .code-head {
  background: var(--code-surface-raised);
  color: var(--code-muted);
  border-bottom-color: color-mix(in srgb, var(--geistface-grey-200) 12%, transparent);
}

.page--design-system .code-block pre { background: var(--code-bg); }

html[data-surface="light"] > .page--design-system .api-module .code-card,
html[data-surface="light"] > .page--design-system .code-block {
  background: var(--code-bg);
  border-color: color-mix(in srgb, var(--deep-navy-900) 18%, transparent);
  box-shadow: var(--shadow-sm);
}

.page--design-system .api-module .tok-import, .page--design-system .api-module .tok-key {
  color: var(--syntax-keyword);
}

.page--design-system .api-module .tok-string { color: var(--syntax-string); }

.page--design-system .api-module .tok-fn, .page--design-system .api-module .tok-method {
  color: var(--syntax-function);
}

.page--design-system .api-module .code-title .method { color: var(--syntax-property); }
.page--design-system .api-module .code-title > span:last-child { color: var(--syntax-variable); }
.page--design-system .api-module .response-panel { border-top-color: color-mix(in srgb, var(--geistface-grey-200) 12%, transparent); }
.page--design-system .api-module .response-head { color: var(--code-muted); }


.page--design-system .portal-library-embed .modal-pills,
.page--portal .modal-pills {
  column-gap: var(--space-16);
  row-gap: var(--space-8);
}


.page--design-system #implementation { scroll-margin-top: 24px; }
.page--design-system #implementation .copy-ready-section { margin-top: var(--space-48); }
.page--design-system #implementation .copy-ready-heading { padding-top: var(--space-32); }
.page--design-system .nav-list a[href="#implementation"] { padding-left: var(--space-24); font-size: var(--text-xs-font-size); }
.page--design-system .nav-list a[href="#implementation"]::before { width: var(--space-4); height: var(--space-4); }

.page--design-system [data-brand-family="customer"],
html:has(> .page--design-system)[data-brand-family="customer"] {
  --surface-brand: var(--core-green-500);
}

.page--design-system [data-brand-family="developer"],
html:has(> .page--design-system)[data-brand-family="developer"] {
  --surface-brand: var(--violet);
}

.page--design-system .logo-stage--dark img, .page--design-system .logo-stage.dark img {
  filter: none !important;
}

.page--design-system .clear-space {
  border-color: color-mix(in srgb, var(--surface-brand) 58%, transparent);
}

.page--design-system .logo-subsection-head { align-items: end; margin: var(--space-72) 0 var(--space-22); }
.page--design-system .logo-subsection-head:first-of-type { margin-top: 0; }
.page--design-system .logo-subsection-head p { max-width: 580px; text-align: right; }
.page--design-system .surface-identity-grid { margin-bottom: var(--space-72); }
.page--design-system .surface-identity-card__head { grid-template-columns: 52px 1fr auto; }
.page--design-system .surface-identity-card__head code { color: var(--surface-brand); }

.page--design-system .surface-identity-mark {
  width: 52px;
  height: 52px;
  background: color-mix(in srgb, var(--surface-brand) 12%, var(--panel-950));
  border: 1px solid color-mix(in srgb, var(--surface-brand) 32%, var(--border));
}

.page--design-system .surface-chip-row { margin-top: var(--space-22); }

.page--design-system .surface-chip-row span {
  min-height: var(--space-28);
  padding: 0 var(--space-8);
  border: 1px solid color-mix(in srgb, var(--surface-brand) 30%, var(--border));
}

.page--design-system .brand-mark-family {
  border: 1px solid color-mix(in srgb, var(--surface-brand) 28%, var(--border));
}

.page--design-system .brand-mark-preview { min-height: 220px; padding: var(--space-44); }
.page--design-system .brand-mark-preview img { width: min(118px, 60%); }
.page--design-system .brand-mark-preview--light { background: var(--deep-navy-25); }
.page--design-system .brand-mark-meta { grid-column: 1/-1; padding: var(--space-18) var(--space-20); }
.page--design-system .brand-mark-meta span { margin-top: var(--space-2); }
.page--design-system .surface-application-card strong { margin: var(--space-22) 0 var(--space-6); }

.page--design-system .surface-application-icon {
  width: var(--space-48);
  height: var(--space-48);
  border: 1px solid color-mix(in srgb, var(--surface-brand) 32%, var(--border));
  background: color-mix(in srgb, var(--surface-brand) 10%, var(--panel-950));
}

.page--design-system .surface-application-icon img { width: 26px; height: 26px; }
.page--design-system .surface-application-split { width: var(--space-56); height: var(--space-48); }
.page--design-system .source-surface-group { --surface-brand: var(--violet); }

.page--design-system .source-surface-group[data-brand-family="customer"] {
  --surface-brand: var(--core-green-500);
}

.page--design-system .source-surface-group[data-brand-family="developer"] {
  --surface-brand: var(--violet);
}

.page--design-system .source-surface-group__head { padding: 0 var(--space-4) var(--space-6); }
.page--design-system .source-surface-group__head > p { max-width: 510px; text-align: right; }
.page--design-system .source-surface-group__identity > img { width: 34px; height: 34px; }

.page--design-system .source-surface-group__identity code {
  margin-top: var(--space-2);
  color: var(--surface-brand);
}

.page--design-system .source-surface-row {
  grid-template-columns: 48px minmax(180px, 260px) minmax(0, 1fr) auto;
}

.page--design-system .source-surface-icon {
  width: var(--space-44);
  height: var(--space-44);
  border: 1px solid color-mix(in srgb, var(--surface-brand) 38%, var(--border));
  background: color-mix(in srgb, var(--surface-brand) 10%, var(--panel-900));
}

.page--design-system .source-surface-row:hover {
  border-color: color-mix(in srgb, var(--surface-brand) 44%, var(--border));
}

.page--design-system .badge-option-label {
  width: max-content;
  margin-top: var(--space-4);
  padding: var(--space-2) var(--space-6);
  border-radius: var(--radius-full);
  font: 600 9px/14px var(--font-mono);
  letter-spacing: 0.06em;
}

.page--design-system .achievement-anatomy-board { gap: var(--space-28); }
.page--design-system .achievement-anatomy-example figcaption { gap: var(--space-4); padding: var(--space-12) var(--space-4) var(--space-2); }

.page--design-system .achievement-anatomy-list .anatomy-item {
  min-height: 136px;
  align-content: start;
}

.page--design-system .sp-popup-close.ui-badge-close {
  --badge-accent: var(--panel-navy-300);
  width: var(--space-28);
  height: var(--space-28);
  min-height: var(--space-28);
  flex: 0 0 28px;
  border: 1px solid color-mix(in srgb, var(--badge-accent) 34%, var(--border));
  border-radius: var(--radius-sm);
  background: var(--panel-900);
  color: var(--badge-accent);
}

.page--design-system .sp-popup-close.ui-badge-close:hover {
  background: color-mix(in srgb, var(--badge-accent) 14%, var(--panel-900));
  border-color: color-mix( in srgb, var(--badge-accent) 44%, var(--border-strong) );
  color: var(--text);
}

.page--design-system .panel-surface-demo__action svg {
  width: var(--space-12);
  height: var(--space-12);
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-width: 1.6;
  stroke-linejoin: round;
  --icon-stroke: var(--icon-stroke-12);
}

.page--design-system .sp-popup-close.ui-badge-close svg {
  width: var(--space-12);
  height: var(--space-12);
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  --icon-stroke: var(--icon-stroke-12);
}

.page--design-system .sp-popup-dialog--light .sp-popup-close.ui-badge-close {
  --badge-accent: var(--deep-navy-500);
  background: color-mix(in srgb, var(--pure-white) 42%, transparent);
  border-color: color-mix(in srgb, var(--deep-navy-900) 16%, transparent);
  color: var(--deep-navy-500);
}

.page--design-system .sp-popup-dialog--light .sp-popup-close.ui-badge-close:hover {
  background: color-mix(in srgb, var(--pure-white) 68%, transparent);
  border-color: color-mix(in srgb, var(--deep-navy-900) 24%, transparent);
  color: var(--geistface-grey-900);
}

.page--design-system .panel-surface-system {
  margin-bottom: var(--space-64);
  padding: var(--space-32);
  border-radius: var(--surface-radius-lg);
  box-shadow: var(--shadow-sm, 0 4px 12px color-mix(in srgb, var(--deep-navy-950) 16%, transparent));
}

.page--design-system .panel-surface-system__intro {
  grid-template-columns: minmax(220px, 0.72fr) minmax(0, 1.28fr);
  gap: var(--space-48);
  align-items: end;
  margin-bottom: var(--space-28);
}


.page--design-system .panel-surface-demo {
  --demo-canvas: var(--deep-navy-900);
  --demo-panel: var(--panel-navy-950);
  --demo-box: var(--panel-navy-900);
  --demo-text: var(--geistface-grey-200);
  --demo-muted: color-mix(in srgb, var(--geistface-grey-200) 68%, transparent);
  --demo-border: color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
  --demo-border-interactive: color-mix(in srgb, var(--deep-navy-25) 18%, transparent);
  border: 1px solid var(--demo-border-interactive);
  border-radius: var(--surface-radius-md);
  background: var(--demo-canvas);
  color: var(--demo-text);
}

.page--design-system .panel-surface-demo--light {
  --demo-canvas: var(--deep-navy-25);
  --demo-panel: var(--panel-navy-50);
  --demo-box: var(--geistface-grey-25);
  --demo-text: var(--geistface-grey-900);
  --demo-muted: color-mix(in srgb, var(--geistface-grey-800) 86%, transparent);
  --demo-border: color-mix(in srgb, var(--deep-navy-900) 11%, transparent);
  --demo-border-interactive: color-mix(in srgb, var(--deep-navy-900) 20%, transparent);
}

.page--design-system .panel-surface-demo__head {
  min-height: 72px;
  padding: var(--space-16) var(--space-20);
  border-bottom: 1px solid var(--demo-border);
  background: var(--demo-panel);
}

.page--design-system .panel-surface-demo__head span {
  margin-top: var(--space-2);
  color: var(--demo-muted);
  font: 500 var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono);
}

.page--design-system .panel-surface-demo__chip {
  padding: var(--space-4) var(--space-8);
  border: 1px solid var(--demo-border-interactive);
  background: var(--demo-box);
  color: var(--demo-muted);
  font: 600 9px/14px var(--font-mono);
}

.page--design-system .panel-surface-demo__canvas { background: var(--demo-canvas); }

.page--design-system .panel-surface-demo__panel {
  border: 1px solid var(--demo-border);
  border-radius: var(--radius-xl);
  background: var(--demo-panel);
}

.page--design-system .panel-surface-demo__panel-head {
  border-bottom: 1px solid var(--demo-border);
}

.page--design-system .panel-surface-demo__panel-head p {
  margin: var(--space-2) 0 0;
  color: var(--demo-muted);
}

.page--design-system .panel-surface-demo__action {
  width: var(--space-28);
  height: var(--space-28);
  flex: 0 0 28px;
  border: 1px solid var(--demo-border);
  background: var(--demo-box);
  color: var(--demo-muted);
}

.page--design-system .panel-surface-box {
  border: 1px solid var(--demo-border);
  background: var(--demo-box);
}

.page--design-system .panel-surface-box span { letter-spacing: 0.07em; }

.page--design-system .panel-surface-box strong {
  margin-top: var(--space-6);
  font: 500 20px/26px var(--font-mono);
  letter-spacing: -0.03em;
}

.page--design-system .panel-surface-item {
  min-height: var(--space-48);
  padding: var(--space-10) var(--space-12);
  grid-template-columns: 28px minmax(0, 1fr) auto;
  border: 1px solid transparent;
  border-radius: var(--radius-md);
}

.page--design-system .panel-surface-item__icon {
  width: var(--space-28);
  height: var(--space-28);
  border: 1px solid var(--demo-border);
  background: var(--demo-box);
  color: var(--demo-muted);
  font: 600 9px/1 var(--font-mono);
}

.page--design-system .panel-surface-item small { color: var(--demo-muted); }

.page--design-system .panel-surface-box span, .page--design-system .panel-surface-item code {
  color: var(--demo-muted);
  font: 500 9px/14px var(--font-mono);
}

.page--design-system .panel-surface-item--hover, .page--design-system .panel-surface-item--pressed {
  border-color: var(--demo-border-interactive);
}

.page--design-system .panel-surface-legend { padding: 0 var(--space-20) var(--space-20); background: var(--demo-canvas); }

.page--design-system .panel-surface-token {
  padding: var(--space-10);
  border: 1px solid var(--demo-border);
  border-radius: var(--radius-md);
  background: var(--token-color);
  color: var(--demo-text);
}

.page--design-system .panel-surface-demo--light .panel-surface-token { color: var(--geistface-grey-900); }

.page--design-system .panel-surface-token code {
  margin-top: var(--space-2);
  color: var(--demo-muted);
  font: 500 8px/13px var(--font-mono);
  overflow-wrap: anywhere;
}

.page--design-system .panel-surface-rule { min-height: 150px; border-radius: var(--surface-radius-md); }

.page--design-system .panel-surface-rule strong { margin: var(--space-18) 0 var(--space-6); }


.page--homepage .footer-col a:hover {
  color: var(--text-strong);
}

.page--homepage .compare-row:last-child {
  border-bottom: 0;
}


/* One chart-tooltip contract across the benchmark, catalog and partner
   surfaces. Size, radius, surface and motion are the legitimate differences. */
.page--partner-analysis .chart-tooltip {
  position: absolute;
  z-index: 5;
  border: 1px solid var(--border-strong);
  color: var(--text);
  box-shadow: var(--shadow-md);
  pointer-events: none;
  opacity: 0;
}


/* The title above that meta line, wherever it appears. */
.page--docs-v2 .search-result strong,
.page--docs-v2 .toc-card strong,
.page--partner-analysis .moment-panel-head strong {
  display: block;
  color: var(--text-strong);
  font-size: var(--text-xs-font-size);
}


/* Disclosure summary row. The benchmark and the partner analysis differ only
   in density: padding, font size and the partner's minimum row height. */
.page--partner-analysis details.disclosure summary {
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-16);
  color: var(--text-strong);
  font-weight: var(--weight-semibold);
  cursor: pointer;
}


.page--design-system .chart-view-label { letter-spacing: 0.12em; }


.page--design-system .portal-library-embed, .page--portal-games {
  --canvas: var(--deep-navy-900);
  --surface-panel: var(--panel-navy-950);
  --surface-box: var(--panel-navy-900);
  --surface-raised: var(--panel-navy-800);
  --surface-strong: var(--panel-navy-700);
  --quiet: rgb(227 229 235 / 50%);
  --border-interactive: rgb(248 250 252 / 18%);
  --action: var(--mindset-violet-500);
  --action-hover: var(--mindset-violet-600);
  --action-active: var(--mindset-violet-700);
  --focus: var(--mindset-violet-400);
  --customer: var(--core-green-500);
  --logo-word: var(--geistface-grey-25);
  --cognition: var(--personality-mint-500);
  --content: 1280px;
}

html:has(> .page--portal-games)[data-surface="light"],
html[data-surface="light"] > .page--design-system .portal-library-embed {
  --canvas: var(--deep-navy-25);
  --surface-panel: var(--panel-navy-50);
  --surface-box: var(--geistface-grey-25);
  --surface-raised: var(--geistface-grey-100);
  --surface-strong: var(--panel-navy-100);
  --logo-word: var(--deep-navy-950);
  --quiet: rgb(68 75 97 / 50%);
  --border-interactive: color-mix(in srgb, var(--deep-navy-900) 20%, transparent);
}

.page--design-system .portal-library-embed, html:has(> .page--portal-games) {
  scroll-behavior: smooth;
  scroll-padding-top: var(--space-72);
}

.page--design-system .portal-library-embed, .page--portal-games {
  min-width: var(--space-320);
  background: var(--canvas);
  font-size: var(--text-md-font-size);
  -webkit-font-smoothing: antialiased;
}

.page--design-system .portal-library-embed svg, .page--portal-games svg {
  width: var(--space-20);
  height: var(--space-20);
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
  --icon-stroke: var(--icon-stroke-20);
}


.page--design-system .portal-library-embed [hidden], .page--portal-games [hidden] {
  display: none !important;
}


.topbar--portal {
  height: 72px;
  display: flex;
  align-items: center;
  gap: var(--space-28);
}

/* The portal matches the marketing site's measure. The system's 1280px default
   left these pages narrower than the site they sit behind, which read as a
   change of product when moving between them. Text blocks keep their own
   max-widths, so only the grids, media and nav take the extra room - the same
   trade the site makes at .page--site. */
[data-skillprint-page^="portal-"] {
  --grid-container-max: 1440px;
}

[data-skillprint-page^="portal-"] .logo-link {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  padding: var(--space-8) 0;
  text-decoration: none;
}

[data-skillprint-page^="portal-"] .brand-logo {
  display: block;
  width: 142px;
  height: auto;
  overflow: visible;
  stroke: none;
}

[data-skillprint-page^="portal-"] .primary-nav {
  height: 72px;
  margin-left: var(--space-12);
}

[data-skillprint-page^="portal-"] .header-actions { margin-left: auto; }

[data-skillprint-page^="portal-"] .mobile-nav {
  display: none;
  overflow: auto;
  gap: var(--space-20);
  padding: 0;
  border-top: 1px solid var(--border);
}

/* Bare selector on purpose: the responsive rules further down are inside media
   queries and have to keep winning. This replaces an unscoped !important rule
   that was overriding all of them. */
.hero--portal, .page--design-system .portal-library-embed .hero--portal {
  padding: var(--space-32) 0 var(--space-48);
}

.hero-grid--portal, .page--design-system .portal-library-embed .hero-grid--portal {
  grid-template-columns: minmax(0, 1.1fr) minmax(390px, 0.9fr);
  gap: var(--grid-gutter);
  align-items: stretch;
}

/* The portal hero sits on the 8px rhythm like every other block. It used to open
   on --space-42, the one value in this file off that rhythm, which read as a
   half-step against the 40/24 spacing either side of it. */
.hero-copy--portal, .page--design-system .portal-library-embed .hero-copy--portal {
  padding: var(--space-40) 0 var(--space-24);
}

.page--design-system .portal-library-embed h1,
.page--design-system .portal-library-embed h2,
.page--design-system .portal-library-embed h3,
.page--design-system .portal-library-embed p,
.page--portal-games h1,
.page--portal-games h2,
.page--portal-games h3,
.page--portal-games p {
  margin-top: 0;
}

.page--design-system .portal-library-embed h1, .page--portal-games h1 {
  max-width: 760px;
}

.hero-copy--portal > p,
.page--design-system .portal-library-embed .hero-copy--portal > p {
  max-width: 690px;
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
}

.page--design-system .portal-library-embed .progress-panel,
.page--portal-games .progress-panel {
  padding: var(--space-24);
}

.page--design-system .portal-library-embed .week-label,
.page--portal-games .week-label {
  font: 600 11px/18px var(--font-mono);
}

.page--design-system .portal-library-embed .score-ring,
.page--portal-games .score-ring {
  width: 82px;
  height: 82px;
  flex: 0 0 82px;
}

.page--design-system .portal-library-embed .score-ring svg,
.page--portal-games .score-ring svg {
  width: 82px;
  height: 82px;
  transform: rotate(-90deg);
}

.page--design-system .portal-library-embed .score-ring div,
.page--portal-games .score-ring div {
  place-content: center;
}

.page--design-system .portal-library-embed .score-ring strong,
.page--portal-games .score-ring strong {
  font: 600 22px/24px var(--font-mono);
}


.page--design-system .portal-library-embed .metric-grid,
.page--portal-games .metric-grid {
  grid-template-columns: repeat(3, 1fr);
}

.metric--portal, .page--design-system .portal-library-embed .metric--portal {
  border-radius: var(--radius-compact);
  background: var(--surface-box);
}

.metric--portal strong,
.page--design-system .portal-library-embed .metric--portal strong {
  margin-top: var(--space-4);
  font: 600 18px/26px var(--font-mono);
}

.metric--portal small,
.page--design-system .portal-library-embed .metric--portal small {
  margin-top: var(--space-2);
  color: var(--customer);
}

.page--design-system .portal-library-embed .streak-row,
.page--portal-games .streak-row {
  margin-top: var(--space-16);
  padding-top: var(--space-16);
}

.page--design-system .portal-library-embed .day, .page--portal-games .day {
  width: var(--space-24);
  height: var(--space-24);
  background: var(--surface-box);
  font: 600 9px/1 var(--font-mono);
}

.page--design-system .portal-library-embed .day.done, .page--portal-games .day.done {
  background: var(--customer);
  border-color: var(--customer);
}

.page--design-system .portal-library-embed .feature, .page--portal-games .feature {
  padding: 0 0 var(--space-48);
}

.page--design-system .portal-library-embed .featured-card,
.page--portal-games .featured-card {
  grid-template-columns: minmax(0, 0.92fr) minmax(440px, 1.08fr);
}

/* The badge, heading and lede carried no spacing between them: h1-h3 and p are
   reset to margin-top 0 across this page and the column set no gap, so the only
   separation was leading and the eyebrow sat on the title. .eyebrow publishes
   the system's label-above-a-heading step as margin-bottom: var(--spacing-lg);
   this is a .status-badge rather than an .eyebrow, so the column supplies the
   same step as its gap and the stack keeps one rhythm. */
.page--design-system .portal-library-embed .featured-copy,
.page--portal-games .featured-copy {
  padding: var(--space-40);
  gap: var(--spacing-lg);
}

.page--design-system .portal-library-embed .status-badge i,
.page--portal-games .status-badge i {
  width: var(--space-6);
  height: var(--space-6);
  background: var(--customer);
}

.page--design-system .portal-library-embed .featured-copy > p,
.page--portal-games .featured-copy > p {
  max-width: 510px;
  font-size: var(--text-md-font-size);
}

/* The column now carries a --spacing-lg gap, so the meta row only adds the
   second step that sets it apart from the lede - 24px in total. It used to add
   --space-22, the odd value that made this the one block off the rhythm. */
.page--design-system .portal-library-embed .featured-meta,
.page--portal-games .featured-meta {
  margin-top: var(--spacing-lg);
}

.page--design-system .portal-library-embed .featured-actions,
.page--portal-games .featured-actions {
  padding-top: var(--space-28);
}

.page--design-system .portal-library-embed .featured-art,
.page--portal-games .featured-art {
  min-height: 330px;
  background: var(--surface-raised);
}

.page--design-system .portal-library-embed .feature-label,
.page--portal-games .feature-label {
  right: var(--space-20);
  top: var(--space-20);
  z-index: 4;
  padding: var(--space-10) var(--space-12);
  border: 1px solid color-mix(in srgb, var(--pure-white) 22%, transparent);
  background: color-mix(in srgb, var(--deep-navy-900) 78%, transparent);
  backdrop-filter: blur(12px);
}

.page--design-system .portal-library-embed .feature-label span,
.page--portal-games .feature-label span {
  opacity: 0.72;
}

.page--design-system .portal-library-embed .art-stack,
.page--portal .art-stack
{
  background: var(--surface-raised);
}

.page--design-system .portal-library-embed .art-layer,
.page--portal .art-layer
{
  inset: -1px;
  width: calc(100% + 2px);
  height: calc(100% + 2px);
  max-width: none;
  object-fit: cover;
  object-position: center;
  transition: opacity var(--duration-fast) ease, transform var(--duration-base) ease;
}

.page--design-system .portal-library-embed .art-animated,
.page--portal .art-animated {
  opacity: 0;
}

/* Only a stack that actually ships an animated twin may hide its static layer.
   Most of the catalogue is single-file artwork,
and without this guard hovering
   one of those cards would fade it to an empty panel. */
.page--design-system .portal-library-embed .is-art-animating .art-stack:has(.art-animated) .art-static,
.page--portal .is-art-animating .art-stack:has(.art-animated) .art-static
{
  opacity: 0;
}

.page--design-system .portal-library-embed .is-art-animating .art-animated,
.page--portal .is-art-animating .art-animated {
  opacity: 1;
}

.game-card--portal:hover .art-layer,
.page--design-system .portal-library-embed .featured-card:focus-within .art-layer,
.page--design-system .portal-library-embed .featured-card:hover .art-layer,
.page--design-system .portal-library-embed .game-card--portal:focus-within .art-layer,
.page--design-system .portal-library-embed .game-card--portal:hover .art-layer,
.page--portal .featured-card:focus-within .art-layer,
.page--portal .featured-card:hover .art-layer,
.page--portal .game-card--portal:focus-within .art-layer
{
  transform: scale(1.012);
}

/* Reduced motion has to zero the transform, not only its duration - the global
   block sets transition-duration: 0.01ms, which still lands the scale, just
   instantly. Same guard shape the benchmark card uses. */
@media (prefers-reduced-motion: reduce) {
.page--design-system .portal-library-embed .featured-card:hover .art-layer,
.page--design-system .portal-library-embed .featured-card:focus-within .art-layer,
.page--design-system .portal-library-embed .game-card--portal:hover .art-layer,
.page--design-system .portal-library-embed .game-card--portal:focus-within .art-layer,
.page--portal .featured-card:hover .art-layer,
.page--portal .featured-card:focus-within .art-layer,
.page--portal .game-card--portal:hover .art-layer,
.page--portal .game-card--portal:focus-within .art-layer
{
    transform: none;
  }
}

.page--design-system .portal-library-embed .games-section,
.page--portal-games .games-section {
  padding: var(--space-48) 0 var(--space-96);
}

.page--design-system .portal-library-embed .section-heading{
  align-items: end;
}

/* The control strip sits on the page, not on a panel. It used to fill with
   --surface-panel, which banded the full width in grey between the section
   heading and the grid and read as a separate surface rather than a toolbar
   belonging to the library. Its own hairlines already delineate it. */

/* One column since the mood row left: the strip holds the search alone, and a
   second track only reserved the space the filters used to sit in. */
.page--design-system .portal-library-embed .control-inner,
.page--portal-games .control-inner {
  min-height: 70px;
  grid-template-columns: minmax(0, 1fr);
}

/* The library's search sits at the right of the All games bar, its box bottom
   level with the last line of the hint beside it - the bar is baseline-aligned
   for a heading and a link, and a 42px field has no baseline to share, so this
   one row aligns on the end instead. It is capped rather than full width: a
   field running the whole column read as a search for the portal rather than
   for the shelf under it. */
.page--portal-games .portal-section__bar:has(.search-field) {
  align-items: flex-end;
  gap: var(--space-24);
}

.page--portal-games .portal-section__bar .search-field {
  flex: 0 1 var(--space-320);
  min-width: 0;
}

@media (max-width: 720px) {
  .page--portal-games .portal-section__bar:has(.search-field) {
    flex-wrap: wrap;
    align-items: stretch;
  }

  .page--portal-games .portal-section__bar .search-field {
    flex: 1 1 100%;
  }
}

.page--design-system .portal-library-embed .search-field input,
.page--portal-games .search-field input {
  height: var(--space-42);
  padding-block: 0;
}

.page--design-system .portal-library-embed .clear-search,
.page--portal-games .clear-search {
  right: var(--space-6);
  top: var(--space-6);
  width: var(--space-28);
  height: var(--space-28);
}

.game-grid--portal, .page--design-system .portal-library-embed .game-grid--portal {
  margin-top: var(--space-24);
}

.game-card--portal.is-hidden,
.page--design-system .portal-library-embed .game-card--portal.is-hidden {
  display: none;
}

/* Each card becomes its own stacking context. .media-badge carries z-index: 4,
and nothing between the badge and <body> creates a context for it to resolve
   against - so without this the badge of the card next door paints straight
   through the lifted card and its shadow,
and the lift reads as the card
   sliding under its neighbours instead of over them. */
.page--design-system .portal-library-embed .game-card--portal,
.page--portal .game-card--portal
{
  isolation: isolate;
}

/* The portal card takes the same lift as the evaluated-catalog card
   (.game-card--benchmark),
on hover and on :focus-within - the card holds real
   buttons,
so a keyboard user has to get the response a pointer user gets. No
   transition is declared here on purpose: .sp-card--interactive already
   transitions transform and box-shadow at --duration-fast,
which is the
   duration the benchmark lift runs at too. z-index raises the moving card
   clear of its siblings; a card is a grid item,
so it applies unpositioned. */
.page--design-system .portal-library-embed .game-card--portal:hover,
.page--design-system .portal-library-embed .game-card--portal:focus-within,
.page--portal .game-card--portal:hover,
.page--portal .game-card--portal:focus-within
{
  z-index: 1;
  transform: translateY(calc(var(--space-4) * -1));
  box-shadow: var(--shadow-md);
}

/* Reduced motion has to zero the transform, not just its duration: the global
   reduced-motion block only sets transition-duration: 0.01ms, which still
   lands the translate, instantly. The shadow stays - a shadow is not motion.
   Same guard shape the benchmark card uses. */
@media (prefers-reduced-motion: reduce) {
.page--design-system .portal-library-embed .game-card--portal:hover,
.page--design-system .portal-library-embed .game-card--portal:focus-within,
.page--portal .game-card--portal:hover,
.page--portal .game-card--portal:focus-within
{
    transform: none;
  }
}

.page--design-system .portal-library-embed .media-open,
.page--portal-games .media-open {
  color: inherit;
}

/* The artwork opener is a .button,
so Part A's press translate shifts the whole
   media block down inside a card that is .clip and .card--flush - the top of
   the artwork tears away from the card border on every click,
and it fights the
   card's own lift. Press feedback lives on the card now; the real controls in
   .card-footer keep theirs. This is a full-bleed hit area,
not a pressable
   control. */
.page--design-system .portal-library-embed .media-open:active:not(:disabled),
.page--portal .media-open:active:not(:disabled) {
  transform: none;
}

.page--design-system .portal-library-embed .game-media,
.page--portal .game-media
{
  aspect-ratio: 1.82/1;
  background: var(--surface-raised);
}

/* THE PILL A GAME TILE CARRIES
   One system, every game tile in the portal - the library's .game-card and the
   rails' .play-card both. Top-right is the tile's status in the portal, and it
   is the only pill on the artwork now: the top-left pill named one skill, and a
   game is read for several, so the card states them in its body instead. The
   status was scoped to the library page only and the homepage grew its own
   .play-card__flag instead, so the same fact appeared in three treatments. */
.page--design-system .portal-library-embed .media-badge,
.page--portal .media-badge {
  position: absolute;
  top: var(--space-14);
  z-index: 4;
  min-height: var(--space-28);
  padding: 0 var(--space-10);
  display: inline-flex;
  align-items: center;
  border-radius: var(--radius-full);
  white-space: nowrap;
}

/* THE STATUS PILL
   A status is metadata, not a headline. Every one of them is the same dark
   Panel Navy chip and only the ink changes, so the word inside carries the
   difference rather than the pill shouting it: sentence case, medium weight, no
   tracking, and 10px - the floor of the type scale. It used to be 10px bold
   uppercase on saturated Core Green, which reads far bigger than its size
   because uppercase measures on cap height and the fill competed with the
   artwork. The card's title, description and buttons stay the loud things.

   Panel Navy 900 is the ground rather than a lighter step because it is the
   only one where all three accents clear 4.5:1: Mindset Magenta 400 measures
   5.14:1 on it against 4.35:1 on Panel Navy 800, and at 10px the 3:1
   large-text allowance does not apply. That choice is the whole accessibility
   treatment - no accent is lightened or swapped for a near neighbour.

   The hairline is the accent itself at 30% over Panel Navy 700, so the chip
   keeps an edge on dark artwork without introducing a second colour. */
.page--design-system .portal-library-embed .media-badge,
.page--portal .media-badge {
  --badge-accent: var(--geistface-grey-400);
  /* the library card's chip also carries .ui-badge, which sets an explicit
     height from this token - re-valuing it keeps the grid and the rail the same
     20px rather than 22 and 20 */
  --badge-height: var(--space-20);
  right: var(--space-14);
  left: auto;
  min-height: var(--space-20);
  padding: 0 var(--space-6);
  border: 1px solid color-mix(in srgb, var(--badge-accent) 30%, var(--panel-navy-700));
  background: var(--panel-navy-900);
  box-shadow: none;
  color: var(--badge-accent);
  font-size: var(--text-xxs-font-size);
  line-height: var(--text-xxs-line-height);
  font-weight: var(--weight-medium);
  letter-spacing: normal;
  text-transform: none;
}

/* The status vocabulary. One declaration each, because colour is the only
   thing that separates them. Grey is the default above, and it is what
   "Played" keeps - a spent state should be the quietest thing on the card. */
.page--design-system .portal-library-embed .media-badge[data-status="in-progress"],
.page--portal .media-badge[data-status="in-progress"] {
  --badge-accent: var(--mindset-magenta-400);
}

.page--design-system .portal-library-embed .media-badge[data-status="new"],
.page--portal .media-badge[data-status="new"] {
  --badge-accent: var(--skills-orange-500);
}

.page--design-system .portal-library-embed .media-badge[data-status="recommended"],
.page--portal .media-badge[data-status="recommended"] {
  --badge-accent: var(--core-green-500);
}

.page--design-system .portal-library-embed .game-body,
.page--portal .game-body
{
  flex: 1;
  padding: var(--space-20);
}

.page--design-system .portal-library-embed .game-description,
.page--portal .game-description
{
  /* Kept in step with the declaration that actually wins, further down in
     "the library tile, tightened", where this floor is argued. */
  min-height: calc(var(--space-22) * 2);
  margin: var(--space-8) 0 var(--space-16);
}

/* The label stacks over its values rather than sitting beside them. The card
   names what it develops now - "Skills Developed" measures 96px at the label's
   own weight and the widest label column this card can afford is 72px,
so a
   side-by-side label breaks onto two lines against a one-line value and the
   two rows stop lining up. Stacked,
the label is the card's own micro-label at the
   eyebrow step. It no longer mirrors Game Detail: that page's Skills section is
   now a house .portal-section__title with the labels grouped by dimension, and
   has no micro-label at all. The card keeps this one because it is the compact
   form - a 232px body cannot carry a section head. */
.page--design-system .portal-library-embed .trait-group,
.page--portal .trait-group
{
  grid-template-columns: 1fr;
}

/* SKILLS DEVELOPED - one flowing list,
not a labelled row per dimension.
   The card used to spend two labelled rows saying "Mood" and "Cognition" and a
   generic per-dimension glyph on each,
which cost four lines of vertical space
   to communicate a grouping the player never asked for. It is one list now:
   every skill the game develops,
each with the icon the trait system already
   assigns it,
and the dimension carried only by that icon's colour
   (.sp-icon--mood / --cognition / --personality,
already defined). No category
   label,
no second row,
no pill.

   The type is deliberately the quietest thing in the body - 10px at medium
   weight against a 15px semibold title and a 14px description - and the icon
   is driven down to 12px through .sp-icon's own --icon-size hook rather than a
   new size modifier. --icon-stroke-12 comes with it,
because a 24-unit symbol
   drawn at 12px keeps the sheet's ~1.25px painted line only if the stroke is
   restated; leaving it at the 16px step paints visibly heavier at the smaller
   size. Row and column gaps are set separately so a wrap costs 4px,
not 10. */
.page--design-system .portal-library-embed .trait-skills,
.page--portal .trait-skills
{
  list-style: none;
  column-gap: var(--space-10);
  row-gap: var(--space-4);
}

.page--design-system .portal-library-embed .trait-skill,
.page--portal .trait-skill
{
  display: inline-flex;
  align-items: center;
  gap: var(--space-4);
  color: var(--ui-text);
  font-size: var(--font-size-xs);
  line-height: var(--line-height-sm);
  font-weight: var(--weight-medium);
}

/* .sp-icon--2xs already carries the 12px size and --icon-stroke-12, so the list
   only has to let it through. This page opens with a blunt
   `.page--portal-games svg { width: var(--space-20) }` at css/skillprint.css:12868,
   and an element selector outweighs .sp-icon's own `width: var(--icon-size)` -
   so every --2xs icon here, the mood/cognition/personality filter glyphs
   included, was resolving --icon-size to 12px and still painting at 20. Stating
   width and height at modifier level restores the size the modifier asks for.
   The stroke needs no such rescue: the symbol draws itself with
   stroke-width="var(--icon-stroke, 2)", and a custom property crosses into the
   <use> shadow tree where that rule's plain stroke-width cannot. */
.page--design-system .portal-library-embed .sp-icon--2xs,
.page--portal-games .sp-icon--2xs,
.page--portal-skill-progression .sp-icon--2xs
{
  width: var(--icon-size);
  height: var(--icon-size);
}

.page--design-system .portal-library-embed .trait-group + .trait-group,
.page--portal .trait-group + .trait-group
{
  margin-top: var(--space-12);
}

/* .trait-icon,
.mood-icon and .cognition-icon sized and coloured the one
   generic glyph each labelled row used to carry. The list above gives every
   skill its own trait icon and takes its colour from .sp-icon--*,
so all four
   rules stopped matching anything on this page. (.mood-icon and
   .cognition-icon still have their own unrelated rules under
   .page--portal-welcome and .page--docs-v2.) */

.page--design-system .portal-library-embed .card-footer,
.page--portal .card-footer
{
  padding-top: var(--space-20);
}

.page--design-system .portal-library-embed .play-btn svg,
.page--portal .play-btn svg
{
  fill: currentColor;
  stroke: none;
}

.page--design-system .portal-library-embed .empty-state,
.page--portal-games .empty-state {
  display: none;
  margin-top: var(--space-24);
  padding: var(--space-64) var(--space-24);
  border: 1px dashed var(--border-interactive);
  border-radius: var(--radius-panel);
  background: var(--surface-panel);
}

.page--design-system .portal-library-embed .empty-state.is-visible,
.page--design-system .portal-library-embed .modal-media-label span,
.page--design-system .portal-library-embed .modal-media-label strong,
.page--portal-games .empty-state.is-visible,
.page--portal .modal-media-label span,
.page--portal .modal-media-label strong {
  display: block;
}

/* The game modal is a two-pane dialog: artwork on the left, detail on the
   right. The shell sizes to its content and is only capped by the padded
   backdrop area, so a game whose detail fits gets no scrollbar at all and one
   that overruns the viewport scrolls the detail pane rather than the page. */
.page--design-system .portal-library-embed .popup,
.page--portal .popup {
  width: min(var(--space-1024), 100%);
  max-height: min(var(--space-1024), 100%);
  grid-template-columns: minmax(0, 0.87fr) minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
}

/* Taking the artwork out of flow leaves the detail pane as the only thing that
   sizes the row, so the shell is exactly as tall as the detail needs and the
   artwork follows it instead of setting a floor of its own. */
.page--design-system .portal-library-embed .modal-media img,
.page--portal .modal-media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* Below this the panes stack with the artwork first — it is the fastest way to
   recognise the game — and the detail below it keeps its own scroll. The
   backdrop keeps a wider margin than the shared dialog default so nothing
   reaches a rounded screen corner. */
@media (max-width: 900px) {
  .page--design-system .portal-library-embed .popup-backdrop,
  .page--portal .popup-backdrop {
    padding: var(--space-20);
  }

  .page--design-system .portal-library-embed .popup,
  .page--portal .popup {
    width: min(var(--space-480), 100%);
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr);
  }

  .page--design-system .portal-library-embed .modal-media,
  .page--portal .modal-media {
    height: clamp(var(--space-120), 26vh, var(--space-256));
  }
}

.page--design-system .portal-library-embed .modal-close,
.page--portal .modal-close {
  right: var(--space-18);
  top: var(--space-18);
  z-index: 5;
  width: var(--space-40);
  height: var(--space-40);
  border: 1px solid color-mix(in srgb, var(--pure-white) 18%, transparent);
  background: color-mix(in srgb, var(--deep-navy-900) 78%, transparent);
}

.page--design-system .portal-library-embed .modal-close:active,
.page--design-system .portal-library-embed .modal-close:hover,
.page--portal .modal-close:active,
.page--portal .modal-close:hover {
  background: var(--deep-navy-900);
  border-color: color-mix(in srgb, var(--pure-white) 32%, transparent);
}

.page--design-system .portal-library-embed .modal-media,
.page--portal .modal-media {
  min-height: 0;
  background: var(--surface-raised);
}

.page--design-system .portal-library-embed .modal-media-label,
.page--portal .modal-media-label {
  left: var(--space-22);
  bottom: var(--space-22);
  max-width: calc(100% - 44px);
  padding: var(--space-10) var(--space-12);
  border: 1px solid color-mix(in srgb, var(--pure-white) 22%, transparent);
  background: color-mix(in srgb, var(--deep-navy-900) 78%, transparent);
  backdrop-filter: blur(12px);
}

.page--design-system .portal-library-embed .modal-content,
.page--portal .modal-content {
  min-height: 0;
  overflow: auto;
  overscroll-behavior: contain;
}

.page--design-system .portal-library-embed .modal-inner,
.page--portal .modal-inner {
  padding: var(--space-42) var(--space-40) var(--space-32);
}

.page--design-system .portal-library-embed .modal-category,
.page--portal .modal-category {
  color: var(--customer);
}

/* The dialog's own page title, and drawn as one: Display XS 24/32 at -0.01em,
   which is exactly what `.page--portal .portal-head h1` takes.

   It was Display MD - 36/44 - so the game modal's title was half again the size
   of the title of the page that opened it, on every portal screen that has a
   modal (the library and Skill Progression). The portal ladder never reached it
   to say otherwise: `.page--portal .portal-content h2` only claims headings
   inside the content column, and both modals are written after `</main>`. This
   is the same correction the ladder's own preamble describes - "three levels of
   hierarchy all drawn at roughly one size is the 'oversized' read the brief
   names" - applied to the one heading that sat outside its reach. */
.page--design-system .portal-library-embed .modal-title,
.page--portal .modal-title {
  margin: var(--space-6) var(--space-56) var(--space-10) 0;
  font-size: var(--display-xs-font-size);
  line-height: var(--display-xs-line-height);
  letter-spacing: -0.01em;
}


.page--design-system .portal-library-embed .modal-meta,
.page--portal .modal-meta {
  margin: var(--space-24) 0;
}

.page--design-system .portal-library-embed .meta-card strong,
.page--portal .meta-card strong {
  margin-top: var(--space-2);
}

.page--design-system .portal-library-embed .modal-section,
.page--portal .modal-section {
  padding: var(--space-20) 0;
}

.page--design-system .portal-library-embed .modal-pill,
.page--portal .modal-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--space-6);
  color: var(--text);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-sm-line-height);
  font-weight: var(--weight-semibold);
}

.page--design-system .portal-library-embed .modal-pill i,
.page--portal .modal-pill i {
  width: var(--space-8);
  height: var(--space-8);
  border-radius: 50%;
  background: var(--pill);
}

.page--design-system .portal-library-embed .insight-box,
.page--portal .insight-box {
  gap: var(--space-10);
  padding: var(--space-14);
  border: 1px solid color-mix(in srgb, var(--customer) 36%, var(--border));
  background: color-mix(in srgb, var(--customer) 8%, var(--surface-box));
}

.page--design-system .portal-library-embed .insight-box svg,
.page--portal .insight-box svg {
  width: var(--space-20);
  height: var(--space-20);
  color: var(--customer);
  --icon-stroke: var(--icon-stroke-20);
}

.page--design-system .portal-library-embed .modal-actions,
.page--portal .modal-actions {
  position: sticky;
  bottom: 0;
  padding: var(--space-16) var(--space-40) var(--space-24);
  background: color-mix(in srgb, var(--surface-box) 94%, transparent);
  backdrop-filter: blur(14px);
}

/* The embed isolates its own stacking context so the portal preview's layers
   stay inside it — which also pinned its dialogs under the docs sidebar once
   the game modal grew past the content column. Raising the context clears it:
   the embed itself sits in the content column, so only its viewport-fixed
   dialog ever reaches the sidebar. */
.page--design-system .portal-library-embed {
  position: relative;
  z-index: 60;
  border: 1px solid var(--border, var(--skillprint-shared-border));
  border-radius: var(--radius-panel, var(--radius-4xl));
  background: var(--page, var(--skillprint-deep-navy));
}

.page--design-system .portal-library-embed > .hero--portal { padding-top: var(--space-64); }

.page--design-system .interface-preview-card[data-brand-family="developer"] {
  --interface-accent: var(--mindset-violet-400);
}

.page--design-system .interface-preview-card[data-brand-family="customer"] {
  --interface-accent: var(--core-green-500);
}

/* ---------------------------------------------------------------------------
   work.skillprint.co - the O*NET workforce taxonomy visualiser

   The one surface that reads the system outward: O*NET occupations against
   the evaluated catalog. It sits in the developer family, but the miniature
   keeps its two-accent split, because that split is the surface's whole
   argument - Personality Mint carries the O*NET side (the occupation code,
   the profile link) and Mindset Violet carries the Skillprint side (the job
   zone). The live app draws them as raw hex; here they are ramp steps, which
   is the point of showing it in the reference at all.
   --------------------------------------------------------------------------- */

.page--design-system .work-interface-mini {
  min-height: 410px;
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr);
  background: var(--deep-navy-900);
  color: var(--geistface-grey-200);
}

.page--design-system .work-mini-nav {
  min-height: 46px;
  padding: 0 var(--space-14);
  border-bottom: 1px solid color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
}

.page--design-system .work-mini-brand img {
  width: var(--space-14);
  height: var(--space-14);
}

.page--design-system .work-mini-pill {
  padding: 0 var(--space-4);
  border: 1px solid color-mix(in srgb, var(--deep-navy-25) 18%, transparent);
  border-radius: var(--radius-sm);
  color: var(--geistface-grey-400);
  font: 600 6px/12px var(--skillprint-font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.page--design-system .work-mini-links i {
  width: var(--space-24);
  height: var(--space-4);
  background: var(--panel-navy-600);
}

.page--design-system .work-mini-links b {
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--mindset-violet-400);
  font-size: 7px;
}

.page--design-system .work-mini-hero {
  padding: var(--space-20) var(--space-16) var(--space-14);
  text-align: center;
}

.page--design-system .work-mini-eyebrow {
  display: inline-block;
  padding: var(--space-2) var(--space-8);
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--personality-mint-500) 16%, transparent);
  color: var(--personality-mint-500);
  font: 600 6px/12px var(--skillprint-font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.page--design-system .work-mini-hero h3 {
  margin: var(--space-8) 0 var(--space-4);
  font-size: var(--text-md-font-size);
  line-height: var(--text-md-line-height);
}

.page--design-system .work-mini-hero p {
  max-width: 300px;
  margin-inline: auto;
}

.page--design-system .work-mini-search {
  max-width: 300px;
  margin: var(--space-12) auto 0;
  padding: var(--space-6) var(--space-8);
  border: 1px solid color-mix(in srgb, var(--deep-navy-25) 12%, transparent);
  background: var(--panel-navy-900);
  font-size: 7px;
}

.page--design-system .work-mini-select {
  padding-inline-start: var(--space-8);
  border-inline-start: 1px solid color-mix(in srgb, var(--deep-navy-25) 12%, transparent);
  color: var(--geistface-grey-400);
}

.page--design-system .work-mini-body {
  min-height: 0;
  padding: 0 var(--space-16) var(--space-16);
}

.page--design-system .work-mini-label {
  margin-bottom: var(--space-8);
  color: var(--geistface-grey-400);
  font: 600 7px/12px var(--skillprint-font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.page--design-system .work-mini-jobs { grid-template-columns: repeat(3, minmax(0, 1fr)); }

/* Column, so a two-line job title does not drag its profile link out of step
   with the rest of the row. */
.page--design-system .work-mini-job {
  padding: var(--space-8);
  display: flex;
  flex-direction: column;
  border: 1px solid color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
  background: var(--panel-navy-900);
}

.page--design-system .work-mini-job p {
  overflow: hidden;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  font-size: 6px;
  line-height: 10px;
}

/* The O*NET code and the Skillprint job zone, the two taxonomies this screen
   exists to reconcile. */
.page--design-system .work-mini-code {
  padding: 0 var(--space-4);
  border: 1px solid color-mix(in srgb, var(--personality-mint-500) 34%, transparent);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--personality-mint-500) 10%, transparent);
  color: var(--personality-mint-500);
  font: 600 6px/12px var(--skillprint-font-mono);
}

.page--design-system .work-mini-zone {
  padding: 0 var(--space-4);
  border: 1px solid color-mix(in srgb, var(--mindset-violet-400) 40%, transparent);
  border-radius: var(--radius-sm);
  color: var(--mindset-violet-200);
  font: 600 6px/12px var(--skillprint-font-mono);
  font-style: normal;
}

.page--design-system .work-mini-job__title {
  margin: var(--space-6) 0 var(--space-2);
  font-size: 8px;
  line-height: 12px;
}

.page--design-system .work-mini-job__link {
  margin-top: auto;
  padding-top: var(--space-6);
  border-top: 1px solid color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
  color: var(--personality-mint-500);
  font-size: 6px;
  font-weight: var(--weight-semibold);
  text-align: end;
}


.page--design-system .docs-mini-copy h3 {
  max-width: 390px;
}


.page--design-system .homepage-mini-copy b {
  padding: var(--space-4) var(--space-8);
  border-radius: var(--radius-xs);
  background: var(--mindset-violet-500);
}


.page--design-system .homepage-interface-mini {
  min-height: 410px;
  background: var(--deep-navy-900);
  color: var(--geistface-grey-200);
}

.page--design-system .homepage-mini-nav,
.page--design-system .docs-mini-nav,
.page--design-system .labs-mini-nav {
  min-height: 46px;
  padding: 0 var(--space-14);
  border-bottom: 1px solid color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
}

.page--design-system .homepage-mini-links { gap: var(--space-8); }

.page--design-system .homepage-mini-links i {
  width: var(--space-24);
  height: var(--space-4);
  background: var(--panel-navy-600);
}

.page--design-system .homepage-mini-nav > b {
  padding: var(--space-4) var(--space-8);
  border-radius: var(--radius-sm);
  background: var(--mindset-violet-500);
  font-size: 7px;
}

.page--design-system .homepage-mini-hero {
  padding: var(--space-28) var(--space-22) var(--space-20);
  grid-template-columns: 0.95fr 1.05fr;
  gap: var(--space-14);
}

.page--design-system .homepage-mini-copy > span {
  color: var(--geistface-grey-400);
  font: 600 7px/11px var(--font-mono);
}

.page--design-system .homepage-mini-copy p {
  color: color-mix(in srgb, var(--geistface-grey-200) 68%, transparent);
  font-size: 8px;
  line-height: 13px;
}

.page--design-system .homepage-mini-copy > div {
  gap: var(--space-8);
  margin-top: var(--space-12);
  font-size: 7px;
}

.page--design-system .homepage-mini-copy i {
  padding: var(--space-4) 0;
  color: var(--geistface-grey-200);
  font-style: normal;
}

.page--design-system .homepage-mini-signal {
  padding: var(--space-10);
  border: 1px solid color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
  background: var(--panel-navy-900);
}

.page--design-system .homepage-mini-signal > header {
  color: var(--geistface-grey-400);
  font: 500 6px/10px var(--font-mono);
}

.page--design-system .homepage-mini-signal > header i {
  width: var(--space-6);
  height: var(--space-6);
  background: var(--core-green-500);
}

.page--design-system .homepage-mini-playfield {
  height: 90px;
  margin-top: var(--space-8);
  background: radial-gradient( circle at 30% 50%, color-mix(in srgb, var(--core-green-500) 35%, transparent), transparent 28% ), radial-gradient( circle at 72% 38%, color-mix(in srgb, var(--personality-mint-500) 28%, transparent), transparent 22% ), var(--panel-navy-950);
}

.page--design-system .homepage-mini-playfield i {
  width: 34px;
  height: 34px;
  border: 1px solid color-mix(in srgb, var(--deep-navy-25) 14%, transparent);
}

.page--design-system .homepage-mini-playfield i:nth-child(1) { left: 14%; top: 22%; }
.page--design-system .homepage-mini-playfield i:nth-child(2) { left: 46%; top: 48%; }
.page--design-system .homepage-mini-playfield i:nth-child(3) { right: 13%; top: 18%; }

.page--design-system .homepage-mini-playfield b {
  right: var(--space-10);
  bottom: var(--space-8);
  font: 600 11px/15px var(--font-mono);
}

.page--design-system .homepage-mini-metrics {
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-4);
  margin-top: var(--space-6);
}

.page--design-system .homepage-mini-metrics article {
  padding: var(--space-6);
  border: 1px solid color-mix(in srgb, var(--deep-navy-25) 8%, transparent);
  border-radius: var(--radius-sm);
}

.page--design-system .homepage-mini-metrics span {
  color: var(--geistface-grey-400);
  font-size: 6px;
}

.page--design-system .homepage-mini-metrics strong {
  margin-top: var(--space-2);
  font: 500 11px/14px var(--font-mono);
}

.page--design-system .homepage-mini-proof {
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  padding: 0 var(--space-22);
}

.page--design-system .homepage-mini-proof span {
  padding: var(--space-8);
  border: 1px solid color-mix(in srgb, var(--deep-navy-25) 8%, transparent);
  color: var(--geistface-grey-400);
  font: 500 6px/10px var(--font-mono);
}

.page--design-system .homepage-mini-dimensions {
  grid-template-columns: repeat(3, 1fr);
  padding: var(--space-10) var(--space-22) var(--space-22);
}

.page--design-system .homepage-mini-dimensions article {
  min-height: 58px;
  padding: var(--space-8);
  border: 1px solid color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
  background: var(--panel-navy-900);
}

.page--design-system .homepage-mini-dimensions i {
  width: var(--space-8);
  height: var(--space-8);
  margin-bottom: var(--space-8);
}

.page--design-system .homepage-mini-dimensions article:nth-child(1) i {
  background: var(--personality-mint-500);
}

.page--design-system .homepage-mini-dimensions article:nth-child(2) i {
  background: var(--core-lime-500);
}

.page--design-system .homepage-mini-dimensions article:nth-child(3) i {
  background: var(--personality-blue-500);
}

.page--design-system .homepage-mini-dimensions strong { font-size: 7px; line-height: 11px; }

.page--design-system .labs-mini-stats article {
  padding: var(--space-10);
  border: 1px solid color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
  border-radius: var(--radius-md);
  background: var(--panel-navy-900);
}

.page--design-system .interface-applications-section {
  --interface-accent: var(--mindset-violet-400);
}

.page--design-system .interface-application-summary { margin-bottom: var(--space-32); }

.page--design-system .interface-application-rule {
  min-height: 174px;
  box-shadow: var(--shadow-xs);
}

.page--design-system .contract-card b,
.page--design-system .nav-spec-label,
.page--design-system .interface-application-rule > span { font: 600 var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono); }
.page--design-system .interface-application-rule strong { margin-bottom: var(--space-6); }
.page--design-system .interface-preview-card__head { min-height: 74px; padding: var(--space-16) var(--space-20); }

.page--design-system .interface-preview-mark {
  width: 34px;
  height: 34px;
  flex: 0 0 34px;
  border: 1px solid color-mix(in srgb, var(--interface-accent) 38%, var(--border));
  background: color-mix(in srgb, var(--interface-accent) 10%, var(--panel-950));
}

.page--design-system .interface-preview-identity div { gap: 1px; }
.page--design-system .interface-preview-identity strong { text-overflow: ellipsis; }

.page--design-system .interface-window {
  margin: var(--space-24) var(--space-24) 0;
  background: var(--deep);
  box-shadow: var(--shadow-sm);
}

.page--design-system .interface-window__dots { gap: var(--space-4); }

.page--design-system .interface-window__dots i {
  width: var(--space-6);
  height: var(--space-6);
  background: var(--panel-navy-500);
}

.page--design-system .interface-window__address {
  text-overflow: ellipsis;
  font: 500 9px/15px var(--font-mono);
}

.page--design-system .interface-window__body {
  min-height: 410px;
  background: var(--deep);
  color: var(--geistface-grey-200);
}

/* ---------------------------------------------------------------------------
   Live previews

   The portal is fifteen screens of one journey. Redrawing each as a hand-built
   miniature would be fifteen approximations to keep in step with fifteen real
   pages, and the first edit to any of them makes the reference lie. So these
   frames render the prototype itself.

   The scale is expressed as a ratio rather than a pixel width: the frame is
   sized to 1/scale of the card and then scaled back down, so whatever the
   column is worth the prototype always lays out at a desktop viewport - about
   1400px in a two-up grid - instead of collapsing into its own mobile
   breakpoints inside a 560px card.

   The frame is inert and takes no pointer events: it is a picture of the
   screen, not a second interface inside the page. Tabbing through the
   reference must not walk into fifteen embedded documents, and scrolling over
   a card must scroll the reference. The card's own link is how you open the
   real thing.
   --------------------------------------------------------------------------- */

.page--design-system .interface-window__body--live {
  --frame-scale: 0.4;

  position: relative;
  overflow: hidden;
}

.page--design-system .interface-frame {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  /* Overshoot the width by a scrollbar's worth. The embedded document keeps
     its own scrollbar and nothing in this sheet can reach across to hide it,
     so the frame is drawn wider than the card and the surplus - scrollbar
     included - falls outside the clip. */
  width: calc(100% / var(--frame-scale) + var(--space-20));
  height: calc(100% / var(--frame-scale));
  border: 0;
  pointer-events: none;
  transform: scale(var(--frame-scale));
  transform-origin: top left;
}


.page--design-system .docs-mini-links i, .page--design-system .labs-mini-links i {
  width: var(--space-28);
  height: var(--space-4);
  background: var(--panel-navy-600);
}

.page--design-system .docs-mini-links b {
  min-height: var(--space-22);
  padding: 0 var(--space-8);
  border-radius: var(--radius-sm);
  background: var(--mindset-violet-500);
  font-size: 7px;
}

.page--design-system .labs-mini-nav > b {
  min-height: var(--space-22);
  padding: 0 var(--space-6);
  border: 1px solid color-mix(in srgb, var(--mindset-violet-400) 34%, transparent);
  color: var(--mindset-violet-200);
  font: 600 7px/12px var(--font-mono);
}

.page--design-system .docs-mini-hero { padding: var(--space-32) var(--space-24); }

.page--design-system .docs-mini-copy > span,
.page--design-system .labs-mini-heading > span,
.page--design-system .docs-mini-paths b,
.page--design-system .labs-mini-chart > span,
.page--design-system .labs-mini-control > span {
  color: var(--geistface-grey-400);
  font: 600 7px/12px var(--font-mono);
}

.page--design-system .docs-mini-copy p { max-width: 430px; color: color-mix(in srgb, var(--geistface-grey-200) 66%, transparent); }
.page--design-system .docs-mini-paths { grid-template-columns: repeat(3, 1fr); margin-top: var(--space-28); }

.page--design-system .docs-mini-paths article {
  min-height: 108px;
  padding: var(--space-10);
  border: 1px solid color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
  background: var(--panel-navy-900);
}

.page--design-system .docs-mini-paths span {
  margin-top: var(--space-16);
  color: var(--geistface-grey-400);
  font-size: 7px;
  line-height: 11px;
}

.page--design-system .docs-mini-paths strong { margin-top: var(--space-2); line-height: 13px; }

.page--design-system .docs-mini-paths i {
  width: var(--space-28);
  height: var(--space-2);
  background: var(--mindset-violet-400);
}

.page--design-system .docs-mini-code {
  min-height: var(--space-42);
  margin-top: var(--space-10);
  padding: 0 var(--space-10);
  border: 1px solid color-mix(in srgb, var(--deep-navy-25) 12%, transparent);
  border-radius: var(--radius-md);
  background: var(--deep-navy-950);
  color: var(--panel-navy-100);
  font: 500 8px/13px var(--font-mono);
}

.page--design-system .docs-mini-code b {
  padding: var(--space-4) var(--space-6);
  border: 1px solid color-mix(in srgb, var(--deep-navy-25) 18%, transparent);
  border-radius: var(--radius-xs);
  background: var(--panel-navy-900);
  color: var(--geistface-grey-200);
  font-size: 7px;
}

.page--design-system .labs-mini-content { padding: var(--space-22); }
.page--design-system .labs-mini-stats { grid-template-columns: repeat(3, 1fr); margin-top: var(--space-14); }

.page--design-system .labs-mini-stats span {
  color: var(--geistface-grey-400);
  font-size: 7px;
  line-height: 11px;
  letter-spacing: 0.07em;
}

.page--design-system .labs-mini-stats strong {
  margin-top: var(--space-4);
  font: 500 15px/20px var(--font-mono);
}

.page--design-system .labs-mini-stats i {
  margin-top: var(--space-2);
  font: 500 7px/11px var(--font-mono);
  font-style: normal;
}

.page--design-system .labs-mini-grid { grid-template-columns: 0.82fr 1.18fr; margin-top: var(--space-8); }

.page--design-system .labs-mini-chart, .page--design-system .labs-mini-control {
  min-height: 208px;
  padding: var(--space-12);
  border: 1px solid color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
  background: var(--panel-navy-900);
}

.page--design-system .labs-mini-control > strong { margin-top: var(--space-4); }

.page--design-system .labs-mini-control > div i {
  height: var(--space-24);
  border: 1px solid color-mix(in srgb, var(--deep-navy-25) 12%, transparent);
}

.page--design-system .labs-mini-control button {
  min-height: var(--space-28);
  margin-top: var(--space-10);
  background: var(--mindset-violet-500);
  font-size: 7px;
}

.page--design-system .labs-mini-chart svg {
  height: 132px;
  margin-top: var(--space-6);
  color: var(--geistface-grey-400);
}

.page--design-system .labs-mini-chart svg circle,
.page--design-system .labs-mini-chart svg path:last-child,
.page--design-system .labs-mini-chart svg rect {
  fill: var(--violet);
}

.page--design-system .labs-mini-chart > div i {
  width: var(--space-24);
  height: var(--space-4);
  background: var(--panel-navy-600);
}

.page--design-system .interface-preview-card__copy { gap: var(--space-22); }

/* Width only. Height, padding, radius, hover, focus and both surfaces come
   from .button--secondary.button--md, so this card cannot drift from the
   button section. */
.page--design-system .interface-preview-link {
  width: max-content;
  max-width: 100%;
}

.page--design-system .interface-comparison-grid article { border-radius: var(--radius-card); }


.page--design-system .interface-comparison-grid strong { margin-bottom: var(--space-6); }

html[data-surface="light"] > .page--design-system .interface-preview-card,
html[data-surface="light"] > .page--design-system .interface-window {
  background: var(--panel-navy-50);
}

html[data-surface="light"] > .page--design-system .interface-application-rule,
html[data-surface="light"] > .page--design-system .interface-comparison-grid article,
html[data-surface="light"] > .page--design-system .interface-preview-card__head {
  background: var(--geistface-grey-25);
}

[data-skillprint-page^="portal-"] .primary-nav { align-items: center; gap: var(--space-2); }

.page--design-system .trait-module .button-group__item[data-filter="mood"].is-active {
  background: var(--core-lime-500);
  color: var(--deep-navy-900);
}

.page--design-system .trait-module .button-group__item[data-filter="cognition"].is-active {
  background: var(--personality-mint-500);
  color: var(--deep-navy-900);
}

.page--design-system .trait-module .button-group__item[data-filter="personality"].is-active {
  background: var(--personality-blue-500);
  color: var(--deep-navy-900);
}

.page--design-system .trait-module .trait-pillar {
  min-height: var(--space-22);
  padding: var(--space-2) var(--space-6);
  background: var(--panel-950);
  color: var(--text);
  border: 1px solid color-mix(in srgb, var(--pillar, var(--mood-500)) 48%, var(--panel-700));
  border-radius: var(--radius-full);
  font-family: var(--font-ui);
  font-size: var(--text-xxs-font-size);
  line-height: var(--text-xxs-line-height);
  font-weight: var(--weight-medium);
  letter-spacing: 0;
  /* Mood, Cognition and Personality are the dimensions' names, so the card
     prints them as names. The pill used to force them lowercase, which meant
     capitalising the string the script writes changed nothing on screen -
     the casing has to be right in both places or neither. */
  text-transform: none;
  box-shadow: none;
}

.page--design-system .trait-module .guidance.good {
  border-color: color-mix(in srgb, var(--core-green-500) 34%, transparent);
}

.page--design-system .trait-module .guidance.bad {
  border-color: color-mix(in srgb, var(--skills-pink-500) 34%, transparent);
}

/* ---------------------------------------------------------------------------
   38 · Forms

   Account entry, ported from the Figma library. Everything inside a form is
   already a shared component - field, button, choice control, icon frame,
   badge - so the only new geometry is the canvas a form sits on, the shell
   that centres it, and the six small pieces the flows need: the "or" rule, the
   password reveal, the verification code row, the requirement list, the step
   indicators and the arrangement diagram.
   -------------------------------------------------------------------------- */

.page--design-system .auth-subsection { margin-top: var(--space-80); }

.page--design-system .auth-subsection-head {
  align-items: end;
  margin-bottom: var(--space-22);
}

.page--design-system .auth-subsection-head p {
  max-width: var(--space-560);
  text-align: right;
}

/* The canvas is the product page behind the form: one flat surface, a faint
   construction grid and a vignette that fades the grid out before it reaches
   the form sitting on it. */
.page--design-system .auth-canvas {
  --auth-canvas-surface: var(--surface-canvas);
  --auth-canvas-line   : color-mix(in srgb, var(--surface-text) 8%, transparent);
  --auth-canvas-cell   : var(--space-48);

  position: relative;
  display: grid;
  place-items: center;
  padding: var(--space-48) var(--space-32);
  overflow: hidden;
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-card);
  background: var(--auth-canvas-surface);
}

.page--design-system .auth-canvas::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image:
    radial-gradient(
      130% 110% at 50% 32%,
      transparent 20%,
      var(--auth-canvas-surface) 74%
    ),
    linear-gradient(to right, var(--auth-canvas-line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--auth-canvas-line) 1px, transparent 1px);
  background-size:
    100% 100%,
    var(--auth-canvas-cell) var(--auth-canvas-cell),
    var(--auth-canvas-cell) var(--auth-canvas-cell);
  pointer-events: none;
}

/* Both the grid and the form are positioned, so paint order is tree order and
   the form always sits above its own backdrop. */
.page--design-system .auth-canvas > * { position: relative; }

/* Concentric rings replace the grid on the surfaces that lead with the mark. */
.page--design-system .auth-canvas--rings::before {
  background-image:
    radial-gradient(
      130% 110% at 50% 30%,
      transparent 14%,
      var(--auth-canvas-surface) 70%
    ),
    repeating-radial-gradient(
      circle at 50% 30%,
      transparent 0 calc(var(--space-56) - 1px),
      var(--auth-canvas-line) calc(var(--space-56) - 1px) var(--space-56)
    );
  background-size: 100% 100%, 100% 100%;
}

.page--design-system .auth-canvas--tile {
  --auth-canvas-cell: var(--space-32);

  padding: var(--space-32) var(--space-24);
}

.page--design-system .auth-canvas--flush {
  place-items: stretch;
  padding: 0;
}

/* ---------------------------------------------------------------------------
   The shell

   One column, one width. Every arrangement in the family - centred, boxed,
   split, railed - is this shell placed differently on the canvas.
   -------------------------------------------------------------------------- */

.page--design-system .auth-shell {
  --auth-shell-width: var(--space-384);

  width: min(100%, var(--auth-shell-width));
  display: grid;
  gap: var(--space-32);
}

.page--design-system .auth-shell--boxed {
  padding: var(--space-32);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-card);
  background: var(--surface-box);
  box-shadow: var(--shadow-md);
}

.page--design-system .auth-shell__head {
  display: grid;
  justify-items: center;
  gap: var(--space-12);
  text-align: center;
}

.page--design-system .auth-shell__head--start {
  justify-items: start;
  text-align: start;
}

.page--design-system .auth-shell__title {
  margin: 0;
  color: var(--surface-text);
  font-family: var(--skillprint-font-ui);
  font-size: var(--display-sm-font-size);
  line-height: var(--display-sm-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.02em;
}

.page--design-system .auth-shell--compact .auth-shell__title {
  font-size: var(--display-xs-font-size);
  line-height: var(--display-xs-line-height);
}

.page--design-system .auth-shell__subtitle {
  margin: 0;
  color: var(--surface-text-muted);
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-md-font-size);
  line-height: var(--text-md-line-height);
}

.page--design-system .auth-shell__foot {
  margin: 0;
  color: var(--surface-text-muted);
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
  text-align: center;
}

/* The brand mark that opens the centred arrangements. It uses the shared
   dark/light lockup pair, so it must not pick up the navigation lockup's
   fixed width - that rule needs the .brand-logo-set wrapper, which this is
   deliberately not. */
.page--design-system .auth-brandmark {
  width: var(--space-40);
  display: grid;
  justify-items: center;
}

.page--design-system .auth-brandmark .brand-logo {
  width: 100%;
  height: auto;
}

/* ---------------------------------------------------------------------------
   Form body

   A 20px stack of fields, a 12px stack of actions, and one meta row that keeps
   "remember me" and "forgot password" on the same optical line.
   -------------------------------------------------------------------------- */

/* The shared button group, stretched to the form's measure and split into
   equal halves so neither mode reads as the default. */
.page--design-system .auth-switch {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  width: 100%;
}

.page--design-system .auth-form {
  display: grid;
  gap: var(--space-20);
}

.page--design-system .auth-actions {
  display: grid;
  gap: var(--space-12);
}

.page--design-system .auth-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-12);
  flex-wrap: wrap;
}

/* The password field carries a reveal control inside the control box, so the
   input and the affix share one positioning context rather than the whole
   field, which also owns the label and the helper line. */
.page--design-system .auth-affix {
  position: relative;
  display: grid;
}

/* The wrapper carries .field__control--trailing, which is Part A's contract for
   reserving the affix gutter. On this page that rule loses: the page's own
   ".page--design-system .field input" reset re-declares the padding shorthand
   at a higher specificity, so the gutter has to be restated here. */
.page--design-system .auth-affix.field__control--trailing input {
  padding-inline-end: var(--space-40);
}

.page--design-system .auth-reveal {
  position: absolute;
  inset-block: 0;
  inset-inline-end: var(--space-6);
  width: var(--space-32);
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--surface-text-muted);
  cursor: pointer;
  transition: var(--transition-interactive);
}

.page--design-system .auth-reveal:hover { color: var(--surface-text); }

.page--design-system .auth-reveal:focus-visible {
  outline: 0;
  color: var(--surface-text);
  box-shadow:
    0 0 0 var(--focus-ring-width) var(--focus-inner),
    0 0 0 calc(var(--focus-ring-width) * 2) var(--focus-default);
}

/* ---------------------------------------------------------------------------
   Federated entry

   A full-width secondary button per provider, separated from the primary path
   by a labelled rule. The provider mark is a slot: third-party artwork drops
   in unmodified, and the design system ships a monogram in its place.
   -------------------------------------------------------------------------- */

.page--design-system .auth-divider {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  align-items: center;
  gap: var(--space-12);
  color: var(--surface-text-muted);
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-xxs-font-size);
  line-height: var(--text-xxs-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.page--design-system .auth-divider::before,
.page--design-system .auth-divider::after {
  content: "";
  height: 1px;
  background: var(--surface-border);
}

.page--design-system .auth-provider { --button-gap: var(--space-8); }

.page--design-system .auth-provider__mark {
  width: var(--space-20);
  height: var(--space-20);
  flex: 0 0 var(--space-20);
  display: grid;
  place-items: center;
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-xs);
  background: var(--surface-raised);
  color: var(--surface-text);
  font-family: var(--font-mono);
  font-size: var(--text-xxs-font-size);
  line-height: 1;
  font-weight: var(--weight-bold);
}

/* ---------------------------------------------------------------------------
   Verification code

   Four independent single-character controls - the digit count is markup, not
   a token, and four is what fits the 384px measure. Filled, active and error
   are the only three states, and each is an attribute so the row renders
   correctly before any script runs.
   -------------------------------------------------------------------------- */

.page--design-system .auth-code {
  display: flex;
  justify-content: center;
  gap: var(--space-10);
}

.page--design-system .auth-code__digit {
  width: var(--space-56);
  height: var(--space-64);
  min-width: 0;
  padding: 0;
  border: 1px solid var(--surface-border-hover);
  border-radius: var(--radius-card);
  background: var(--surface-box);
  color: var(--surface-text-muted);
  font-family: var(--skillprint-font-ui);
  font-size: var(--display-sm-font-size);
  line-height: var(--display-sm-line-height);
  font-weight: var(--weight-medium);
  text-align: center;
  outline: 0;
  transition: var(--transition-interactive);
}

.page--design-system .auth-code__digit[data-filled] {
  border-color: var(--mindset-violet-400);
  color: var(--violet);
}

.page--design-system .auth-code__digit[data-active],
.page--design-system .auth-code__digit:focus-visible {
  border-color: var(--mindset-violet-400);
  color: var(--violet);
  box-shadow:
    0 0 0 var(--space-4)
      color-mix(
        in srgb,
        var(--mindset-violet-400) 24%,
        transparent
      );
}

.page--design-system .auth-code[data-state="error"] .auth-code__digit {
  border-color: var(--skills-pink-500);
  color: var(--skills-pink-500);
  box-shadow: none;
}

/* The error row drops the violet ring, so it has to re-state focus in pink.
   Without this the higher-specificity rule above wins on :focus-visible too
   and the row has no focus indicator at all, since the digit sets outline: 0. */
.page--design-system .auth-code[data-state="error"] .auth-code__digit[data-active],
.page--design-system .auth-code[data-state="error"] .auth-code__digit:focus-visible {
  box-shadow:
    0 0 0 var(--space-4)
      color-mix(
        in srgb,
        var(--skills-pink-500) 24%,
        transparent
      );
}

/* ---------------------------------------------------------------------------
   Password requirements

   The rule list is never a validation message. It states the requirement up
   front and marks each line as it is satisfied.
   -------------------------------------------------------------------------- */

.page--design-system .auth-rules {
  display: grid;
  gap: var(--space-8);
}

.page--design-system .auth-rule {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  color: var(--surface-text-muted);
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

.page--design-system .auth-rule__icon {
  width: var(--space-16);
  height: var(--space-16);
  flex: 0 0 var(--space-16);
  display: grid;
  place-items: center;
  border-radius: var(--radius-full);
  background: var(--surface-raised);
  color: var(--surface-text-muted);
}

.page--design-system .auth-rule[data-state="met"] { color: var(--surface-text); }

.page--design-system .auth-rule[data-state="met"] .auth-rule__icon {
  background: var(--core-green-500);
  color: var(--deep-navy-900);
}

.page--design-system .auth-rule[data-state="unmet"] .auth-rule__icon {
  background: color-mix(in srgb, var(--skills-pink-500) 22%, transparent);
  color: var(--skills-pink-500);
}

/* ---------------------------------------------------------------------------
   Progress

   Three indicators for three densities: a connected track when the steps are
   named elsewhere, plain dots when the copy already says where you are, and a
   rail when each step needs its own title and hint.
   -------------------------------------------------------------------------- */

.page--design-system .auth-track {
  display: flex;
  align-items: center;
  justify-content: center;
}

.page--design-system .auth-track__node {
  width: var(--space-18);
  height: var(--space-18);
  flex: 0 0 var(--space-18);
  display: grid;
  place-items: center;
  border: 1px solid var(--surface-border-hover);
  border-radius: var(--radius-full);
  background: var(--surface-box);
  color: var(--surface-text-muted);
}

.page--design-system .auth-track__link {
  width: var(--space-40);
  height: 2px;
  flex: 0 1 var(--space-40);
  background: var(--surface-border-hover);
}

.page--design-system .auth-track__node[data-state="complete"] {
  border-color: var(--mindset-violet-500);
  background: var(--mindset-violet-500);
  color: var(--white);
}

.page--design-system .auth-track__node[data-state="current"] {
  border-color: var(--mindset-violet-500);
  color: var(--mindset-violet-500);
  box-shadow:
    0 0 0 var(--space-4)
      color-mix(
        in srgb,
        var(--mindset-violet-500) 22%,
        transparent
      );
}

.page--design-system .auth-track__node[data-state="current"]::before {
  content: "";
  width: var(--space-8);
  height: var(--space-8);
  border-radius: var(--radius-full);
  background: var(--mindset-violet-500);
}

.page--design-system .auth-track__link[data-state="complete"] {
  background: var(--mindset-violet-500);
}

.page--design-system .auth-dots {
  display: flex;
  justify-content: center;
  gap: var(--space-8);
}

.page--design-system .auth-dot {
  width: var(--space-8);
  height: var(--space-8);
  border-radius: var(--radius-full);
  background: var(--surface-border-hover);
}

.page--design-system .auth-dot[data-state="complete"] {
  background: color-mix(in srgb, var(--mindset-violet-500) 46%, transparent);
}

.page--design-system .auth-dot[data-state="current"] {
  background: var(--mindset-violet-500);
}

.page--design-system .auth-rail {
  display: grid;
  align-content: start;
  gap: 0;
}

.page--design-system .auth-rail__step {
  position: relative;
  display: grid;
  grid-template-columns: var(--space-40) minmax(0, 1fr);
  align-items: start;
  gap: var(--space-16);
  padding-bottom: var(--space-24);
}

.page--design-system .auth-rail__step:last-child { padding-bottom: 0; }

.page--design-system .auth-rail__step::before {
  content: "";
  position: absolute;
  top: var(--space-44);
  bottom: var(--space-4);
  left: calc(var(--space-20) - 1px);
  width: 2px;
  background: var(--surface-border);
}

.page--design-system .auth-rail__step:last-child::before { content: none; }

.page--design-system .auth-rail__step[data-state="complete"]::before {
  background: var(--mindset-violet-500);
}

.page--design-system .auth-rail__step[data-state="complete"] .sp-icon-frame {
  border-color: var(--mindset-violet-500);
  background: var(--mindset-violet-500);
  color: var(--white);
}

.page--design-system .auth-rail__step[data-state="current"] .sp-icon-frame {
  border-color: var(--mindset-violet-400);
  background: color-mix(in srgb, var(--mindset-violet-500) 14%, transparent);
  color: var(--violet);
}

.page--design-system .auth-rail__title {
  display: block;
  color: var(--surface-text);
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
  font-weight: var(--weight-semibold);
}

.page--design-system .auth-rail__hint {
  display: block;
  color: var(--surface-text-muted);
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

/* ---------------------------------------------------------------------------
   Split and railed arrangements

   The wide arrangements pair the shell with a second region: a testimonial, a
   product still, or the step rail. The regions share the canvas border so the
   pair reads as one screen.
   -------------------------------------------------------------------------- */

.page--design-system .auth-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  min-height: var(--space-560);
}

.page--design-system .auth-split--rail {
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
}

/* One rule between the panes, whichever side the aside is on. */
.page--design-system .auth-split > * + * {
  border-inline-start: 1px solid var(--surface-border);
}

.page--design-system .auth-split__pane {
  display: grid;
  place-items: center;
  padding: var(--space-48) var(--space-40);
}

.page--design-system .auth-split__pane--aside {
  position: relative;
  overflow: hidden;
  background: var(--surface-raised);
}

.page--design-system .auth-split__pane--brand {
  align-content: space-between;
  align-items: start;
  justify-items: stretch;
  gap: var(--space-40);
}

.page--design-system .auth-quote {
  display: grid;
  justify-items: center;
  gap: var(--space-24);
  max-width: var(--space-384);
  text-align: center;
}

.page--design-system .auth-quote__copy {
  margin: 0;
  color: var(--surface-text);
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-xl-font-size);
  line-height: var(--text-xl-line-height);
  font-weight: var(--weight-medium);
  letter-spacing: -0.01em;
}

.page--design-system .auth-quote__stars {
  display: flex;
  gap: var(--space-2);
  color: var(--skills-orange-500);
}

/* ---------------------------------------------------------------------------
   Arrangement diagram

   The four arrangements are shown as plans rather than shrunken screens: at
   thumbnail size the only readable information is where the form sits and what
   surrounds it, which is exactly what the choice is about.
   -------------------------------------------------------------------------- */

.page--design-system .auth-plan {
  position: relative;
  display: grid;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-compact);
  background: var(--surface-canvas);
}

.page--design-system .auth-plan--split { grid-template-columns: 1fr 1fr; }

.page--design-system .auth-plan--rail { grid-template-columns: 0.8fr 1.2fr; }

.page--design-system .auth-plan__region {
  display: grid;
  place-items: center;
  padding: var(--space-16);
}

.page--design-system .auth-plan > * + * {
  border-inline-start: 1px solid var(--surface-border);
}

.page--design-system .auth-plan__region--muted {
  background: var(--surface-raised);
}

.page--design-system .auth-plan__form {
  width: min(100%, var(--space-112));
  display: grid;
  gap: var(--space-6);
}

.page--design-system .auth-plan__form--boxed {
  width: min(100%, var(--space-128));
  padding: var(--space-12);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-sm);
  background: var(--surface-box);
  box-shadow: var(--shadow-sm);
}

.page--design-system .auth-plan__bar {
  height: var(--space-6);
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--surface-text) 16%, transparent);
}

.page--design-system .auth-plan__bar--title {
  width: 66%;
  height: var(--space-8);
  justify-self: center;
  margin-bottom: var(--space-4);
  background: color-mix(in srgb, var(--surface-text) 38%, transparent);
}

.page--design-system .auth-plan__bar--start { justify-self: start; }

.page--design-system .auth-plan__bar--action { background: var(--mindset-violet-500); }

.page--design-system .auth-plan__steps {
  display: grid;
  gap: var(--space-8);
  align-content: center;
  width: 100%;
}

.page--design-system .auth-plan__step {
  display: grid;
  grid-template-columns: var(--space-10) minmax(0, 1fr);
  align-items: center;
  gap: var(--space-8);
}

.page--design-system .auth-plan__node {
  width: var(--space-10);
  height: var(--space-10);
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--surface-text) 18%, transparent);
}

.page--design-system .auth-plan__node[data-state="current"] {
  background: var(--mindset-violet-500);
}

/* ---------------------------------------------------------------------------
   Anatomy list

   Numbered parts, keyed to the annotated example beside it.
   -------------------------------------------------------------------------- */

.page--design-system .auth-anatomy,
.page--design-system .footer-anatomy {
  display: grid;
  gap: var(--space-14);
  align-content: start;
}

.page--design-system .auth-anatomy__item,
.page--design-system .footer-anatomy__item {
  display: grid;
  grid-template-columns: var(--space-24) minmax(0, 1fr);
  align-items: start;
  gap: var(--space-12);
}

.page--design-system .auth-anatomy__number,
.page--design-system .footer-anatomy__number {
  width: var(--space-24);
  height: var(--space-24);
  display: grid;
  place-items: center;
  border: 1px solid color-mix(in srgb, var(--mindset-violet-500) 42%, transparent);
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--mindset-violet-500) 12%, transparent);
  color: var(--violet);
  font-family: var(--font-mono);
  font-size: var(--text-xxs-font-size);
  line-height: 1;
  font-weight: var(--weight-bold);
}

/* Light mode keeps the same recipe; only the canvas needs a touch more
   contrast against the page, which sits on the same white. */
html[data-surface="light"] > .page--design-system .auth-canvas {
  --auth-canvas-surface: var(--geistface-grey-25);
}

html[data-surface="light"] > .page--design-system .auth-track__node[data-state="complete"],
html[data-surface="light"] > .page--design-system .auth-rail__step[data-state="complete"] .sp-icon-frame {
  color: var(--geistface-grey-25);
}

html[data-surface="light"] > .page--design-system .auth-rule[data-state="met"] .auth-rule__icon {
  color: var(--core-green-900);
}

/* Small bold numerals: violet 400 on the light tint lands near 2.9:1, so the
   light theme steps down the ramp rather than lightening the tint. */
html[data-surface="light"] > .page--design-system .auth-anatomy__number,
html[data-surface="light"] > .page--design-system .footer-anatomy__number {
  border-color: color-mix(in srgb, var(--mindset-violet-700) 38%, transparent);
  color: var(--mindset-violet-700);
}

/* ==========================================================================
   09 - Unique composition · Documentation Application
   ========================================================================== */

/* Token contract shared by both documentation applications. They differ only
   in shell metrics (sidebar/topbar/rail widths), which stay on each page
   below; every colour, radius and surface value is one contract. */
.page--docs-v2 {
  --subtle: color-mix(in srgb, var(--geistface-grey-200) 48%, transparent);
  --border-hover: color-mix(in srgb, var(--deep-navy-25) 27%, transparent);
  --violet-400: var(--mindset-violet-400);
  min-width: var(--space-320);
  background: var(--deep);
}


html:has(> .page--docs-v2)[data-theme="light"] {
  --deep: var(--deep-navy-25);
  --panel-950: var(--panel-navy-50);
  --panel-900: var(--geistface-grey-25);
  --panel-800: var(--geistface-grey-100);
  --panel-700: var(--panel-navy-100);
  --panel-600: var(--geistface-grey-300);
  --text: var(--geistface-grey-800);
  --text-strong: var(--deep-navy-900);
  --muted: color-mix(in srgb, var(--geistface-grey-800) 72%, transparent);
  --subtle: color-mix(in srgb, var(--geistface-grey-800) 56%, transparent);
  --border: color-mix(in srgb, var(--deep-navy-900) 11%, transparent);
  --border-strong: color-mix(in srgb, var(--deep-navy-900) 20%, transparent);
  --border-hover: color-mix(in srgb, var(--deep-navy-900) 30%, transparent);
}


.page--design-system .library-module [hidden] { display: none !important; }
.page--docs-v2 .app,
.page--documentation-landing .app { min-height: 100vh; }

/* The skip link. Parked above the viewport and slid in on focus, so it reaches
   keyboard users without showing as stray text for everyone else. Shared by
   every surface that carries one, the marketing site included. */
.page--docs-v2 .skip-link,
.page--site .skip-link,
.page--adaptive-assist .skip-link {
  position: fixed;
  left: var(--space-16);
  top: var(--space-12);
  z-index: 500;
  padding: var(--space-10) var(--space-14);
  background: var(--text-strong);
  color: var(--deep);
  transform: translateY(-150%);
  transition: transform var(--duration-fast) ease;
}

.page--docs-v2 .skip-link:focus,
.page--site .skip-link:focus,
.page--adaptive-assist .skip-link:focus { transform: translateY(0); }


.page--docs-v2 .brand-mark { color: var(--violet-400); }

/* The sidebar carries the full developer lockup, so the wordmark comes from the
   logo asset rather than being typed alongside a mark. */
.page--docs-v2 .brand-row .brand-logo {
  width: var(--space-128);
}

.page--docs-v2 .sidebar-close { display: none; }
.page--docs-v2 .brand-word { font-weight: 650; letter-spacing: -0.02em; }

.page--docs-v2 .sidebar-search {
  padding: 0 var(--space-10);
  transition: border-color var(--duration-fast), background var(--duration-fast);
}


.page--docs-v2 .sidebar-search:hover {
  border-color: var(--border-hover);
  background: var(--panel-800);
}

.page--docs-v2 .sidebar-search kbd {
  padding: 1px var(--space-6);
  border-radius: var(--radius-xs);
  font: 500 10px/17px var(--font-mono);
}

.page--docs-v2 .nav-scroll {
  flex: 1;
  overflow: auto;
}


.page--docs-v2 .nav-group:first-child { margin-top: 0; }

.page--docs-v2 .nav-title {
  margin: 0 var(--space-10) var(--space-6);
  color: var(--subtle);
  font: 600 var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono);
  letter-spacing: 0.08em;
}


.page--docs-v2 .version-select { padding: 0 var(--space-10); }

.page--docs-v2 .version-select:hover {
  color: var(--text-strong);
  border-color: var(--border-hover);
}

.page--docs-v2 .sidebar-meta { margin-top: var(--space-10); font: 500 var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono); }
.page--docs-v2 .mobile-header { display: none; }
.page--docs-v2 .main { margin-left: var(--sidebar); }

/* The documentation topbar. */
.page--docs-v2 .topbar {
  position: sticky;
  top: 0;
  z-index: 80;
  min-height: var(--topbar);
  padding: 0 var(--space-28);
  background: color-mix(in srgb, var(--deep) 92%, transparent);
  -webkit-backdrop-filter: blur(16px);
  backdrop-filter: blur(16px);
}

.page--docs-v2 .sp-breadcrumb { font-size: var(--text-xs-font-size); line-height: var(--text-xs-line-height); }
.page--docs-v2 .sp-breadcrumb__list { flex-wrap: nowrap; }
.page--docs-v2 .sp-breadcrumb__item { flex: 0 1 auto; }
.page--docs-v2 .sp-breadcrumb__item:last-child { flex: 1 1 auto; }
.page--docs-v2 .sp-breadcrumb__current { color: var(--text-strong); font-weight: var(--weight-semibold); }

.page--docs-v2 .status-dot {
  width: var(--space-6);
  height: var(--space-6);
  background: var(--green);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--green) 12%, transparent);
}


/* Shared documentation typography. The two applications differ only in
   measure and in the utilities their markup already carries. */
.page--docs-v2 .eyebrow-chip {
  min-height: var(--space-22);
  border: 1px solid color-mix(in srgb, var(--violet-400) 45%, var(--border));
  background: color-mix(in srgb, var(--violet-400) 10%, transparent);
}


.page--docs-v2 .lead {
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
}


.page--docs-v2 .hero-meta i {
  width: var(--space-4);
  height: var(--space-4);
  background: var(--violet-400);
}


/* Section rhythm is shared by both documentation applications; only the
   vertical padding and the head measure genuinely differ. */
.page--docs-v2 .section { scroll-margin-top: calc(var(--topbar) + 28px); }

.page--docs-v2 .section:first-child { border-top: 0; padding-top: 0; }


.page--docs-v2 .quick-number {
  color: var(--docs-violet-text);
  font: 650 var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono);
}

.page--docs-v2 .quick-card:hover .quick-arrow {
  border-color: var(--violet-400);
  color: var(--violet-400);
}


/* Callouts are the Part A alert: info takes the developer family's violet,
   a design note keeps the dashed edge that marks copy still to confirm. */
.page--docs-v2 .sp-alert { --alert-accent: var(--mindset-violet-400); }
.page--docs-v2 .sp-alert--draft { --alert-accent: var(--personality-blue-500); border-style: dashed; }
html:is([data-theme="light"], [data-surface="light"]) .page--docs-v2 .sp-alert { --alert-accent: var(--mindset-violet-600); }
html:is([data-theme="light"], [data-surface="light"]) .page--docs-v2 .sp-alert--draft { --alert-accent: var(--personality-blue-600); }
.page--docs-v2 .sp-alert__text a { color: var(--docs-violet-text); }


.page--docs-v2 .inline-code {
  padding: var(--space-2) var(--space-6);
  border-radius: var(--radius-xs);
  font: 500 11px/17px var(--font-mono);
}


.page--design-system .interface-comparison-grid span { font: 600 9px/15px var(--font-mono); letter-spacing: 0.09em; }


/* The bullet dot on the documentation fact list. */
.page--docs-v2 .fact-list li::before {
  content: "";
  width: var(--space-4);
  height: var(--space-4);
  margin-top: var(--space-8);
  border-radius: 50%;
  background: var(--violet-400);
}


/* One data-table contract for both documentation applications; only the
   minimum table width differs. */
.page--docs-v2 .table-wrap { overflow: auto; }
.page--docs-v2 table { border-collapse: collapse; }

.page--docs-v2 :is(td, th) { padding: var(--space-12) var(--space-14); vertical-align: top; }
.page--docs-v2 th { font: 600 var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono); letter-spacing: 0.08em; }
.page--docs-v2 td { font-size: var(--text-sm-font-size); line-height: var(--text-sm-line-height); }


/* One table-of-contents contract for both documentation applications.
   Only the sticky offset differs. */
.page--docs-v2 .toc {
  position: sticky;
  align-self: start;
}

.page--docs-v2 .toc { top: calc(var(--topbar) + var(--space-24)); }

.page--docs-v2 .toc-title { margin-bottom: var(--space-10); font: 600 var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono); letter-spacing: 0.08em; }
.page--docs-v2 .toc-links { border-left: 1px solid var(--border); }

.page--docs-v2 .toc-link {
  padding: var(--space-4) 0 var(--space-4) var(--space-14);
  color: var(--muted);
  font-size: var(--text-xs-font-size);
  text-decoration: none;
  border-left: 1px solid transparent;
  margin-left: -1px;
}

.page--docs-v2 .toc-link.is-active,
.page--docs-v2 .toc-link:hover {
  color: var(--text-strong);
  border-left-color: var(--violet-400);
}

.page--docs-v2 .toc-card p { margin: var(--space-4) 0 var(--space-10); }
.page--docs-v2 .toc-card a { display: inline-flex; align-items: center; gap: var(--space-6); color: var(--docs-violet-text); font-weight: 600; }

/* The documentation command-palette overlay. */
.page--docs-v2 .search-overlay {
  position: fixed;
  z-index: 300;
  display: none;
  place-items: start center;
  padding: 11vh var(--space-22) var(--space-22);
  background: color-mix(in srgb, var(--deep-navy-950) 76%, transparent);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
}

.page--docs-v2 .search-dialog {
  width: min(680px, 100%);
  border-radius: var(--radius-3xl);
  box-shadow: 0 32px 90px color-mix(in srgb, var(--deep-navy-950) 40%, transparent);
}

/* One search field across both documentation applications; the dialog
   header height is the only real difference. */
.page--docs-v2 .search-input-wrap { padding: 0 var(--space-18); }


.page--docs-v2 .search-input {
  flex: 1;
  outline: 0;
  border: 0;
  background: transparent;
  font-size: var(--text-md-font-size);
}

.page--docs-v2 .search-input::placeholder { color: var(--subtle); }
.page--docs-v2 .search-input::-webkit-search-cancel-button { display: none; }

.page--docs-v2 .esc-key {
  padding: var(--space-2) var(--space-6);
  border-radius: var(--radius-sm);
  font: 500 var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono);
}

.page--docs-v2 .search-results {
  max-height: 440px;
  overflow: auto;
  padding: var(--space-10);
}

.page--docs-v2 .search-result {
  width: 100%;
  gap: var(--space-10);
  border: 1px solid transparent;
  border-radius: var(--radius-lg);
  background: 0 0;
  color: inherit;
  text-align: left;
}


.page--docs-v2 .search-result:hover,
.page--docs-v2 .search-result.is-active {
  background: var(--panel-900);
  border-color: var(--border);
}

.page--docs-v2 .search-result.is-active { border-color: var(--nav-current-border); background: var(--nav-current-surface); }
.page--docs-v2 .search-result .method { justify-self: center; }

.page--docs-v2 .result-icon {
  place-items: center;
  border-radius: var(--radius-md);
  background: var(--panel-800);
  color: var(--violet-400);
}

/* The secondary meta line under a result or tooltip title. */
.page--docs-v2 .search-result > span:last-child > span,
.page--partner-analysis .chart-tooltip span,
.page--partner-analysis .moment-panel-head span {
  display: block;
  margin-top: var(--space-2);
  color: var(--muted);
  font-size: var(--text-xxs-font-size);
}

.page--docs-v2 .no-results {
  padding: var(--space-42) var(--space-20);
  color: var(--muted);
  text-align: center;
  font-size: var(--text-xs-font-size);
}

/* The shared documentation token block sets --subtle and --border-hover on the
   body, so the light overrides set on html lose to it for every descendant.
   Re-declare them on the body element itself, for both applications. */
html:is([data-theme="light"], [data-surface="light"]) > .page--docs-v2 {
  --subtle: color-mix(in srgb, var(--geistface-grey-800) 56%, transparent);
  --border-hover: color-mix(in srgb, var(--deep-navy-900) 30%, transparent);
  color-scheme: light;
}

/* ==========================================================================
   09 - Unique composition · Documentation Landing
   ========================================================================== */

.page--documentation-landing {
  /* The landing page navigates entirely from the top bar - it is an entry
     screen with three destinations, not a reference tree - so it carries a
     bar height and no sidebar column. The two documentation applications
     behind it keep their own --sidebar. */
  --topbar: 72px;
  --deep-navy: var(--deep-navy-900);
  --primary-text: var(--geistface-grey-200);
  --muted-text: color-mix(in srgb, var(--geistface-grey-200) 72%, transparent);
  --quiet-text: color-mix(in srgb, var(--geistface-grey-200) 48%, transparent);
  --action-violet: var(--mindset-violet-500);
  --action-hover: var(--mindset-violet-600);
  --action-active: var(--mindset-violet-700);
  --focus-violet: var(--mindset-violet-400);
  --focus-soft: var(--mindset-violet-200);
  --cognition-mint: var(--personality-mint-500);
  --personality-blue: var(--personality-blue-500);
  --skills-pink: var(--skills-pink-500);
  --skills-orange: var(--skills-orange-500);
  --border-hover: color-mix(in srgb, var(--deep-navy-25) 30%, transparent);
  --shadow-color: 10 13 18;
  --content: 1240px;
  min-width: var(--space-320);
  min-height: 100vh;
  overflow-x: hidden;
  background: var(--deep-navy);
  color: var(--primary-text);
  transition: background-color var(--duration-base) ease, color var(--duration-base) ease;
}

html:has(> .page--documentation-landing)[data-theme="light"] {
  --deep-navy: var(--deep-navy-25);
  --panel-950: var(--panel-navy-50);
  --panel-900: var(--geistface-grey-25);
  --panel-800: var(--geistface-grey-100);
  --panel-700: var(--panel-navy-100);
  --primary-text: var(--geistface-grey-800);
  --muted-text: color-mix(in srgb, var(--geistface-grey-800) 72%, transparent);
  --quiet-text: color-mix(in srgb, var(--geistface-grey-800) 50%, transparent);
  --border: color-mix(in srgb, var(--deep-navy-900) 11%, transparent);
  --border-strong: color-mix(in srgb, var(--deep-navy-900) 20%, transparent);
  --border-hover: color-mix(in srgb, var(--deep-navy-900) 30%, transparent);
  color-scheme: light;
}

html:has(> .page--documentation-landing) { color-scheme: dark; scroll-behavior: smooth; }


.page--documentation-landing ::selection {
  background: var(--focus-soft);
  color: var(--deep-navy-900);
}

.page--documentation-landing :focus-visible {
  outline: 2px solid var(--focus-violet);
  outline-offset: 3px;
}


/* The bar is the only navigation on the page, so its contents sit on the same
   container as the hero and the card grid rather than inside .sp-nav__inner's
   own gutter, which would step them in by a further 32px. */
.page--documentation-landing .header-inner {
  width: min(var(--content), calc(100% - 48px));
  min-height: var(--topbar);
  margin: 0 auto;
  padding-inline: 0;
}

/* Anchor targets clear the sticky bar instead of opening underneath it. */
.page--documentation-landing .feature-section,
.page--documentation-landing .start-panel {
  scroll-margin-top: calc(var(--topbar) + var(--space-16));
}

.page--documentation-landing .brand img { width: 142px; }
.page--documentation-landing .brand-logo--light { display: none !important; }

/* Search moved out of the sidebar and into the bar, so it stops being a
   full-width block and becomes a fixed field that gives the routes room. */
.page--documentation-landing .header-search {
  width: 236px;
  height: var(--space-36);
  min-height: var(--space-36);
  padding: 0 var(--space-10);
  transition: border-color var(--duration-fast), background var(--duration-fast);
}

.page--documentation-landing .header-search:hover {
  border-color: var(--border-hover);
  background: var(--panel-800);
}

.page--documentation-landing .header-search kbd {
  padding: 1px var(--space-6);
  border-radius: var(--radius-xs);
  font: 500 10px/17px var(--font-mono);
}

/* The drawer is placed off the shared container, which is 32px wide here; this
   page's container is 24px, so it would otherwise sit 8px inside the bar. */
.page--documentation-landing .sp-nav__drawer {
  right: max(var(--space-24), calc((100vw - var(--content)) / 2 + var(--space-24)));
}

html:has(> .page--documentation-landing)[data-theme="light"] .brand-logo--dark {
  display: none !important;
}

html:has(> .page--documentation-landing)[data-theme="light"] .brand-logo--light {
  display: block !important;
}

.page--documentation-landing .icon-button { margin-left: var(--space-8); }

/* The header call to action is `.button.button--primary`; its surface, border,
   shadow, radius, type and every interactive state come from that component.
   This page restated all of them at an off-scale 38px height - the values
   were already identical (--action-violet is --button-primary), so the
   duplicate is gone and only the position it sits in remains. */
.page--documentation-landing .header-cta {
  margin-left: var(--space-8);
}

.page--documentation-landing main { isolation: isolate; }
.page--documentation-landing .background-marks { pointer-events: none; z-index: -1; }

.page--documentation-landing .background-marks::before {
  content: "";
  position: absolute;
  width: 420px;
  height: 420px;
  left: calc(50% - 720px);
  top: 130px;
  border: 1px solid color-mix(in srgb, var(--action-violet) 22%, transparent);
  border-radius: 50%;
  opacity: 0.55;
}

.page--documentation-landing .background-marks::after {
  content: "";
  position: absolute;
  width: 270px;
  height: 270px;
  right: calc(50% - 690px);
  top: 250px;
  border: 1px solid color-mix(in srgb, var(--personality-blue) 18%, transparent);
  transform: rotate(18deg);
  opacity: 0.45;
}

.hero--docs-landing {
  width: min(var(--content), calc(100% - 48px));
  margin: 0 auto;
  padding: var(--space-88) 0 var(--space-48);
}

.page--documentation-landing .eyebrow-dot {
  height: var(--space-16);
  border-radius: var(--radius-xs);
  background: var(--action-violet);
  width: var(--space-16);
  min-height: 0;
  margin: 0;
  padding: 0;
  border: 0;
}

.page--documentation-landing .eyebrow-dot svg { width: var(--space-10); height: var(--space-10);
  --icon-stroke: var(--icon-stroke-10);
}
.page--documentation-landing h1 { max-width: 850px; text-wrap: balance; }

/* ---------------------------------------------------------------------------
   Hero and card typography, restored from the approved page.

   The consolidation moved this page onto the shared utility layer, which cost
   it the sizes it was designed around: the title fell back to the display
   scale, the eyebrow picked up the mono/uppercase treatment, the card heading
   rendered at 48px instead of 25px, and the icon stage inherited the panel's
   rounded-square radius. These restore the approved values.
   -------------------------------------------------------------------------- */
.page--documentation-landing #page-title {
  max-width: 850px;
  margin: 0 auto var(--space-18);
  font-size: clamp(42px, 5vw, 64px);
  line-height: 1.03;
  letter-spacing: -0.05em;
  font-weight: var(--weight-medium);
  text-wrap: balance;
}

.page--documentation-landing .eyebrow--pill {
  width: max-content;
  max-width: 100%;
  min-height: var(--space-30);
  margin: 0 auto var(--space-18);
  padding: var(--space-4) var(--space-10) var(--space-4) var(--space-6);
  gap: var(--space-8);
  font-family: var(--font-ui);
  font-size: var(--text-xs-font-size);
  letter-spacing: normal;
  text-transform: none;
}

.page--documentation-landing .feature-grid { gap: var(--space-18); }

.page--documentation-landing .feature-card {
  min-height: 420px;
  padding: var(--space-22);
  border-radius: var(--radius-panel);
}

.page--documentation-landing .feature-card h2 {
  margin-bottom: var(--space-10);
  font-size: var(--display-xs-font-size);
  line-height: var(--display-xs-line-height);
  letter-spacing: -0.025em;
  font-weight: var(--weight-semibold);
}

.page--documentation-landing .card-copy .card-kicker {
  display: block;
  margin-bottom: var(--space-6);
  font-family: var(--font-ui);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.page--documentation-landing .feature-card p {
  font-size: var(--text-sm-font-size);
  line-height: 22px;
}

.page--documentation-landing .icon-stage {
  padding: 0;
  border-radius: var(--radius-full);
}

.page--documentation-landing .card-link {
  gap: var(--space-14);
  font-size: var(--text-sm-font-size);
}

.hero-copy--docs-landing {
  max-width: 690px;
  margin: 0 auto;
  color: var(--muted-text);
  text-wrap: balance;
}

.page--documentation-landing .feature-section {
  width: min(var(--content), calc(100% - 48px));
  margin: 0 auto;
  padding: var(--space-14) 0 var(--space-64);
}

.page--documentation-landing .feature-card { --accent: var(--action-violet); }

.page--documentation-landing .feature-card[data-card="integrate"] {
  --accent: var(--skills-orange);
}

.page--documentation-landing .feature-card[data-card="experience"] { --accent: var(--skills-pink); }

.page--documentation-landing .feature-card[data-card="insight"] {
  --accent: var(--personality-blue);
}

.page--documentation-landing .feature-card:focus-visible {
  outline: 2px solid var(--focus-violet);
  outline-offset: 4px;
}

.page--documentation-landing .feature-card::after {
  content: "";
  position: absolute;
  width: 130px;
  height: 130px;
  right: -68px;
  bottom: -72px;
}

.page--documentation-landing .step-badge i {
  width: var(--space-6);
  height: var(--space-6);
  background: var(--accent);
}

.page--documentation-landing .icon-stage {
  width: 146px;
  height: 146px;
  margin: var(--space-30) auto var(--space-24);
}

.page--documentation-landing .icon-stage::after, .page--documentation-landing .icon-stage::before {
  content: "";
  position: absolute;
  border-radius: 50%;
  background: var(--accent);
  opacity: 0.28;
  transition: opacity var(--duration-base) ease, transform var(--duration-slow) ease;
}

.page--documentation-landing .icon-stage::before {
  width: var(--space-6);
  height: var(--space-6);
  left: var(--space-18);
  top: 26px;
}

.page--documentation-landing .icon-stage::after {
  width: var(--space-4);
  height: var(--space-4);
  right: var(--space-22);
  bottom: var(--space-22);
}

.page--documentation-landing .feature-card:hover .icon-stage::before {
  opacity: 0.9;
  transform: translate(-3px, -3px) scale(1.25);
}

.page--documentation-landing .feature-card:hover .icon-stage::after {
  opacity: 0.7;
  transform: translate(4px, 4px) scale(1.35);
}

.page--documentation-landing .icon-asset {
  inset: var(--space-24);
  width: calc(100% - 50px);
  height: calc(100% - 50px);
  object-fit: contain;
  transition: opacity var(--duration-base) ease, transform var(--duration-slow) cubic-bezier(0.2, 0.8, 0.2, 1);
}

.page--documentation-landing .icon-outline {
  filter: brightness(0) saturate(100%) invert(32%) sepia(81%) saturate(3270%) hue-rotate(238deg) brightness(92%) contrast(103%);
}

.page--documentation-landing .icon-colour { opacity: 0; transform: scale(0.9); }

.page--documentation-landing .feature-card:focus-visible .icon-outline,
.page--documentation-landing .feature-card:hover .icon-outline {
  opacity: 0;
  transform: scale(0.92);
}

.page--documentation-landing .feature-card:focus-visible .icon-colour,
.page--documentation-landing .feature-card:hover .icon-colour {
  opacity: 1;
  transform: scale(1);
}

.page--documentation-landing .feature-card p { margin-bottom: var(--space-22); color: var(--muted-text); }
.page--documentation-landing .card-link { padding-top: var(--space-18); color: var(--primary-text); }

.page--documentation-landing .card-link svg {
  width: var(--space-16);
  height: var(--space-16);
  color: var(--muted-text);
  transition: transform var(--duration-base) ease, color var(--duration-base) ease;
  --icon-stroke: var(--icon-stroke-16);
}

.page--documentation-landing .feature-card:hover .card-link svg {
  transform: translateX(3px);
  color: var(--accent);
}

.page--documentation-landing .start-panel {
  width: min(var(--content), calc(100% - 48px));
  margin: 0 auto var(--space-72);
  grid-template-columns: 1fr auto;
}

.page--documentation-landing .terminal-icon {
  width: var(--space-42);
  height: var(--space-42);
  flex: 0 0 42px;
  border-radius: var(--radius-lg);
  color: var(--cognition-mint);
}
.page--documentation-landing .start-panel strong { margin-bottom: var(--space-2); font-size: var(--text-sm-font-size); }
.page--documentation-landing .start-panel p { color: var(--muted-text); }

.page--documentation-landing .command code {
  min-height: var(--space-42);
  padding: var(--space-10) var(--space-12);
  border-radius: var(--radius-control);
  background: var(--deep-navy-950);
  color: var(--panel-navy-100);
}

html:has(> .page--documentation-landing)[data-theme="light"] .command code {
  background: var(--panel-navy-950);
  color: var(--geistface-grey-200);
}

/* The landing's footer takes the same container as its bar, hero and card
   grid, so .sp-container's own gutter stands down and the row lines up with
   everything above it. */
.page--documentation-landing .sp-footer__inner {
  --grid-padding: 0px;

  width: min(var(--content), calc(100% - 48px));
  margin: 0 auto;
}

html[data-surface="light"] > .page--documentation-landing,
html[data-theme="light"] > .page--documentation-landing {
  color-scheme: light;
}

html[data-surface="light"] > .page--documentation-landing,
html[data-theme="light"] > .page--documentation-landing {
  --deep-navy: var(--deep-navy-25);
  --primary-text: var(--geistface-grey-800);
  --muted-text: color-mix(in srgb, var(--geistface-grey-800) 72%, transparent);
  --quiet-text: color-mix(in srgb, var(--geistface-grey-800) 50%, transparent);
  --border-hover: color-mix(in srgb, var(--deep-navy-900) 30%, transparent);
}

/* ==========================================================================
   09 - Unique composition · Marketing Homepage
   ========================================================================== */

.page--homepage {
  --quiet: color-mix(in srgb, var(--geistface-grey-200) 50%, transparent);
  --border-hover: color-mix(in srgb, var(--deep-navy-25) 20%, transparent);
  --container: 1280px;
  background: var(--deep);
  overflow-x: hidden;
}

html:has(> .page--homepage) {
  scroll-behavior: smooth;
  scroll-padding-top: var(--space-88);
  overflow-x: hidden;
}

.page--homepage a { color: inherit; }
.page--homepage img { max-width: 100%; }
.page--homepage ::selection { background: var(--mindset-violet-200); color: var(--deep); }
.page--homepage :focus-visible { outline: 2px solid var(--mindset-violet-400); outline-offset: 3px; }
.page--design-system .sp-popup-preview-head span,
.page--design-system .panel-surface-rule code,
.page--homepage .live-status { font: 500 var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono); }

.page--homepage .live-status i {
  width: var(--space-6);
  height: var(--space-6);
  background: var(--green);
  animation: homepage-pulse 1.8s infinite;
}

.page--homepage .target {
  width: var(--space-28);
  height: var(--space-28);
  border: 2px solid var(--pink);
  animation: homepage-targetmove 6s ease-in-out infinite;
}

.page--homepage .target::after, .page--homepage .target::before {
  content: "";
  position: absolute;
  background: var(--pink);
}

.page--homepage .target::before {
  width: var(--space-12);
  height: var(--space-2);
  left: var(--space-6);
  top: var(--space-10);
}

.page--homepage .target::after {
  width: var(--space-2);
  height: var(--space-12);
  left: var(--space-10);
  top: var(--space-6);
}

.page--homepage .game-meta {
  left: var(--space-16);
  right: var(--space-16);
  top: var(--space-16);
  font: 500 var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono);
  letter-spacing: 0.06em;
}

.page--homepage .stat { padding: var(--space-22) var(--space-24); border-right: 1px solid var(--border); }
.page--homepage .stat:last-child { border-right: 0; }
.page--homepage .stat strong { font: 500 28px/34px var(--font-mono); letter-spacing: -0.035em; }
.page--homepage .stat span { margin-top: var(--space-4); }


.page--homepage .badge-card { width: 230px; }
.page--homepage .badge-card img { aspect-ratio: 1; object-fit: contain; }
.page--homepage .badge-card span { margin-top: var(--space-8); }

.page--homepage .footer-brand img { width: 170px; }
.page--homepage .footer-brand p { max-width: 360px; margin: var(--space-18) 0 0; }

.page--homepage .reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity var(--duration-slowest) ease, transform var(--duration-slowest) ease;
}

.page--homepage .reveal.is-visible { opacity: 1; transform: none; }


/* ---------------------------------------------------------------------------
   Card padding and heading rhythm, folded in from the marketing stylesheet

   The utility layer resets every heading with
   `.page :where(h1, h2, h3, h4) { margin: 0 }` and the composition never
   restores the per-component margins the approved design carried, so an
   icon tile, its title and its body copy read as one solid block. The
   same markup and the same defect exist on the prototype homepage, so
   these are not scoped to `.page--site`.
   --------------------------------------------------------------------------- */

html:not([data-reveal]) .page--site .reveal {
  opacity: 1;
  transform: none;
}
.page--homepage .section-head h2 { margin-bottom: var(--space-18); }

.page--homepage :is(.hero-copy, .profile-copy, .process-copy, .badge-copy, .cta-copy)
  :is(h1, h2) {
  margin-block: var(--space-18) var(--space-20);
}

/* ==========================================================================
   09 - Unique composition · Labs Benchmark
   ========================================================================== */

/* The benchmark is the meeting point between the marketing site and the
   portal: marketing section rhythm and display type on the outside, portal
   panels, tiles and data rows on the inside. Every colour is a page token
   resolved from the brand ramps; the three chart series own the only
   colours that carry meaning on the page, and each is paired with a shape so
   the chart reads without them. Rules are grouped by section, top to bottom,
   with the responsive tiers at the end on the grid's own breakpoints. */

/* --- Page tokens ---------------------------------------------------------- */

.page--labs-benchmark {
  color-scheme: dark;
  --page: var(--deep-navy-900);
  --surface: var(--panel-navy-950);
  --surface-card: var(--panel-navy-900);
  --surface-raised: var(--panel-navy-800);
  --quiet: color-mix(in srgb, var(--geistface-grey-200) 50%, transparent);
  --series-reasoning: var(--mindset-violet-400);
  --series-vision: var(--personality-blue-500);
  --series-base: var(--geistface-grey-400);
  /* Signal's page accent, the one the marketing site themes AI Labs in. */
  --sp-accent: var(--skills-orange-500);
  --sp-accent-2: var(--skills-pink-500);
  --sp-accent-ink: var(--sp-accent);
  --bench-section: var(--space-96);
  --bench-stack: var(--space-24);
  --bench-panel-pad: var(--space-28);
  --bench-label-col: 220px;
  --bench-value-col: 64px;
  min-width: var(--space-320);
  background: var(--page);
  color: var(--text);
  font-size: var(--text-md-font-size);
  -webkit-font-smoothing: antialiased;
}

html[data-surface="light"] > .page--labs-benchmark,
html[data-theme="light"] > .page--labs-benchmark {
  color-scheme: light;
  --page: var(--deep-navy-25);
  --surface: var(--panel-navy-50);
  --surface-card: var(--geistface-grey-25);
  --surface-raised: var(--geistface-grey-100);
  --quiet: color-mix(in srgb, var(--geistface-grey-800) 50%, transparent);
  --series-reasoning: var(--mindset-violet-500);
  --series-vision: var(--personality-blue-600);
  --series-base: var(--geistface-grey-500);
  --sp-accent-ink: var(--skills-orange-900);
}

html:has(> .page--labs-benchmark) {
  scroll-behavior: smooth;
  scroll-padding-top: var(--space-88);
}

@media (prefers-reduced-motion: reduce) {
  html:has(> .page--labs-benchmark) { scroll-behavior: auto; }
}

.page--labs-benchmark [hidden] { display: none !important; }

.page--labs-benchmark .mono {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

.page--labs-benchmark h1,
.page--labs-benchmark h2,
.page--labs-benchmark h3,
.page--labs-benchmark p { margin-top: 0; }

.page--labs-benchmark .skip-link {
  position: fixed;
  left: var(--space-16);
  top: var(--space-12);
  z-index: 500;
  padding: var(--space-10) var(--space-14);
  border-radius: var(--radius-md);
  background: var(--text-strong);
  color: var(--page);
  font-weight: var(--weight-semibold);
  text-decoration: none;
  transform: translateY(-150%);
  transition: transform var(--duration-fast) ease;
}

.page--labs-benchmark .skip-link:focus { transform: translateY(0); }

/* The one uppercase micro label on the page: hero board, filter context,
   metric tiles, card sections and table headers all share it. */
.page--labs-benchmark .micro-label,
.page--labs-benchmark .leaderboard th,
.page--labs-benchmark .telemetry dt,
.page--labs-benchmark .hero-stat dt,
.page--labs-benchmark .metric-card dt {
  color: var(--muted);
  font: var(--weight-semibold) var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.page--labs-benchmark .micro-label { display: block; }

.page--labs-benchmark .count-chip {
  display: inline-flex;
  align-items: center;
  flex: 0 0 auto;
  min-height: var(--space-28);
  padding: 0 var(--space-10);
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  background: var(--surface-card);
  color: var(--muted);
  font: var(--weight-semibold) var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono);
  letter-spacing: 0.04em;
  white-space: nowrap;
}

/* --- Top bar ---------------------------------------------------------------- */

/* The bar takes the marketing site's measure - the 1560px header container on
   24px gutters, 76px tall, 168px wordmark, 14px routes - while carrying this
   page's own routes, the live badge and the waitlist button. */
/* Fixed over the hero and transparent until the reader scrolls, when it takes
   the blurred page surface - the .scrolled toggle the site's bar uses. Fixed
   from the first paint, so the state change moves nothing. */
.page--labs-benchmark .topbar--benchmark {
  --nav-surface: color-mix(in srgb, var(--page) 88%, transparent);
  position: fixed;
  inset-inline: 0;
  inset-block-start: 0;
  z-index: 100;
  background: transparent;
  border-bottom-color: transparent;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

@media (prefers-reduced-motion: no-preference) {
  .page--labs-benchmark .topbar--benchmark {
    transition: background-color var(--duration-base) var(--ease-standard), border-bottom-color var(--duration-base) var(--ease-standard);
  }
}

.page--labs-benchmark .topbar--benchmark.scrolled {
  background: var(--nav-surface);
  border-bottom-color: var(--nav-border);
  -webkit-backdrop-filter: blur(var(--backdrop-blur-xl));
  backdrop-filter: blur(var(--backdrop-blur-xl));
}

/* The site's row: brand and actions take equal flanks, so the routes sit on
   the centre line whatever the two ends weigh. */
.page--labs-benchmark .sp-nav__inner {
  --grid-container-max: 1560px;
  --grid-padding: var(--space-24);
  min-height: 76px;
  gap: var(--space-32);
}

.page--labs-benchmark .sp-nav__brand,
.page--labs-benchmark .sp-nav__actions { flex: 1 1 0; min-width: 0; }

.page--labs-benchmark .sp-nav__routes { flex: 0 0 auto; }

.page--labs-benchmark .brand { gap: var(--space-12); }

/* Width only: the shared nav rule decides which of the two logos shows. */
.page--labs-benchmark .brand-logo { width: 168px; max-width: 100%; height: auto; }

.page--labs-benchmark .brand-divider {
  width: 1px;
  height: var(--space-22);
  background: var(--border-strong);
}

/* The flask is the AI Labs mark on the site's product menu; beside the
   wordmark it is what makes this page recognisable as that product. */
.page--labs-benchmark .brand-labs {
  display: inline-flex;
  align-items: center;
  gap: var(--space-6);
  color: var(--sp-accent-ink);
  font: var(--weight-semibold) var(--text-xs-font-size)/var(--text-xs-line-height) var(--font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.page--labs-benchmark .brand-labs .sp-icon { --icon-size: var(--icon-size-xs); --icon-stroke: var(--icon-stroke-16); }

.page--labs-benchmark .sp-nav__routes { gap: var(--space-4); }

.page--labs-benchmark .sp-nav__route {
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
  font-weight: var(--weight-medium);
}

.page--labs-benchmark .sp-nav__actions { justify-content: flex-end; gap: var(--space-10); }

.page--labs-benchmark .live-status { --badge-text: var(--text); }

.page--labs-benchmark .live-dot {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--core-green-500) 18%, transparent);
}

/* --- Hero · the site's product hero, in Signal's orange ---------------------- */

/* The website's product hero: a fixed Skills Orange panel with the dots
   texture, Recoleta on the headline, the audience badge and the note, fading
   into the page across its last fifth. The panel stays dark in both themes,
   so its ink is restated here rather than read from the page. The live board
   sits where the site puts its product screenshot. */
.page--labs-benchmark .hero--product {
  --hero-100: var(--skills-orange-100);
  --hero-300: var(--skills-orange-300);
  --hero-400: var(--skills-orange-400);
  --hero-500: var(--skills-orange-500);
  --hero-900: var(--skills-orange-900);
  --hero-950: var(--skills-orange-950);
  --hero-texture: url("../assets/images/website/texture-orange-dots.svg");
  --text: var(--hero-100);
  --text-strong: var(--geistface-grey-25);
  --muted: color-mix(in srgb, var(--hero-100) 74%, transparent);
  --quiet: color-mix(in srgb, var(--hero-100) 84%, transparent);
  --border: color-mix(in srgb, var(--hero-300) 22%, transparent);
  --sp-accent-ink: var(--sp-accent);
  --button-surface: color-mix(in srgb, var(--hero-900) 32%, var(--hero-950));
  --button-surface-hover: color-mix(in srgb, var(--hero-900) 46%, var(--hero-950));
  --button-border: color-mix(in srgb, var(--hero-300) 30%, transparent);
  --button-text: var(--hero-100);
  position: relative;
  min-height: 100svh;
  padding-block: calc(76px + var(--space-40)) var(--space-72);
  display: grid;
  align-items: center;
  color: var(--text);
  background-color: var(--hero-950);
  background-image:
    linear-gradient(to bottom, transparent 78%, var(--page) 100%),
    var(--hero-texture);
  background-repeat: no-repeat, repeat;
  background-size: auto, 120px 120px;
}

/* The fold shares the bar's measure: the 1560px header container on 24px
   gutters, so the badge sits under the wordmark and the board's edge meets
   the theme toggle's. Sections below keep the 1280 content column. */
.page--labs-benchmark .hero--product > .sp-container {
  --grid-container-max: 1560px;
  --grid-padding: var(--space-24);
}

.page--labs-benchmark .hero-split {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(360px, 0.9fr);
  align-items: center;
  gap: var(--space-64);
}

.page--labs-benchmark .audience-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
  margin-bottom: var(--space-24);
  padding: var(--space-6) var(--space-14) var(--space-6) var(--space-12);
  border: 1px solid color-mix(in srgb, var(--sp-accent) 28%, transparent);
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--sp-accent) 10%, transparent);
  color: var(--sp-accent-ink);
  font-family: var(--skillprint-font-mono);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* The flask is the AI Labs mark on the site's product menu; carrying it here
   is what makes the page recognisable as that product. */
.page--labs-benchmark .audience-badge .sp-icon { --icon-size: var(--icon-size-xs); --icon-stroke: var(--icon-stroke-16); }

/* Recoleta on the h1. Only Regular ships, so presence comes from size and
   tracking, never from weight. */
.page--labs-benchmark h1 {
  margin-bottom: var(--space-20);
  color: var(--hero-500);
  font-family: var(--skillprint-font-display);
  font-size: clamp(40px, 5.2vw, 68px);
  line-height: 1.06;
  font-weight: var(--weight-regular);
  letter-spacing: -0.015em;
  text-wrap: balance;
}

.page--labs-benchmark h1 .text-positive { color: var(--hero-300); }

.page--labs-benchmark .hero-lede {
  max-width: 60ch;
  margin: 0;
  color: var(--muted);
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
  text-wrap: pretty;
}

.page--labs-benchmark .hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-12);
  margin-top: var(--space-32);
}

.page--labs-benchmark .hero-note {
  margin: var(--space-24) 0 0;
  color: var(--quiet);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

.page--labs-benchmark .hero-note a {
  color: var(--hero-300);
  font-weight: var(--weight-medium);
  text-decoration: none;
  transition: var(--transition-interactive);
}

.page--labs-benchmark .hero-note a:hover { color: var(--hero-100); text-decoration: underline; text-underline-offset: 3px; }

.page--labs-benchmark .hero-note__dot {
  display: inline-block;
  width: var(--space-6);
  height: var(--space-6);
  margin-inline-end: var(--space-10);
  border-radius: 50%;
  background: var(--hero-500);
  vertical-align: middle;
}

/* The way down: label over the arrow, centred on the fold. It sits on the
   faded end of the panel, so it takes the page's ink rather than the hero's. */
.page--labs-benchmark .hero-more {
  position: absolute;
  inset-block-end: var(--space-16);
  inset-inline: 0;
  width: fit-content;
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-6);
  padding: var(--space-6) var(--space-4);
  color: var(--text-muted);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-medium);
  letter-spacing: 0.01em;
  text-decoration: none;
  transition: color var(--duration-fast) var(--ease-standard);
}

.page--labs-benchmark .hero-more:hover { color: var(--hero-300); }
.page--labs-benchmark .hero-more span { font-family: var(--skillprint-font-mono); font-size: var(--text-sm-font-size); line-height: 1; }

/* The live board: a portal panel in the hero, so the first thing beside the
   headline is a real result rather than a promise. */
.page--labs-benchmark .hero-board {
  color-scheme: dark;
  --surface: var(--panel-navy-950);
  --surface-card: var(--panel-navy-900);
  --surface-raised: var(--panel-navy-800);
  --text: var(--geistface-grey-200);
  --text-strong: var(--geistface-grey-25);
  --muted: color-mix(in srgb, var(--geistface-grey-200) 72%, transparent);
  --quiet: color-mix(in srgb, var(--geistface-grey-200) 50%, transparent);
  --border: color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
  --border-strong: color-mix(in srgb, var(--deep-navy-25) 18%, transparent);
  --violet: var(--mindset-violet-400);
  display: grid;
  gap: var(--space-20);
  padding: var(--space-24);
  border: 1px solid color-mix(in srgb, var(--deep-navy-25) 14%, transparent);
  border-radius: var(--radius-4xl);
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow-xl);
}

.page--labs-benchmark .hero-board__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-16);
}

.page--labs-benchmark .hero-board__title {
  display: block;
  margin-top: var(--space-4);
  color: var(--text-strong);
  font-size: var(--text-md-font-size);
  line-height: var(--text-md-line-height);
  font-weight: var(--weight-semibold);
}

.page--labs-benchmark .hero-board__list {
  display: grid;
  gap: var(--space-8);
  margin: 0;
  padding: 0;
  list-style: none;
}

.page--labs-benchmark .standing {
  display: grid;
  grid-template-columns: var(--space-28) var(--space-32) minmax(0, 1fr) 52px;
  align-items: center;
  gap: var(--space-12);
  min-height: var(--space-56);
  padding: var(--space-8) var(--space-12) var(--space-8) var(--space-8);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  background: var(--surface-card);
}

.page--labs-benchmark .standing--top { border-color: color-mix(in srgb, var(--violet) 40%, var(--border)); }

.page--labs-benchmark .standing__body { min-width: 0; }

.page--labs-benchmark .standing__name {
  display: block;
  overflow: hidden;
  color: var(--text-strong);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
  font-weight: var(--weight-semibold);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.page--labs-benchmark .standing__bar {
  --progress-colour: var(--provider, var(--violet));
  margin-top: var(--space-6);
}

.page--labs-benchmark .standing__bar .sp-progress__track { height: var(--space-4); }

.page--labs-benchmark .standing__rating {
  color: var(--text-strong);
  font: var(--weight-medium) var(--text-md-font-size)/var(--text-md-line-height) var(--font-mono);
  letter-spacing: -0.02em;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.page--labs-benchmark .hero-board__stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-8);
  margin: 0;
}

.page--labs-benchmark .hero-stat {
  display: grid;
  gap: var(--space-2);
  min-width: 0;
  padding: var(--space-10) var(--space-12);
  border-radius: var(--radius-lg);
  background: var(--surface-raised);
}

.page--labs-benchmark .hero-stat dd {
  margin: 0;
  overflow: hidden;
  color: var(--text-strong);
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The board is navy in both themes, so its links take the board's ink, not
   the page's light-mode ink. */
.page--labs-benchmark .hero-board .button--link-gray { color: var(--muted); }
.page--labs-benchmark .hero-board .button--link-gray:hover { color: var(--text-strong); }
.page--labs-benchmark .hero-board .button--link-color { color: var(--mindset-violet-300); }

.page--labs-benchmark .hero-board__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-8);
}

/* --- Sections and panels ---------------------------------------------------- */

.page--labs-benchmark .section--benchmark {
  padding: var(--bench-section) 0;
  border-bottom: 1px solid var(--border);
}

.page--labs-benchmark .section--benchmark:last-of-type { border-bottom: 0; }

.page--labs-benchmark .section-head--benchmark {
  max-width: var(--grid-readable-max);
  margin-bottom: var(--space-32);
}

.page--labs-benchmark .section-kicker {
  display: block;
  margin-bottom: var(--space-14);
  color: var(--sp-accent-ink);
  font: var(--weight-semibold) var(--text-xs-font-size)/var(--text-xs-line-height) var(--font-mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.page--labs-benchmark h2 {
  margin-bottom: var(--space-12);
  color: var(--text-strong);
  font-size: var(--display-md-font-size);
  line-height: var(--display-md-line-height);
  font-weight: var(--weight-medium);
  letter-spacing: -0.025em;
  text-wrap: balance;
}

.page--labs-benchmark .section-intro {
  max-width: 62ch;
  margin: 0;
  color: var(--muted);
  font-size: var(--text-md-font-size);
  line-height: var(--text-md-line-height);
  text-wrap: pretty;
}

.page--labs-benchmark .bench-panel {
  padding: var(--bench-panel-pad);
  border-radius: var(--radius-4xl);
  background: var(--surface);
}

.page--labs-benchmark .bench-panel + .bench-panel,
.page--labs-benchmark .filter-panel + .metrics-grid,
.page--labs-benchmark .metrics-grid + .bench-panel { margin-top: var(--bench-stack); }

.page--labs-benchmark .panel-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-24);
  margin-bottom: var(--space-24);
  padding-bottom: var(--space-20);
  border-bottom: 1px solid var(--border);
}

.page--labs-benchmark .panel-head h3 {
  margin: 0;
  color: var(--text-strong);
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: 0;
}

.page--labs-benchmark .panel-head p {
  max-width: 64ch;
  margin: var(--space-4) 0 0;
  color: var(--muted);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

.page--labs-benchmark .panel-foot {
  margin: var(--space-20) 0 0;
  color: var(--quiet);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
}

/* --- 01 · Arena --------------------------------------------------------------- */

.page--labs-benchmark .arena-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-16);
}

.page--labs-benchmark .select-wrap { position: relative; }

.page--labs-benchmark .select-wrap select {
  width: 100%;
  min-height: var(--space-44);
  padding-block: var(--space-10);
  padding-inline-start: var(--space-12);
  appearance: none;
  font-size: var(--text-md-font-size);
  font-weight: var(--weight-regular);
  cursor: pointer;
}

.page--labs-benchmark .select-wrap .sp-icon {
  position: absolute;
  right: var(--space-12);
  top: 50%;
  --icon-size: var(--icon-size-xs);
  --icon-stroke: var(--icon-stroke-16);
  transform: translateY(-50%);
  color: var(--muted);
  pointer-events: none;
}

.page--labs-benchmark .play-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-12);
  margin-top: var(--space-24);
}

.page--labs-benchmark .play-notes {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-12) var(--space-24);
  margin: var(--space-16) 0 0;
}

.page--labs-benchmark .play-notes dt {
  color: var(--text);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-semibold);
}

.page--labs-benchmark .play-notes dd {
  margin: var(--space-2) 0 0;
  color: var(--quiet);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
}

.page--labs-benchmark .session-progress {
  margin-top: var(--space-20);
  padding: var(--space-16);
  border: 1px solid color-mix(in srgb, var(--violet) 35%, var(--border));
  border-radius: var(--radius-2xl);
  background: color-mix(in srgb, var(--violet) 7%, var(--surface-card));
}

.page--labs-benchmark .progress-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-20);
  margin-bottom: var(--space-10);
}

.page--labs-benchmark .progress-top strong {
  color: var(--text-strong);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
  font-weight: var(--weight-semibold);
}

.page--labs-benchmark .progress-top span {
  color: var(--muted);
  font-size: var(--text-xs-font-size);
  font-weight: var(--weight-semibold);
}

.page--labs-benchmark .session-progress .sp-progress__track { height: var(--space-6); }

/* --- 02 · Leaderboard --------------------------------------------------------- */

/* Provider tones, named in the data and resolved here, so a provider's mark
   and its rating bar draw from one token. */
.page--labs-benchmark [data-tone="violet"] { --provider: var(--mindset-violet-400); }
.page--labs-benchmark [data-tone="green"] { --provider: var(--core-green-500); }
.page--labs-benchmark [data-tone="mint"] { --provider: var(--personality-mint-500); }
.page--labs-benchmark [data-tone="blue"] { --provider: var(--personality-blue-500); }
.page--labs-benchmark [data-tone="magenta"] { --provider: var(--mindset-magenta-400); }
.page--labs-benchmark [data-tone="pink"] { --provider: var(--skills-pink-500); }
.page--labs-benchmark [data-tone="orange"] { --provider: var(--skills-orange-500); }
.page--labs-benchmark [data-tone="lime"] { --provider: var(--core-lime-500); }

.page--labs-benchmark .leaderboard-wrap {
  margin-inline: calc(var(--bench-panel-pad) * -1);
  padding-inline: var(--bench-panel-pad);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: thin;
}

.page--labs-benchmark .leaderboard {
  width: 100%;
  min-width: 760px;
  border-collapse: collapse;
}

.page--labs-benchmark .leaderboard :is(th, td) {
  padding: var(--space-12) var(--space-12);
  border-bottom: 1px solid var(--border);
  text-align: left;
  vertical-align: middle;
}

.page--labs-benchmark .leaderboard :is(th, td):first-child { padding-inline-start: 0; }
.page--labs-benchmark .leaderboard :is(th, td):last-child { padding-inline-end: 0; }
.page--labs-benchmark .leaderboard tbody tr:last-child td { border-bottom: 0; }

.page--labs-benchmark .leaderboard th { padding-top: 0; white-space: nowrap; }

.page--labs-benchmark .leaderboard__num,
.page--labs-benchmark .leaderboard td.num { text-align: right; }

.page--labs-benchmark .leaderboard__rank { width: var(--space-44); }
.page--labs-benchmark .leaderboard__rating { width: 30%; }

.page--labs-benchmark .leaderboard-sort {
  display: inline-flex;
  align-items: center;
  gap: var(--space-4);
  min-height: var(--space-24);
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--radius-xs);
  background: none;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  cursor: pointer;
  transition: var(--transition-interactive);
}

.page--labs-benchmark .leaderboard__num .leaderboard-sort { flex-direction: row-reverse; }

.page--labs-benchmark .leaderboard-sort .sp-icon {
  --icon-size: 14px;
  --icon-stroke: var(--icon-stroke-16);
  color: var(--quiet);
  transition: var(--transition-interactive);
}

.page--labs-benchmark .leaderboard-sort:hover,
.page--labs-benchmark th[aria-sort="ascending"] .leaderboard-sort,
.page--labs-benchmark th[aria-sort="descending"] .leaderboard-sort { color: var(--text-strong); }

.page--labs-benchmark th[aria-sort="ascending"] .leaderboard-sort .sp-icon,
.page--labs-benchmark th[aria-sort="descending"] .leaderboard-sort .sp-icon { color: var(--violet); }

.page--labs-benchmark .leaderboard td {
  color: var(--muted);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

.page--labs-benchmark .leaderboard td.mono { font-size: var(--text-xs-font-size); }

.page--labs-benchmark .leaderboard tbody tr { transition: background-color var(--duration-fast) var(--ease-standard); }
.page--labs-benchmark .leaderboard tbody tr:hover { background: color-mix(in srgb, var(--surface-raised) 50%, transparent); }

.page--labs-benchmark .rank {
  display: grid;
  place-items: center;
  width: var(--space-28);
  height: var(--space-28);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-card);
  color: var(--text-strong);
  font: var(--weight-semibold) var(--text-xs-font-size)/1 var(--font-mono);
}

.page--labs-benchmark .rank--top {
  border-color: color-mix(in srgb, var(--violet) 50%, var(--border));
  background: color-mix(in srgb, var(--violet) 14%, var(--surface-card));
}

.page--labs-benchmark .provider-cell {
  display: flex;
  align-items: center;
  gap: var(--space-10);
  min-width: 0;
}

.page--labs-benchmark .provider-cell strong {
  overflow: hidden;
  color: var(--text-strong);
  font-weight: var(--weight-semibold);
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The mark is the provider's own tone on the raised surface; the tint keeps
   the letters legible whichever tone the provider brings. */
.page--labs-benchmark .provider-avatar {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: var(--space-30);
  height: var(--space-30);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--provider, var(--violet)) 16%, var(--surface-raised));
  color: var(--text-strong);
  font: var(--weight-bold) var(--text-xxs-font-size)/1 var(--font-mono);
  letter-spacing: 0.04em;
}

.page--labs-benchmark .rating-cell {
  display: grid;
  grid-template-columns: var(--space-40) minmax(80px, 1fr);
  align-items: center;
  gap: var(--space-12);
}

.page--labs-benchmark .rating-cell strong {
  color: var(--text-strong);
  font: var(--weight-semibold) var(--text-sm-font-size)/var(--text-sm-line-height) var(--font-mono);
  font-variant-numeric: tabular-nums;
}

.page--labs-benchmark .rating-cell .sp-progress { --progress-colour: var(--provider, var(--violet)); }
.page--labs-benchmark .rating-cell .sp-progress__track { height: var(--space-6); }

.page--labs-benchmark .mood-cell { color: var(--text); font-weight: var(--weight-medium); }

.page--labs-benchmark .leaderboard tr.is-new td { animation: bench-row-in var(--duration-slower) var(--ease-standard); }

@keyframes bench-row-in {
  from { background-color: color-mix(in srgb, var(--violet) 18%, transparent); }
  to { background-color: transparent; }
}

/* --- 03 · Performance --------------------------------------------------------- */

.page--labs-benchmark .filter-panel {
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr);
  align-items: end;
  gap: var(--space-24);
  padding: var(--space-20) var(--space-24);
  border-radius: var(--radius-2xl);
  background: var(--surface);
}

.page--labs-benchmark .filter-group {
  display: grid;
  gap: var(--space-8);
  min-width: 0;
}

.page--labs-benchmark .filter-label {
  color: var(--surface-text);
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-semibold);
}

.page--labs-benchmark .filter-group .button-group { justify-self: start; }

.page--labs-benchmark .evaluation-context {
  min-width: 0;
  text-align: right;
}

.page--labs-benchmark .evaluation-context strong {
  display: block;
  margin-top: var(--space-6);
  color: var(--text-strong);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
  font-weight: var(--weight-semibold);
}

.page--labs-benchmark .metrics-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-16);
  margin: 0;
}

.page--labs-benchmark .metric-card {
  display: grid;
  align-content: start;
  gap: var(--space-2);
  padding: var(--space-20);
  border-radius: var(--radius-2xl);
  background: var(--surface-card);
  transition: border-color var(--duration-fast) var(--ease-standard);
}

.page--labs-benchmark .metric-card dt { margin-bottom: var(--space-10); }

.page--labs-benchmark .metric-card dd {
  margin: 0;
  overflow: hidden;
  color: var(--text-strong);
  font: var(--weight-medium) var(--display-xs-font-size)/var(--display-xs-line-height) var(--font-mono);
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.page--labs-benchmark .metric-card small {
  color: var(--muted);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-medium);
}

.page--labs-benchmark .metric-card--top { border-color: color-mix(in srgb, var(--violet) 40%, var(--border)); }
.page--labs-benchmark .metric-card--top dd { color: var(--mindset-violet-300); }

html[data-surface="light"] > .page--labs-benchmark .metric-card--top dd,
html[data-theme="light"] > .page--labs-benchmark .metric-card--top dd { color: var(--mindset-violet-600); }

.page--labs-benchmark .chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
  flex: 0 0 auto;
}

.page--labs-benchmark .legend-button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
  min-height: var(--space-32);
  padding: 0 var(--space-12) 0 var(--space-10);
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  background: var(--surface-card);
  color: var(--text);
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-semibold);
  cursor: pointer;
  transition: var(--transition-interactive);
}

.page--labs-benchmark .legend-button:hover { border-color: var(--border-hover); color: var(--text-strong); }

.page--labs-benchmark .legend-button[aria-pressed="false"] {
  color: var(--quiet);
  text-decoration: line-through;
  text-decoration-color: color-mix(in srgb, var(--quiet) 60%, transparent);
}

.page--labs-benchmark .legend-button[aria-pressed="false"] .legend-swatch { opacity: 0.35; }

/* The row carries the series colour; the fill and the mark read it. */
.page--labs-benchmark .bar-row--reasoning { --series: var(--series-reasoning); }
.page--labs-benchmark .bar-row--vision { --series: var(--series-vision); }
.page--labs-benchmark .bar-row--base { --series: var(--series-base); }

/* One shape per series, on the legend and beside every bar: circle for
   reasoning, square for vision, diamond for base. */
.page--labs-benchmark .legend-swatch,
.page--labs-benchmark .bar-row__mark {
  display: inline-block;
  width: var(--space-10);
  height: var(--space-10);
  flex: 0 0 auto;
  background: var(--series);
  transition: opacity var(--duration-fast) var(--ease-standard);
}

.page--labs-benchmark .legend-swatch--reasoning,
.page--labs-benchmark .bar-row__mark--reasoning { --series: var(--series-reasoning); border-radius: 50%; }
.page--labs-benchmark .legend-swatch--vision,
.page--labs-benchmark .bar-row__mark--vision { --series: var(--series-vision); border-radius: var(--radius-xxs); }
.page--labs-benchmark .legend-swatch--base,
.page--labs-benchmark .bar-row__mark--base { --series: var(--series-base); border-radius: var(--radius-xxs); transform: rotate(45deg) scale(0.86); }

.page--labs-benchmark .chart-area { position: relative; }

.page--labs-benchmark .bar-chart {
  display: grid;
  gap: var(--space-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* A row is a button so the run behind it can be inspected with the keyboard.
   The grid puts the label, the track and the value on one line; the track
   carries the gridlines at 1 to 5 so every bar is read against the same
   scale. */
.page--labs-benchmark .bar-row {
  display: grid;
  grid-template-columns: var(--bench-label-col) minmax(0, 1fr) var(--bench-value-col);
  align-items: center;
  gap: var(--space-16);
  width: 100%;
  min-height: var(--space-40);
  padding: var(--space-4) var(--space-8);
  border: 1px solid transparent;
  border-radius: var(--radius-lg);
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: var(--transition-interactive);
}

.page--labs-benchmark .bar-row:hover,
.page--labs-benchmark .bar-row.is-active { background: color-mix(in srgb, var(--surface-raised) 55%, transparent); }

.page--labs-benchmark .bar-row:focus-visible {
  outline: 0;
  border-color: var(--focus-default);
  box-shadow: 0 0 0 var(--focus-ring-width) color-mix(in srgb, var(--focus-default) 30%, transparent);
}

.page--labs-benchmark .bar-row__label {
  display: flex;
  align-items: center;
  gap: var(--space-10);
  min-width: 0;
}

.page--labs-benchmark .bar-row__name {
  overflow: hidden;
  color: var(--text);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
  font-weight: var(--weight-medium);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.page--labs-benchmark .bar-row--top .bar-row__name { color: var(--text-strong); font-weight: var(--weight-semibold); }

.page--labs-benchmark .bar-row__track {
  position: relative;
  height: var(--space-24);
  min-width: 0;
  border-radius: var(--radius-xs);
  background-image: repeating-linear-gradient(to right, var(--border) 0 1px, transparent 1px 25%);
  background-position: 0 0;
  background-size: calc(100% - 1px) 100%;
}

.page--labs-benchmark .bar-row__track::after {
  content: "";
  position: absolute;
  inset: 0 0 0 auto;
  width: 1px;
  background: var(--border-strong);
}

.page--labs-benchmark .bar-row__fill {
  position: absolute;
  inset: var(--space-4) auto var(--space-4) 0;
  width: var(--bar, 0%);
  border-radius: 0 var(--radius-xs) var(--radius-xs) 0;
  background: var(--series);
  transition: width var(--duration-slow) var(--ease-standard);
}

.page--labs-benchmark .bar-row--top .bar-row__fill { box-shadow: 0 0 0 1px color-mix(in srgb, var(--series) 40%, transparent); }

.page--labs-benchmark .bar-row__value {
  color: var(--text-strong);
  font: var(--weight-semibold) var(--text-sm-font-size)/var(--text-sm-line-height) var(--font-mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.page--labs-benchmark .bar-axis {
  display: grid;
  grid-template-columns: var(--bench-label-col) minmax(0, 1fr) var(--bench-value-col);
  gap: var(--space-16);
  margin-top: var(--space-8);
  padding: 0 var(--space-8);
}

.page--labs-benchmark .bar-axis__ticks {
  position: relative;
  grid-column: 2;
  display: flex;
  justify-content: space-between;
  padding-top: var(--space-6);
  border-top: 1px solid var(--border-strong);
  color: var(--quiet);
  font: var(--weight-medium) var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono);
}

.page--labs-benchmark .bar-axis__ticks span { width: 0; display: flex; justify-content: center; }
.page--labs-benchmark .bar-axis__ticks span:first-child { justify-content: flex-start; }
.page--labs-benchmark .bar-axis__ticks span:last-child { justify-content: flex-end; }

.page--labs-benchmark .bar-axis__title {
  grid-column: 2;
  margin-top: var(--space-4);
  color: var(--muted);
  font: var(--weight-semibold) var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-align: center;
}

.page--labs-benchmark .chart-empty {
  display: grid;
  justify-items: center;
  gap: var(--space-4);
  padding: var(--space-48) var(--space-24);
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-2xl);
  text-align: center;
}

.page--labs-benchmark .chart-empty strong { color: var(--text-strong); font-size: var(--text-sm-font-size); }
.page--labs-benchmark .chart-empty span { color: var(--muted); font-size: var(--text-xs-font-size); line-height: var(--text-xs-line-height); }

/* The data tooltip from section 27 with position handed to the script, which
   keeps it inside the chart area on both axes. */
.page--labs-benchmark .chart-tooltip {
  position: absolute;
  z-index: 5;
  min-width: 176px;
  max-width: 260px;
  padding: var(--space-10) var(--space-12);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-lg);
  background: var(--surface-card);
  color: var(--text);
  box-shadow: var(--shadow-md);
  pointer-events: none;
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-standard);
}

.page--labs-benchmark .chart-tooltip.is-visible { opacity: 1; }

.page--labs-benchmark .chart-tooltip strong {
  display: block;
  color: var(--text-strong);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
}

.page--labs-benchmark .chart-tooltip span {
  display: block;
  margin-top: var(--space-2);
  color: var(--muted);
  font: var(--weight-medium) var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono);
}

/* --- 04 · Catalog ------------------------------------------------------------- */

.page--labs-benchmark .catalog__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-24);
  align-items: stretch;
}

.page--labs-benchmark .game-card {
  --card-current-padding: 0px;
  display: flex;
  flex-direction: column;
  min-width: 0;
  overflow: hidden;
  border-radius: var(--radius-2xl);
  background: var(--surface-card);
  transition: border-color var(--duration-fast) var(--ease-standard), box-shadow var(--duration-fast) var(--ease-standard), transform var(--duration-fast) var(--ease-standard);
}

.page--labs-benchmark .game-card:hover,
.page--labs-benchmark .game-card:focus-within {
  border-color: var(--surface-border-hover);
  box-shadow: var(--shadow-md);
}

.page--labs-benchmark .game-card.is-selected {
  border-color: color-mix(in srgb, var(--violet) 55%, var(--border));
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--violet) 12%, transparent);
}

.page--labs-benchmark .game-card__art {
  position: relative;
  margin: 0;
  aspect-ratio: 380 / 210;
  overflow: hidden;
  background: var(--surface-raised);
}

.page--labs-benchmark .game-card__art img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--duration-slow) var(--ease-standard);
}

@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .page--labs-benchmark .game-card:hover .game-card__art img { transform: scale(1.03); }
}

.page--labs-benchmark .game-card__mood {
  position: absolute;
  left: var(--space-12);
  top: var(--space-12);
  display: inline-flex;
  align-items: center;
  gap: var(--space-6);
  min-height: var(--space-28);
  padding: 0 var(--space-10) 0 var(--space-8);
  border: 1px solid color-mix(in srgb, var(--geistface-grey-25) 18%, transparent);
  border-radius: var(--radius-full);
  background: var(--glass-surface-brand);
  color: var(--geistface-grey-25);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-semibold);
  -webkit-backdrop-filter: blur(var(--backdrop-blur-sm));
  backdrop-filter: blur(var(--backdrop-blur-sm));
}

.page--labs-benchmark .game-card__mood .sp-icon { --icon-stroke: var(--icon-stroke-sm); color: var(--core-lime-500); }

.page--labs-benchmark .game-card__body {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  padding: var(--space-24);
}

.page--labs-benchmark .game-card__title {
  margin: 0 0 var(--space-8);
  color: var(--text-strong);
  font-size: var(--text-xl-font-size);
  line-height: var(--text-xl-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.01em;
}

.page--labs-benchmark .game-card__description {
  margin: 0 0 var(--space-20);
  color: var(--muted);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

.page--labs-benchmark .ability-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-8);
  margin: var(--space-8) 0 var(--space-20);
  padding: 0;
  list-style: none;
}

.page--labs-benchmark .ability {
  display: inline-flex;
  align-items: center;
  gap: var(--space-6);
  min-height: var(--space-28);
  padding: 0 var(--space-10) 0 var(--space-8);
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  background: var(--surface);
  color: var(--text);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-medium);
}

.page--labs-benchmark .ability .sp-icon { --icon-stroke: var(--icon-stroke-16); color: var(--personality-mint-500); }

html[data-surface="light"] > .page--labs-benchmark .ability .sp-icon,
html[data-theme="light"] > .page--labs-benchmark .ability .sp-icon { color: var(--personality-mint-800); }

.page--labs-benchmark .telemetry {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-12);
  margin: auto 0 0;
  padding-top: var(--space-16);
  border-top: 1px solid var(--border);
}

.page--labs-benchmark .telemetry dt { margin-bottom: var(--space-4); letter-spacing: 0.06em; }

.page--labs-benchmark .telemetry dd {
  margin: 0;
  color: var(--text);
  font: var(--weight-medium) var(--text-xs-font-size)/var(--text-xs-line-height) var(--font-mono);
  overflow-wrap: anywhere;
}

.page--labs-benchmark .card-action {
  width: 100%;
  margin-top: var(--space-20);
}

.page--labs-benchmark .selected-dot {
  display: none;
  width: var(--space-6);
  height: var(--space-6);
  border-radius: 50%;
  background: currentColor;
}

.page--labs-benchmark .game-card.is-selected .selected-dot { display: block; }

.page--labs-benchmark .card-action[aria-pressed="true"] {
  --button-icon-color: var(--button-primary-icon);
  border-color: var(--button-primary);
  background: var(--button-primary);
  color: var(--button-text-strong);
}

.page--labs-benchmark .card-action[aria-pressed="true"]:hover { background: var(--button-primary-hover); }

/* --- 05 · Methodology --------------------------------------------------------- */

.page--labs-benchmark .method-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-24);
}

.page--labs-benchmark .method-card {
  display: flex;
  flex-direction: column;
  padding: var(--space-28);
  border-radius: var(--radius-2xl);
  background: var(--surface);
}

.page--labs-benchmark .method-card__index {
  display: inline-grid;
  place-items: center;
  width: var(--space-36);
  height: var(--space-36);
  margin-bottom: var(--space-20);
  border: 1px solid color-mix(in srgb, var(--sp-accent) 32%, var(--border));
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--sp-accent) 10%, var(--surface-card));
  color: var(--sp-accent-ink);
  font-size: var(--text-xs-font-size);
  font-weight: var(--weight-semibold);
}

.page--labs-benchmark .method-card h3 {
  margin: 0 0 var(--space-12);
  color: var(--text-strong);
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.01em;
  text-wrap: balance;
}

.page--labs-benchmark .method-card p {
  margin: 0;
  color: var(--muted);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

.page--labs-benchmark .method-card p + p { margin-top: var(--space-12); }

/* --- 06 · Waitlist ------------------------------------------------------------ */

.page--labs-benchmark .waitlist-grid {
  display: grid;
  grid-template-columns: minmax(0, 0.8fr) minmax(0, 1fr);
  align-items: start;
  gap: var(--space-72);
}

.page--labs-benchmark .waitlist-copy {
  position: sticky;
  top: var(--space-96);
}

.page--labs-benchmark .waitlist-points {
  display: grid;
  gap: var(--space-10);
  margin: var(--space-28) 0 0;
  padding: 0;
  list-style: none;
}

.page--labs-benchmark .waitlist-points li {
  display: flex;
  align-items: center;
  gap: var(--space-10);
  color: var(--text);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

.page--labs-benchmark .waitlist-points .sp-icon { --icon-stroke: var(--icon-stroke-16); color: var(--core-green-500); }

.page--labs-benchmark .waitlist-form {
  display: grid;
  gap: var(--space-20);
  padding: var(--space-28);
  border-radius: var(--radius-4xl);
  background: var(--surface);
  box-shadow: var(--shadow-xs);
}

.page--labs-benchmark .waitlist-form .field { gap: var(--space-8); }

.page--labs-benchmark .waitlist-form .help {
  color: var(--quiet);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
}

.page--labs-benchmark .waitlist-form textarea { min-height: var(--space-112); resize: vertical; }

.page--labs-benchmark .interest-field {
  margin: 0;
  padding: 0;
  border: 0;
}

.page--labs-benchmark .interest-field legend {
  padding: 0;
  margin-bottom: var(--space-8);
  color: var(--surface-text);
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-semibold);
}

.page--labs-benchmark .interest-options {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-8);
}

.page--labs-benchmark .interest-options label { position: relative; }

.page--labs-benchmark .interest-options input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.page--labs-benchmark .interest-options span {
  display: inline-flex;
  align-items: center;
  min-height: var(--space-36);
  padding: 0 var(--space-14);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-full);
  background: var(--surface-card);
  color: var(--muted);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
  font-weight: var(--weight-medium);
  transition: var(--transition-interactive);
}

.page--labs-benchmark .interest-options label:hover span { border-color: var(--border-hover); color: var(--text-strong); }

.page--labs-benchmark .interest-options input:checked + span {
  border-color: var(--violet);
  background: color-mix(in srgb, var(--violet) 12%, var(--surface-card));
  color: var(--text-strong);
}

/* The input is invisible, so the ring is projected onto the pill. */
.page--labs-benchmark .interest-options input:focus-visible + span {
  box-shadow: 0 0 0 var(--space-4) color-mix(in srgb, var(--mindset-violet-400) 24%, transparent);
}

.page--labs-benchmark .waitlist-submit { justify-self: stretch; }

.page--labs-benchmark .waitlist-form[aria-busy="true"] .waitlist-submit { cursor: progress; }

.page--labs-benchmark .field[data-state="error"] .help { color: var(--skills-pink-400); }

html[data-surface="light"] > .page--labs-benchmark .field[data-state="error"] .help,
html[data-theme="light"] > .page--labs-benchmark .field[data-state="error"] .help { color: var(--skills-pink-700); }

/* --- Footer ----------------------------------------------------------------- */

.page--labs-benchmark .sp-footer { --footer-border: var(--border); }

/* The footer runs on the bar's measure, as it does on the site: the 1560px
   header container on 24px gutters, 16px on phones. */
.page--labs-benchmark .sp-footer__inner {
  --grid-container-max: 1560px;
  --grid-padding: var(--space-24);
}

/* --- Responsive tiers --------------------------------------------------------- */

@media (max-width: 1279px) {
  .page--labs-benchmark { --bench-label-col: 180px; }
  /* The bar has the routes, the live badge and two icon buttons to seat at
     1024; the waitlist button is the one thing the hero already says. */
  .page--labs-benchmark .nav-cta,
  .page--labs-benchmark .live-status { display: none; }
  .page--labs-benchmark .sp-nav__inner { gap: var(--space-24); }
  .page--labs-benchmark .hero-split { grid-template-columns: minmax(0, 1.1fr) minmax(340px, 0.9fr); gap: var(--space-48); }
}

@media (max-width: 1023px) {
  .page--labs-benchmark {
    --bench-section: var(--space-72);
    --bench-panel-pad: var(--space-24);
    --bench-label-col: 160px;
  }

  .page--labs-benchmark .hero--product { min-height: 0; padding-block: calc(76px + var(--space-40)) var(--space-48); }
  .page--labs-benchmark .hero-split { grid-template-columns: minmax(0, 1fr); gap: var(--space-40); }
  .page--labs-benchmark .hero-board { max-width: 640px; }
  .page--labs-benchmark .hero-more { position: static; margin-block-start: var(--space-40); }
  .page--labs-benchmark .filter-panel { grid-template-columns: auto auto; }
  .page--labs-benchmark .evaluation-context { grid-column: 1 / -1; text-align: left; }
  .page--labs-benchmark .metrics-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .page--labs-benchmark .catalog__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-20); }
  /* The odd card takes the full row as a feature: art beside the body, so
     the 380x210 drawing is shown whole rather than cropped to a strip. */
  .page--labs-benchmark .game-card:last-child { grid-column: 1 / -1; flex-direction: row; }
  .page--labs-benchmark .game-card:last-child .game-card__art { flex: 0 0 42%; aspect-ratio: auto; }
  .page--labs-benchmark .game-card:last-child .game-card__body { flex: 1 1 auto; }
  .page--labs-benchmark .method-grid { grid-template-columns: 1fr; gap: var(--space-16); }
  .page--labs-benchmark .waitlist-grid { grid-template-columns: minmax(0, 1fr); gap: var(--space-40); }
  .page--labs-benchmark .waitlist-copy { position: static; }
}

@media (max-width: 767px) {
  .page--labs-benchmark {
    --bench-section: var(--space-56);
    --bench-panel-pad: var(--space-20);
    --bench-stack: var(--space-16);
    --bench-value-col: 52px;
  }

  .page--labs-benchmark .sp-nav__inner { --grid-padding: var(--space-16); min-height: var(--space-64); gap: var(--space-12); }
  .page--labs-benchmark .sp-footer__inner { --grid-padding: var(--space-16); }
  .page--labs-benchmark .hero--product > .sp-container { --grid-padding: var(--space-16); }
  .page--labs-benchmark .sp-nav__actions { flex: 0 0 auto; }
  .page--labs-benchmark .brand-logo { width: 124px; }
  .page--labs-benchmark .live-status { display: none; }
  .page--labs-benchmark .hero--product { padding-block: calc(var(--space-64) + var(--space-24)) var(--space-32); }
  .page--labs-benchmark .audience-badge { margin-bottom: var(--space-16); }
  .page--labs-benchmark h1 { margin-bottom: var(--space-16); }
  .page--labs-benchmark .hero-lede { font-size: var(--text-md-font-size); line-height: var(--text-md-line-height); }
  .page--labs-benchmark .hero-actions { margin-top: var(--space-24); }
  .page--labs-benchmark .hero-actions .button { flex: 1 1 auto; }
  .page--labs-benchmark .hero-board { padding: var(--space-16); gap: var(--space-16); border-radius: var(--radius-3xl); }
  .page--labs-benchmark .hero-board__stats { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-6); }
  .page--labs-benchmark .hero-board__foot .button { min-height: var(--space-44); }
  .page--labs-benchmark .hero-stat { padding: var(--space-8) var(--space-10); }
  .page--labs-benchmark .hero-stat dd { font-size: var(--text-md-font-size); line-height: var(--text-md-line-height); }
  .page--labs-benchmark .section-head--benchmark { margin-bottom: var(--space-24); }
  .page--labs-benchmark .bench-panel { border-radius: var(--radius-3xl); }
  .page--labs-benchmark .panel-head { flex-direction: column; gap: var(--space-12); }
  .page--labs-benchmark .arena-grid { grid-template-columns: 1fr; gap: var(--space-12); }
  .page--labs-benchmark .play-grid { grid-template-columns: 1fr; }
  .page--labs-benchmark .play-notes { grid-template-columns: 1fr; }
  .page--labs-benchmark .filter-panel { grid-template-columns: 1fr; gap: var(--space-16); padding: var(--space-16); }
  .page--labs-benchmark .filter-group .button-group { justify-self: stretch; display: flex; width: 100%; }
  /* Segments never shrink below their label; the group scrolls instead. */
  .page--labs-benchmark .filter-group .button-group__item { flex: 1 0 auto; padding-inline: var(--space-10); }
  .page--labs-benchmark .metrics-grid { gap: var(--space-12); }
  .page--labs-benchmark .metric-card { padding: var(--space-16); }
  .page--labs-benchmark .metric-card dd { font-size: var(--text-xl-font-size); line-height: var(--text-xl-line-height); }
  .page--labs-benchmark .chart-legend { width: 100%; }

  /* The chart drops to two lines per row: name above, bar and value below. */
  .page--labs-benchmark .bar-row { grid-template-columns: minmax(0, 1fr) var(--bench-value-col); row-gap: var(--space-4); padding-block: var(--space-8); }
  .page--labs-benchmark .bar-row__label { grid-column: 1 / -1; }
  .page--labs-benchmark .bar-row__track { height: var(--space-20); }
  .page--labs-benchmark .bar-axis { grid-template-columns: minmax(0, 1fr) var(--bench-value-col); }
  .page--labs-benchmark .bar-axis__ticks,
  .page--labs-benchmark .bar-axis__title { grid-column: 1; }

  /* The table becomes a stack of provider cards; each cell names itself. */
  .page--labs-benchmark .leaderboard-wrap { margin-inline: 0; padding-inline: 0; overflow: visible; }
  .page--labs-benchmark .leaderboard { min-width: 0; }
  .page--labs-benchmark .leaderboard thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  .page--labs-benchmark .leaderboard tbody { display: grid; gap: var(--space-10); }
  .page--labs-benchmark .leaderboard tbody tr {
    display: grid;
    grid-template-columns: var(--space-28) minmax(0, 1fr);
    gap: var(--space-8) var(--space-12);
    padding: var(--space-14);
    border: 1px solid var(--border);
    border-radius: var(--radius-xl);
    background: var(--surface-card);
  }
  .page--labs-benchmark .leaderboard tbody tr:hover { background: var(--surface-card); }
  .page--labs-benchmark .leaderboard tbody td { display: block; padding: 0; border: 0; }
  .page--labs-benchmark .leaderboard tbody td:nth-child(n + 3) {
    grid-column: 2;
    display: grid;
    grid-template-columns: 96px minmax(0, 1fr);
    align-items: center;
    gap: var(--space-10);
  }
  .page--labs-benchmark .leaderboard tbody td:nth-child(n + 3)::before {
    content: attr(data-label);
    color: var(--quiet);
    font: var(--weight-semibold) var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono);
    letter-spacing: 0.08em;
    text-transform: uppercase;
  }
  .page--labs-benchmark .leaderboard td.num { text-align: left; }
  .page--labs-benchmark .leaderboard tbody tr:last-child td { border-bottom: 0; }
  .page--labs-benchmark .rating-cell { grid-template-columns: var(--space-40) minmax(60px, 1fr); }
  .page--labs-benchmark .catalog__grid { grid-template-columns: 1fr; gap: var(--space-16); }
  .page--labs-benchmark .game-card:last-child { flex-direction: column; }
  .page--labs-benchmark .game-card:last-child .game-card__art { flex: none; aspect-ratio: 380 / 210; }
  .page--labs-benchmark .game-card__body { padding: var(--space-20); }
  .page--labs-benchmark .telemetry { grid-template-columns: 1fr; gap: var(--space-10); }
  .page--labs-benchmark .telemetry > div { display: grid; grid-template-columns: 110px minmax(0, 1fr); align-items: baseline; gap: var(--space-10); }
  .page--labs-benchmark .telemetry dt { margin: 0; }
  .page--labs-benchmark .method-card { padding: var(--space-20); }
  .page--labs-benchmark .waitlist-form { padding: var(--space-20); border-radius: var(--radius-3xl); }
}

@media (max-width: 479px) {
  .page--labs-benchmark .hero-board__stats { grid-template-columns: 1fr 1fr; }
  .page--labs-benchmark .hero-stat:last-child { grid-column: 1 / -1; }
  .page--labs-benchmark .hero-board__foot { flex-direction: column; align-items: flex-start; }
  .page--labs-benchmark .standing { grid-template-columns: var(--space-24) var(--space-28) minmax(0, 1fr) 44px; gap: var(--space-8); }
  .page--labs-benchmark .standing__rating { font-size: var(--text-sm-font-size); }
  .page--labs-benchmark .leaderboard tbody td:nth-child(n + 3) { grid-template-columns: 84px minmax(0, 1fr); }
  .page--labs-benchmark .rating-cell { grid-template-columns: var(--space-36) minmax(48px, 1fr); }
}

@media (max-width: 359px) {
  .page--labs-benchmark .brand-divider,
  .page--labs-benchmark .brand-labs { display: none; }
  .page--labs-benchmark .metrics-grid { grid-template-columns: 1fr; }
  .page--labs-benchmark .hero-board__stats { grid-template-columns: 1fr; }
  .page--labs-benchmark .hero-stat:last-child { grid-column: auto; }
  .page--labs-benchmark .filter-group .button-group__item { font-size: var(--text-xs-font-size); }
  .page--labs-benchmark .telemetry > div { grid-template-columns: 1fr; gap: var(--space-2); }
}

@media (prefers-reduced-motion: reduce) {
  .page--labs-benchmark .bar-row__fill,
  .page--labs-benchmark .game-card,
  .page--labs-benchmark .game-card__art img { transition: none; }
  .page--labs-benchmark .leaderboard tr.is-new td { animation: none; }
}

/* ---------------------------------------------------------------------------
   Hero and panel composition, restored from the approved page.

   Consolidation left this page relying entirely on shared defaults, which cost
   it three things the approved design had: a title that wraps, hero columns
   that are actually separated, and panels that are padded once.
   -------------------------------------------------------------------------- */


.hero-copy,
.page--homepage .profile-copy > p,
.page--homepage .process-copy p,
.page--homepage .badge-copy p { font-size: var(--text-lg-font-size); line-height: var(--text-lg-line-height); }


/* ==========================================================================
   09 - Unique composition · Seed Deck
   ========================================================================== */

.text-inverse-soft { color: var(--white); }

html:has(> .page--seed-deck)[data-design="refined"] .title-accent {
  color: var(--mindset-violet-300);
  text-shadow: 0 0 24px color-mix(in srgb, var(--mindset-violet-400) 18%, transparent);
}

.page--seed-deck .slide__title { text-wrap: balance; }
.page--seed-deck .slide__lede { color: var(--panel-navy-200); }
.page--seed-deck .data-panel { padding: var(--space-28); }
.page--seed-deck .data-panel__head strong { font-size: var(--text-xl-font-size); letter-spacing: -0.02em; }
.page--seed-deck .data-token { min-height: 76px; color: var(--deep-navy-200); }
.page--seed-deck .data-panel--missing .data-token { background: color-mix(in srgb, var(--personality-blue-500) 10%, transparent); }
.page--seed-deck .data-panel__caption { color: var(--geistface-grey-400); }
.page--seed-deck .history-track { padding-top: var(--space-18); }
.page--seed-deck .history-bottom { gap: var(--space-22); }

.page--seed-deck .precedent-card, .page--seed-deck .quote-card {
  min-height: 150px;
  padding: var(--space-28) var(--space-28);
  justify-content: flex-start;
}

.page--seed-deck .precedent-card p { color: var(--deep-navy-200); line-height: 1.5; }
.page--seed-deck .pipeline-step { min-height: 146px; }

.page--seed-deck .pipeline-step strong {
  font-size: var(--text-md-font-size);
  line-height: 1.3;
  max-width: 10ch;
}

.page--seed-deck .product-extract__visual { filter: saturate(1.12) contrast(1.06); }

.page--seed-deck .product-extract {
  border-color: color-mix( in srgb, var(--product, var(--green)) 38%, color-mix(in srgb, var(--geistface-grey-25) 12%, transparent) );
}

.page--seed-deck .flywheel-stage { height: 430px; }

.page--seed-deck .flywheel-ring {
  width: 390px;
  height: 390px;
  border: 1px solid color-mix(in srgb, var(--mindset-violet-400) 38%, transparent);
  background: radial-gradient( circle, color-mix(in srgb, var(--mindset-violet-400) 8%, transparent), transparent 60% );
}

.page--seed-deck .flywheel-ring::before {
  content: "";
  position: absolute;
  inset: var(--space-42);
  border-radius: 50%;
  border: 1px dashed color-mix(in srgb, var(--personality-mint-500) 28%, transparent);
}

.page--seed-deck .flywheel-node {
  width: 116px;
  min-height: 58px;
  padding: var(--space-10) var(--space-12);
  color: var(--deep-navy-25);
  font: 700 11px/1.25 var(--font-mono);
  letter-spacing: 0.04em;
  background: var(--panel-navy-950);
  border: 1px solid color-mix(in srgb, var(--node) 58%, var(--border));
  box-shadow: var(--shadow-md);
}

.page--seed-deck .flywheel-node::before {
  content: "";
  width: var(--space-6);
  height: var(--space-6);
  border-radius: 50%;
  background: var(--node);
  margin-bottom: var(--space-4);
}

.page--seed-deck .flywheel-node--top {
  --node: var(--lime);
  left: 137px;
  top: -3px;
}

.page--seed-deck .flywheel-node--right {
  --node: var(--mint);
  right: -4px;
  top: 166px;
}

.page--seed-deck .flywheel-node--bottom {
  --node: var(--blue);
  left: 137px;
  bottom: -3px;
}

.page--seed-deck .flywheel-node--left {
  --node: var(--violet-400);
  left: -4px;
  top: 166px;
}

.page--seed-deck .flywheel-arrow { color: var(--panel-navy-400); font: 500 22px/1 var(--font-mono); }

.page--seed-deck .flywheel-arrow--tr {
  right: 54px;
  top: 76px;
  transform: rotate(45deg);
}

.page--seed-deck .flywheel-arrow--br {
  right: 54px;
  bottom: 76px;
  transform: rotate(135deg);
}

.page--seed-deck .flywheel-arrow--bl {
  left: 54px;
  bottom: 76px;
  transform: rotate(225deg);
}

.page--seed-deck .flywheel-arrow--tl {
  left: 54px;
  top: 76px;
  transform: rotate(315deg);
}

.page--seed-deck .flywheel-center {
  width: 158px;
  height: 158px;
  border-color: color-mix(in srgb, var(--mindset-violet-400) 62%, transparent);
}

.page--seed-deck .flywheel-center strong { color: var(--mindset-violet-200); }

.page--seed-deck .flywheel-center span {
  max-width: 13ch;
  margin: var(--space-6) auto 0;
  color: var(--panel-navy-200);
}

.page--seed-deck .compound-card h3 { font-size: var(--display-xs-font-size); }
.page--seed-deck .compound-item { color: var(--deep-navy-200); }

.page--seed-deck .proof-card {
  min-height: 178px;
  background: linear-gradient( 145deg, color-mix(in srgb, var(--proof) 8%, var(--deep-navy-800)), var(--panel-navy-950) );
}

.page--seed-deck .proof-card strong { font-size: var(--text-lg-font-size); }
.page--seed-deck .proof-card p { color: var(--panel-navy-200); }
.page--seed-deck .market-card strong { font-size: 40px; }
.page--seed-deck .market-card span { margin-top: var(--space-8); color: var(--panel-navy-100); }
.page--seed-deck .market-card p { color: var(--geistface-grey-400); }
.page--seed-deck .timeline-track { padding: var(--space-22) var(--space-8) var(--space-8); }
.page--seed-deck .milestone { padding: 0 var(--space-6); }

.page--seed-deck .milestone span {
  margin-top: var(--space-12);
  padding: var(--space-10) var(--space-8);
  min-height: 54px;
  border-radius: var(--radius-md);
  color: var(--deep-navy-200);
  background: color-mix(in srgb, var(--geistface-grey-25) 4%, transparent);
  border: 1px solid color-mix(in srgb, var(--geistface-grey-25) 9%, transparent);
  line-height: 1.45;
}

.page--seed-deck .team-card { min-height: 182px; padding: var(--space-20); }
.page--seed-deck .team-avatar { width: var(--space-42); height: var(--space-42); }
.page--seed-deck .team-card .role { margin-top: var(--space-4); }
.page--seed-deck .team-card p { color: var(--panel-navy-200); line-height: 1.55; }
.page--seed-deck .round-value { font-size: 68px; }
.page--design-system .portal-library-embed .modal-description,
.page--portal .modal-description,
.page--seed-deck .use-card strong { font-size: var(--text-md-font-size); }
.page--seed-deck .use-card__detail { color: var(--panel-navy-200); }
.page--seed-deck .counter { width: 78px; font-variant-numeric: tabular-nums; }

.page--seed-deck .counter strong {
  display: inline-block;
  width: 2.25ch;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.page--seed-deck {
  --white: var(--deep-navy-25);
  --muted: color-mix(in srgb, var(--geistface-grey-200) 70%, transparent);
  --quiet: color-mix(in srgb, var(--geistface-grey-200) 46%, transparent);
  --violet-400: var(--mindset-violet-400);
  --nav-h: 64px;
  min-width: var(--space-320);
  background: var(--deep);
  line-height: 1.5;
}

html:has(> .page--seed-deck) { background: var(--deep); scroll-behavior: smooth; }

.page--seed-deck button { cursor: pointer; }
.page--seed-deck ::selection { background: var(--mindset-violet-200); color: var(--deep); }

.page--seed-deck :focus-visible {
  outline: 2px solid var(--mindset-violet-200);
  outline-offset: 3px;
}

.page--seed-deck .app {
  position: fixed;
  grid-template-rows: var(--nav-h) 1fr;
  background: radial-gradient(circle at 88% 6%, color-mix(in srgb, var(--mindset-violet-500) 11%, transparent), transparent 24%), var(--deep);
}

.topbar--seed-deck {
  z-index: 50;
  padding: 0 var(--space-24);
  background: color-mix(in srgb, var(--deep-navy-900) 82%, transparent);
  backdrop-filter: blur(18px);
}

.page--seed-deck .topbar__brand img { width: var(--space-128); }

.page--seed-deck .topbar__meta {
  padding-left: var(--space-14);
  border-left: 1px solid var(--border-strong);
  font: 600 var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono);
}

.page--seed-deck .icon-btn { width: var(--space-36); }
.page--seed-deck .deck-shell { min-height: 0; }

.page--seed-deck .deck {
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  overscroll-behavior: contain;
}

.page--seed-deck .slide-wrap {
  flex: 0 0 100%;
  height: 100%;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  padding: var(--space-18) var(--space-80) var(--space-36);
}

.page--seed-deck .slide {
  width: min(calc((100vh - var(--nav-h) - 54px) * 16 / 9), calc(100vw - 164px));
  aspect-ratio: 16/9;
  border-radius: var(--radius-3xl);
  background: var(--deep);
  box-shadow: var(--shadow-xl);
  isolation: isolate;
  transform: translateZ(0);
}

.page--seed-deck .slide::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -4;
  background: linear-gradient(color-mix(in srgb, var(--deep-navy-25) 2%, transparent) 1px, transparent 1px), linear-gradient(90deg, color-mix(in srgb, var(--deep-navy-25) 2%, transparent) 1px, transparent 1px);
  background-size: 72px 72px;
  mask-image: linear-gradient( to bottom right, color-mix(in srgb, var(--deep-navy-950) 35%, transparent), transparent 68% );
}

.page--seed-deck .slide::after {
  content: "";
  position: absolute;
  width: 340px;
  height: 340px;
  right: -160px;
  top: -170px;
  border-radius: 50%;
  background: var(--halo, color-mix(in srgb, var(--mindset-violet-500) 20%, transparent));
  filter: blur(0.2px);
  opacity: 0.9;
  z-index: -3;
}

.page--seed-deck .slide__inner { grid-template-rows: auto 1fr auto; padding: var(--space-48) var(--space-56) var(--space-30); }
.page--seed-deck .slide__head { grid-template-columns: 1fr auto; gap: var(--space-28); }

.page--seed-deck .slide__kicker {
  margin: 0 0 var(--space-10);
  color: var(--accent, var(--green));
  font: 700 10px/15px var(--font-mono);
  letter-spacing: 0.14em;
}

.page--seed-deck .slide__title {
  max-width: 1120px;
  font-size: clamp(30px, 3.1vw, 58px);
  line-height: 1.03;
  letter-spacing: -0.048em;
}

.page--seed-deck .slide__lede {
  max-width: 920px;
  margin: var(--space-12) 0 0;
  font-size: clamp(12px, 1.05vw, 18px);
  line-height: 1.45;
  text-wrap: balance;
}

.page--seed-deck .slide__number {
  min-width: var(--space-44);
  color: var(--quiet);
  font: 600 11px/16px var(--font-mono);
  text-align: right;
}

.page--seed-deck .slide__body { min-height: 0; padding: var(--space-24) 0 var(--space-16); }
.page--seed-deck .slide__foot { color: var(--quiet); font: 600 9px/14px var(--font-mono); }
.page--seed-deck .slide__foot img { width: 94px; opacity: 0.94; }
.page--seed-deck .mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.page--design-system .font-card.serif .alphabet,
.page--design-system .font-brand,
.page--seed-deck .serif { font-family: var(--font-brand); }
.page--seed-deck .accent { color: var(--accent); }

.page--seed-deck .chip {
  min-height: var(--space-28);
  padding: 0 var(--space-8);
  font: 600 10px/15px var(--font-ui);
}

.page--seed-deck .chip i {
  width: var(--space-6);
  height: var(--space-6);
  background: var(--chip, var(--green));
}

.page--seed-deck .hero-slide { --accent: var(--green); --halo: color-mix(in srgb, var(--mindset-violet-500) 22%, transparent); }
.page--seed-deck .hero-slide .slide__inner { grid-template-rows: auto 1fr auto; }

.hero-grid--seed-deck {
  grid-template-columns: minmax(0, 1.05fr) minmax(360px, 0.95fr);
  height: 100%;
}

.hero-copy--seed-deck .slide__title { max-width: 900px; font-size: clamp(44px, 4.35vw, 76px); }
.hero-copy--seed-deck .slide__lede { max-width: 770px; font-size: clamp(14px, 1.15vw, 20px); }
.page--seed-deck .thesis-card { grid-template-columns: auto 1fr; max-width: 650px; }

.page--seed-deck .thesis-card__icon {
  width: 38px;
  height: 38px;
  background: color-mix(in srgb, var(--core-green-500) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--core-green-500) 28%, transparent);
}
.page--seed-deck .thesis-card span { margin-top: var(--space-2); }
.page--seed-deck .network-stage { height: 100%; min-height: 360px; }

.page--seed-deck .network-stage::before {
  content: "";
  position: absolute;
  width: 72%;
  aspect-ratio: 1;
  border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--mindset-violet-400) 18%, transparent);
  box-shadow: inset 0 0 70px color-mix(in srgb, var(--mindset-violet-500) 5%, transparent);
}

.page--seed-deck .network-stage svg { width: 92%; }

.page--seed-deck .network-stage .orbit {
  transform-origin: 50% 50%;
  animation: seed-deck-orbit 28s linear infinite;
}

.page--seed-deck .problem-slide { --accent: var(--pink); --halo: color-mix(in srgb, var(--skills-pink-500) 17%, transparent); }
.page--seed-deck .data-compare { grid-template-columns: 1fr 86px 1fr; align-items: stretch; }
.page--seed-deck .data-panel { min-height: 330px; }
.page--design-system .spacing-token strong, .page--design-system .span-item b,
.page--seed-deck .data-token { font: 600 11px/16px var(--font-mono); }

.page--seed-deck .data-panel--missing .data-token {
  border-color: color-mix(in srgb, var(--personality-blue-500) 30%, transparent);
  background: color-mix(in srgb, var(--personality-blue-500) 6%, transparent);
}

.page--seed-deck .data-panel__caption { margin: auto 0 0; padding-top: var(--space-20); }
.page--seed-deck .bridge { color: var(--violet-400); }
.page--seed-deck .bridge svg { width: 76px; height: var(--space-36); }
.page--seed-deck .opportunity-bar { padding: var(--space-14) var(--space-18); gap: var(--space-12); }

.page--seed-deck .opportunity-bar i {
  width: var(--space-8);
  height: var(--space-8);
  background: var(--pink);
}

.page--seed-deck .history-slide { --accent: var(--violet-400); --halo: color-mix(in srgb, var(--mindset-violet-400) 18%, transparent); }
.page--seed-deck .history-track { padding: var(--space-24) var(--space-32) var(--space-8); }

.page--seed-deck .history-line {
  left: 72px;
  right: 72px;
  top: 72px;
  height: 1px;
  background: var(--border-strong);
}

.page--seed-deck .history-items { grid-template-columns: repeat(4, 1fr); }

.page--seed-deck .history-dot {
  width: var(--space-30);
  height: var(--space-30);
  margin: 0 auto var(--space-18);
  background: var(--dot);
  box-shadow: 0 0 0 9px color-mix(in srgb, var(--dot) 12%, transparent);
  border: 5px solid var(--deep);
}

.page--seed-deck .history-item span { margin-top: var(--space-4); font: 600 9px/14px var(--font-mono); }
.page--seed-deck .precedent-card p { max-width: 520px; }

.page--seed-deck .quote-card blockquote {
  font-family: var(--font-brand);
  line-height: 1.25;
  letter-spacing: -0.02em;
  font-size: var(--display-xs-font-size);
  color: var(--personality-mint-100);
}

.page--seed-deck .quote-card cite {
  font: 600 9px/14px var(--font-mono);
  letter-spacing: 0.07em;
  font-style: normal;
  color: var(--geistface-grey-400);
}

.page--seed-deck .model-slide { --accent: var(--mint); --halo: color-mix(in srgb, var(--personality-mint-500) 13%, transparent); }
.page--seed-deck .model-grid { grid-template-columns: 0.88fr 1.12fr; align-items: stretch; }

.page--seed-deck .model-core {
  padding: var(--space-30);
  min-height: 360px;
  border-color: color-mix(in srgb, var(--mindset-violet-400) 44%, transparent);
}

.page--seed-deck .model-core::after {
  content: "";
  position: absolute;
  inset: 34% 30%auto auto;
  width: var(--space-160);
  height: var(--space-160);
  border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--mindset-violet-400) 25%, transparent);
  box-shadow: 0 0 0 26px color-mix(in srgb, var(--mindset-violet-400) 4%, transparent), 0 0 0 52px color-mix(in srgb, var(--mindset-violet-400) 2%, transparent);
}

.page--seed-deck .model-core h3 { max-width: 500px; }
.page--seed-deck .model-core p { max-width: 520px; margin: var(--space-22) 0 0; }
.page--seed-deck .stat-grid { grid-template-columns: repeat(3, 1fr); }

.page--seed-deck .stat-card::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: var(--space-2);
}

.page--seed-deck .stat-card strong, .page--seed-deck .market-card strong {
  font: 500 30px/1 var(--font-mono);
  letter-spacing: -0.04em;
}

.page--seed-deck .model-note { margin-top: var(--space-14); padding: var(--space-12) var(--space-16); }
.page--seed-deck .moat-slide { --accent: var(--lime); --halo: color-mix(in srgb, var(--core-lime-500) 10%, transparent); }
.page--seed-deck .pipeline { grid-template-columns: repeat(5, 1fr); }

.page--seed-deck .pipeline::before {
  content: "";
  position: absolute;
  left: 7%;
  right: 7%;
  top: 50%;
  height: 1px;
  background: var(--border-strong);
  z-index: -1;
}

.page--seed-deck .pipeline-step {
  border-color: color-mix(in srgb, var(--step) 44%, var(--border));
}

.page--seed-deck .pipeline-step__icon {
  width: var(--space-36);
  height: var(--space-36);
  color: var(--deep);
  background: var(--step);
}

.page--seed-deck .moat-bar {
  margin-top: var(--space-24);
  padding: var(--space-18) var(--space-20);
  grid-template-columns: auto repeat(3, 1fr);
}

.page--seed-deck .moat-bar__label { padding-right: var(--space-20); border-right: 1px solid var(--border); }

.page--seed-deck .moat-point i {
  width: var(--space-6);
  height: var(--space-6);
  background: var(--point);
}

.page--seed-deck .product-slide, .page--seed-deck .traction-slide {
  --accent: var(--green);
  --halo: color-mix(in srgb, var(--core-green-500) 14%, transparent);
}

.page--seed-deck .flywheel-slide { --accent: var(--mint); --halo: color-mix(in srgb, var(--personality-mint-500) 12%, transparent); }
.page--seed-deck .flywheel-layout { grid-template-columns: 1.1fr 0.9fr; gap: var(--space-40); }

.page--seed-deck .flywheel-stage svg {
  width: 410px;
  height: 410px;
  overflow: visible;
}

.page--design-system .nav-spec-mobiletitle,
.page--seed-deck .flywheel-center span { font: 600 9px/14px var(--font-mono); }
.page--seed-deck .compound-item { gap: var(--space-10); }

.page--seed-deck .compound-item i {
  width: var(--space-8);
  height: var(--space-8);
  background: var(--item);
  box-shadow: 0 0 0 6px color-mix(in srgb, var(--item) 9%, transparent);
}

.page--seed-deck .proof-card__icon {
  width: 34px;
  height: 34px;
  background: color-mix(in srgb, var(--proof) 13%, var(--panel-900));
  color: var(--proof);
  border: 1px solid color-mix(in srgb, var(--proof) 34%, var(--border));
}

.page--seed-deck .proof-card p { font: 600 var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono); letter-spacing: 0.06em; }
.page--seed-deck .proof-note { margin-top: var(--space-14); padding: var(--space-14) var(--space-16); }

.page--seed-deck .market-slide { --accent: var(--blue); --halo: color-mix(in srgb, var(--personality-blue-500) 14%, transparent); }
.page--seed-deck .market-layout { grid-template-columns: 1.25fr 0.75fr; align-items: stretch; }
.page--seed-deck .market-card p { margin: var(--space-14) 0 0; }
.page--seed-deck .market-note p { margin: var(--space-18) 0 0; }

.page--seed-deck .sniff {
  padding-top: var(--space-14);
  color: var(--orange);
  font: 700 9px/14px var(--font-mono);
}

.page--seed-deck .timeline-slide { --accent: var(--green); --halo: color-mix(in srgb, var(--core-green-500) 12%, transparent); }

.page--seed-deck .timeline-track::before {
  content: "";
  position: absolute;
  left: var(--space-64);
  right: var(--space-64);
  top: var(--space-56);
  height: 1px;
  background: var(--border-strong);
}

.page--seed-deck .timeline-grid { grid-template-columns: repeat(6, 1fr); }

.page--seed-deck .milestone__dot {
  width: 26px;
  height: 26px;
  margin: 0 auto var(--space-18);
  border: 5px solid var(--deep);
  background: var(--milestone);
  box-shadow: 0 0 0 8px color-mix(in srgb, var(--milestone) 11%, transparent);
}

.page--seed-deck .milestone strong { min-height: 34px; line-height: 1.35; }

.page--seed-deck .milestone span, .page--seed-deck .team-card .role {
  color: var(--quiet);
  font: 600 8px/13px var(--font-mono);
  letter-spacing: 0.04em;
}

.page--seed-deck .team-card .role { color: var(--team); font: 700 8px/13px var(--font-mono); }
.page--seed-deck .timeline-note { margin-top: var(--space-28); padding: var(--space-14) var(--space-16); }
.page--seed-deck .team-slide { --accent: var(--orange); --halo: color-mix(in srgb, var(--skills-orange-500) 13%, transparent); }
.page--seed-deck .team-grid { grid-template-columns: repeat(3, 1fr); }
.page--seed-deck .team-card__head { gap: var(--space-10); }

.page--seed-deck .team-avatar {
  background: color-mix(in srgb, var(--team) 16%, var(--panel-900));
  color: var(--team);
  border: 1px solid color-mix(in srgb, var(--team) 40%, var(--border));
  font: 700 10px/1 var(--font-mono);
}

.page--seed-deck .team-card p { margin: var(--space-16) 0 0; }
.page--seed-deck .raise-slide { --accent: var(--lime); --halo: color-mix(in srgb, var(--core-lime-500) 12%, transparent); }
.page--seed-deck .raise-grid { grid-template-columns: 0.9fr 1.1fr; align-items: stretch; }

.page--seed-deck .round-value {
  margin: var(--space-18) 0 0;
  font: 500 58px/1 var(--font-mono);
  letter-spacing: -0.06em;
}

.page--seed-deck .round-card p { margin: var(--space-8) 0 0; }

.page--seed-deck .use-card i {
  width: var(--space-12);
  height: var(--space-12);
  background: var(--use);
  box-shadow: 0 0 0 7px color-mix(in srgb, var(--use) 10%, transparent);
}

.page--seed-deck .raise-milestone { margin-top: var(--space-12); padding: var(--space-14) var(--space-16); }

.page--seed-deck .rail {
  left: var(--space-20);
  top: 50%;
  z-index: 60;
  transform: translateY(-50%);
  padding: var(--space-10) var(--space-8);
  background: color-mix(in srgb, var(--panel-navy-950) 72%, transparent);
  backdrop-filter: blur(14px);
}

.page--seed-deck .rail button {
  width: var(--space-10);
  height: var(--space-10);
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: color-mix(in srgb, var(--geistface-grey-200) 22%, transparent);
  transition: transform var(--duration-fast) ease, background var(--duration-fast) ease;
}

.page--seed-deck .rail button:hover {
  background: color-mix(in srgb, var(--geistface-grey-200) 58%, transparent);
  transform: scale(1.2);
}

.page--seed-deck .rail button.is-active {
  background: var(--nav-current);
  box-shadow: 0 0 0 5px color-mix(in srgb, var(--nav-current) 18%, transparent);
}

.page--seed-deck .progress {
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 55;
  height: var(--space-2);
  background: color-mix(in srgb, var(--deep-navy-25) 5%, transparent);
}

.page--seed-deck .progress i {
  height: 100%;
  width: 8.333%;
  background: var(--green);
  transition: width var(--duration-slow) ease;
}

.page--seed-deck .counter {
  right: var(--space-24);
  bottom: var(--space-12);
  z-index: 55;
  align-items: baseline;
  gap: var(--space-4);
  color: var(--quiet);
  font: 600 10px/15px var(--font-mono);
  pointer-events: none;
}

.page--seed-deck [data-animate] {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.55s ease, transform 0.55s cubic-bezier(0.2, 0.75, 0.25, 1);
}

.page--seed-deck .slide-wrap.is-visible [data-animate] { opacity: 1; transform: translateY(0); }
.page--seed-deck .slide-wrap.is-visible [data-delay="1"] { transition-delay: var(--duration-instant); }
.page--seed-deck .slide-wrap.is-visible [data-delay="2"] { transition-delay: var(--duration-fast); }
.page--seed-deck .slide-wrap.is-visible [data-delay="3"] { transition-delay: var(--duration-base); }
.page--seed-deck .slide-wrap.is-visible [data-delay="4"] { transition-delay: var(--duration-slow); }
html:has(> .page--seed-deck)[data-view="overview"] .page--seed-deck { overflow: auto; }

html:has(> .page--seed-deck)[data-view="overview"] .app {
  position: relative;
  min-height: 100vh;
  display: block;
}

html:has(> .page--seed-deck)[data-view="overview"] .topbar--seed-deck {
  position: sticky;
  top: 0;
  height: var(--nav-h);
}

html:has(> .page--seed-deck)[data-view="overview"] .deck-shell { overflow: visible; }

html:has(> .page--seed-deck)[data-view="overview"] .deck {
  position: relative;
  inset: auto;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-18);
  padding: var(--space-24);
  overflow: visible;
  scroll-snap-type: none;
}

html:has(> .page--seed-deck)[data-view="overview"] .slide-wrap {
  width: auto;
  height: auto;
  padding: 0;
  display: block;
}

html:has(> .page--seed-deck)[data-view="overview"] .slide {
  width: 100%;
  aspect-ratio: 16/9;
  border-radius: var(--radius-xl);
  cursor: pointer;
  transition: transform var(--duration-base) ease, border-color var(--duration-base) ease, box-shadow var(--duration-base) ease;
}

html:has(> .page--seed-deck)[data-view="overview"] .slide:hover {
  transform: translateY(-4px);
  border-color: var(--border-strong);
  box-shadow: var(--shadow-xl);
}

html:has(> .page--seed-deck)[data-view="overview"] .counter,
html:has(> .page--seed-deck)[data-view="overview"] .progress,
html:has(> .page--seed-deck)[data-view="overview"] .rail {
  display: none;
}

html:has(> .page--seed-deck)[data-view="overview"] [data-animate] { opacity: 1; transform: none; }

.page--seed-deck .help {
  position: fixed;
  z-index: 100;
  background: color-mix(in srgb, var(--deep-navy-950) 78%, transparent);
  backdrop-filter: blur(16px);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--duration-base) ease;
}

.page--seed-deck .help.is-open { opacity: 1; pointer-events: auto; }

.page--seed-deck .help__card {
  width: min(560px, 100%);
  padding: var(--space-28);
  box-shadow: var(--shadow-xl);
}

.page--seed-deck .help__grid { margin-top: var(--space-22); }
.page--seed-deck .shortcut { padding: var(--space-12); }

.page--seed-deck .shortcut kbd {
  min-width: 26px;
  min-height: var(--space-24);
  padding: 0 var(--space-6);
  border-radius: var(--radius-sm);
  background: var(--deep);
  font: 600 10px/1 var(--font-mono);
}

.page--seed-deck .shortcut span { margin-top: var(--space-8); }

html:has(> .page--seed-deck)[data-design="refined"] {
  --white: var(--deep-navy-25);
  --text: var(--geistface-grey-200);
  --muted: color-mix(in srgb, var(--geistface-grey-200) 72%, transparent);
  --quiet: color-mix(in srgb, var(--geistface-grey-200) 48%, transparent);
  --border: color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
  --border-strong: color-mix(in srgb, var(--deep-navy-25) 18%, transparent);
}

html:has(> .page--seed-deck)[data-design="refined"] .topbar--seed-deck {
  padding-inline: var(--space-28);
  background: color-mix(in srgb, var(--deep-navy-900) 94%, transparent);
}

html:has(> .page--seed-deck)[data-design="refined"] .topbar__brand img { width: 136px; }

html:has(> .page--seed-deck)[data-design="refined"] .topbar__meta {
  color: color-mix(in srgb, var(--geistface-grey-200) 58%, transparent);
  font-size: var(--text-xxs-font-size);
  letter-spacing: 0.16em;
}

html:has(> .page--seed-deck)[data-design="refined"] .slide {
  border-color: color-mix(in srgb, var(--deep-navy-25) 12%, transparent);
  background: var(--deep-navy-900);
  box-shadow: 0 30px 90px color-mix(in srgb, var(--deep-navy-950) 34%, transparent);
}

html:has(> .page--seed-deck)[data-design="refined"] .slide::before {
  opacity: 0.64;
  background-size: 80px 80px;
}

html:has(> .page--seed-deck)[data-design="refined"] .slide__inner { padding: var(--space-48) var(--space-56) var(--space-30); }
html:has(> .page--seed-deck)[data-design="refined"] .slide__head { gap: var(--space-32); }

html:has(> .page--seed-deck)[data-design="refined"] .slide__kicker {
  align-items: center;
  gap: var(--space-10);
  margin-bottom: var(--space-14);
  color: var(--accent, var(--green));
  font-size: var(--text-xxs-font-size);
  line-height: 14px;
  font-weight: var(--weight-bold);
  letter-spacing: 0.16em;
}

html:has(> .page--seed-deck)[data-design="refined"] .slide__kicker::before {
  content: "";
  width: var(--space-18);
  height: 1px;
  background: currentColor;
  opacity: 0.86;
}

html:has(> .page--seed-deck)[data-design="refined"] .slide__title {
  max-width: 1160px;
  margin: 0;
  font-size: clamp(35px, 3.45vw, 62px);
  line-height: 0.98;
  letter-spacing: -0.05em;
  font-weight: var(--weight-medium);
}

html:has(> .page--seed-deck)[data-design="refined"] .title-accent,
html:has(> .page--seed-deck)[data-design="refined"] .title-main {
  display: block;
}

html:has(> .page--seed-deck)[data-design="refined"] .title-main { color: var(--white); }

html:has(> .page--seed-deck)[data-design="refined"] .title-accent {
  margin-top: var(--space-2);
  color: var(--white);
  font-family: var(--font-brand);
  font-size: 1.03em;
  line-height: 1.02;
  letter-spacing: -0.035em;
  font-weight: var(--weight-medium);
}

html:has(> .page--seed-deck)[data-design="refined"] .slide__lede {
  max-width: 900px;
  margin-top: var(--space-16);
  color: color-mix(in srgb, var(--geistface-grey-200) 72%, transparent);
  font-size: clamp(13px, 1.05vw, 18px);
  line-height: 1.5;
  letter-spacing: -0.008em;
}

html:has(> .page--seed-deck)[data-design="refined"] .slide__number {
  min-width: 62px;
  padding: var(--space-6) var(--space-8);
  border: 1px solid color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--panel-navy-950) 58%, transparent);
  color: color-mix(in srgb, var(--geistface-grey-200) 54%, transparent);
  font-size: var(--text-xxs-font-size);
  letter-spacing: 0.1em;
  text-align: center;
}

html:has(> .page--seed-deck)[data-design="refined"] .slide__body { padding: var(--space-24) 0 var(--space-14); }

html:has(> .page--seed-deck)[data-design="refined"] .slide__foot {
  color: color-mix(in srgb, var(--geistface-grey-200) 40%, transparent);
  font-size: 8px;
  letter-spacing: 0.12em;
}

html:has(> .page--seed-deck)[data-design="refined"] .slide__foot img { width: 90px; }

html:has(> .page--seed-deck)[data-design="refined"] .chip {
  min-height: var(--space-28);
  padding: 0 var(--space-10);
  background: var(--panel-navy-950);
  border-color: color-mix(in srgb, var(--deep-navy-25) 14%, transparent);
  color: color-mix(in srgb, var(--geistface-grey-200) 76%, transparent);
  font-size: var(--text-xxs-font-size);
  font-weight: var(--weight-semibold);
}

html:has(> .page--seed-deck)[data-design="refined"] .hero-grid--seed-deck {
  grid-template-columns: minmax(0, 1.08fr) minmax(360px, 0.92fr);
}

html:has(> .page--seed-deck)[data-design="refined"] .hero-copy--seed-deck .slide__title {
  max-width: 850px;
  font-size: clamp(48px, 4.55vw, 78px);
}

html:has(> .page--seed-deck)[data-design="refined"] .hero-copy--seed-deck .slide__lede {
  max-width: 760px;
  font-size: clamp(14px, 1.15vw, 19px);
}

html:has(> .page--seed-deck)[data-design="refined"] .thesis-card {
  max-width: 680px;
  margin-top: var(--space-20);
}

html:has(> .page--seed-deck)[data-design="refined"] .thesis-card strong {
  color: var(--green);
  font: 700 9px/14px var(--font-mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

html:has(> .page--seed-deck)[data-design="refined"] .thesis-card span {
  margin-top: var(--space-4);
  color: color-mix(in srgb, var(--geistface-grey-200) 78%, transparent);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
}

html:has(> .page--seed-deck)[data-design="refined"] .network-stage::before { opacity: 0.72; }

html:has(> .page--seed-deck)[data-design="refined"] .data-panel {
  min-height: 310px;
  padding: var(--space-22);
}

html:has(> .page--seed-deck)[data-design="refined"] .data-panel__head strong {
  font-size: var(--text-md-font-size);
  letter-spacing: -0.01em;
}

html:has(> .page--seed-deck)[data-design="refined"] .data-token {
  min-height: 66px;
  background: var(--panel-navy-950);
  font-size: var(--text-xxs-font-size);
  letter-spacing: 0.1em;
}

html:has(> .page--seed-deck)[data-design="refined"] .data-panel__caption {
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
}

html:has(> .page--seed-deck)[data-design="refined"] .opportunity-bar {
  border-color: color-mix(in srgb, var(--skills-pink-500) 24%, transparent);
  font-size: var(--text-xs-font-size);
}

html:has(> .page--seed-deck)[data-design="refined"] .history-item strong {
  font: 600 12px/18px var(--font-mono);
  letter-spacing: 0.05em;
}

html:has(> .page--seed-deck)[data-design="refined"] .history-item span { font-size: 8px; }

html:has(> .page--seed-deck)[data-design="refined"] .precedent-card p {
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

html:has(> .page--seed-deck)[data-design="refined"] .quote-card blockquote {
  font-size: var(--display-xs-font-size);
  line-height: 1.18;
}

html:has(> .page--seed-deck)[data-design="refined"] .model-core {
  min-height: 350px;
  padding: var(--space-28);
}

html:has(> .page--seed-deck)[data-design="refined"] .compound-card h3,
html:has(> .page--seed-deck)[data-design="refined"] .model-core h3 {
  max-width: 520px;
}

html:has(> .page--seed-deck)[data-design="refined"] .model-core p {
  max-width: 500px;
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

html:has(> .page--seed-deck)[data-design="refined"] .market-card strong,
html:has(> .page--seed-deck)[data-design="refined"] .stat-card strong {
  font-size: var(--display-md-font-size);
  line-height: 1;
  font-weight: var(--weight-medium);
}

html:has(> .page--seed-deck)[data-design="refined"] .market-card span,
html:has(> .page--seed-deck)[data-design="refined"] .stat-card span {
  color: color-mix(in srgb, var(--geistface-grey-200) 72%, transparent);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-semibold);
}

html:has(> .page--seed-deck)[data-design="refined"] .pipeline { counter-reset: pipeline; }

html:has(> .page--seed-deck)[data-design="refined"] .pipeline-step {
  counter-increment: pipeline;
  min-height: 140px;
  padding: var(--space-18);
}

html:has(> .page--seed-deck)[data-design="refined"] .pipeline-step::after {
  content: "0" counter(pipeline);
  position: absolute;
  right: var(--space-14);
  top: var(--space-14);
  color: color-mix(in srgb, var(--geistface-grey-200) 36%, transparent);
  font: 600 9px/14px var(--font-mono);
  letter-spacing: 0.08em;
}

html:has(> .page--seed-deck)[data-design="refined"] .pipeline-step strong {
  max-width: 120px;
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
}

html:has(> .page--seed-deck)[data-design="refined"] .moat-bar { padding: var(--space-18) var(--space-20); }
html:has(> .page--seed-deck)[data-design="refined"] .moat-point { font-size: var(--text-xxs-font-size); }

html:has(> .page--seed-deck)[data-design="refined"] .compound-item {
  min-height: 34px;
  font-size: var(--text-xs-font-size);
}

html:has(> .page--seed-deck)[data-design="refined"] .proof-card { position: relative; }

html:has(> .page--seed-deck)[data-design="refined"] .proof-card__head {
  align-items: flex-start;
  flex-wrap: wrap;
}

html:has(> .page--seed-deck)[data-design="refined"] .proof-card strong {
  font-size: var(--text-md-font-size);
  line-height: 22px;
}

html:has(> .page--seed-deck)[data-design="refined"] .proof-card p {
  color: color-mix(in srgb, var(--geistface-grey-200) 66%, transparent);
  font: 500 11px/17px var(--font-ui);
  letter-spacing: 0;
  text-transform: none;
}

html:has(> .page--seed-deck)[data-design="refined"] .proof-status {
  margin-left: auto;
  padding: var(--space-2) var(--space-6);
  border: 1px solid color-mix(in srgb, var(--deep-navy-25) 12%, transparent);
  border-radius: var(--radius-full);
  color: color-mix(in srgb, var(--geistface-grey-200) 48%, transparent);
  font: 600 8px/13px var(--font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

html:has(> .page--seed-deck)[data-design="refined"] .proof-note { font-size: var(--text-xxs-font-size); }

html:has(> .page--seed-deck)[data-design="refined"] .market-card p {
  max-width: 250px;
  margin-top: var(--space-12);
  color: color-mix(in srgb, var(--geistface-grey-200) 58%, transparent);
  font-size: var(--text-xxs-font-size);
  line-height: var(--text-xxs-line-height);
}

html:has(> .page--seed-deck)[data-design="refined"] .market-note { padding: var(--space-24); }

html:has(> .page--seed-deck)[data-design="refined"] .sniff {
  color: var(--orange);
  font-size: 8px;
  line-height: 13px;
}

html:has(> .page--seed-deck)[data-design="refined"] .milestone strong {
  min-height: 38px;
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
}

html:has(> .page--seed-deck)[data-design="refined"] .milestone span {
  max-width: 150px;
  margin-inline: auto;
  font-size: 7px;
  line-height: 12px;
}

html:has(> .page--seed-deck)[data-design="refined"] .timeline-note { font-size: var(--text-xxs-font-size); }

html:has(> .page--seed-deck)[data-design="refined"] .team-grid {
  grid-template-columns: repeat(12, minmax(0, 1fr));
}

html:has(> .page--seed-deck)[data-design="refined"] .team-card { grid-column: span 3; }

html:has(> .page--seed-deck)[data-design="refined"] .team-card:nth-child(-n + 2) {
  grid-column: span 6;
}

html:has(> .page--seed-deck)[data-design="refined"] .team-card .role { font-size: 7px; }

html:has(> .page--seed-deck)[data-design="refined"] .team-card p {
  margin-top: var(--space-14);
  color: color-mix(in srgb, var(--geistface-grey-200) 68%, transparent);
  font-size: var(--text-xxs-font-size);
  line-height: 14px;
}

html:has(> .page--seed-deck)[data-design="refined"] .team-card:nth-child(-n + 2) p {
  max-width: 470px;
  font-size: var(--text-xxs-font-size);
  line-height: var(--text-xxs-line-height);
}

html:has(> .page--seed-deck)[data-design="refined"] .round-value {
  margin-top: var(--space-16);
  font-size: 66px;
  line-height: 0.95;
}

html:has(> .page--seed-deck)[data-design="refined"] .use-card {
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: auto auto;
  align-content: center;
}

html:has(> .page--seed-deck)[data-design="refined"] .use-card i { grid-row: 1/3; }

html:has(> .page--seed-deck)[data-design="refined"] .use-card strong {
  align-self: end;
  font-size: var(--text-sm-font-size);
}

html:has(> .page--seed-deck)[data-design="refined"] .use-card__detail {
  color: color-mix(in srgb, var(--geistface-grey-200) 58%, transparent);
  font-size: var(--text-xxs-font-size);
  line-height: 14px;
}

html:has(> .page--seed-deck)[data-design="refined"] .raise-milestone {
  grid-template-columns: auto 1fr;
  gap: var(--space-12);
  align-items: center;
}

html:has(> .page--seed-deck)[data-design="refined"] .raise-milestone strong {
  color: var(--lime);
  font: 700 9px/14px var(--font-mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

html:has(> .page--seed-deck)[data-design="refined"] .raise-milestone span {
  color: color-mix(in srgb, var(--geistface-grey-200) 72%, transparent);
  font-size: var(--text-xs-font-size);
}

html:has(> .page--seed-deck)[data-design="refined"] .opportunity-bar span {
  color: color-mix(in srgb, var(--geistface-grey-200) 72%, transparent);
}

html:has(> .page--seed-deck)[data-design="refined"] [data-animate] { transition-duration: var(--duration-slower); }

html:has(> .page--seed-deck)[data-design="refined"] .slide-wrap.is-visible [data-delay="1"] {
  transition-delay: var(--duration-instant);
}

html:has(> .page--seed-deck)[data-design="refined"] .slide-wrap.is-visible [data-delay="2"] {
  transition-delay: var(--duration-instant);
}

html:has(> .page--seed-deck)[data-design="refined"] .slide-wrap.is-visible [data-delay="3"] {
  transition-delay: var(--duration-fast);
}

html:has(> .page--seed-deck)[data-design="refined"] .slide-wrap.is-visible [data-delay="4"] {
  transition-delay: var(--duration-base);
}

.page--design-system .trait-module .hero-system-head strong,
.page--design-system .panel-surface-system__intro p,
.page--seed-deck .thesis-card strong { font-size: var(--text-sm-font-size); }
.page--seed-deck .proof-card strong { letter-spacing: -0.012em; }
.page--seed-deck .product-slide .slide__body { padding-top: var(--space-20); padding-bottom: var(--space-10); }
.page--seed-deck .product-showcase { gap: var(--space-14); align-items: stretch; }
.page--seed-deck .product-extract { border-color: var(--border-strong); }
.page--seed-deck .product-extract__visual { height: 172px; }
.page--seed-deck .product-extract__copy { padding: var(--space-18) var(--space-18) var(--space-16); flex: 1; }
.page--seed-deck .product-extract__meta { padding-top: var(--space-12); }

.page--seed-deck .product-extract__meta span {
  min-height: var(--space-22);
  padding: 0 var(--space-6);
  font: 600 8px/13px var(--font-mono);
  letter-spacing: 0.04em;
}

.page--seed-deck .product-extract__visual--portal {
  background: linear-gradient(145deg, var(--panel-navy-950), var(--panel-navy-900));
}

.page--seed-deck .game-art {
  position: absolute;
  margin: 0;
  overflow: hidden;
  border: 1px solid color-mix(in srgb, var(--deep-navy-25) 14%, transparent);
  border-radius: var(--radius-md);
  background: var(--panel-navy-800);
  box-shadow: var(--shadow-sm);
}

.page--seed-deck .signal-preview {
  left: var(--space-16);
  right: var(--space-16);
  bottom: var(--space-8);
  min-height: var(--space-48);
  padding: var(--space-8) var(--space-10);
  grid-template-columns: auto 1fr;
  border-color: color-mix(in srgb, var(--core-green-500) 32%, transparent);
  background: color-mix(in srgb, var(--panel-navy-950) 88%, transparent);
  backdrop-filter: blur(12px);
  box-shadow: var(--shadow-md);
}

.page--seed-deck .signal-preview__score {
  grid-template-columns: auto auto;
  gap: var(--space-4);
  align-items: baseline;
  padding-right: var(--space-10);
  border-right: 1px solid var(--border);
}

.page--seed-deck .signal-preview__score strong { font: 600 19px/1 var(--font-mono); }

.page--seed-deck .signal-preview__metrics span, .page--seed-deck .signal-preview__score span {
  font: 600 7px/11px var(--font-mono);
}

.page--seed-deck .signal-preview__metrics { grid-template-columns: repeat(3, 1fr); gap: var(--space-4); }
.page--seed-deck .signal-preview__metrics span { color: var(--quiet); }
.page--design-system .portal-library-embed .score-ring span,
.page--portal-games .score-ring span,
.page--seed-deck .signal-preview__metrics b { line-height: 12px; }

.page--seed-deck .product-extract__visual--sdk {
  padding: var(--space-12);
  background: radial-gradient( circle at 85% 10%, color-mix(in srgb, var(--mindset-violet-400) 14%, transparent), transparent 36% ), var(--panel-navy-950);
}

.page--seed-deck .sdk-window {
  height: 100%;
  border-radius: var(--radius-lg);
  background: var(--deep-navy-900);
  box-shadow: var(--shadow-md);
}

.page--seed-deck .sdk-window__bar {
  height: var(--space-28);
  gap: var(--space-4);
  padding: 0 var(--space-8);
  background: var(--panel-navy-900);
}

.page--seed-deck .sdk-window__bar i {
  width: var(--space-6);
  height: var(--space-6);
  background: var(--panel-600);
}

.page--seed-deck .sdk-window__bar i:nth-child(1) { background: var(--pink); }
.page--seed-deck .sdk-window__bar i:nth-child(2) { background: var(--orange); }
.page--seed-deck .sdk-window__bar i:nth-child(3) { background: var(--green); }
.page--seed-deck .sdk-window__bar span { font: 500 7px/11px var(--font-mono); }
.page--seed-deck .sdk-fields { grid-template-columns: repeat(3, 1fr); padding: var(--space-10); }
.page--seed-deck .sdk-fields div { padding: var(--space-8); }

.page--seed-deck .sdk-fields span {
  color: var(--quiet);
  font: 600 6px/10px var(--font-mono);
  letter-spacing: 0.06em;
}

.page--seed-deck .sdk-fields strong {
  margin-top: var(--space-4);
  font: 600 9px/12px var(--font-ui);
  text-overflow: ellipsis;
}

.page--seed-deck .sdk-actions { padding: 0 var(--space-10) var(--space-10); }

.page--seed-deck .sdk-actions span, .page--seed-deck .sdk-actions strong {
  min-height: var(--space-28);
  font: 600 8px/12px var(--font-ui);
}

.page--seed-deck .sdk-actions span { background: var(--panel-navy-900); }

.page--seed-deck .sdk-actions strong {
  background: var(--violet);
  box-shadow: inset 0-2px 0 color-mix(in srgb, var(--deep-navy-950) 15%, transparent);
}

.page--seed-deck .product-extract__visual--benchmark {
  padding: var(--space-12);
  background: radial-gradient(circle at 92%0, color-mix(in srgb, var(--personality-blue-500) 13%, transparent), transparent 38%), var(--panel-navy-950);
}

.page--seed-deck .benchmark-mini__top div { padding: var(--space-6) var(--space-8); background: var(--panel-navy-900); }

.page--seed-deck .benchmark-mini__top span {
  color: var(--quiet);
  font: 600 6px/9px var(--font-mono);
}

.page--seed-deck .benchmark-mini__top strong {
  margin-top: var(--space-2);
  font: 600 12px/14px var(--font-mono);
}

.page--seed-deck .benchmark-mini__chart {
  height: 78px;
  margin-top: var(--space-6);
  grid-template-columns: 20px 1fr;
}

.page--seed-deck .benchmark-axis {
  padding: 1px 0 var(--space-8);
  color: var(--quiet);
  font: 500 6px/9px var(--font-mono);
  text-align: right;
}

.page--seed-deck .benchmark-plot { border-left: 1px solid var(--border); }

.page--seed-deck .benchmark-grid {
  left: 0;
  right: 0;
  height: 1px;
  background: var(--border);
}

.page--seed-deck .benchmark-grid--one { top: 12%; }
.page--seed-deck .benchmark-grid--two { top: 49%; }
.page--seed-deck .benchmark-grid--three { top: 82%; }

.page--seed-deck .benchmark-point {
  width: var(--space-14);
  height: var(--space-14);
  border: 2px solid var(--deep-navy-900);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--geistface-grey-25) 12%, transparent);
}

.page--seed-deck .benchmark-point b { font: 700 5px/1 var(--font-mono); }

.page--seed-deck .benchmark-point--o1 {
  left: 64%;
  top: 8%;
  background: var(--violet-400);
}

.page--seed-deck .benchmark-point--gemini {
  left: 30%;
  top: 36%;
  background: var(--blue);
}

.page--seed-deck .benchmark-point--claude {
  left: 48%;
  top: 48%;
  background: var(--orange);
}

.page--seed-deck .benchmark-point--gpt {
  left: 76%;
  top: 58%;
  background: var(--green);
}

.page--seed-deck .benchmark-mini__footer { margin-top: var(--space-6); padding-top: var(--space-6); }

.page--seed-deck .benchmark-mini__footer span {
  color: var(--quiet);
  font: 600 6px/10px var(--font-mono);
}

.page--seed-deck .benchmark-mini__footer strong { font: 600 8px/11px var(--font-mono); }

.page--seed-deck .product-system-bar {
  margin-top: var(--space-12);
  min-height: 53px;
  padding: var(--space-10) var(--space-14);
  grid-template-columns: auto 1fr auto;
}

.page--seed-deck .product-system-bar > div b {
  font: 600 8px/12px var(--font-mono);
  letter-spacing: 0.06em;
}

.page--seed-deck .product-system-bar > div i {
  width: var(--space-22);
  height: 1px;
  background: var(--border-strong);
}

.page--seed-deck .product-system-bar > strong { font: 600 9px/13px var(--font-mono); }

.page--seed-deck .portal-collage {
  height: 100%;
  object-fit: cover;
  opacity: 0.96;
}

.page--seed-deck .product-extract__visual--portal::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient( to bottom, transparent 48%, color-mix(in srgb, var(--deep-navy-900) 35%, transparent) );
  pointer-events: none;
}

.page--seed-deck .signal-preview { z-index: 2; }

/* ==========================================================================
   09 - Unique composition · Games Portal
   ========================================================================== */

.surface-logo--light { display: none !important; }

html[data-surface="light"] .surface-logo--dark, html[data-theme="light"] .surface-logo--dark {
  display: none !important;
}

html[data-surface="light"] .surface-logo--light, html[data-theme="light"] .surface-logo--light {
  display: block !important;
}

html[data-surface="light"] > .page--portal-games, html[data-theme="light"] > .page--portal-games {
  --canvas: var(--deep-navy-25);
  --surface-panel: var(--panel-navy-50);
  --surface-box: var(--geistface-grey-25);
  --surface-raised: var(--geistface-grey-100);
  --surface-strong: var(--panel-navy-100);
  --logo-word: var(--deep-navy-950);
  --quiet: rgb(68 75 97 / 50%);
  --border-interactive: color-mix(in srgb, var(--deep-navy-900) 20%, transparent);
  color-scheme: light;
}

/* The game modal carries no surface of its own - it blurs whatever sits behind
   it, so the panel is really the backdrop showing through. That only reads as a
   panel while the backdrop is dark. Light mode pairs it with the light popup
   tokens instead, otherwise the modal keeps a near-black body under the light
   theme's navy text. */
html[data-surface="light"] > .page--portal .popup-backdrop,
html[data-theme="light"] > .page--portal .popup-backdrop,
html[data-surface="light"] > .page--design-system .portal-library-embed .popup-backdrop {
  background: var(--popup-backdrop-light);
}

html[data-surface="light"] > .page--portal .popup,
html[data-theme="light"] > .page--portal .popup,
html[data-surface="light"] > .page--design-system .portal-library-embed .popup {
  background: var(--popup-light);
  border: 1px solid color-mix(in srgb, var(--geistface-grey-25) 52%, transparent);
}



/* ==========================================================================
   09 - Unique composition · Partner Session Analysis
   ========================================================================== */

.page--partner-analysis {
  --pa-sidebar: 256px;
  --pa-gutter: var(--space-24);
  --pa-section-gap: var(--space-32);
  --pa-panel-pad: var(--space-24);
  --pa-tile-pad: var(--space-16);
  --pa-tile-min: 160px;
  --pa-update-min: 240px;
  --pa-plot-height: 224px;
  --pa-axis: var(--space-40);
  --pa-tooltip-min: 136px;
  --pa-chart-line: var(--mint);
  --pa-chart-target: var(--violet);
  --pa-chart-update: var(--orange);
}

.page--partner-analysis.nav-open,
.page--partner-analysis.pa-modal-open { overflow: hidden; }

.page--partner-analysis .pa-app { min-height: 100dvh; }

.page--partner-analysis .pa-sidebar {
  position: fixed;
  inset-block: 0;
  inset-inline-start: 0;
  z-index: 90;
  width: var(--pa-sidebar);
  height: 100dvh;
}

.page--partner-analysis .pa-sidebar__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-8);
}

.page--partner-analysis .pa-brand {
  min-width: 0;
  display: grid;
  gap: var(--space-8);
  padding: var(--space-20) var(--space-20) var(--space-16);
  color: inherit;
  text-decoration: none;
}

.page--partner-analysis .pa-brand .brand-logo,
.page--partner-analysis .pa-topbar .brand-logo {
  width: 132px;
  height: auto;
}

.page--partner-analysis .pa-brand .brand-logo--light,
html:is([data-theme="light"], [data-surface="light"]) > .page--partner-analysis .pa-brand .brand-logo--dark { display: none; }
html:is([data-theme="light"], [data-surface="light"]) > .page--partner-analysis .pa-brand .brand-logo--light { display: block; }

.page--partner-analysis .pa-brand__product {
  color: var(--side-nav-muted);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
  font-weight: var(--weight-medium);
  letter-spacing: 0.06em;
}

.page--partner-analysis .pa-sidebar__close {
  display: none;
  margin: var(--space-16) var(--space-12) 0 0;
}

.page--partner-analysis .pa-sidebar .sp-side-nav__scroll {
  flex: 1 1 auto;
  padding-inline: var(--space-12);
}

.page--partner-analysis .pa-sidebar .sp-side-nav__link {
  min-height: var(--space-40);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
}

.page--partner-analysis .pa-sidebar button.sp-side-nav__link {
  appearance: none;
  cursor: pointer;
  text-align: start;
}

.page--partner-analysis .sp-side-nav__label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.page--partner-analysis .pa-sidebar__foot {
  padding: var(--space-12);
  display: grid;
  gap: var(--space-8);
  border-top: 1px solid var(--side-nav-border);
}

.page--partner-analysis .pa-account {
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--space-10);
  padding: var(--space-6) var(--space-10);
}

.page--partner-analysis .pa-account__copy {
  min-width: 0;
  display: grid;
}

.page--partner-analysis .pa-account__copy strong {
  overflow: hidden;
  color: var(--side-nav-text);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
  font-weight: var(--weight-semibold);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.page--partner-analysis .pa-account__copy span {
  color: var(--side-nav-muted);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
}

.page--partner-analysis .pa-sidebar__utilities {
  padding-top: var(--space-8);
  border-top: 1px solid var(--side-nav-border);
}

.page--partner-analysis .pa-scrim {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: none;
  padding: 0;
  border: 0;
  background: var(--side-nav-scrim);
}

.page--partner-analysis .pa-main {
  min-width: 0;
  min-height: 100dvh;
  margin-inline-start: var(--pa-sidebar);
  display: flex;
  flex-direction: column;
}

.page--partner-analysis .pa-topbar {
  position: sticky;
  top: 0;
  z-index: 60;
  min-height: var(--space-56);
  padding: var(--space-8) var(--space-12);
  display: none;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-12);
  border-bottom: 1px solid var(--ui-border);
  background: color-mix(in srgb, var(--ui-page) 88%, transparent);
  backdrop-filter: blur(16px);
}

.page--partner-analysis .pa-topbar__brand {
  min-width: 0;
  display: block;
  text-decoration: none;
}

.page--partner-analysis .pa-topbar .brand-logo { display: block; }
.page--partner-analysis .pa-topbar .brand-logo--light { display: none; }
html:is([data-theme="light"], [data-surface="light"]) > .page--partner-analysis .pa-topbar .brand-logo--dark { display: none; }
html:is([data-theme="light"], [data-surface="light"]) > .page--partner-analysis .pa-topbar .brand-logo--light { display: block; }

.page--partner-analysis .pa-topbar__actions {
  display: flex;
  gap: var(--space-8);
}

.page--partner-analysis .pa-content {
  flex: 1 1 auto;
  width: 100%;
  min-width: 0;
  padding: var(--space-32) var(--pa-gutter) var(--space-40);
}

.page--partner-analysis .pa-foot {
  padding: var(--space-20) var(--pa-gutter) var(--space-28);
  border-top: 1px solid var(--ui-border);
  color: var(--muted);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
}

.page--partner-analysis .pa-head {
  display: grid;
  gap: var(--space-10);
  margin-bottom: var(--pa-section-gap);
}

.page--partner-analysis .pa-back {
  justify-self: start;
  display: inline-flex;
  align-items: center;
  gap: var(--space-6);
  color: var(--muted);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-md-line-height);
  font-weight: var(--weight-semibold);
  text-decoration: none;
}

.page--partner-analysis .pa-back:hover,
.page--partner-analysis .pa-back:focus-visible { color: var(--text-strong); }

.page--partner-analysis .pa-head__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-12) var(--space-20);
}

.page--partner-analysis .pa-head__identity {
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--space-12);
}

.page--partner-analysis .pa-head__glyph {
  color: var(--accent-text);
  border-color: color-mix(in srgb, var(--mint) 28%, var(--surface-border));
  background: color-mix(in srgb, var(--mint) 10%, var(--surface-box));
}

.page--partner-analysis .pa-head__title {
  min-width: 0;
  display: grid;
}

.page--partner-analysis .pa-head__title .eyebrow { margin-bottom: var(--space-2); }

.page--partner-analysis .pa-head h1 {
  margin: 0;
  overflow-wrap: anywhere;
  color: var(--text-strong);
  font-size: var(--app-display-xs-font-size);
  line-height: var(--app-display-xs-line-height);
  font-weight: var(--app-heading-weight);
  letter-spacing: var(--app-title-tracking);
}

.page--partner-analysis .pa-head__status {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-8);
}

.page--partner-analysis .pa-meta {
  margin: var(--space-2) 0 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-6) var(--space-20);
  color: var(--muted);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
}

.page--partner-analysis .pa-meta li {
  min-width: 0;
  display: inline-flex;
  align-items: center;
  gap: var(--space-6);
  overflow-wrap: anywhere;
}

.page--partner-analysis .pa-meta .sp-icon { color: var(--muted); }

.page--partner-analysis .inline-code {
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
}

.page--partner-analysis .pa-sections {
  display: grid;
  gap: var(--pa-section-gap);
}

.page--partner-analysis .pa-section {
  min-width: 0;
  display: grid;
  align-content: start;
  gap: var(--space-14);
}

.page--partner-analysis .pa-section__head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-10) var(--space-20);
}

.page--partner-analysis .pa-section__copy { min-width: 0; }

.page--partner-analysis .pa-section__title {
  margin: 0;
  display: flex;
  align-items: center;
  gap: var(--space-8);
  color: var(--text-strong);
  font-size: var(--app-text-lg-font-size);
  line-height: var(--app-text-lg-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--app-heading-tracking);
}

.page--partner-analysis .pa-section__title .sp-icon { color: var(--violet); }

.page--partner-analysis .pa-section__hint {
  margin: var(--space-2) 0 0;
  color: var(--muted);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
}

.page--partner-analysis .pa-section__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-8) var(--space-12);
}

.page--partner-analysis .pa-overview {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
  gap: var(--pa-section-gap) var(--space-24);
}

.page--partner-analysis .pa-panel {
  min-width: 0;
  display: grid;
  align-content: start;
  gap: var(--space-16);
  padding: var(--pa-panel-pad);
  border: 1px solid var(--surface-border);
}

.page--partner-analysis .pa-panel__head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-8) var(--space-16);
}

.page--partner-analysis .pa-panel__title {
  margin: 0;
  color: var(--text-strong);
  font-size: var(--app-text-md-font-size);
  line-height: var(--app-text-md-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--app-heading-tracking);
}

.page--partner-analysis .pa-panel__hint {
  margin: var(--space-2) 0 0;
  color: var(--muted);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
}

.page--partner-analysis .pa-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--pa-tile-min)), 1fr));
  gap: var(--space-12);
}

.page--partner-analysis .pa-tile {
  --card-current-padding: var(--pa-tile-pad);
  --card-current-radius: var(--radius-compact);
  min-width: 0;
  display: grid;
  align-content: start;
  gap: var(--space-4);
}

.page--partner-analysis .pa-tile__label {
  color: var(--muted);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
  font-weight: var(--weight-medium);
}

.page--partner-analysis .pa-tile__value {
  overflow-wrap: anywhere;
  color: var(--text-strong);
  font-family: var(--font-mono);
  font-size: var(--app-text-lg-font-size);
  line-height: var(--app-text-lg-line-height);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

.page--partner-analysis .pa-tile--lead .pa-tile__value {
  font-size: var(--app-display-xs-font-size);
  line-height: var(--app-display-xs-line-height);
  font-weight: var(--weight-medium);
  letter-spacing: -0.02em;
}

.page--partner-analysis .pa-tile__value--accent { color: var(--accent-text); }
.page--partner-analysis .pa-tile__value--positive { color: var(--positive-text); }

.page--partner-analysis .pa-tile__note {
  color: var(--muted);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
}

.page--partner-analysis .pa-tile__badge {
  justify-self: start;
  margin-top: var(--space-6);
}

.page--partner-analysis .pa-radar {
  position: relative;
  width: min(100%, var(--space-480));
  margin: 0 auto;
}

.page--partner-analysis .pa-radar__svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

.page--partner-analysis .pa-radar__svg .grid-line {
  fill: none;
  stroke: var(--surface-border);
  stroke-width: 1;
}

.page--partner-analysis .pa-radar__svg .grid-line--outer { stroke: var(--surface-border-hover); }
.page--partner-analysis .pa-radar__svg .axis { stroke: var(--surface-border); stroke-width: 1; }

.page--partner-analysis .pa-radar__svg .value-shape {
  fill: color-mix(in srgb, var(--pa-chart-line) 14%, transparent);
  stroke: var(--pa-chart-line);
  stroke-width: 2;
  stroke-linejoin: round;
}

.page--partner-analysis .pa-radar__svg .point {
  fill: var(--pa-chart-line);
  stroke: var(--surface-panel);
  stroke-width: 2;
}

.page--partner-analysis .pa-radar__label {
  position: absolute;
  transform: translate(-50%, -50%);
  color: var(--muted);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
  font-weight: var(--weight-medium);
  white-space: nowrap;
  pointer-events: none;
}

.page--partner-analysis .pa-radar__label[data-anchor="start"] { transform: translate(0, -50%); }
.page--partner-analysis .pa-radar__label[data-anchor="end"] { transform: translate(-100%, -50%); }

.page--partner-analysis .pa-meters {
  display: grid;
  gap: var(--space-16);
}

.page--partner-analysis .pa-meter {
  display: grid;
  gap: var(--space-8);
}

.page--partner-analysis .pa-meter__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-16);
  color: var(--text);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
}

.page--partner-analysis .pa-meter__head strong {
  color: var(--accent-text);
  font-family: var(--font-mono);
  font-size: var(--app-text-sm-font-size);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

.page--partner-analysis .pa-meter--mint .sp-progress { --progress-colour: var(--mint); }
.page--partner-analysis .pa-meter--violet .sp-progress { --progress-colour: var(--violet); }

.page--partner-analysis .pa-facts {
  margin: 0;
  display: grid;
}

.page--partner-analysis .pa-fact {
  min-height: var(--space-40);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-16);
  border-top: 1px solid var(--ui-border);
}

.page--partner-analysis .pa-fact dt {
  color: var(--muted);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
}

.page--partner-analysis .pa-fact dd {
  margin: 0;
  color: var(--text-strong);
  font-family: var(--font-mono);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
  font-weight: var(--weight-semibold);
}

.page--partner-analysis .sp-alert--compact .sp-alert__text {
  color: var(--text);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
}

.page--partner-analysis .pa-fact--accent dd { color: var(--accent-text); }

.page--partner-analysis .pa-analysis {
  display: grid;
  gap: var(--space-16);
}

.page--partner-analysis .pa-analysis__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
  gap: var(--space-16);
  align-items: stretch;
}

.page--partner-analysis .pa-timeline { gap: var(--space-12); }

.page--partner-analysis .pa-readout {
  margin: var(--space-2) 0 0;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
  font-variant-numeric: tabular-nums;
}

.page--partner-analysis .pa-legend {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6) var(--space-16);
  color: var(--muted);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
}

.page--partner-analysis .pa-legend li {
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
}

.page--partner-analysis .pa-legend__line {
  width: var(--space-18);
  height: 0;
  border-top: 2px solid var(--pa-chart-line);
}

.page--partner-analysis .pa-legend__line--target {
  border-top-color: var(--pa-chart-target);
  border-top-style: dashed;
}

.page--partner-analysis .pa-legend__dot {
  width: var(--space-8);
  height: var(--space-8);
  border-radius: var(--radius-full);
  background: var(--pa-chart-update);
}

.page--partner-analysis .pa-chart { min-width: 0; }

.page--partner-analysis .pa-plot {
  position: relative;
  height: var(--pa-plot-height);
  margin-inline-start: var(--pa-axis);
  border-radius: var(--radius-sm);
  touch-action: pan-y;
  cursor: crosshair;
}

.page--partner-analysis .pa-plot:focus-visible {
  outline: 0;
  box-shadow:
    0 0 0 var(--focus-ring-width) var(--focus-inner),
    0 0 0 calc(var(--focus-ring-width) * 2) var(--focus-default);
}

.page--partner-analysis .pa-plot__svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}

.page--partner-analysis .pa-plot__svg .grid {
  stroke: var(--surface-border);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.page--partner-analysis .pa-plot__svg .target-line {
  stroke: var(--pa-chart-target);
  stroke-width: 1.5;
  stroke-dasharray: 5 5;
  vector-effect: non-scaling-stroke;
}

.page--partner-analysis .pa-plot__svg .update-line {
  stroke: color-mix(in srgb, var(--pa-chart-update) 60%, transparent);
  stroke-width: 1;
  stroke-dasharray: 3 4;
  vector-effect: non-scaling-stroke;
}

.page--partner-analysis .pa-plot__svg .flow-area {
  fill: color-mix(in srgb, var(--pa-chart-line) 10%, transparent);
  stroke: none;
}

.page--partner-analysis .pa-plot__svg .flow-line {
  fill: none;
  stroke: var(--pa-chart-line);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.page--partner-analysis .pa-plot__svg .hover-line {
  stroke: var(--surface-border-hover);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.page--partner-analysis .pa-plot__point {
  position: absolute;
  width: var(--space-10);
  height: var(--space-10);
  transform: translate(-50%, -50%);
  border-radius: var(--radius-full);
  background: var(--pa-chart-update);
  box-shadow: 0 0 0 2px var(--surface-panel);
  pointer-events: none;
}

.page--partner-analysis .pa-plot__point--hover {
  width: var(--space-12);
  height: var(--space-12);
  background: var(--pa-chart-line);
  box-shadow: 0 0 0 3px var(--surface-panel);
}

.page--partner-analysis .pa-plot__y,
.page--partner-analysis .pa-plot__x {
  margin: 0;
  padding: 0;
  list-style: none;
}

.page--partner-analysis .pa-plot__y {
  position: absolute;
  inset-block: 0;
  right: 100%;
  width: var(--pa-axis);
}

.page--partner-analysis .pa-plot__y li,
.page--partner-analysis .pa-plot__x li {
  position: absolute;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.page--partner-analysis .pa-plot__y li {
  right: var(--space-8);
  transform: translateY(-50%);
}

.page--partner-analysis .pa-plot__x {
  position: relative;
  height: var(--app-text-xs-line-height);
  margin: var(--space-8) 0 0 var(--pa-axis);
}

.page--partner-analysis .pa-plot__x li {
  top: 0;
  transform: translateX(-50%);
}

.page--partner-analysis .pa-plot__x li[data-anchor="start"] { transform: none; }
.page--partner-analysis .pa-plot__x li[data-anchor="end"] { transform: translateX(-100%); }

.page--partner-analysis .chart-tooltip {
  min-width: var(--pa-tooltip-min);
  padding: var(--space-8) var(--space-10);
  border-radius: var(--radius-md);
  background: var(--surface-box);
  transform: translate(-50%, calc(-100% - var(--space-10)));
  transition: opacity var(--duration-instant) ease;
}

.page--partner-analysis .chart-tooltip.is-below { transform: translate(-50%, var(--space-12)); }
.page--partner-analysis .chart-tooltip.is-visible { opacity: 1; }

.page--partner-analysis .chart-tooltip strong {
  display: block;
  color: var(--text-strong);
  font-family: var(--font-mono);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

.page--partner-analysis .chart-tooltip span {
  font-family: var(--font-mono);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
  font-variant-numeric: tabular-nums;
}

.page--partner-analysis .pa-moments {
  padding: 0;
  gap: 0;
  overflow: hidden;
}

.page--partner-analysis .pa-moments__head { padding: var(--space-20) var(--space-24) var(--space-14); }

.page--partner-analysis .pa-moments__list {
  display: grid;
  align-content: start;
  list-style: none;
}

.page--partner-analysis .pa-moment {
  min-height: var(--space-56);
  display: grid;
  grid-template-columns: var(--space-32) minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--space-12);
  padding: var(--space-8) var(--space-24);
  border-top: 1px solid var(--ui-border);
}

.page--partner-analysis .pa-moment__index {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
  font-variant-numeric: tabular-nums;
}

.page--partner-analysis .pa-moment__copy {
  min-width: 0;
  display: grid;
}

.page--partner-analysis .pa-moment__title {
  overflow: hidden;
  color: var(--text-strong);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
  font-weight: var(--weight-semibold);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.page--partner-analysis .pa-moment__meta {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
  font-variant-numeric: tabular-nums;
}

.page--partner-analysis .pa-moment__value {
  color: var(--accent-text);
  font-family: var(--font-mono);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

.page--partner-analysis .pa-moments__foot {
  margin-top: auto;
  padding: var(--space-16) var(--space-24) var(--space-24);
  border-top: 1px solid var(--ui-border);
}

.page--partner-analysis .pa-moments__foot .button { width: 100%; }

.page--partner-analysis .pa-table {
  padding: 0;
  gap: 0;
  overflow: hidden;
}

.page--partner-analysis .pa-table .sp-table { min-width: var(--space-640); }
.page--partner-analysis .sp-table :is(th, td) { vertical-align: middle; }
.page--partner-analysis .sp-table th {
  color: var(--muted);
  white-space: nowrap;
}

.page--partner-analysis .pa-num {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

.page--partner-analysis .pa-reason {
  color: var(--text-strong);
  font-weight: var(--weight-semibold);
}

.page--partner-analysis .pa-check {
  width: var(--space-22);
  height: var(--space-22);
  display: inline-grid;
  place-items: center;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--green) 12%, var(--surface-box));
  color: var(--positive-text);
  vertical-align: middle;
}

.page--partner-analysis .pa-updates {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--pa-update-min)), 1fr));
  gap: var(--space-12);
}

.page--partner-analysis .pa-update {
  --card-current-padding: var(--space-16);
  --card-current-radius: var(--radius-compact);
  min-width: 0;
  display: grid;
  align-content: start;
  gap: var(--space-10);
}

.page--partner-analysis .pa-update .eyebrow { margin: 0; }

.page--partner-analysis .pa-update__name {
  margin: 0;
  overflow-wrap: anywhere;
  color: var(--text-strong);
  font-family: var(--font-mono);
  font-size: var(--app-text-md-font-size);
  line-height: var(--app-text-md-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--app-heading-tracking);
}

.page--partner-analysis .pa-update__change {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-6) var(--space-8);
  color: var(--muted);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
}

.page--partner-analysis .pa-update__change strong {
  color: var(--accent-text);
  font-family: var(--font-mono);
  font-size: var(--app-text-sm-font-size);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

.page--partner-analysis .pa-update__change .sp-icon { color: var(--muted); }

.page--partner-analysis .accordion__summary { gap: var(--space-16); }

.page--partner-analysis .pa-accordion__title {
  min-width: 0;
  display: inline-flex;
  align-items: center;
  gap: var(--space-10);
}

.page--partner-analysis .pa-accordion__title .sp-icon { color: var(--violet); }

.page--partner-analysis .pa-accordion__action {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: var(--space-6);
  color: var(--muted);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
  font-weight: var(--weight-semibold);
}

.page--partner-analysis .accordion__panel {
  max-width: none;
  padding-bottom: var(--space-24);
}

.page--partner-analysis .code-head { justify-content: space-between; }

.page--partner-analysis .code-block pre {
  padding: var(--space-18) var(--space-20);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-sm-line-height);
}

.page--partner-analysis .popup-backdrop { padding: var(--space-24); }

.page--partner-analysis .pa-modal {
  width: min(var(--space-1024), 100%);
  max-height: calc(100dvh - var(--space-48));
  display: grid;
  grid-template-rows: auto auto minmax(0, 1fr) auto;
  border: 1px solid var(--surface-border-hover);
  color: var(--text);
  background: var(--surface-panel);
}

.page--partner-analysis .pa-modal__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-20);
  padding: var(--space-20) var(--space-24) var(--space-16);
  border-bottom: 1px solid var(--ui-border);
}

.page--partner-analysis .pa-modal__head .popup__close { flex: 0 0 auto; }

.page--partner-analysis .pa-modal__head h2 {
  margin: 0;
  color: var(--text-strong);
  font-size: var(--app-text-xl-font-size);
  line-height: var(--app-text-xl-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--app-title-tracking);
}

.page--partner-analysis .pa-modal__head p {
  margin: var(--space-4) 0 0;
  color: var(--muted);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
}

.page--partner-analysis .pa-modal__controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-12) var(--space-16);
  padding: var(--space-12) var(--space-24);
  border-bottom: 1px solid var(--ui-border);
}

.page--partner-analysis .pa-search {
  position: relative;
  width: min(var(--space-384), 100%);
}

.page--partner-analysis .pa-search .sp-icon {
  position: absolute;
  left: var(--space-12);
  top: 50%;
  transform: translateY(-50%);
  color: var(--muted);
  pointer-events: none;
}

.page--partner-analysis .pa-search input {
  min-height: var(--space-40);
  padding-block: var(--space-8);
}

.page--partner-analysis .pa-modal__count {
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
  font-variant-numeric: tabular-nums;
}

.page--partner-analysis .pa-modal__scroll {
  min-height: 0;
  overflow: auto;
  overscroll-behavior: contain;
}

.page--partner-analysis .pa-modal__scroll .sp-table { min-width: var(--space-720); }

.page--partner-analysis .pa-modal__scroll th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--surface-panel);
}

.page--partner-analysis .pa-modal__scroll tbody tr:hover td {
  background: color-mix(in srgb, var(--text-strong) 4%, transparent);
}

.page--partner-analysis .pa-modal__empty td {
  padding: var(--space-28) var(--space-24);
  color: var(--muted);
  text-align: center;
}

.page--partner-analysis .pa-modal__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-12) var(--space-16);
  padding: var(--space-12) var(--space-24) var(--space-16);
  border-top: 1px solid var(--ui-border);
}

.page--partner-analysis .pa-modal__foot .pagination {
  flex-wrap: wrap;
  justify-content: center;
}

.page--partner-analysis .pa-modal__page {
  min-width: var(--space-96);
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.page--partner-analysis .pagination .button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

@media (max-width: 1023px) {
  .page--partner-analysis .pa-sidebar {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transform: translateX(-100%);
    transition:
      transform var(--duration-base) var(--ease-standard),
      opacity var(--duration-base) var(--ease-standard),
      visibility 0s linear var(--duration-base);
  }

  .page--partner-analysis.nav-open .pa-sidebar {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: none;
    box-shadow: var(--shadow-xl);
    transition-delay: 0s, 0s, 0s;
  }

  .page--partner-analysis.nav-open .pa-scrim { display: block; }
  .page--partner-analysis .pa-sidebar__close { display: inline-grid; }
  .page--partner-analysis .pa-main { margin-inline-start: 0; }
  .page--partner-analysis .pa-topbar { display: flex; }

  .page--partner-analysis .pa-overview,
  .page--partner-analysis .pa-analysis__grid {
    grid-template-columns: minmax(0, 1fr);
  }
}

@media (max-width: 767px) {
  .page--partner-analysis {
    --pa-gutter: var(--space-12);
    --pa-section-gap: var(--space-24);
    --pa-panel-pad: var(--space-14);
    --pa-tile-pad: var(--space-10);
    --pa-plot-height: 176px;
    --pa-axis: var(--space-36);
  }

  .page--partner-analysis .pa-content { padding-block: var(--space-16) var(--space-28); }
  .page--partner-analysis .pa-foot { padding: var(--space-16) var(--pa-gutter) var(--space-20); }
  .page--partner-analysis .pa-head { gap: var(--space-8); }

  .page--partner-analysis .pa-meta {
    display: grid;
    gap: var(--space-4);
  }

  .page--partner-analysis .pa-section__actions { width: 100%; }
  .page--partner-analysis .pa-section__actions .button-group { flex: 1 1 auto; }
  .page--partner-analysis .pa-section__actions .button-group__item { flex: 1 1 0; min-width: 0; }

  .page--partner-analysis .pa-moments__head { padding: var(--space-14) var(--space-14) var(--space-10); }
  .page--partner-analysis .pa-moment { padding-inline: var(--space-14); }
  .page--partner-analysis .pa-moments__foot { padding: var(--space-12) var(--space-14) var(--space-14); }
  .page--partner-analysis .pa-update { --card-current-padding: var(--space-14); }

  .page--partner-analysis .accordion__summary {
    min-height: var(--space-48);
    padding-block: var(--space-12);
  }

  .page--partner-analysis .popup-backdrop { padding: var(--space-12); }
  .page--partner-analysis .pa-modal { max-height: calc(100dvh - var(--space-24)); }

  .page--partner-analysis .pa-modal__head,
  .page--partner-analysis .pa-modal__controls,
  .page--partner-analysis .pa-modal__foot {
    padding-inline: var(--space-14);
  }

  .page--partner-analysis .pa-modal__foot { justify-content: center; }
}

@media (max-width: 479px) {
  .page--partner-analysis .pa-radar {
    width: calc(100% + var(--space-12));
    margin-inline: calc(var(--space-6) * -1);
  }

  .page--partner-analysis .pa-radar__label {
    max-width: 5.5em;
    line-height: var(--space-14);
    white-space: normal;
  }

  .page--partner-analysis .pa-radar__label[data-anchor="end"] { text-align: right; }
}

@media (prefers-reduced-motion: reduce) {
  .page--partner-analysis .pa-sidebar,
  .page--partner-analysis .chart-tooltip {
    transition: none;
  }
}

/* ---------------------------------------------------------------------------
   Interface miniatures for the two surfaces section 15 was missing.
   Same idiom as the other minis: 6-9px type, mono for labels and numerals,
   palette straight from the tokens, everything scoped to the reference page.
   -------------------------------------------------------------------------- */
.page--design-system .partner-interface-mini {
  min-height: 410px;
  grid-template-columns: 96px minmax(0, 1fr);
  background: var(--deep-navy-900);
  color: var(--geistface-grey-200);
}

.page--design-system .partner-mini-sidebar {
  gap: var(--space-10);
  padding: var(--space-12) var(--space-10);
  border-right: 1px solid color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
}

.page--design-system .partner-mini-brand { font-size: 8px; }

.page--design-system .partner-mini-nav { gap: var(--space-4); }

.page--design-system .partner-mini-nav b {
  margin-top: var(--space-4);
  color: var(--panel-navy-400);
  font: 600 6px/10px var(--font-mono);
}

.page--design-system .partner-mini-nav i {
  width: 72%;
  height: var(--space-4);
  background: var(--panel-navy-700);
}

.page--design-system .partner-mini-nav i.is-active {
  width: 88%;
  background: var(--mindset-violet-400);
}

.page--design-system .partner-mini-profile {
  color: var(--panel-navy-400);
  font: 500 6px/10px var(--font-mono);
}

.page--design-system .partner-mini-profile i {
  width: var(--space-10);
  height: var(--space-10);
  background: var(--panel-navy-700);
}

.page--design-system .partner-mini-main > header {
  min-height: 34px;
  padding: 0 var(--space-12);
  border-bottom: 1px solid color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
  color: var(--geistface-grey-400);
  font-size: 7px;
}

.page--design-system .partner-mini-badges b {
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-xs);
  background: var(--panel-navy-700);
  font: 600 5px/9px var(--font-mono);
}

.page--design-system .partner-mini-badges b.brand { background: var(--mindset-violet-500); }

.page--design-system .partner-mini-badges b.focus {
  background: var(--personality-blue-500);
  color: var(--deep-navy-900);
}

.page--design-system .partner-mini-content { padding: var(--space-14) var(--space-12); }
.page--design-system .partner-mini-panels { grid-template-columns: 1fr 1fr; }

.page--design-system .partner-mini-panel {
  padding: var(--space-10);
  border: 1px solid color-mix(in srgb, var(--deep-navy-25) 9%, transparent);
  background: var(--panel-navy-900);
}

.page--design-system .partner-mini-panel-title {
  margin-bottom: var(--space-8);
  color: var(--geistface-grey-400);
  font: 600 6px/10px var(--font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.page--design-system .partner-mini-radar { min-height: 72px; }

.page--design-system .partner-mini-radar-shape {
  width: var(--space-64);
  height: var(--space-64);
  background: color-mix(in srgb, var(--mindset-violet-400) 42%, transparent);
  border: 1px solid var(--mindset-violet-400);
  clip-path: polygon(50% 2%, 76% 14%, 94% 40%, 84% 72%, 62% 92%, 38% 96%, 16% 76%, 6% 46%, 20% 18%);
}

.page--design-system .partner-mini-note {
  margin-top: var(--space-8);
  color: var(--panel-navy-400);
  font: 500 6px/10px var(--font-mono);
}

.page--design-system .partner-mini-tracks i {
  height: var(--space-4);
  background: linear-gradient(to right, var(--core-green-500) 92%, var(--panel-navy-700) 92%);
}

.page--design-system .partner-mini-tracks i.violet {
  background: linear-gradient(to right, var(--mindset-violet-400) 75%, var(--panel-navy-700) 75%);
}

.page--design-system .partner-mini-facts span,
.page--design-system .partner-mini-stats span {
  display: flex;
  justify-content: space-between;
  gap: var(--space-4);
  color: var(--panel-navy-400);
  font: 500 6px/11px var(--font-mono);
}

.page--design-system .partner-mini-facts { margin-top: var(--space-8); }
.page--design-system .partner-mini-facts b,
.page--design-system .partner-mini-stats b { color: var(--geistface-grey-200); font-weight: var(--weight-semibold); }

.page--design-system .partner-mini-analysis { margin-top: var(--space-10); }
.page--design-system .partner-mini-stats { grid-template-columns: repeat(4, 1fr); margin-top: var(--space-8); }

.page--design-system .partner-mini-timeline { height: 46px; }

.page--design-system .partner-mini-line {
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom,
    color-mix(in srgb, var(--core-green-500) 60%, transparent),
    color-mix(in srgb, var(--core-green-500) 8%, transparent));
  clip-path: polygon(0 62%, 9% 40%, 18% 52%, 27% 26%, 36% 44%, 45% 30%, 54% 48%,
    63% 22%, 72% 42%, 81% 34%, 90% 52%, 100% 38%, 100% 100%, 0 100%);
}

.page--design-system .deck-interface-mini {
  min-height: 410px;
  padding: var(--space-18) var(--space-18) var(--space-18) var(--space-32);
  background: var(--deep-navy-900);
  color: var(--geistface-grey-200);
}

.page--design-system .deck-mini-rail {
  left: var(--space-12);
  top: 50%;
  transform: translateY(-50%);
}

.page--design-system .deck-mini-rail i {
  width: var(--space-4);
  height: var(--space-4);
  background: var(--panel-navy-600);
}

.page--design-system .deck-mini-rail i.is-active {
  height: var(--space-10);
  border-radius: var(--radius-full);
  background: var(--nav-current);
}

.page--design-system .deck-mini-slide {
  width: 100%;
  max-width: 336px;
  padding: var(--space-16);
  background: var(--panel-navy-900);
}

.page--design-system .deck-mini-head {
  color: var(--panel-navy-400);
  font: 600 6px/10px var(--font-mono);
}


.page--design-system .deck-mini-title {
  margin: var(--space-10) 0 var(--space-6);
  font-size: var(--text-md-font-size);
  line-height: var(--text-xs-line-height);
  letter-spacing: -0.03em;
  font-weight: var(--weight-medium);
}


.page--design-system .deck-mini-lede {
  color: color-mix(in srgb, var(--geistface-grey-200) 66%, transparent);
  font-size: 7px;
  line-height: 11px;
}

.page--design-system .deck-mini-chips { margin-top: var(--space-10); }

.page--design-system .deck-mini-chips span {
  padding: var(--space-2) var(--space-6);
  border: 1px solid color-mix(in srgb, var(--deep-navy-25) 14%, transparent);
  color: var(--panel-navy-400);
  font: 500 5px/10px var(--font-mono);
}

.page--design-system .deck-mini-stats {
  grid-template-columns: repeat(4, 1fr);
  margin-top: var(--space-10);
}

.page--design-system .deck-mini-stats span {
  display: grid;
  padding: var(--space-6);
  border-radius: var(--radius-xs);
  background: var(--panel-navy-800);
  font: 600 10px/13px var(--font-mono);
}

.page--design-system .deck-mini-stats b {
  color: var(--panel-navy-400);
  font: 500 5px/9px var(--font-mono);
  text-transform: uppercase;
}

.page--design-system .deck-mini-foot {
  margin-top: var(--space-12);
  color: var(--panel-navy-400);
  font-size: 5px;
}

.page--design-system .deck-mini-counter {
  right: var(--space-14);
  bottom: var(--space-10);
  color: var(--panel-navy-400);
  font: 600 6px/10px var(--font-mono);
}

.page--design-system .deck-mini-progress {
  left: 0;
  bottom: 0;
  width: 32%;
  height: var(--space-2);
  background: var(--core-green-500);
}

/* ---------------------------------------------------------------------------
   Date picker

   The panel is a surface, the trigger is the shared field and the month
   controls are ordinary icon buttons. The day grid is the only geometry this
   component introduces, and it is driven by one cell token so the whole
   calendar resizes from a single value.

   Every day state is an attribute rather than a class, so a server-rendered
   calendar is correct before any script runs.
   -------------------------------------------------------------------------- */
.sp-datepicker {
  --datepicker-cell: var(--space-36);
  --datepicker-gap: var(--space-2);

  width: max-content;
  padding: var(--space-16);
  display: grid;
  gap: var(--space-16);
  box-shadow: var(--shadow-md);
}

.sp-datepicker__weekdays,
.sp-datepicker__grid {
  grid-template-columns: repeat(7, var(--datepicker-cell));
  gap: var(--datepicker-gap);
}

.sp-datepicker__weekdays span {
  height: var(--space-24);
  display: grid;
  place-items: center;
  color: var(--surface-text-muted);
  font: var(--weight-semibold) var(--text-xxs-font-size) / 1 var(--font-mono);
  text-transform: uppercase;
}

.sp-datepicker__day {
  width: var(--datepicker-cell);
  height: var(--datepicker-cell);
  padding: 0;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--radius-control);
  background: transparent;
  color: inherit;
  font: var(--weight-medium) var(--font-size-sm) / 1 var(--font-ui);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-standard),
    color var(--duration-fast) var(--ease-standard);
}

.sp-datepicker__day:hover { background: var(--surface-raised); }

.sp-datepicker__day[data-today] {
  box-shadow: inset 0 0 0 1px var(--surface-border-hover);
}

.sp-datepicker__day[data-in-range] {
  border-radius: 0;
  background: color-mix(in srgb, var(--mindset-violet-500) 22%, transparent);
}

.sp-datepicker__day[aria-selected="true"] {
  background: var(--mindset-violet-500);
  color: var(--geistface-grey-25);
}

.sp-datepicker__day[data-range-start] {
  border-radius: var(--radius-control) 0 0 var(--radius-control);
}

.sp-datepicker__day[data-range-end] {
  border-radius: 0 var(--radius-control) var(--radius-control) 0;
}

.sp-datepicker__day[data-outside] {
  color: var(--surface-text-muted);
  opacity: 0.45;
}

.sp-datepicker__day:disabled {
  color: var(--surface-text-muted);
  opacity: 0.4;
  cursor: not-allowed;
}

.sp-datepicker__day:disabled:hover { background: transparent; }

.sp-datepicker__day:focus-visible {
  outline: 0;
  box-shadow: 0 0 0 var(--focus-ring-width) var(--focus-inner),
    0 0 0 calc(var(--focus-ring-width) * 2) var(--focus-violet);
}

.sp-datepicker__foot { padding-top: var(--space-16); }

.sp-datepicker-trigger > .sp-icon {
  position: absolute;
  right: var(--space-12);
  top: 50%;
  transform: translateY(-50%);
  color: var(--surface-text-muted);
  pointer-events: none;
}

/* The 40px gutter is Part A's .field__control--trailing, claimed on the
   wrapper. On this page the ".page--design-system .field input" reset
   re-declares the padding shorthand at higher specificity and defeats it, so
   it is restated here at page scope - the same trap and remedy as .auth-affix. */
.page--design-system .sp-datepicker-trigger.field__control--trailing input {
  padding-inline-end: var(--space-40);
}

.page--design-system .sp-datepicker-legend {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-20);
}

.page--design-system .sp-datepicker-legend__item { text-align: center; }

.page--design-system .trait-module :is(.icon-box, .mini-icon, .anatomy-icon),
.page--design-system .trait-module :is(.pillar-preview, .pillar-card, .metric-card, .trait-chip) .icon-box,
.page--design-system .trait-module .trait-chip .mini-icon {
  border: 1px solid color-mix(in srgb, var(--pillar, var(--mood-500)) 52%, transparent);
  background: var(--panel-950);
  color: var(--pillar, var(--mood-500));
}


/* ==========================================================================
   09 - Unique composition · Ontology Wheel

   The wheel is a correlation dendrogram, not a taxonomy drawn in a circle, and
   that distinction drives everything below.

   Skillprint rates ~700 games on a fixed feature vocabulary. Correlating those
   ratings gives a distance between every pair of features; clustering turns the
   distances into a tree; the tree is wrapped around a circle. So RADIUS IS
   DISTANCE: two features that join near the rim are strongly correlated across
   real games, two that join near the centre are barely related. An earlier
   version of this page arranged the same features into semantic branches -
   Genre, Cognition, Traits - which reads well and throws the measurement away.
   Nothing here may reintroduce that: the ring positions are data.

   Three consequences:

   - the drawing is a disc, so its box has to be square, or the labels at twelve
     and six o'clock get cut off.
   - the leaf terms are drawn at SVG scale, so their size is a custom property
     in user units, raised at the narrow breakpoints where the same drawing is
     scaled down and 10.5px would land under 8px on screen.
   - the guide rings are not decoration. They are the distance axis and they are
     labelled, so the merge heights can actually be read off the picture.

   Cluster colour is published here and nowhere else. Every group, legend entry,
   tooltip and the detail card carries data-cluster; --item resolves from that
   one map, in both surfaces, and the script holds no hex at all.
   ========================================================================== */

:is(.page--ontology-wheel, .ontology-root) {
  --ontology-leaf-size: 10.5px;
  --ontology-card-padding: var(--space-20);
}

/* The six clusters are the k=6 cut of the tree, in descending size. They are
   empirical: they cross-cut Skillprint's own Skill / Mood / Goal / Mechanic
   categories, which is the finding, not a defect. Slugs describe membership so
   they survive a change of display name. */
:is(.page--ontology-wheel, .ontology-root) [data-cluster="core"]     { --item: var(--personality-blue-500); }
:is(.page--ontology-wheel, .ontology-root) [data-cluster="puzzle"]   { --item: var(--skills-pink-500); }
:is(.page--ontology-wheel, .ontology-root) [data-cluster="reflex"]   { --item: var(--mindset-magenta-500); }
:is(.page--ontology-wheel, .ontology-root) [data-cluster="rewards"]  { --item: var(--skills-orange-500); }
:is(.page--ontology-wheel, .ontology-root) [data-cluster="openplay"] { --item: var(--personality-mint-500); }
:is(.page--ontology-wheel, .ontology-root) [data-cluster="levels"]   { --item: var(--core-green-500); }

/* The 500s are tuned for a dark surface, and the drawing now has one: the plate
   is pinned dark in both colour modes so the ink is the same wherever it is
   read and whatever it is exported into. The light-mode step-down that used to
   live here fired on the page's theme, not the plate's, so on a light page it
   put the 700s and 800s on a near-black ground and every accent went muddy.
   One surface, one ramp. */

/* ---- The wheel ----------------------------------------------------------- */

/* The distance axis. Faint, but present at every quarter of the tree's height
   so the eye has something to measure the merges against. */
:is(.page--ontology-wheel, .ontology-root) .ring {
  fill: none;
  stroke: var(--surface-border);
  stroke-width: 1;
  opacity: 0.55;
}

:is(.page--ontology-wheel, .ontology-root) .ring--full { stroke-dasharray: 2 4; }

/* A link is one elbow of the dendrogram: the arc that spans two children plus
   the two radial runs that reach it.

   Neither its colour nor its opacity is set here. Both are written onto the
   element as presentation attributes when the print is drawn, because the
   opacity IS the score and an exported image is serialised markup with no
   stylesheet attached - a score that lived in this file would survive on the
   page and vanish from every download. The only thing this rule may not do is
   declare `stroke` or `opacity`, which would beat those attributes and flatten
   every print to one value. */
:is(.page--ontology-wheel, .ontology-root) .link {
  fill: none;
  stroke: var(--item, var(--surface-border-hover));
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: stroke-width var(--duration-fast) var(--ease-standard);
}

/* Hovering a term traces its own path back to the root. Both of the line's own
   properties are now readings - opacity and width both carry the score - so the
   trace cannot borrow either without lying about a number. It glows instead:
   a halo sits outside the stroke and changes nothing about it. A faint line
   still reads faint while traced, which is correct, and the panel beside the
   drawing carries the figure for the ones too faint to follow. */
:is(.page--ontology-wheel, .ontology-root) .link.is-path {
  filter: drop-shadow(0 0 3px rgb(255 255 255 / 65%));
}

:is(.page--ontology-wheel, .ontology-root) .leaf-label {
  fill: var(--surface-text-muted);
  font-family: var(--font-mono);
  font-size: var(--ontology-leaf-size);
  font-weight: var(--weight-medium);
  pointer-events: none;
  transition: opacity var(--duration-fast) var(--ease-standard),
    fill var(--duration-fast) var(--ease-standard);
}

:is(.page--ontology-wheel, .ontology-root) .leaf-label.is-hovered { fill: var(--surface-text); }

/* The score rides on the term, and is the brighter half of the pair: white and
   a step heavier than the name it follows, so the figures read first and the
   terms read as their labels. Tabular, because 87 of them ring the disc. */
:is(.page--ontology-wheel, .ontology-root) .leaf-value {
  fill: var(--pure-white);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

:is(.page--ontology-wheel, .ontology-root) .leaf-label.is-hovered .leaf-value {
  fill: var(--surface-text);
  opacity: 1;
}

/* ---- Isolating one correlation group ------------------------------------- */

/* Draining the colour, never the ink. Opacity and width are both readings now,
   so the only channel isolation may touch is the one that carries no number.
   A faded group keeps every line at exactly the weight and opacity it had. */
:is(.page--ontology-wheel, .ontology-root) .cluster-group {
  transition: filter var(--duration-fast) var(--ease-standard);
}

:is(.page--ontology-wheel, .ontology-root) .cluster-group.is-faded { filter: saturate(0.06); }
:is(.page--ontology-wheel, .ontology-root) .cluster-group.is-faded .leaf-label { opacity: 0.32; }

:is(.page--ontology-wheel, .ontology-root) .skillprint__wheel.is-isolated .ring { opacity: 0.28; }

/* The hit area is the label, not a dot on the end of the branch: a term is
   read before it is aimed at, so the pointer target is the word itself. */
:is(.page--ontology-wheel, .ontology-root) .leaf-hit { fill: transparent; cursor: pointer; }

:is(.page--ontology-wheel, .ontology-root) .leaf-hit:focus-visible {
  outline: 2px solid var(--focus-default);
  outline-offset: 1px;
}

:is(.page--ontology-wheel, .ontology-root) .centre-disc {
  fill: var(--surface-box);
  stroke: var(--surface-border-hover);
  stroke-width: 1.4;
}

:is(.page--ontology-wheel, .ontology-root) .centre-title {
  fill: var(--surface-text);
  font-family: var(--font-ui);
  font-size: 21px;
  font-weight: var(--weight-medium);
  letter-spacing: -0.03em;
}

:is(.page--ontology-wheel, .ontology-root) .centre-copy {
  fill: var(--surface-text-muted);
  font-family: var(--font-mono);
  font-size: 9px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

/* The key under the disc. It sits inside the drawing, not beside it, so the
   exported image explains itself: a print dropped into a deck arrives with its
   own legend attached. */
/* The key is chrome, not data, so it sits in the UI face rather than the mono
   one. Mono is reserved for figures that line up in a column - the scores in
   the panel, the terms around the rim - and two numbers at the ends of a bar
   line up with nothing. */
:is(.page--ontology-wheel, .ontology-root) .key-value {
  fill: var(--surface-text);
  font-family: var(--skillprint-font-ui);
  font-size: 13px;
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

:is(.page--ontology-wheel, .ontology-root) .key-foot {
  fill: var(--surface-text-muted);
  font-family: var(--skillprint-font-ui);
  font-size: 12px;
  letter-spacing: 0;
}

/* The ramp is neutral on purpose. The lines in the disc are coloured by the
   group they belong to, so a coloured key would look like it named one group;
   this one is about the other channel entirely. */
:is(.page--ontology-wheel, .ontology-root) .key-ramp { stroke-linecap: round; }

/* The cluster-isolation states that used to live here - .is-faded, .is-active,
   .is-isolated - are gone with the colour key that drove them. Dimming a
   cluster meant overriding opacity across the tree, and opacity is now the
   score, so isolation and the reading cannot both have it. Reading one skill is
   the hover trace plus the panel beside the drawing instead. */

/* ---- The plate ----------------------------------------------------------- */

/* A print is drawn in white ink, so the ground under it has to stay dark in
   both colour modes - white on the light theme's panel would erase the whole
   drawing. This re-declares the panel surface tokens to their dark values for
   this subtree only, in the shape .chart-card.dark already uses for a dark
   island on a light page.

   data-surface="dark" is NOT enough on its own: it re-declares --surface-raised
   and nothing else, so on the light theme it produces a half-flipped plate with
   a light panel and light text. Every token the drawing reads has to be listed.

   It is also what makes the export honest - the PNG is the same ink on the same
   ground whichever mode the page happened to be in when the button was hit. */
:is(.page--ontology-wheel, .ontology-root) [data-sp-terms] { display: none; }

:is(.page--ontology-wheel, .ontology-root) .ontology-visual {
  color-scheme: dark;
  --surface-panel: var(--panel-navy-950);
  --surface-box: var(--panel-navy-900);
  --surface-raised: var(--panel-navy-800);
  --surface-text: var(--geistface-grey-200);
  --surface-text-muted: var(--geistface-grey-400);
  --surface-border: color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
  --surface-border-hover: color-mix(in srgb, var(--deep-navy-25) 20%, transparent);
  /* The plate carries a card head as well as the drawing, and that head is
     written with the ordinary text utilities - .text-muted, .text-subtle - which
     read the --ui-* family, not --surface-*. Flipping only the surface tokens
     left the caption as light-theme grey on a near-black plate.

     --ui-muted and its siblings have to be set HERE, as literals. They are
     declared once on .page as `var(--muted-text, var(--muted, ...))`, and a
     custom property's value is resolved where it is declared: by the time a
     descendant sees --ui-muted it is already a concrete colour, so overriding
     --muted further down the tree changes nothing. Re-declaring the source
     token is the mistake that looks like it should work. */
  --ui-text: var(--geistface-grey-200);
  --ui-muted: var(--geistface-grey-400);
  --ui-surface: var(--panel-navy-900);
  --ui-surface-raised: var(--panel-navy-800);
  --ui-border: color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
  --ui-border-strong: color-mix(in srgb, var(--deep-navy-25) 20%, transparent);
  --text: var(--geistface-grey-200);
  --text-strong: var(--pure-white);
  --muted: var(--geistface-grey-400);
  --border: color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
  background: var(--surface-panel);
  color: var(--surface-text);
}

/* ---- The readout --------------------------------------------------------- */

/* One skill at a time, in figures, beside the drawing. It carries the scores
   the drawing cannot: a skill at 4% is correctly almost invisible in the disc,
   and this is where its number is legible. */
:is(.page--ontology-wheel, .ontology-root) .sp-read__score {
  display: block;
  margin-top: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--display-md-font-size);
  line-height: var(--display-md-line-height);
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.04em;
}

:is(.page--ontology-wheel, .ontology-root) .sp-read__name {
  display: block;
  margin-top: var(--space-10);
  color: var(--ui-muted);
  font-family: var(--font-mono);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  letter-spacing: 0.04em;
}

/* Nothing under the pointer: the two filled-on-hover rows come out of the flow
   entirely rather than rendering empty, so the card is exactly its note tall. */
:is(.page--ontology-wheel, .ontology-root) .ontology-detail[data-read="idle"] .sp-read__name,
:is(.page--ontology-wheel, .ontology-root) .ontology-detail[data-read="idle"] .sp-read__score { display: none; }

:is(.page--ontology-wheel, .ontology-root) .ontology-detail[data-read="skill"] [data-sp-read-note] {
  margin-top: var(--space-6);
}

/* ---- Strength by group --------------------------------------------------- */

/* The six correlation groups, averaged for this person. The bar says the score
   twice - once as width, once as the opacity the disc would have given that
   line - so the key under the wheel reads these rows too.

   The ink here is the card's own text colour, not the white of the drawing:
   these cards sit on the page, not on the pinned-dark plate, so a white fill
   would be invisible in light mode. Opacity carries the meaning; the hue just
   has to survive both themes. */
/* A control, so it needs the button reset: a <button> brings its own font,
   background, border and centred text, none of which belong on a table row. */
:is(.page--ontology-wheel, .ontology-root) .sp-strength {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 84px auto;
  align-items: center;
  gap: var(--space-10);
  width: 100%;
  min-height: var(--space-28);
  padding: var(--space-2) 0;
  border: 0;
  background: none;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

:is(.page--ontology-wheel, .ontology-root) .sp-strength:hover .sp-strength__name,
:is(.page--ontology-wheel, .ontology-root) .sp-strength.is-active .sp-strength__name {
  color: var(--ui-text);
}

:is(.page--ontology-wheel, .ontology-root) .sp-strength[aria-pressed="true"] .sp-strength__name {
  font-weight: var(--weight-semibold);
}

:is(.page--ontology-wheel, .ontology-root) .sp-strength:focus-visible {
  outline: 2px solid var(--focus-default);
  outline-offset: 2px;
  border-radius: var(--radius-compact);
}

:is(.page--ontology-wheel, .ontology-root) .sp-strength__name {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  min-width: 0;
  color: var(--ui-text);
  font-size: var(--text-xs-font-size);
}

/* The dot is the colour key. These rows sit on the page rather than the plate,
   so they take the light-mode accent step-down that the drawing no longer
   needs - a 500 that reads on near-black is too pale on a white card. */
:is(.page--ontology-wheel, .ontology-root) .sp-strength__name::before {
  content: "";
  width: var(--space-8);
  height: var(--space-8);
  flex: 0 0 auto;
  border-radius: var(--radius-full);
  background: var(--item, var(--ui-muted));
}

html:is([data-theme="light"], [data-surface="light"]) :is(.page--ontology-wheel, .ontology-root) .sp-strength[data-cluster="core"]     { --item: var(--personality-blue-700); }
html:is([data-theme="light"], [data-surface="light"]) :is(.page--ontology-wheel, .ontology-root) .sp-strength[data-cluster="puzzle"]   { --item: var(--skills-pink-600); }
html:is([data-theme="light"], [data-surface="light"]) :is(.page--ontology-wheel, .ontology-root) .sp-strength[data-cluster="reflex"]   { --item: var(--mindset-magenta-700); }
html:is([data-theme="light"], [data-surface="light"]) :is(.page--ontology-wheel, .ontology-root) .sp-strength[data-cluster="rewards"]  { --item: var(--skills-orange-700); }
html:is([data-theme="light"], [data-surface="light"]) :is(.page--ontology-wheel, .ontology-root) .sp-strength[data-cluster="openplay"] { --item: var(--personality-mint-800); }
html:is([data-theme="light"], [data-surface="light"]) :is(.page--ontology-wheel, .ontology-root) .sp-strength[data-cluster="levels"]   { --item: var(--core-green-700); }

:is(.page--ontology-wheel, .ontology-root) .sp-strength__track {
  height: var(--space-6);
  border-radius: var(--radius-full);
  background: var(--surface-raised);
  overflow: hidden;
}

:is(.page--ontology-wheel, .ontology-root) .sp-strength__fill {
  display: block;
  height: 100%;
  border-radius: var(--radius-full);
  background: var(--ui-text);
}

:is(.page--ontology-wheel, .ontology-root) .sp-strength__value {
  color: var(--surface-text-muted);
  font-family: var(--font-mono);
  font-size: var(--text-xxs-font-size);
  font-variant-numeric: tabular-nums;
}

/* ---- Page composition ---------------------------------------------------- */

.page--ontology-wheel .ontology-shell {
  padding-block: var(--space-32) var(--space-32);
  gap: var(--space-28);
  grid-template-rows: auto auto auto;
}

.page--ontology-wheel .ontology-intro {
  grid-template-columns: minmax(0, 1fr) minmax(320px, 480px);
  gap: var(--space-64);
}

.page--ontology-wheel .ontology-title {
  max-width: 920px;
  font-size: clamp(38px, 4.6vw, var(--display-2xl-font-size));
  line-height: 1.02;
  letter-spacing: -0.05em;
}

:is(.page--ontology-wheel, .ontology-root) .ontology-stage { grid-template-columns: minmax(0, 1fr) 340px; }

/* The blank dial has no panel beside it - there is nothing to read out - so it
   drops the second column. Its disc is held to the width the two prints below
   get after their panel is subtracted, so all three read at the same size and
   the comparison is honest. */
:is(.page--ontology-wheel, .ontology-root) .ontology-stage--base { grid-template-columns: minmax(0, 1fr); }

:is(.page--ontology-wheel, .ontology-root) .ontology-stage--base .ontology-visual__stage {
  width: 100%;
  max-width: 760px;
  margin-inline: auto;
}

/* Square: no key band under this one. */
:is(.page--ontology-wheel, .ontology-root) .ontology-stage--base .skillprint__wheel { aspect-ratio: 1; }
/* Cards size to their content and stack from the top. The middle row used to be
   minmax(0, 1fr), which stretched the reading card to whatever height was left
   beside the disc - a short line of text holding open half a metre of empty
   panel. The column being shorter than the drawing is fine; a card full of
   nothing is not. */
:is(.page--ontology-wheel, .ontology-root) .ontology-side {
  grid-template-rows: auto auto auto;
  align-content: start;
}

:is(.page--ontology-wheel, .ontology-root) .ontology-visual { grid-template-rows: auto auto; }

:is(.page--ontology-wheel, .ontology-root) .ontology-visual__head { padding: var(--ontology-card-padding); }

/* The head is title-plus-controls on one row, and the row is wider than a
   phone: the panel clips overflow, so "Download PNG" was cut off the right
   edge and unreachable below ~430px. Wrapping puts the controls on their own
   line instead of hiding them. */

@media (max-width: 560px) {
  :is(.page--ontology-wheel, .ontology-root) .ontology-visual__head {
      flex-wrap: wrap;
      row-gap: var(--space-12);
    }

  :is(.page--ontology-wheel, .ontology-root) .ontology-visual__head > * { flex: 1 1 100%; }
}

/* Sized by the column, and never letterboxed: the labels radiate to a circle,
   so any aspect ratio but the drawing's own crops the terms at twelve and six
   o'clock. The ratio is the viewBox's, disc plus the key band under it. */
:is(.page--ontology-wheel, .ontology-root) .skillprint__wheel {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1000 / 1084;
}

:is(.page--ontology-wheel, .ontology-root) .ontology-visual__stage { overflow: hidden; }

.page--ontology-wheel .sp-nav__brand .brand-logo { width: 158px; }

:is(.page--ontology-wheel, .ontology-root) .ontology-card { padding: var(--ontology-card-padding); }

:is(.page--ontology-wheel, .ontology-root) .ontology-heading {
  font-size: var(--text-xxs-font-size);
  line-height: var(--space-16);
  letter-spacing: 0.11em;
}

:is(.page--ontology-wheel, .ontology-root) .ontology-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }

:is(.page--ontology-wheel, .ontology-root) .ontology-metric {
  min-height: var(--space-80);
  padding: var(--space-12);
  background: var(--surface-raised);
}

/* strong carries the browser default of bolder, which resolves to a weight off
   the 400/500/600/700 scale and read heavier than every other number in the
   system. Stated as medium, the same weight the chart-card stat figures use. */
:is(.page--ontology-wheel, .ontology-root) .ontology-metric strong {
  font-size: var(--display-xs-font-size);
  line-height: var(--display-xs-line-height);
  letter-spacing: -0.035em;
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
}

/* A tile holding a skill name rather than a figure. Two changes from the tile
   next to it: the UI face rather than the mono one, because a name is not a
   figure and mono is both wider and wrong for it, and a body size, because
   "Perceptual speed" at display size breaks mid-word inside a half-column. */
:is(.page--ontology-wheel, .ontology-root) .ontology-metric--term strong {
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
  letter-spacing: -0.02em;
}

/* The card takes data-cluster while a cluster is being read and drops it again
   on reset, so the resting accent is a fallback on each use rather than a
   declared --item: a declaration here would sit at the same specificity as the
   accent map, further down the file, and win permanently. */
:is(.page--ontology-wheel, .ontology-root) .ontology-detail {
  border-color: var(--item, var(--mindset-violet-400));
  transition: border-color var(--duration-fast) var(--ease-standard);
}

/* The correlation-group key on the dial. One column: the names run to two words
   and each carries its group's size, so a two-column key wrapped every second
   entry. Each row is also the isolation control for that group. */
:is(.page--ontology-wheel, .ontology-root) .ontology-legend { grid-template-columns: 1fr; }

/* No --item default here: the entry carries data-cluster and the accent map at
   the top of this section already answers for it. A default declared this far
   down the file has the same specificity and simply wins, which is how every
   dot in the key once ended up violet. */
:is(.page--ontology-wheel, .ontology-root) .ontology-legend .ui-tag {
  min-height: var(--space-36);
  justify-content: flex-start;
  gap: var(--space-8);
  width: 100%;
  cursor: pointer;
}

:is(.page--ontology-wheel, .ontology-root) .ontology-legend .ui-tag::before {
  content: "";
  width: var(--space-8);
  height: var(--space-8);
  flex: 0 0 auto;
  border-radius: var(--radius-full);
  background: var(--item);
}

:is(.page--ontology-wheel, .ontology-root) .ontology-legend__count {
  margin-left: auto;
  color: var(--surface-text-muted);
  font-family: var(--font-mono);
  font-size: var(--text-xxs-font-size);
  font-variant-numeric: tabular-nums;
}

:is(.page--ontology-wheel, .ontology-root) .ontology-legend .ui-tag:hover,
:is(.page--ontology-wheel, .ontology-root) .ontology-legend .ui-tag.is-active,
:is(.page--ontology-wheel, .ontology-root) .ontology-legend .ui-tag[aria-pressed="true"] {
  border-color: var(--item);
  color: var(--surface-text);
}

:is(.page--ontology-wheel, .ontology-root) .ontology-legend .ui-tag[aria-pressed="true"] {
  background: color-mix(in srgb, var(--item) 10%, var(--surface-box));
}

/* The dial's key sits on a page card, not on the pinned-dark plate, so its dots
   take the light-mode step-down the drawing no longer needs. */
html:is([data-theme="light"], [data-surface="light"]) :is(.page--ontology-wheel, .ontology-root) .ontology-legend [data-cluster="core"]     { --item: var(--personality-blue-700); }
html:is([data-theme="light"], [data-surface="light"]) :is(.page--ontology-wheel, .ontology-root) .ontology-legend [data-cluster="puzzle"]   { --item: var(--skills-pink-600); }
html:is([data-theme="light"], [data-surface="light"]) :is(.page--ontology-wheel, .ontology-root) .ontology-legend [data-cluster="reflex"]   { --item: var(--mindset-magenta-700); }
html:is([data-theme="light"], [data-surface="light"]) :is(.page--ontology-wheel, .ontology-root) .ontology-legend [data-cluster="rewards"]  { --item: var(--skills-orange-700); }
html:is([data-theme="light"], [data-surface="light"]) :is(.page--ontology-wheel, .ontology-root) .ontology-legend [data-cluster="openplay"] { --item: var(--personality-mint-800); }
html:is([data-theme="light"], [data-surface="light"]) :is(.page--ontology-wheel, .ontology-root) .ontology-legend [data-cluster="levels"]   { --item: var(--core-green-700); }

:is(.page--ontology-wheel, .ontology-root) .ontology-foot { padding-top: var(--space-4); }

/* The bubble is a document-level overlay, so it is styled as one.

   It used to be scoped `:is(.page--ontology-wheel, .ontology-root)
   .ontology-tooltip`, which asks the tooltip to be a DESCENDANT of the figure
   it describes. It is not, and it cannot be: there is one `#tooltip` per
   document, found with getElementById and positioned `fixed`, and on
   Portal_Profile it deliberately sits outside BOTH `.ontology-root` blocks
   because those two are swapped by state - nested inside either one, it would
   vanish with it. So on that page none of these rules matched and the bubble
   fell back to its utility classes alone: strong, score and facet all inline
   on one run-on line, "Clicker50%Skillprint category . Genre", with no rule
   between the figure and its caption. Ontology_Wheel.html was unaffected
   because its page class is on <body>, and the marketing site's copy happens
   to sit inside the wrapper - which is what kept this hidden.

   Scoping the tooltip to itself is what the component actually is. Nothing
   else in the sheet owns a `.ontology-tooltip*` class, so this reaches exactly
   the same element on every page and one page more than it used to. */
.ontology-tooltip {
  z-index: 50;
  min-width: 148px;
  max-width: 260px;
  padding: var(--space-8) var(--space-10);
  opacity: 0;
  pointer-events: none;
  transform: translateY(var(--space-4));
  transition: opacity var(--duration-fast) var(--ease-standard),
    transform var(--duration-fast) var(--ease-standard);
}

.ontology-tooltip.is-visible { opacity: 1; transform: none; }
.ontology-tooltip strong { display: block; color: var(--surface-text); font-size: var(--text-xs-font-size); }
.ontology-tooltip span { display: block; color: var(--surface-text-muted); font-size: var(--text-xxs-font-size); }

/* The number is the reason the tooltip exists, so it is the largest thing in
   it. Everything else in the bubble is context for it.

   The `span` is load-bearing. The rule above sets every span in the bubble to
   10px and, as an element plus two classes, outweighs a bare .ontology-tooltip__score
   - which left the figure rendering at caption size. Matching the element here
   puts this rule ahead of it. */
.ontology-tooltip span.ontology-tooltip__score {
  color: var(--surface-text);
  font-family: var(--font-mono);
  font-size: var(--display-sm-font-size);
  line-height: var(--display-sm-line-height);
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.035em;
}

/* The facet is Skillprint's own category for the term - Skill, Mood, Goal and
   the rest. It is deliberately shown next to the cluster: seeing a Mood and a
   Genre sitting in one cluster is the whole point of the correlation. */
.ontology-tooltip__facet {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--surface-border);
  color: var(--surface-text-muted);
  font-family: var(--font-mono);
}


/* ==========================================================================
   09 - Unique composition · Game Session

   One surface at two moments. Game Complete shows the result on its own;
   Game Session plays the whole sequence the portal runs - load, start,
   play with live adjustments, calculate, review - so both share this
   composition rather than owning two copies of it.

   Only the stage is page-specific: it reproduces the game's own light canvas,
   which the dark popup and the adjustment banner are read against. The dialog
   follows the canonical Session complete pattern documented in the design
   system; the score block, the signal rows and the sequence states are the
   additions a finished game needs.
   ========================================================================== */

.page--game-session {
  --stage-canvas : var(--geistface-grey-100);
  --stage-ink    : var(--geistface-grey-900);
  --stage-quiet  : color-mix(in srgb, var(--geistface-grey-900) 46%, transparent);
  --stage-hex    : color-mix(in srgb, var(--geistface-grey-900) 7%, transparent);
  --stage-control: color-mix(in srgb, var(--geistface-grey-900) 16%, transparent);
  --stage-hexagon: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);

  /* The stage draws whichever game it is pointed at rather than the one these
     screens were built around, so the contract gains the values that treatment
     needs. --stage-accent is the catalogue's own accent for the current game
     and the module rewrites it on the body per game; it defaults to Hextris's,
     because Hextris is what a missing or unknown ?game= falls back to, so the
     screens still open exactly as they did with the script off. Everything
     below is derived from it, which keeps one place where a game's colour
     enters the stage. */
  --stage-accent   : var(--personality-mint-500);
  --stage-veil-tint: color-mix(in srgb, var(--stage-accent) 12%, var(--geistface-grey-25));
  --stage-mark-veil: color-mix(in srgb, var(--stage-veil-tint) 62%, transparent);
  --stage-art-blur : clamp(28px, 6vw, 72px);
  --stage-art-scale: 1.18;
  --result-line  : color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
  --result-fill  : color-mix(in srgb, var(--panel-navy-900) 60%, transparent);

  min-height: 100dvh;
  background: var(--stage-canvas);
  color: var(--stage-ink);
}

/* ---------------------------------------------------------------------------
   The game canvas behind the dialog
   -------------------------------------------------------------------------- */

.page--game-session .game-stage {
  /* The artwork field is a fixed element sitting behind the whole screen, and
     nothing inside the stage was ever positioned, so an unpositioned grid would
     be painted underneath it. This is what puts the board back on top. */
  position: relative;
  z-index: 1;

  min-height: 100dvh;
  padding: var(--space-24);
  grid-template-rows: auto minmax(0, 1fr);
  gap: var(--space-24);
}

.page--game-session .game-stage__hud {
  position: relative;
}

.page--game-session .game-stage__help {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  width: var(--space-64);
  height: 56px;
  padding: 0;
  border: 0;
  clip-path: var(--stage-hexagon);
  background: var(--stage-control);
  color: var(--geistface-grey-25);
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-xl-font-size);
  font-weight: var(--weight-semibold);
  cursor: pointer;
  transition: var(--transition-interactive);
}

.page--game-session .game-stage__help:hover {
  background: color-mix(in srgb, var(--geistface-grey-900) 26%, transparent);
}

.page--game-session .game-stage__score-label {
  color: var(--stage-quiet);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.page--game-session .game-stage__score-value {
  font-size: var(--display-sm-font-size);
  line-height: var(--display-sm-line-height);
  font-weight: var(--weight-regular);
  letter-spacing: -0.01em;
}

.page--game-session .game-stage__board {
  place-content: center;
  justify-items: center;
  text-align: center;
}

.page--game-session .game-stage__hex {
  width: min(80vw, 680px);
  aspect-ratio: 8 / 7;
  clip-path: var(--stage-hexagon);
  background: var(--stage-hex);
}

/* The wordmark is the current game's title now, and titles are not all seven
   characters long: "Space Adventure Pinball" at a flat 12vw is three times the
   width of "Hextris" and runs off a 1440px screen. CSS cannot measure a string,
   so the module publishes the title's length on the body and the size takes
   whichever is smaller - the poster size, or the size that fits that many
   characters across. At the default seven characters min() picks 12vw, so
   nothing about the screen as it shipped changes. Wrapping stays available
   underneath as the last resort, and the floor drops to 32px so a long title on
   a phone wraps to two readable lines rather than two enormous ones. */
.page--game-session .game-stage__wordmark {
  max-width: 100%;
  font-size:
    clamp(
      var(--space-32),
      min(12vw, calc(150vw / var(--stage-title-chars, 7))),
      156px
    );
  line-height: 1.06;
  font-weight: var(--weight-regular);
  letter-spacing: -0.02em;
  text-wrap: balance;
}

.page--game-session .game-stage__play {
  width: clamp(112px, 15vw, 176px);
  margin-top: clamp(var(--space-20), 3vw, var(--space-40));
  aspect-ratio: 8 / 7;
  padding: 0;
  border: 0;
  clip-path: var(--stage-hexagon);
  background: var(--stage-ink);
  cursor: pointer;
  transition: var(--transition-interactive);
}

.page--game-session .game-stage__play:hover {
  background: color-mix(in srgb, var(--geistface-grey-900) 84%, transparent);
}

.page--game-session .game-stage__play-mark {
  width: 28%;
  aspect-ratio: 5 / 6;
  margin-inline-start: 8%;
  clip-path: polygon(0% 0%, 100% 50%, 0% 100%);
  background: var(--geistface-grey-25);
}

.page--game-session .game-stage__play-label {
  margin-top: var(--space-18);
  font-size: clamp(22px, 3vw, 38px);
  line-height: 1.2;
  font-weight: var(--weight-regular);
}

/* ---------------------------------------------------------------------------
   The session result dialog
   -------------------------------------------------------------------------- */

/* The canvas underneath is the game's own light artwork, so the scrim is
   lighter and softer than the default: enough separation for the dialog,
   little enough that the player still sees the board they just left. */
.page--game-session .popup-backdrop {
  position: fixed;
  padding: var(--space-24);
  background: color-mix(in srgb, var(--deep-navy-950) 26%, transparent);
  -webkit-backdrop-filter: blur(var(--backdrop-blur-sm));
  backdrop-filter: blur(var(--backdrop-blur-sm));
}

/* Wide enough for four skill scores on one row, which is what a session
   returns; the analysis then fits without scrolling on a normal screen. */
/* The ceiling is the backdrop's padded box, not the viewport. Measured against
   `100dvh` it ignored whatever the backdrop had reserved for the floating review
   controls, so on a phone - where the picker docks as a full-width bar and the
   backdrop pads 80px clear of it - a dialog taller than that box overflowed its
   own centring and slid up under the bar by 9px. `100%` resolves against the
   grid area, which already excludes the padding, so the dialog follows whatever
   either screen reserves without restating the arithmetic. */
.page--game-session .popup {
  width: min(var(--space-720), 100%);
  max-height: 100%;
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
}

.page--game-session .popup-backdrop.is-open .popup {
  animation: game-result-enter var(--duration-slow) var(--ease-standard) both;
}

@keyframes game-result-enter {
  from {
    opacity: 0;
    transform: translateY(var(--space-12)) scale(0.98);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

/* The scrollbar gets its own gutter so the content does not shift the moment
   the analysis grows past the viewport, and the scroll stays inside the
   dialog rather than carrying on into the page behind it. The channel itself
   is the subtle variant, applied as a class on the element. */
.page--game-session .popup__content {
  padding: var(--space-20) var(--space-20) var(--space-20) var(--space-24);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
}

/* The analysis is the one scroller on these screens the reader has to work,
   so it carries .scrollbar-violet and opts back out of the page's subtle
   variant. What the class alone cannot do is pick the right ramp: this is a
   dark panel inside a light page, the one case the variants do not cover, so
   `.scrollbar-violet` resolved to the light ramp - violet-500, drawn for white
   grounds - and read at 1.57:1 against this plate, fainter than the grey it
   replaced. These four are the sheet's own dark violet ramp verbatim, restated
   on the element that owns the dark ground. It is the move `.popup--dark` and
   the step rail already document: a custom property is substituted where it is
   declared, so a variant chosen at page level could never reach this panel.

   `.popup--dark` is in the selector to carry it, not for looks. The light ramp
   reaches this scroller through `html[data-surface="light"] > .page`, and an
   attribute inside :is() counts, so that rule is (0,3,1) - a plain page-scoped
   `.popup__content.scrollbar-violet` is (0,3,0) and quietly loses. Naming the
   dark plate makes it (0,4,0) and says which condition is being answered. */
.page--game-session .popup--dark .popup__content.scrollbar-violet {
  --scrollbar-housing: color-mix(in srgb, var(--mindset-violet-200) 16%, transparent);
  --scrollbar-thumb: color-mix(in srgb, var(--mindset-violet-400) 72%, transparent);
  --scrollbar-thumb-hover: color-mix(in srgb, var(--mindset-violet-400) 88%, transparent);
  --scrollbar-thumb-active: var(--mindset-violet-400);
}

/* The label family, on this dialog's own ground

   `.ui-label` and `.portal-eyebrow` are declared under `.page--portal`, and
   these two screens deliberately carry no portal shell — so every label in the
   analysis fell back to the page's 16px body step. A metric's name then sat
   larger than the mono value under it, and the two section eyebrows read as
   headings rather than as eyebrows. The contract is the portal's; the colour is
   not, because `--ui-muted` is the light page's slate and disappears against
   this plate. Muting stays where it already was, on `opacity`. */
.page--game-session .ui-label {
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-medium);
  letter-spacing: normal;
  text-transform: none;
}

.page--game-session .portal-eyebrow {
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.page--game-session .game-result__close svg {
  width: var(--space-16);
  height: var(--space-16);
  flex: 0 0 var(--space-16);
  --icon-stroke: var(--icon-stroke-16);
}

/* The icon anchors the header rather than occupying a row of its own. */
.page--game-session .game-result__head {
  margin-top: var(--space-20);
}

/* `.page :where(h2)` is the page-level heading scale, which resolves to 48px at
   this width — a poster headline inside a 720px dialog, and half again the size
   of the score it was introducing. The dialog names itself at the display step
   the metrics use, so the number stays the loudest thing on the plate. */
.page--game-session .game-result__head h2 {
  font-size: var(--display-xs-font-size);
  line-height: var(--display-xs-line-height);
  letter-spacing: -0.02em;
}

/* Sized against the header block beside it rather than against the design
   system popup, where the same plate is centred above the title with margins. */
.page--game-session .game-result__icon {
  width: var(--space-44);
  height: var(--space-44);
  flex: 0 0 var(--space-44);
}

.page--game-session .game-result__description {
  margin-top: var(--space-4);
  color: color-mix(in srgb, var(--geistface-grey-200) 72%, transparent);
}

.page--game-session :is(.game-result__score, .game-result__summary) {
  border: 1px solid var(--result-line);
  background: var(--result-fill);
}

/* Score and session facts sit side by side; at 720px there is room for both,
   and the analysis below starts a screenful earlier because of it. */
.page--game-session .game-result__overview {
  margin-top: var(--space-20);
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  align-items: stretch;
}

.page--game-session .game-result__score {
  gap: var(--space-4);
  align-content: center;
}

.page--game-session .game-result__score > div span {
  opacity: 0.56;
}

/* A personal best is the one thing on this screen worth shouting about, so the
   badge carries the green rather than tinting it away against the panel. */
.page--game-session .game-result__best {
  --badge-accent: var(--deep-navy-900);
  --badge-bg: var(--green);
  --badge-border: var(--green);
  --badge-text: var(--deep-navy-900);

  font-weight: var(--weight-semibold);
}

.page--game-session .game-result__score-value {
  font-family: var(--font-mono);
  font-size: var(--display-md-font-size);
  line-height: var(--display-md-line-height);
  font-weight: var(--weight-medium);
  letter-spacing: -0.02em;
}

.page--game-session .game-result__score-note {
  line-height: var(--text-xs-line-height);
  color: color-mix(in srgb, var(--geistface-grey-200) 62%, transparent);
}

.page--game-session .game-result__summary {
  align-content: center;
}

.page--game-session .game-result__summary span {
  margin-bottom: var(--space-4);
  opacity: 0.56;
}

/* One step of the real scale, and a step above the label over it: at 11px the
   session facts read smaller than the words naming them. */
.page--game-session .game-result__summary strong {
  font-family: var(--font-mono);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
  font-weight: var(--weight-medium);
  overflow-wrap: anywhere;
}

/* The leading control is borderless, so the footer is padded to where its label
   lands rather than to its button box: the inline start pays the button's own
   padding back below, and the inline end carries the scrollbar gutter that the
   content column reserves, so both edges sit on the column above. */
.page--game-session .popup__footer {
  padding: var(--space-16) var(--space-32) var(--space-20) var(--space-24);
  align-items: center;
  justify-content: space-between;
  border-top-color: var(--result-line);
  background: color-mix(in srgb, var(--deep-navy-900) 26%, transparent);
  box-shadow: 0 -12px 24px -12px color-mix(in srgb, var(--deep-navy-950) 60%, transparent);
}

/* The borderless control aligns on its label rather than on its box: it gives
   back its own inline padding so "Back to games" sits on the content column
   above, and trims what is left so its hover plate still clears the edge. */
.page--game-session .popup__footer .button--tertiary {
  margin-inline-start: calc(var(--space-8) * -1);
  padding-inline: var(--space-8);
}

/* ---------------------------------------------------------------------------
   Result content · the analysis the review step returns

   Skill scores and mood scores arrive from the engine on a 0-1 scale and are
   published out of 100, so the metric cards and the mood cards read the same
   way and share one card contract.
   -------------------------------------------------------------------------- */

/* `.separator-top` draws in `--ui-border`, which is the light page's ink: the
   rule between sections was there the whole time and never visible on the dark
   plate. It takes the hairline the cards and the footer already use. */
.page--game-session .game-result__section {
  margin-top: var(--space-20);
  padding-top: var(--space-16);
  gap: var(--space-12);
  border-top-color: var(--result-line);
}

.page--game-session .game-result__section-title {
  margin: 0;
  opacity: 0.56;
}

.page--game-session .game-result__metric {
  padding: var(--space-14);
  gap: var(--space-4);
  border: 1px solid var(--result-line);
  border-radius: var(--radius-compact);
  background: var(--result-fill);
}

.page--game-session .game-result__metric-name {
  opacity: 0.56;
}

.page--game-session .game-result__metric-value {
  font-family: var(--font-mono);
  font-size: var(--display-xs-font-size);
  line-height: var(--display-xs-line-height);
  font-weight: var(--weight-medium);
  letter-spacing: -0.02em;
}

/* The sheet reserves the mono family for technical information — poster
   numbers, data, code, metadata. A metric carrying no score is not on the 0-100
   scale and is not data: "Focus" is a word, and it is set in Geist like one. */
.page--game-session .game-result__metric:not([data-score]) .game-result__metric-value {
  font-family: var(--skillprint-font-ui);
}

.page--game-session .game-result__metric-total {
  margin-left: var(--space-4);
  font-size: var(--text-xs-font-size);
  line-height: 1;
  opacity: 0.5;
}

.page--game-session .game-result__metric .sp-progress__track {
  height: var(--space-6);
}

/* ---------------------------------------------------------------------------
   Score bands

   One scale across every score on the screen, so a number is readable before
   it is read: green from 80, blue from 60, amber from 40, red below it. The
   band is derived from the score itself rather than authored per card, and it
   colours the bar and the number together so the two never disagree.
   -------------------------------------------------------------------------- */

.page--game-session .game-result__metric {
  --band: var(--panel-navy-300);
}

.page--game-session .game-result__metric[data-band="green"] { --band: var(--core-green-500); }
.page--game-session .game-result__metric[data-band="blue"]  { --band: var(--personality-blue-500); }
.page--game-session .game-result__metric[data-band="amber"] { --band: var(--skills-orange-500); }
.page--game-session .game-result__metric[data-band="red"]   { --band: var(--skills-pink-500); }

.page--game-session .game-result__metric[data-band] .sp-progress {
  --progress-colour: var(--band);
}

.page--game-session .game-result__metric[data-band] .game-result__metric-value {
  color: var(--band);
}

.page--game-session .game-result__mood {
  padding: var(--space-14);
  border: 1px solid var(--result-line);
  border-radius: var(--radius-compact);
  background: var(--result-fill);
  /* No transition on the accent below. `--transition-interactive` would fade
     it, and a fade of a `color-mix()` interpolates through oklab and only
     lands on the declared value when frames are actually running - a document
     that is hidden or throttled freezes it part-way and leaves the panel a
     colour neither rule asked for. The state changes once, on a click the
     reader just made, so it can simply be the value. */
}

/* Asked, and not yet answered.

   Every other panel in this dialog is a readout - a score, a figure, a bar -
   and the survey was wearing the same hairline and the same fill as all of
   them, so the one thing on the screen that wants something back from the
   reader looked exactly like the things that do not. It is the brand violet
   that marks it, which is the same move `.rail-card--record` makes on the
   light side to lift the reader's own card out of a run of the game's: the
   accent means "this one is yours to act on" rather than belonging to any
   particular score.

   Colour only - the padding, radius and geometry are the base rule's and do
   not move - so answering settles the panel rather than reflowing the dialog
   under the reader's cursor.

   `:has()` is already a dependency two rules down. Where it is missing the
   selector simply never matches and the survey falls back to the quiet panel
   it was, which is the right way round for a highlight. */
.page--game-session .game-result__mood:not(:has([aria-pressed="true"])) {
  border-color: color-mix(in srgb, var(--violet) 52%, var(--result-line));
  background: color-mix(in srgb, var(--mindset-violet-500) 15%, var(--result-fill));
}

/* The prompt stops being a prompt once it has been answered: the accent drops
   away to the base panel above and the dialog goes back to being a readout.
   A highlight that never resolves is just a colour. */

.page--game-session .game-result__mood-question {
  opacity: 0.72;
}

/* Each answer carries its own direction: the arrow says which way the mood
   moved and the colour says whether that is the answer the session hoped for.
   Both only appear on hover, press and selection, so the resting row stays
   quiet and the meaning arrives exactly when the player reaches for it. */
.page--game-session .game-result__mood-answer {
  --answer: var(--panel-navy-300);
  --button-height: var(--space-40);

  flex: 1 1 0;
  gap: var(--space-6);
}

.page--game-session .game-result__mood-answer[data-mood-answer="1"] { --answer: var(--core-green-500); }
.page--game-session .game-result__mood-answer[data-mood-answer="0"] { --answer: var(--personality-blue-500); }
.page--game-session .game-result__mood-answer[data-mood-answer="-1"] { --answer: var(--skills-pink-500); }

.page--game-session .game-result__mood-answer svg {
  color: color-mix(in srgb, var(--geistface-grey-200) 62%, transparent);
  transition: color var(--duration-fast) var(--ease-standard);
}

.page--game-session .game-result__mood-answer:hover,
.page--game-session .game-result__mood-answer:focus-visible {
  border-color: var(--answer);
  background: color-mix(in srgb, var(--answer) 14%, var(--panel-navy-900));
  color: var(--geistface-grey-25);
}

.page--game-session .game-result__mood-answer:is(:hover, :focus-visible) svg {
  color: var(--answer);
}

.page--game-session .game-result__mood-answer[aria-pressed="true"] {
  border-color: var(--answer);
  background: color-mix(in srgb, var(--answer) 22%, var(--panel-navy-900));
  color: var(--geistface-grey-25);
  box-shadow: inset 0 0 0 1px var(--answer);
}

.page--game-session .game-result__mood-answer[aria-pressed="true"] svg {
  color: var(--answer);
}

/* Once one is chosen the others step back, so the answer given is unambiguous. */
.page--game-session .game-result__mood:has([aria-pressed="true"]) .game-result__mood-answer[aria-pressed="false"] {
  opacity: 0.5;
}

.page--game-session .game-result__note {
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  opacity: 0.56;
}

/* ---------------------------------------------------------------------------
   Sequence states · load, start, play, calculate, review

   The page owns one state at a time on `data-sequence`. Every step listens for
   its own name, so the sequence is legible from the markup alone.
   -------------------------------------------------------------------------- */

.page--game-session [data-step] {
  display: none;
}

.page--game-session[data-sequence="loading"] [data-step~="loading"],
.page--game-session[data-sequence="start"] [data-step~="start"],
.page--game-session[data-sequence="playing"] [data-step~="playing"],
.page--game-session[data-sequence="calculating"] [data-step~="calculating"],
.page--game-session[data-sequence="review"] [data-step~="review"] {
  display: grid;
}

/* Steps carry their own `display`, so nothing here may set one - a component
   rule that did would outrank the hide above and leave the step on screen. */
.page--game-session .session-veil {
  position: fixed;
  inset: 0;
  z-index: 300;
  place-items: center;
  /* The same reserve every overlay on this screen takes, so a veil's copy is
     never centred into the space the bar is standing in. */
  padding-block-end: var(--play-bar-clear);
  gap: var(--space-16);
  justify-items: center;
  background: color-mix(in srgb, var(--deep-navy-950) 62%, transparent);
  -webkit-backdrop-filter: blur(var(--backdrop-blur-md));
  backdrop-filter: blur(var(--backdrop-blur-md));
  color: var(--geistface-grey-25);
  text-align: center;
}

/* The load step used to paint the flat canvas over the whole screen. Now that
   the ground is the game's own artwork, that hid the game for the first second
   and a half of every session. The field sits behind this veil and is the same
   in every step, so all the veil has to stop is the board - and the sequence
   already hides the board here. The ink stays legible because the field carries
   its own veil. */
.page--game-session .session-veil--canvas {
  background: transparent;
  color: var(--stage-ink);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

.page--game-session .session-veil__copy {
  display: grid;
  gap: var(--space-6);
  max-width: var(--space-320);
}

.page--game-session .session-spinner {
  width: var(--space-32);
  height: var(--space-32);
  border: var(--space-4) solid color-mix(in srgb, currentColor 22%, transparent);
  border-top-color: var(--violet);
  border-radius: var(--radius-full);
  animation: session-spin var(--duration-slowest) linear infinite;
}

@keyframes session-spin {
  to {
    transform: rotate(360deg);
  }
}

/* The exit control the portal puts over every game canvas. */
.page--game-session .session-exit {
  position: fixed;
  left: var(--space-16);
  bottom: var(--space-16);
  z-index: 310;
  width: var(--space-44);
  height: var(--space-44);
  padding: 0;
  place-items: center;
  border: 0;
  border-radius: var(--radius-full);
  background: var(--button-primary); /* fill with light text on it: keeps the 500 ladder, not the accent tier */
  color: var(--geistface-grey-25);
  box-shadow: var(--shadow-md);
  cursor: pointer;
  transition: var(--transition-interactive);
}

.page--game-session .session-exit:hover {
  background: var(--mindset-violet-600);
  transform: scale(1.06);
}

.page--game-session .session-exit svg {
  width: var(--space-20);
  height: var(--space-20);
  fill: none;
  stroke: currentColor;
  stroke-width: var(--icon-stroke-md);
  stroke-linecap: round;
  --icon-stroke: var(--icon-stroke-20);
}

/* ---------------------------------------------------------------------------
   The board in play

   A reading of Hextris rather than a port of it: the hexagon holds, blocks
   fall in on the brand spectrum and the score climbs, which is everything the
   adjustment banner needs to be read against.
   -------------------------------------------------------------------------- */

.page--game-session .game-board {
  place-content: center;
  justify-items: center;
}

/* The board stays behind the result, frozen on the frame the session ended on. */
.page--game-session[data-sequence="review"] :is(.game-board__core, .game-board__block) {
  animation-play-state: paused;
}

.page--game-session .game-board__ring {
  position: relative;
  width: min(72vmin, 420px);
  aspect-ratio: 1;
  display: grid;
  place-items: center;
}

.page--game-session .game-board__core {
  width: 34%;
  aspect-ratio: 8 / 7;
  clip-path: var(--stage-hexagon);
  background: var(--stage-ink);
  animation: game-board-spin 9s linear infinite;
}

@keyframes game-board-spin {
  to {
    transform: rotate(360deg);
  }
}

/* Each block is a spoke running from the rim to the centre of the ring, so one
   rotation places it on its side of the hexagon and one translation walks it
   in. The bar itself is drawn at the outer end of the spoke. */
.page--game-session .game-board__block {
  position: absolute;
  top: 0;
  left: 50%;
  width: 13%;
  height: 50%;
  margin-left: -6.5%;
  transform-origin: 50% 100%;
  animation: game-board-drop var(--block-duration, 2.6s) var(--ease-standard) infinite;
  animation-delay: var(--block-delay, 0s);
}

.page--game-session .game-board__block::before {
  content: "";
  position: absolute;
  top: 0;
  inset-inline: 0;
  height: 17%;
  border-radius: var(--radius-xxs);
  background: var(--block-colour, var(--violet));
}

@keyframes game-board-drop {
  0% {
    opacity: 0;
    transform: rotate(var(--block-angle, 0deg)) translateY(-32%);
  }

  14% {
    opacity: 1;
  }

  70%,
  100% {
    opacity: 1;
    transform: rotate(var(--block-angle, 0deg)) translateY(46%);
  }
}

.page--game-session .game-board__block:nth-child(1) { --block-angle: 0deg; --block-colour: var(--violet); --block-delay: 0s; }
.page--game-session .game-board__block:nth-child(2) { --block-angle: 60deg; --block-colour: var(--mint); --block-delay: 0.5s; }
.page--game-session .game-board__block:nth-child(3) { --block-angle: 120deg; --block-colour: var(--lime); --block-delay: 1.1s; }
.page--game-session .game-board__block:nth-child(4) { --block-angle: 180deg; --block-colour: var(--pink); --block-delay: 1.6s; }
.page--game-session .game-board__block:nth-child(5) { --block-angle: 240deg; --block-colour: var(--blue); --block-delay: 2.1s; }
.page--game-session .game-board__block:nth-child(6) { --block-angle: 300deg; --block-colour: var(--orange); --block-delay: 2.7s; }

/* ---------------------------------------------------------------------------
   The step rail

   Not part of the product: it is the control that makes a linear sequence
   reviewable without reloading the page.
   -------------------------------------------------------------------------- */

.page--game-session .session-rail {
  /* The plate is dark; the page under it is not. `<html data-surface="light">`
     declares the whole button family on the html element, where
     `--button-surface-hover` resolves to `--surface-raised` - a near-white - so
     `.button--tertiary:hover` painted a near-white pill and the label vanished
     into it at 1.03:1. This is the failure `.popup--dark` documents: a custom
     property is substituted where it is declared, so re-valuing a surface token
     here could never reach a button token declared on `<html>`. The leaf tokens
     the steps read are restated below, on the element that owns the dark ground.

     The hover fill is the one value that is not simply the dark page's.
     `--panel-navy-800` is right on an opaque panel and wrong on glass: this
     plate is 82% translucent, so it composites to about rgb(50 54 76) over the
     stage canvas, and an opaque 800 pill lands at 1.04:1 against that - a state
     change nobody can see - and it drifts every time what sits behind the blur
     changes. A wash of the plate's own light ink lifts by the same amount
     whatever is underneath, which is what a hover on glass has to do. */
  --button-text: var(--geistface-grey-200);
  --button-text-muted: color-mix(in srgb, var(--geistface-grey-200) 72%, transparent);
  --button-surface-hover: color-mix(in srgb, var(--geistface-grey-25) 14%, transparent);

  /* The ring's inner band is meant to be the ground the ring is seated on, and
     the light page declares it near-white on `<html>` for the same reason as
     above, which drew a white halo around a focused step rather than a gap.
     This is the dark value; the violet band outside it reads 3.24:1 against the
     plate. */
  --button-focus-inner: var(--panel-navy-950);

  position: fixed;
  right: var(--space-16);
  bottom: var(--space-16);
  z-index: 310;
  padding: var(--space-8);
  gap: var(--space-4);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-full);
  background: var(--glass-surface-brand);
  box-shadow: var(--shadow-md);
  -webkit-backdrop-filter: blur(var(--backdrop-blur-md));
  backdrop-filter: blur(var(--backdrop-blur-md));
}

.page--game-session .session-rail__step {
  --button-height: var(--space-32);
  --button-padding: 0 var(--space-12);
  --button-font-size: var(--text-xs-font-size);

  /* `.button` draws `box-shadow: var(--button-current-shadow)` and the focus
     rule prepends that same variable to its two ring layers. On a tertiary
     button the variable is `none`, and `none` is not a legal layer inside a
     shadow list, so the whole declaration is invalid at computed-value time and
     box-shadow falls back to its initial value - these steps had no focus ring
     at all, only a matching `:focus-visible`. A zero-size transparent layer
     draws nothing and keeps the list valid. */
  --button-current-shadow: 0 0 0 0 transparent;

  border-radius: var(--radius-full);

  /* The hand-mixed `color` that used to sit here restated `--button-text-muted`
     at 70%, and being far later in the sheet at the same specificity as
     `.button--tertiary:hover` it beat that rule's `color` too, so the label
     never brightened when the pill did. Both inks now come from the tokens
     restated on the rail, which keeps the step on the component's own ladder. */
}

.page--game-session .session-rail__step[aria-pressed="true"] {
  background: var(--button-primary); /* fill with light text on it: keeps the 500 ladder, not the accent tier */
  color: var(--geistface-grey-25);
}


/* ---------------------------------------------------------------------------
   The stage is the current game's own artwork

   These two screens were built around one game, so the canvas was flat grey
   with a translucent hexagon on it and the word "Hextris" across the middle.
   Reviewing the dialog against a second game's colours meant editing the page,
   and writing fifty grounds into the markup would have meant fifty copies of
   two screens. The artwork the library already ships for every game is the
   ground instead, drawn from the same `<img src>` the game cards use - Chrome
   blocks a cross-file `<use>` over file:// but an image loads either way, so
   the ground survives a page opened straight from disk.

   Each file is a 380x210 card - a full-bleed patterned ground with the game's
   icon centred on it - not a photograph, so it is drawn twice and each copy
   does one job.

   `.game-stage__field` is the ground. A cover fit of a 380x210 source crops
   hard at a phone's 390x844: you would be looking at a quarter of the picture
   magnified four times. So the copy that fills the viewport is blurred well
   past the point where the crop can be read. What survives is the game's
   colour, which is the thing being reviewed, and a field with no legible detail
   in it cannot compete with the dialog for attention. The scale is what hides
   the transparent edge a blur leaves behind.

   `.game-stage__hex` is the mark. The same file drawn crisp, through the
   hexagon the stage already used, so the reader can see which game this is. At
   8:7 the cover crop lands on the middle 240px of the 380px card, which is
   exactly where every one of these files puts the game's icon.
   -------------------------------------------------------------------------- */

.page--game-session .game-stage__field {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  background: var(--stage-canvas);
}

.page--game-session .game-stage__wash {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(var(--stage-art-scale));
  filter: blur(var(--stage-art-blur)) saturate(1.18);
}

/* The veil is what lets fifty different artworks all be safe ground for one
   dark dialog. It is strongest through the middle, where the dialog and the
   wordmark sit, and weakest at the corners, where the game's own colour is the
   whole point of the review. At 88% the darkest ground in the library
   (#2b3159) still clears 6.7:1 against --stage-ink and the lightest (#f9eed8)
   clears 11:1, so no game has to be checked by hand; at 34% the corners still
   read as navy, mint, blush or violet rather than as one grey. The tint carries
   the game's accent, so the wash is the game's brand colour as well as its
   picture. */
.page--game-session .game-stage__field::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(
      ellipse 76% 68% at 50% 46%,
      color-mix(in srgb, var(--stage-veil-tint) 88%, transparent) 0%,
      color-mix(in srgb, var(--stage-veil-tint) 70%, transparent) 46%,
      color-mix(in srgb, var(--stage-veil-tint) 34%, transparent) 100%
    );
}

/* The hexagon keeps --stage-hex underneath rather than losing it: that flat
   wash is what is left if a game's artwork cannot be loaded, and the shape
   still reads. */
.page--game-session .game-stage__hex-art {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The mark carries its own veil rather than borrowing the field's. It sits
   inside the field's strongest band, so without one it would be the only fully
   saturated thing on the screen and the wordmark across it would be reading
   against whatever colour that game's icon happens to use. At 62% the artwork
   keeps enough colour to be recognisable while both a black and a white detail
   inside it clear 4.4:1 against --stage-ink - the veil compresses the range
   from both ends, which is what makes one number safe for fifty pictures. */
.page--game-session .game-stage__hex::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--stage-mark-veil);
}

/* Flipping games swaps two image sources at once, and until both have decoded
   the stage shows a torn frame - the next game's ground behind this game's
   mark. The pair fades for the length of the swap. */
.page--game-session :is(.game-stage__field, .game-stage__hex) {
  transition: opacity var(--duration-base) var(--ease-standard);
}

.page--game-session[data-stage-swap="true"]
:is(.game-stage__field, .game-stage__hex) {
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  /* With no fade there is no reason to hide the stage at all: a hard blank
     followed by a hard return is a bigger movement than the cross-fade it would
     be replacing. */
  .page--game-session :is(.game-stage__field, .game-stage__hex) {
    transition: none;
  }

  .page--game-session[data-stage-swap="true"]
  :is(.game-stage__field, .game-stage__hex) {
    opacity: 1;
  }
}

/* ---------------------------------------------------------------------------
   The game picker

   The same kind of control as the step rail: fixed, floating, and not part of
   the product. It exists so fifty grounds can be flipped through without
   hand-editing a query string.

   It is the shared .sp-dropdown, so the menu, the groups, the items, the open
   state and the selected mark all come from Part A and from Part B's menu
   ground. It takes a light plate rather than the rail's dark glass, and that is
   the reason it needs no token work at all: these screens carry
   html[data-surface="light"], which already points --button-text, --surface-box
   and --surface-border at the light surface, so every part of .sp-dropdown
   resolves correctly on a near-white ground with nothing restated. The rail can
   afford dark glass because its entire content is five text buttons whose
   colour it restates in one line; forcing a composed dropdown dark means
   restating a dozen button and surface tokens, which is the failure already
   documented on .popup--dark.
   -------------------------------------------------------------------------- */

.page--game-session .game-switch {
  position: fixed;
  top: var(--space-16);
  right: var(--space-16);
  /* Above the result (300) and the review controls (310), below the adjustment
     banner (320): the banner is the product and holds for three seconds, so it
     keeps the top of the screen for as long as it is there. */
  z-index: 318;
  width: var(--space-256);
  padding: var(--space-6);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-2xl);
  background: color-mix(in srgb, var(--geistface-grey-25) 88%, transparent);
  box-shadow: var(--shadow-md);
  -webkit-backdrop-filter: blur(var(--backdrop-blur-md));
  backdrop-filter: blur(var(--backdrop-blur-md));
}

.page--game-session .game-switch__menu {
  right: 0;
  left: auto;
  width: max(100%, var(--space-320));
  max-height: min(60vh, var(--space-480));
}

/* .button draws its focus ring as a box-shadow, which a scrolling menu would
   normally clip - except that the menu's own 6px padding is wider than the 4px
   the ring needs, so the first and last rows keep theirs. What the ring does
   need is to be lifted over the neighbouring row, whose background would
   otherwise be painted on top of it. */
.page--game-session .game-switch__menu .sp-dropdown__item:focus-visible {
  position: relative;
  z-index: 1;
}


/* ==========================================================================
   09 - Tailwind handoff

   The sheet handed to a team rebuilding this system in Tailwind. It is
   generated by `scripts/skillprint.py tailwind` and composes almost entirely
   from classes that already exist - sp-table for every scale, code-block for
   the samples, and the design system's own scale-group/scale-row/shade for the
   colour ramps, which is why those three moved to a shared scope above.

   What is left here is the page's own rhythm: the section spacing, the display
   title, the prose measure, and the size of the semantic wells. No token, no
   colour and no component is redefined.
   ========================================================================== */

/* padding-block, not padding: .sp-container owns the inline gutters through
   --grid-padding, and the shorthand would reset them to zero. */
.page--tailwind-handoff .handoff-shell {
  gap: var(--space-96);
  padding-block: var(--space-88) var(--space-64);
  align-content: start;
}

/* The wordmark carries no intrinsic size, so every page that mounts .sp-nav
   states one. */
.page--tailwind-handoff .sp-nav__brand .brand-logo { width: 158px; }

.page--tailwind-handoff .handoff-title {
  font-size: var(--display-lg-font-size);
  line-height: var(--display-lg-line-height);
  letter-spacing: -0.02em;
  max-width: 18ch;
}

.page--tailwind-handoff h2 {
  font-size: var(--display-sm-font-size);
  line-height: var(--display-sm-line-height);
  letter-spacing: -0.01em;
}

/* The prose measure. Every page in this project sets its own rather than
   reaching for a utility, because the ideal measure follows the column the
   copy sits in, not a global default. */
.page--tailwind-handoff .handoff-lede { max-width: var(--measure-md); }

/* The wells under section 03. Their colour is the .swatch component reading
   --swatch-color from a .swatch--* modifier, so this only has to give them a
   shape to be seen in. */
.page--tailwind-handoff .handoff-well {
  display: block;
  min-height: var(--space-64);
}

.page--tailwind-handoff .handoff-foot { padding-block: var(--space-32) var(--space-88); }

/* A scale table is a reference, not a layout: let a long value column scroll
   inside its own frame rather than widen the page. */
.page--tailwind-handoff .sp-table { table-layout: auto; }

/* ==========================================================================
   10 - Responsive rules
   ========================================================================== */


@media (max-width: 900px) {
  .page--tailwind-handoff .handoff-shell { gap: var(--space-64); padding-block-start: var(--space-64); }
  .page--tailwind-handoff .handoff-title {
    font-size: var(--display-md-font-size);
    line-height: var(--display-md-line-height);
  }
  .page--tailwind-handoff h2 {
    font-size: var(--display-xs-font-size);
    line-height: var(--display-xs-line-height);
  }
}

@media (max-width: 1180px) {
  .page--ontology-wheel .ontology-intro { grid-template-columns: 1fr; gap: var(--space-18); }
  :is(.page--ontology-wheel, .ontology-root) .ontology-stage { grid-template-columns: 1fr; }
  :is(.page--ontology-wheel, .ontology-root) .ontology-side { grid-template-rows: none; }
  /* Once the wheel is no longer beside the column it has the full width, so
     the terms are drawn smaller in user units to land at the same size on
     screen that they were reading at in the desktop layout. */
  :is(.page--ontology-wheel, .ontology-root) { --ontology-leaf-size: 9.5px; --ontology-branch-size: 8.5px; }
  :is(.page--ontology-wheel, .ontology-root) .ontology-metrics { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}


/* The wheel is one drawing at a fixed scale, so its type size has to track the
   width it is actually rendered at, not the viewport. Below this the column is
   narrow enough that the radiating terms would land under 8px. Two things a
   reader wants are in conflict here and cannot both be had on a phone: seeing
   the whole disc, and reading the terms around it. 87 terms around a ring at
   375px is roughly 13px of arc each, so no layout gets both.
   The page used to answer that by always keeping the terms and scrolling the
   disc sideways. It now defaults to the whole disc and offers the terms
   behind a toggle - a reader who cannot see the shape has nothing to read
   terms against, and the readout names any spoke on tap regardless. */
@media (max-width: 900px) {
  :is(.page--ontology-wheel, .ontology-root) {
    --ontology-leaf-size: 12.5px;
    --ontology-branch-size: 10.5px;
    --ontology-half-size: 9.5px;
  }

  :is(.page--ontology-wheel, .ontology-root) .ontology-visual__stage {
    overflow-x: auto;
    overflow-y: hidden;
    overscroll-behavior-x: contain;
  }

  :is(.page--ontology-wheel, .ontology-root) .skillprint__wheel { min-width: var(--space-720); }
  .page--design-system { --nav: 232px; }
  .page--design-system .hero-inner { grid-template-columns: 1fr; gap: var(--space-42); }
  .section-head--design-system { grid-template-columns: 1fr; gap: var(--space-12); }
  .page--design-system .study-grid { grid-template-columns: repeat(3, 1fr); }
  .page--design-system .rules { grid-template-columns: repeat(2, 1fr); }
  .page--design-system .font-lockup { grid-template-columns: 1fr 1fr; }
  .page--design-system .font-card.serif { grid-column: 1/-1; }
  .page--design-system .weight-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .page--design-system .type-combination-grid { grid-template-columns: 1fr 1fr; }
  .page--design-system .product-combo { grid-column: 1/-1; }
  .page--design-system .poster-type-key { grid-template-columns: 1fr 1fr; }
  .page--design-system .poster-type-rule:last-child { grid-column: 1/-1; }
  .page--design-system .spacing-token-grid { grid-template-columns: repeat(4, 1fr); }
  .page--design-system .radius-scale { grid-template-columns: repeat(6, 1fr); }
  .page--design-system .button-demo-grid { grid-template-columns: repeat(3, 1fr); }

  .page--design-system .implementation-overview__grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .page--design-system .trait-module { --sidebar: 230px; }
  .section-head--trait { grid-template-columns: 1fr; gap: var(--space-12); }
  .page--design-system .trait-module .hero-inner { grid-template-columns: 1fr; gap: var(--space-42); }
  .page--design-system .trait-module .hero-system { max-width: 760px; }

  .page--design-system .trait-module .trait-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .game-grid--portal, .page--design-system .trait-module .grid-4 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .page--design-system .api-module { --sidebar-w: 260px; }

  .page--design-system .api-module .endpoint-grid,
  .page--design-system .api-module .intro-grid,
  .page--design-system .api-module .operations-card,
  .page--design-system .api-module .section-heading-grid {
    grid-template-columns: minmax(0, 1fr) minmax(340px, 0.82fr);
    gap: var(--space-32);
  }

  

  
}

/* ---- The wheel on a phone ------------------------------------------------- */

/* Down to about 786px the drawing still fits inside its own 720px floor and
   nothing scrolls; the terms sit near 9px and are readable, so the block above
   is left alone. Below that the floor is wider than the stage and the reader
   was made to drag: half the disc off screen at 375px on the ontology page,
   60% of it on the profile. That is not a smaller reading of a radial chart,
   it is no reading at all - the relation a reader wants is usually between two
   points on opposite sides of the ring, and dragging never puts both on screen
   at once.

   So below here the drawing fits and the terms come off, because at this width
   they are about 4px of texture rather than words, and enough of it to make the
   disc look soiled. Nothing is lost that the page does not already say better:
   every spoke keeps its hit target and a tap names the term and its score in
   the readout, which is how this page names one skill at a time at any width.
   Reading all 87 is the other job and it still needs the room the drawing was
   designed at - so it is offered behind [data-sp-terms] rather than imposed.
   The breakpoint is 800 rather than 786 because the profile's print carries
   more chrome and crosses the same line at about 810. */
@media (max-width: 800px) {
  :is(.page--ontology-wheel, .ontology-root) .ontology-visual__stage {
    overflow-x: hidden;
  }

  /* .ontology-visual is a grid with one implicit auto column, so that column
     sizes to the stage's max-content. While the stage was a scroll container
     that contribution was zero and the column took the width it was given; the
     moment it stops scrolling, the SVG's own intrinsic width sizes the column
     and the plate lays out wider than the screen - clipped instead of scrolled,
     which is the same bug wearing a different coat. Pinning the column to the
     space available is what makes "fit" mean fit. */
  :is(.page--ontology-wheel, .ontology-root) .ontology-visual {
    grid-template-columns: minmax(0, 1fr);
  }

  :is(.page--ontology-wheel, .ontology-root) .skillprint__wheel {
    min-width: 0;
    width: 100%;
    max-width: 100%;
    height: auto;
  }

  :is(.page--ontology-wheel, .ontology-root)
    .ontology-visual:not([data-terms="on"]) .leaf-label {
    display: none;
  }

  /* Opted in: the floor and the sideways scroll come back, and with them the
     larger user-unit type the floor was sized against. */
  :is(.page--ontology-wheel, .ontology-root)
    .ontology-visual[data-terms="on"] .ontology-visual__stage {
    overflow-x: auto;
    overscroll-behavior-x: contain;
  }

  :is(.page--ontology-wheel, .ontology-root)
    .ontology-visual[data-terms="on"] .skillprint__wheel {
    min-width: var(--space-720);
    width: var(--space-720);
  }

  /* The toggle is a narrow-screen affordance; wider than this the whole drawing
     already fits at full size and there is nothing to switch between. */
  :is(.page--ontology-wheel, .ontology-root) [data-sp-terms] {
    display: inline-flex;
  }
}


@media (max-width: 1100px) {
  .page--design-system .badge-tone-grid, .page--design-system .integration-summary {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .page--design-system .trait-module .demo-row:last-child .demo-content {
    grid-template-columns: 1fr;
  }

  .page--design-system .badge-detail-grid { grid-template-columns: 1fr; }
  .page--design-system .nav-spec-board__head { flex-direction: column; gap: var(--space-8); }
  .page--design-system .nav-spec-board__head p { text-align: left; }

  .page--design-system .nav-spec-rulegrid,
.page--design-system .nav-spec-stategrid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .page--seed-deck .slide-wrap { padding-left: var(--space-56); padding-right: var(--space-56); }

  .page--seed-deck .slide {
    width: min( calc((100vh - var(--nav-h) - 54px) * 16 / 9), calc(100vw - 116px) );
  }

  .page--seed-deck .slide__inner { padding: var(--space-36) var(--space-44) var(--space-24); }
  .page--seed-deck .slide__title { font-size: clamp(28px, 3.5vw, 48px); }
  .page--seed-deck .slide__lede { font-size: var(--text-xs-font-size); }
  .hero-grid--seed-deck { grid-template-columns: 1fr 0.88fr; }
  .hero-copy--seed-deck .slide__title { font-size: clamp(38px, 5vw, 64px); }
  .page--seed-deck .data-compare { grid-template-columns: 1fr 50px 1fr; }
  .page--seed-deck .bridge svg { width: var(--space-48); }
  .page--seed-deck .flywheel-stage { height: 350px; }
  .page--seed-deck .flywheel-stage svg { width: 340px; height: 340px; }
  html:has(> .page--seed-deck)[data-design="refined"] .slide__inner { padding: var(--space-40) var(--space-44) var(--space-24); }

  html:has(> .page--seed-deck)[data-design="refined"] .slide__title {
    font-size: clamp(30px, 3.6vw, 49px);
  }

  html:has(> .page--seed-deck)[data-design="refined"] .hero-copy--seed-deck .slide__title {
    font-size: clamp(40px, 5vw, 64px);
  }

  html:has(> .page--seed-deck)[data-design="refined"] .team-card p {
    font-size: 8px;
    line-height: 13px;
  }

  .page--seed-deck .product-showcase { gap: var(--space-10); }
  .page--seed-deck .product-extract__visual { height: 152px; }
  .page--seed-deck .product-extract__copy { padding: var(--space-14); }
  .page--seed-deck .product-extract p { font-size: var(--text-xxs-font-size); line-height: var(--text-xxs-line-height); }

  .page--seed-deck .product-system-bar {
    grid-template-columns: 1fr;
    gap: var(--space-6);
    text-align: center;
  }

  

  

  

  

  

  


  .page--design-system .interface-application-summary {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .page--design-system .interface-preview-grid { grid-template-columns: 1fr; }
  .page--design-system .interface-window__body { min-height: 390px; }
}


@media (max-width: 1023px) {
  :root {
    --grid-columns: 6;

    /* Type, tablet tier. Only the three largest display sizes compress;
       xl and 2xl step onto the existing desktop pairs one rung down, lg
       takes the one intermediate pair the ramp needs. Text tokens and the
       smaller display sizes are device-invariant. */
    --display-lg-font-size    : 40px;
    --display-lg-line-height  : 50px;
    --display-xl-font-size    : 48px;
    --display-xl-line-height  : 60px;
    --display-2xl-font-size   : 60px;
    --display-2xl-line-height : 72px;
  }

  .page--design-system .auth-subsection { margin-top: var(--space-64); }
  .page--design-system .auth-split { grid-template-columns: minmax(0, 1fr); min-height: 0; }
  .page--design-system .auth-split--rail { grid-template-columns: minmax(0, 1fr); }
  .page--design-system .auth-split > * + * { border-inline-start: 0; border-block-start: 1px solid var(--surface-border); }
  .page--design-system .auth-split__pane--brand { gap: var(--space-24); }

  .sp-span-12,
  .sp-span-8,
  .sp-span-7,
  .sp-span-6 {
    grid-column: span 6;
  }

  .sp-span-5,
  .sp-span-4,
  .sp-span-3 {
    grid-column: span 3;
  }
  .sp-nav__routes {
    display: none;
  }

  /* The routes have gone to the drawer, so search gives up its label and holds
     the bar's icon width rather than crowding what is left. */
  .page--documentation-landing .header-search {
    width: var(--space-36);
    padding: 0;
    justify-content: center;
  }

  .page--documentation-landing .header-search-label { display: none; }

  .sp-side-nav {
    position: fixed;
    inset: 0 auto 0 0;
    z-index: 140;
    width: min(var(--side-nav-width), calc(100vw - var(--space-48)));
    height: 100vh;
    box-shadow: var(--side-nav-shadow);
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transform: translateX(-100%);
    transition: var(--transition-interactive);
  }

  .sp-side-nav.is-open,
  .sp-side-nav[data-open="true"] {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: translateX(0);
  }

  /* The rule above turns .sp-side-nav into an off-canvas drawer at this width,
     so every shell that offsets its content by the sidebar has to stop
     reserving that column here. They each did it at their own narrower
     breakpoint - 860px for the two documentation apps - which left 250-286px
     of the viewport held open beside squeezed content for the whole range
     between. --sidebar itself still carries the drawer's width, so only the
     offset is cleared. */
  .page--docs-v2 .main {
    margin-left: 0;
  }

  .chart-pair {
    grid-template-columns: 1fr;
  }
  .page--design-system .copy-ready-grid { grid-template-columns: 1fr; }

  .page--design-system .contract-grid, .page--design-system .copy-ready-notes {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .page--design-system .code-state-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .page--design-system .copy-ready-heading { align-items: flex-start; flex-direction: column; }
  .page--design-system .copy-ready-heading p { text-align: left; }

  .hero-grid--portal,
  .page--design-system .portal-library-embed .hero-grid--portal,
  .page--design-system .panel-surface-mode-grid,
  .page--design-system .sp-popup-preview-grid {
    grid-template-columns: 1fr;
  }

  .page--design-system .panel-surface-contract { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* Every other block steps down on small screens (the featured card goes 40 to
     28, the panel 24 to 20). The hero only reset its bottom, so it kept a 42px
     opening above a 4px close - the widest top padding on the page paired with
     the narrowest bottom. Both ends step down together now. */
  .hero-copy--portal, .page--design-system .portal-library-embed .hero-copy--portal {
    padding-block: var(--space-24) var(--space-16);
  }

  .page--design-system .portal-library-embed .featured-card,
  .page--portal-games .featured-card {
    grid-template-columns: 1fr;
  }

  .page--design-system .portal-library-embed .featured-art,
  .page--portal-games .featured-art {
    min-height: 360px;
    order: -1;
  }

  .game-grid--portal, .page--design-system .portal-library-embed .game-grid--portal {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  

  
  
  .page--design-system .surface-application-grid { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 960px) {
  .page--documentation-landing .feature-grid { grid-template-columns: 1fr; }

  .page--documentation-landing .feature-card {
    display: grid;
    grid-template-columns: 160px 1fr;
    grid-template-rows: auto 1fr auto;
  }

  .page--documentation-landing .feature-card .card-top { grid-column: 1/-1; }

  .page--documentation-landing .icon-stage {
    grid-column: 1;
    grid-row: 2 / span 2;
    align-self: center;
    margin: var(--space-20) auto;
  }

  .page--documentation-landing .card-copy { grid-column: 2; align-self: center; }
  .page--documentation-landing .card-link { grid-column: 2; }
  .page--documentation-landing .start-panel { grid-template-columns: 1fr; }
  .page--documentation-landing .command { justify-content: flex-start; }
}

@media (max-width: 940px) {
  .page--design-system .api-module { --sidebar-w: 0px; --topbar-h: 58px; }

  .page--design-system .api-module .endpoint-grid,
  .page--design-system .api-module .intro-grid,
  .page--design-system .api-module .operations-card,
  .page--design-system .api-module .section-heading-grid {
    grid-template-columns: 1fr;
    gap: var(--space-24);
  }

  .page--design-system .api-module .code-card { position: relative; top: auto; }
}

@media (max-width: 900px) {
  .page--design-system { --nav: 0; }
  .page--design-system .sidebar { display: none; }

  .page--design-system .mobile-bar {
    position: sticky;
    top: 0;
    z-index: 60;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-12) var(--space-18);
    background: var(--panel-950);
    border-bottom: 1px solid var(--border);
  }

  .page--design-system .mobile-bar .brand-logo { width: 132px; }
  .page--design-system .mobile-bar a { display: block; }

  .page--design-system .trait-module main, .page--design-system main { margin-left: 0; }

  .page--design-system main > .sp-footer > .sp-footer__inner,
  .page--design-system .hero-inner,
  .page--design-system .section-inner {
    width: min(var(--content), calc(100% - 36px));
  }

  .page--design-system .hero-inner, .page--design-system .section-inner { padding: var(--space-72) 0; }

  .page--design-system .badge-gallery,
  .page--design-system .form-state-grid,
  .page--design-system .grid-4,
  .page--design-system .guideline-grid,
  .page--design-system .story-grid,
  .page--design-system .study-grid,
  .page--design-system .token-grid {
    grid-template-columns: repeat(2, 1fr);
  }

  .page--design-system .spectrum-band { grid-template-columns: repeat(4, 1fr); }
  .page--design-system .logo-stage { min-height: 280px; }
  .page--design-system .implementation-grid { grid-template-columns: 1fr; }

  .page--design-system .state-row {
    grid-template-columns: 110px repeat(5, minmax(120px, 1fr));
    overflow: auto;
    padding-bottom: var(--space-8);
  }

  :is(.page--design-system, .page--tailwind-handoff) .scale-row { overflow: auto; grid-template-columns: repeat(12, 72px); }
  .page--design-system .pair-grid { grid-template-columns: repeat(2, 1fr); }
  .page--design-system .hero-inner { max-width: none; }

  .page--design-system .source-surface-row {
    grid-template-columns: minmax(150px, 210px) minmax(0, 1fr) auto;
    padding: var(--space-22) var(--space-24);
  }

  .page--design-system .source-surface-url {
    font-size: var(--text-md-font-size);
    line-height: var(--text-md-line-height);
  }

  .page--design-system .study-title { align-items: start; flex-direction: column; }

  .page--design-system .asset-icon-guide__head p,
  .page--design-system .auth-subsection-head p,
  .page--design-system .badge-reference-head p,
  .page--design-system .implementation-overview__head p,
  .page--design-system .logo-subsection-head p,
  .page--design-system .source-surface-group__head > p,
  .page--design-system .study-note,
  .page--design-system .type-subsection-head p {
    text-align: left;
  }

  .page--design-system .auth-subsection-head,
  .page--design-system .badge-reference-head,
  .page--design-system .type-subsection-head,
  .page--design-system .asset-icon-guide__head,
  .page--design-system .implementation-overview__head {
    align-items: start;
    flex-direction: column;
    gap: var(--space-10);
  }

  .page--design-system .badge-subsection { margin-top: var(--space-64); }
  .page--design-system .font-lockup { grid-template-columns: 1fr; }
  .page--design-system .font-card.serif { grid-column: auto; }
  .page--design-system .shared-scale-callout { grid-template-columns: 1fr; gap: var(--space-12); }

  .page--design-system .scale-family-demo, .page--design-system .type-combination-grid {
    grid-template-columns: 1fr;
  }

  .page--design-system .product-combo { grid-column: auto; }
  .page--design-system .poster-type-key { grid-template-columns: 1fr; }
  .page--design-system .poster-type-rule:last-child { grid-column: auto; }
  .page--design-system .trait-module { --sidebar: 0px; }

  .page--design-system .trait-module .hero-inner, .page--design-system .trait-module .section-inner {
    width: min(var(--content), calc(100% - 36px));
  }

  .page--design-system .trait-module .section-inner { padding: var(--space-72) 0; }

  .page--design-system .trait-module .trait-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .page--design-system .trait-module .demo-row,
  .page--design-system .trait-module .grid-3,
  .page--design-system .trait-module .icon-spec,
  .page--design-system .trait-module .implementation-grid {
    grid-template-columns: 1fr;
  }

  .page--design-system .subsection-heading { grid-template-columns: 1fr; gap: var(--spacing-lg); }
  .page--design-system .spacing-token-grid { grid-template-columns: repeat(3, 1fr); }
  .page--design-system .grid-system-cards { grid-template-columns: 1fr; }
  .page--design-system .span-grid { grid-template-columns: repeat(6, 1fr); }
  .page--design-system .span-5, .page--design-system .span-7 { grid-column: span 6; }
  .page--design-system .span-4 { grid-column: span 2; }
  .page--design-system .radius-scale { grid-template-columns: repeat(4, 1fr); }
  .page--design-system .button-demo-grid { grid-template-columns: repeat(2, 1fr); }
  .page--design-system .code-system-layout { grid-template-columns: 1fr; }

  .page--design-system .master-state-row {
    grid-template-columns: 110px repeat(5, minmax(130px, 1fr));
  }

  .page--design-system .blur-system-grid, .page--design-system .shadow-system-grid {
    grid-template-columns: repeat(2, 1fr);
  }

  .page--design-system .asset-icon-row {
    grid-template-columns: 140px repeat(3, minmax(120px, 1fr));
    min-width: 620px;
  }

  .page--design-system .asset-icon-matrix { overflow-x: auto; }

  .page--design-system .brand-mark-family-grid, .page--design-system .surface-identity-grid {
    grid-template-columns: 1fr;
  }

  .page--design-system .logo-subsection-head, .page--design-system .source-surface-group__head {
    align-items: flex-start;
    flex-direction: column;
    gap: var(--space-10);
  }

  .page--design-system .achievement-anatomy-examples { grid-template-columns: 1fr 1fr; }

  .page--design-system .achievement-anatomy-example:last-child {
    grid-column: 1/-1;
    max-width: 520px;
    width: 100%;
    justify-self: center;
  }
  .page--homepage .stat:nth-child(2) { border-right: 0; }
  .page--homepage .stat:nth-child(-n + 2) { border-bottom: 1px solid var(--border); }

  .page--homepage .profile-copy { order: -1; }






}

@media (max-width: 860px) {


  .page--docs-v2 .main { margin-left: 0; }


  .page--docs-v2 .mobile-spacer { flex: 1; }


}

@media (max-width: 850px) {
  .page--design-system .base-component-grid { grid-template-columns: 1fr; }
}


@media (max-width: 767px) {
  :root {
    --grid-columns: 4;
    --grid-gutter: var(--space-16);
    --grid-padding: var(--space-16);

    /* Type, mobile tier. The display ramp lands on an even 4px ladder
       (24-28-32-36-40-44) with display-xs as its fixed floor; line heights
       tighten as size grows (1.33 down to 1.18). Text tokens never move.
       The app page title steps to the text-xl pair, matching what the
       portal already does on small screens. */
    --display-sm-font-size    : 28px;
    --display-sm-line-height  : 36px;
    --display-md-font-size    : 32px;
    --display-md-line-height  : 40px;
    --display-lg-font-size    : 36px;
    --display-lg-line-height  : 44px;
    --display-xl-font-size    : 40px;
    --display-xl-line-height  : 48px;
    --display-2xl-font-size   : 44px;
    --display-2xl-line-height : 52px;

    --app-display-xs-font-size  : 20px;
    --app-display-xs-line-height: 30px;
    --app-display-sm-font-size  : 24px;
    --app-display-sm-line-height: 32px;
    --app-display-md-font-size  : 28px;
    --app-display-md-line-height: 36px;
    --app-display-lg-font-size  : 30px;
    --app-display-lg-line-height: 38px;
    --app-display-xl-font-size  : 32px;
    --app-display-xl-line-height: 40px;
    --app-display-2xl-font-size : 36px;
    --app-display-2xl-line-height: 44px;
  }

  .page--design-system .auth-subsection { margin-top: var(--space-56); }
  .page--design-system .auth-canvas:not(.auth-canvas--flush) { padding: var(--space-32) var(--space-20); }
  .page--design-system .auth-split__pane { padding: var(--space-32) var(--space-24); }

  [class*="sp-span-"] {
    grid-column: span 4;
  }
  .sp-nav__inner {
    padding-inline: var(--space-16);
    gap: var(--space-8);
  }

  .sp-nav__actions,
  .sp-nav__endcap {
    gap: var(--space-4);
  }
  .sp-side-nav {
    width: min(var(--side-nav-width), calc(100vw - var(--space-24)));
  }
  .sp-panel {
    padding: var(--space-16);
  }
  .chart-card-head {
    padding: var(--space-20);
    flex-direction: column;
  }

  .chart-actions {
    justify-content: flex-start;
  }

  .chart-stats {
    padding: var(--space-16) var(--space-20) 0;
    grid-template-columns: 1fr;
  }

  .chart-frame {
    min-height: var(--space-320);
    padding: var(--space-16)
      var(--space-8)
      var(--space-4);
  }

  .chart-key {
    padding: var(--space-4)
      var(--space-20)
      var(--space-18);
  }

  .chart-insight {
    margin: 0
      var(--space-20)
      var(--space-20);
  }
  .sp-dropdown--compact {
    width: 100%;
  }

  .sp-dropdown--menu .sp-dropdown__menu {
    right: auto;
    left: 0;
  }
  .page--ontology-wheel .ontology-shell { gap: var(--space-18); padding-block: var(--space-24); }
  /* No height floor for the wheel here: it is square and fluid, and its type
     size and scroll floor are already handled at 900px. */
  :is(.page--ontology-wheel, .ontology-root) { --ontology-card-padding: var(--space-16); }
  :is(.page--ontology-wheel, .ontology-root) .ontology-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  
  .hero--portal { padding: var(--space-48) 0 var(--space-36); }
  .game-grid--portal { grid-template-columns: 1fr; }
  .page--design-system .library-module .modal-media { min-height: 280px; max-height: 310px; }
  .page--design-system .library-module .modal-media::after { display: none; }
  .page--design-system .library-module .modal-content { overflow: visible; }
  .page--design-system .library-module .modal-actions { position: static; }
  .page--design-system .library-module .modal-title { font-size: var(--display-md-font-size); line-height: var(--display-md-line-height); }
  .page--seed-deck { overflow: auto; }

  .page--seed-deck .app {
    position: relative;
    display: block;
    min-height: 100vh;
  }

  .topbar--seed-deck {
    position: sticky;
    top: 0;
    height: 58px;
    padding: 0 var(--space-14);
  }

  .page--seed-deck .topbar__actions .nav-btn span, .page--seed-deck .topbar__meta { display: none; }
  .page--seed-deck .topbar__brand img { width: 112px; }
  .page--seed-deck .deck-shell { overflow: visible; }

  .page--seed-deck .deck {
    position: relative;
    display: block;
    overflow: visible;
    scroll-snap-type: none;
  }

  .page--seed-deck .slide-wrap {
    width: 100%;
    height: auto;
    padding: var(--space-12);
    display: block;
  }

  .page--seed-deck .slide {
    width: 100%;
    aspect-ratio: auto;
    min-height: var(--space-720);
    border-radius: var(--radius-2xl);
  }

  .page--seed-deck .slide__inner {
    position: relative;
    min-height: var(--space-720);
    padding: var(--space-30) var(--space-24) var(--space-22);
  }

  .page--seed-deck .slide__head { grid-template-columns: 1fr; }

  .page--seed-deck .slide__number {
    position: absolute;
    right: var(--space-24);
    top: var(--space-30);
  }

  .page--seed-deck .hero-copy--seed-deck .slide__title, .page--seed-deck .slide__title {
    font-size: var(--display-md-font-size);
  }

  .page--seed-deck .hero-copy--seed-deck .slide__lede, .page--seed-deck .slide__lede {
    font-size: var(--text-md-font-size);
  }

  .hero-grid--seed-deck,
  .page--seed-deck .flywheel-layout,
  .page--seed-deck .market-layout,
  .page--seed-deck .model-grid,
  .page--seed-deck .raise-grid {
    grid-template-columns: 1fr;
  }

  .page--seed-deck .network-stage { min-height: 330px; }
  .page--seed-deck .data-compare { grid-template-columns: 1fr; }
  .page--seed-deck .bridge { transform: rotate(90deg); min-height: 46px; }
  .page--seed-deck .history-items { grid-template-columns: repeat(2, 1fr); gap: var(--space-28); }
  .page--seed-deck .history-line { display: none; }
  .page--seed-deck .history-bottom { grid-template-columns: 1fr; }
  .page--seed-deck .stat-grid { grid-template-columns: repeat(2, 1fr); }
  .page--seed-deck .moat-bar, .page--seed-deck .pipeline { grid-template-columns: 1fr; }

  .page--seed-deck .pipeline::before {
    left: var(--space-28);
    top: 7%;
    bottom: 7%;
    width: 1px;
    height: auto;
  }

  .page--seed-deck .moat-bar__label {
    border: 0;
    border-bottom: 1px solid var(--border);
    padding: 0 0 var(--space-12);
  }

  .page--seed-deck .timeline-track::before { display: none; }
  .page--seed-deck .flywheel-stage { height: 360px; }

  .page--seed-deck .market-cards, .page--seed-deck .proof-grid, .page--seed-deck .use-grid {
    grid-template-columns: 1fr;
  }

  .page--seed-deck .timeline-grid { grid-template-columns: repeat(2, 1fr); }
  .page--seed-deck .team-grid { grid-template-columns: 1fr; }
  .page--seed-deck .counter, .page--seed-deck .progress, .page--seed-deck .rail { display: none; }

  html:has(> .page--seed-deck)[data-view="overview"] .deck {
    grid-template-columns: 1fr;
    padding: var(--space-12);
  }

  html:has(> .page--seed-deck)[data-view="overview"] .slide { min-height: 0; }
  .page--seed-deck .help__grid, .page--seed-deck .product-showcase { grid-template-columns: 1fr; }
  html:has(> .page--seed-deck)[data-design="refined"] .slide__inner { padding: var(--space-32) var(--space-24) var(--space-22); }

  html:has(> .page--seed-deck)[data-design="refined"] .hero-copy--seed-deck .slide__title,
  html:has(> .page--seed-deck)[data-design="refined"] .slide__title {
    font-size: 40px;
  }

  html:has(> .page--seed-deck)[data-design="refined"] .slide__number {
    padding: var(--space-4) var(--space-6);
    min-width: 54px;
  }

  html:has(> .page--seed-deck)[data-design="refined"] .team-grid { grid-template-columns: 1fr; }

  html:has(> .page--seed-deck)[data-design="refined"] .team-card,
  html:has(> .page--seed-deck)[data-design="refined"] .team-card:nth-child(-n + 2) {
    grid-column: auto;
  }

  html:has(> .page--seed-deck)[data-design="refined"] .hero-grid--seed-deck {
    grid-template-columns: 1fr;
  }

  html:has(> .page--seed-deck)[data-design="refined"] .network-stage { min-height: var(--space-320); }

  html:has(> .page--seed-deck)[data-design="refined"] .hero-copy--seed-deck .slide__lede {
    max-width: none;
  }

  .page--seed-deck .product-extract__visual { height: 210px; }
  .page--seed-deck .product-system-bar { grid-template-columns: 1fr; }
  .page--seed-deck .product-system-bar > div { flex-wrap: wrap; }

  

  

  

  

  

  

  .page :where( .grid-4, .grid-3) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  

  

  

  

  

  .page .card--flush { --card-current-padding: 0px; }
  .page--design-system .integration-summary { grid-template-columns: 1fr; }
  
  

  .page--design-system .badge-system-head,
  .page--design-system .badge-system-subhead {
    align-items: flex-start;
    flex-direction: column;
  }

  
  
  

  .page--design-system .panel-surface-system__intro {
    grid-template-columns: 1fr;
    gap: var(--space-12);
  }

  
  .page--design-system .badge-system-board { padding: var(--spacing-3xl); }

  .page--design-system .badge-system-subhead p, .page--design-system .nav-spec-stateboard__head p {
    text-align: left;
  }

  .page--design-system .badge-size-grid { grid-template-columns: 1fr; }
  .page--design-system .badge-tone-grid { grid-template-columns: 1fr 1fr; }
  .page--design-system .badge-anatomy-list { grid-template-columns: 1fr; }
  .page--design-system .nav-spec-toolbar { align-items: flex-start; flex-wrap: wrap; }
  .page--design-system .nav-spec-help { width: 100%; margin-left: 0; }
  .page--design-system .nav-spec-switch span { display: none; }
  .page--design-system .nav-spec-stage { padding: var(--space-14); }
  .page--design-system .nav-spec-responsive { grid-template-columns: 1fr; padding: 0 var(--space-14) var(--space-14); }
  .page--design-system .nav-spec-stateboard { padding: var(--space-16); }

  .page--design-system .nav-spec-stateboard__head {
    align-items: flex-start;
    flex-direction: column;
    gap: var(--space-6);
  }

  .page--design-system .nav-spec-rulegrid, .page--design-system .nav-spec-stategrid {
    grid-template-columns: 1fr;
  }

  .page--design-system .nav-spec-board__head { padding: var(--space-18); }
  .page--design-system .panel-surface-system { padding: var(--space-20); }
  .page--design-system .panel-surface-legend { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .topbar--portal { height: var(--space-64); }
  [data-skillprint-page^="portal-"] .primary-nav { display: none; }
  [data-skillprint-page^="portal-"] .mobile-nav { display: flex; height: var(--space-42); }

  .hero--portal, .page--design-system .portal-library-embed .hero--portal {
    padding-top: var(--space-32);
  }

  .page--design-system .portal-library-embed .progress-panel,
  .page--portal-games .progress-panel {
    padding: var(--space-20);
  }

  .page--design-system .portal-library-embed .featured-copy,
  .page--portal-games .featured-copy {
    padding: var(--space-28);
  }

  .page--design-system .portal-library-embed .featured-art,
  .page--portal-games .featured-art {
    min-height: 280px;
  }

  .page--design-system .portal-library-embed .control-inner,
  .page--portal-games .control-inner {
    grid-template-columns: 1fr;
    padding: var(--space-14) 0;
  }

  .game-grid--portal, .page--design-system .portal-library-embed .game-grid--portal {
    grid-template-columns: 1fr;
  }

.page--design-system .portal-library-embed .game-description,
.page--portal .game-description
{
    min-height: 0;
  }

  [data-skillprint-page^="portal-"] .mobile-nav { align-items: center; gap: var(--space-2); }
}

@media (max-width: 700px) {

  .page--design-system .homepage-mini-hero {
    grid-template-columns: 1fr;
  }

  .page--design-system .homepage-mini-proof { grid-template-columns: repeat(2, 1fr); }

  .page--design-system .homepage-mini-dimensions,
  .page--design-system .interface-application-summary,
  .page--design-system .interface-comparison-grid {
    grid-template-columns: 1fr;
  }

  .page--design-system .interface-preview-card__head {
    align-items: flex-start;
    flex-direction: column;
  }

  .page--design-system .interface-window { margin: var(--space-16) var(--space-16) 0; }
  .page--design-system .interface-preview-card__copy { padding: var(--space-20); }
  .page--design-system .docs-mini-paths { grid-template-columns: 1fr; }
  .page--design-system .docs-mini-paths article { min-height: 76px; }
  .page--design-system .interface-window__body { min-height: 520px; }
  .page--design-system .labs-mini-grid { grid-template-columns: 1fr; }

  .page--design-system .labs-mini-chart, .page--design-system .labs-mini-control {
    min-height: 176px;
  }
}


@media (max-width: 620px) {
  .page--ontology-wheel .sp-nav__actions > span { display: none; }

  /* Below this the chart card is too narrow to sit two actions side by side,
     so every popover takes the confined form whether or not it asked for it.
     Only the ceiling changes; the room still has to come from the script,
     because a percentage here would measure the anchor - a single chart bar -
     and land under the component's own min-width, which wins. */
  .sp-chart-popover { max-width: min(var(--space-256), var(--popover-room, calc(100vw - var(--space-32)))); }
  .sp-chart-popover .sp-chart-popover__actions { flex-direction: column; }
  .sp-chart-popover .sp-chart-popover__actions > * { width: 100%; }

  .sp-tour { max-width: min(var(--space-320), calc(100vw - var(--space-32))); }

  .page--design-system .auth-code { gap: var(--space-8); }
  .page--design-system .auth-code__digit { width: var(--space-48); height: var(--space-56); font-size: var(--display-xs-font-size); }
  .page--design-system .auth-shell__title { font-size: var(--display-xs-font-size); line-height: var(--display-xs-line-height); }

  .page--ontology-wheel .sp-nav__inner {
    flex-wrap: wrap;
    gap: var(--space-12);
  }

  .button-group {
    width: 100%;
    display: flex;
  }

  .button-group__item {
    padding-inline: var(--space-10);
    flex: 1 1 auto;
  }
  .video-controls {
    padding: var(--space-24)
      var(--space-8)
      var(--space-8);
  }

  .video-preview,
  .video-action--optional {
    display: none;
  }
  .page--homepage .stat { padding: var(--space-18) var(--space-18); }

  
  .page--homepage .badge-card { width: 210px; }

  .page--docs-v2 .sp-breadcrumb__item:first-child { display: none; }

  


  .page--docs-v2 .search-overlay { padding-top: 6vh; }
  .page--design-system .hero-inner, .page--design-system .section-inner { padding: var(--space-56) 0; }

  .page--design-system .badge-gallery,
  .page--design-system .do-dont,
  .page--design-system .form-state-grid,
  .page--design-system .grid-2,
  .page--design-system .grid-3,
  .page--design-system .grid-4,
  .page--design-system .guideline-grid,
  .page--design-system .logo-grid,
  .page--design-system .story-grid,
  .page--design-system .study-grid,
  .page--design-system .token-grid {
    grid-template-columns: 1fr;
  }

  .page--design-system .spectrum-band { grid-template-columns: repeat(2, 1fr); }
  .page--design-system .logo-stage { min-height: 240px; padding: var(--space-42); }
  .page--design-system .anatomy-list { grid-template-columns: 1fr; }
  .page--design-system .phase { grid-template-columns: 1fr; gap: var(--space-8); }
  .page--design-system .pair-grid { grid-template-columns: 1fr; }
  .page--design-system .clear-space { inset: var(--space-28) var(--space-28) 70px; }

  .page--design-system .source-surface-row {
    min-height: 0;
    padding: var(--space-20);
    border-radius: var(--radius-2xl);
  }

  .page--design-system .source-surface-url { font-size: var(--text-sm-font-size); line-height: var(--text-sm-line-height); }

  .page--design-system .chart-view-toolbar,
  .page--design-system .sp-popup-preview-head {
    align-items: flex-start;
    flex-direction: column;
  }

  .page--design-system .chart-view-switch { width: 100%; }
  .page--design-system .chart-view-switch button { min-width: 0; }
  .page--design-system .chart-frame.compact { min-height: var(--space-320); padding: var(--space-16) var(--space-8) var(--space-4); }
  .page--design-system .rules { grid-template-columns: 1fr; }
  .page--design-system .badge-subsection { margin-top: var(--space-56); }
  .page--design-system .weight-grid { grid-template-columns: 1fr; }
  .page--design-system .font-card.mono .alphabet { font-size: var(--display-md-font-size); line-height: var(--display-md-line-height); }
  .page--design-system .type-combo { min-height: 350px; padding: var(--space-20); }
  .page--design-system .poster-type-rule { grid-template-columns: 62px 1fr; }
  .page--design-system .shared-scale-callout { padding: var(--space-20); }
  .page--design-system .trait-module .hero-inner { min-height: auto; padding: var(--space-64) 0; }

  .page--design-system .trait-module .do-dont,
  .page--design-system .trait-module .grid-2,
  .page--design-system .trait-module .grid-3,
  .page--design-system .trait-module .grid-4,
  .page--design-system .trait-module .trait-grid {
    grid-template-columns: 1fr;
  }

  .page--design-system .trait-module .library-toolbar,
  .page--documentation-landing .command {
    align-items: stretch;
    flex-direction: column;
  }

  .page--documentation-landing .command code { width: 100%; overflow-x: auto; }
  .page--design-system .trait-module .search-field { width: 100%; }
  .page--design-system .trait-module .hero-system { padding: var(--space-18); }
  .page--design-system .trait-module .pillar-preview { grid-template-columns: 42px 1fr; }

  .page--design-system .surface-identity-card__head code,
  .page--design-system .trait-module .pillar-preview code {
    grid-column: 2;
  }

  .page--design-system .trait-module .demo-head { flex-direction: column; }

  .page--design-system .trait-module .component-demo, .page--design-system .trait-module .demo-row {
    padding: var(--space-16);
  }

  .page--design-system .api-module .intro-grid { padding-bottom: var(--space-44); }

  .page--design-system .api-module .endpoint, .page--design-system .api-module .section-block {
    padding: var(--space-42) 0;
  }

  .page--design-system .api-module .setup-row { grid-template-columns: 1fr; }
  

  .page--design-system .api-module .code-head {
    align-items: flex-start;
    flex-direction: column;
    padding: var(--space-10) var(--space-12);
  }

  .page--design-system .api-module .code-actions { width: 100%; justify-content: space-between; }.page--design-system .section-inner {
    padding-top: var(--spacing-7xl);
    padding-bottom: var(--spacing-7xl);
  }

  .page--design-system .master-subsection { margin-top: var(--spacing-7xl); }
  .page--design-system .spacing-token-grid { grid-template-columns: repeat(2, 1fr); }
  .page--design-system .span-grid { grid-template-columns: repeat(4, 1fr); }

  .page--design-system .span-4, .page--design-system .span-5, .page--design-system .span-7 {
    grid-column: span 4;
  }

  .page--design-system .radius-scale { grid-template-columns: repeat(3, 1fr); }

  .page--design-system .blur-system-grid,
  .page--design-system .button-demo-grid,
  .page--design-system .shadow-system-grid {
    grid-template-columns: 1fr;
  }

  .page--design-system .syntax-token-grid { grid-template-columns: 1fr; }

  .page--design-system .code-state-grid,
  .page--design-system .contract-grid,
  .page--design-system .copy-ready-notes {
    grid-template-columns: 1fr;
  }

  .page--design-system .copy-ready-block pre { max-height: 520px; padding: var(--space-18); }
  .page--design-system .copy-ready-head { align-items: flex-start; }
  .page--design-system .copy-ready-head button { flex: 0 0 auto; }

  .page--design-system .sp-popup-footer .button, .page--design-system .sp-popup-preview-head .button {
    width: 100%;
  }

  .page--design-system .sp-popup-preview-stage { min-height: 620px; }
  .page--design-system .sp-popup-stage-overlay { padding: var(--space-14); }
  .page--design-system .sp-popup-content { padding: var(--space-22); }
  .page--design-system .sp-popup-footer { padding: var(--space-16) var(--space-22) var(--space-20); flex-direction: column-reverse; }
  .page--design-system .sp-popup-summary { grid-template-columns: 1fr; }
  .page--design-system .sp-popup-live { padding: var(--space-14); }
  .page--design-system .asset-icon-guide { padding: var(--space-16); }
  .page--design-system .asset-icon { width: 58px; height: 58px; }
  .page--design-system .asset-icon-stage { height: 92px; }
  .page--design-system .implementation-overview__grid { grid-template-columns: 1fr; }
  .page--design-system #implementation .copy-ready-section { margin-top: var(--space-36); }

  .page--design-system .surface-application-grid, .page--design-system .surface-identity-grid {
    grid-template-columns: 1fr;
  }

  .page--design-system .surface-identity-card__head { grid-template-columns: 48px 1fr; }
  .page--design-system .brand-mark-preview { min-height: 170px; padding: var(--space-28); }
  .page--design-system .source-surface-row { grid-template-columns: 40px 1fr auto; }

  .page--design-system .source-surface-icon {
    width: 38px;
    height: 38px;
    grid-row: 1 / span 2;
  }

  .page--design-system .source-surface-name { grid-column: 2; }
  .page--design-system .source-surface-action { grid-column: 3; grid-row: 1; }
  .page--design-system .source-surface-url { grid-column: 2/-1; }

  .page--design-system .achievement-anatomy-examples, .page--design-system .achievement-anatomy-list {
    grid-template-columns: 1fr;
  }

  .page--design-system .achievement-anatomy-example:last-child {
    grid-column: auto;
    max-width: none;
  }

  .page--design-system .achievement-anatomy-list .anatomy-item { min-height: 0; }

  .page--documentation-landing .feature-section,
  .page--documentation-landing .sp-footer__inner,
  .page--documentation-landing .header-inner,
  .page--documentation-landing .hero--docs-landing,
  .page--documentation-landing .start-panel {
    width: min(100% - 28px, var(--content));
  }

  .page--documentation-landing { --topbar: var(--space-64); }
  .page--documentation-landing .header-inner { gap: var(--space-12); }
  .page--documentation-landing .brand img { width: 124px; }

  /* The drawer already repeats the call to action, so the bar drops it and
     keeps the brand, search and menu. At 320px those three plus the product
     word are 6px wider than the gutters allow, and the wordmark beside them
     already says which product this is. */
  .page--documentation-landing .header-cta,
  .page--documentation-landing .brand-product { display: none; }

  .hero--docs-landing { padding: var(--space-56) 0 var(--space-36); }
  
  .page--documentation-landing .feature-section { padding-bottom: var(--space-48); }
  .page--documentation-landing .feature-card { display: flex; }

  .page--documentation-landing .icon-stage {
    width: 132px;
    height: 132px;
    margin: var(--space-24) auto var(--space-22);
  }

  .page--documentation-landing .card-link { width: 100%; }
  .page--documentation-landing .start-panel { margin-bottom: var(--space-48); padding: var(--space-20); }





  .section-head--homepage p,
  .page--homepage .badge-copy p,
  .page--homepage .cta-copy p,
  .page--homepage .process-copy p,
  .page--homepage .profile-copy > p,
.page--design-system .api-module .endpoint-path,
.hero-copy--docs-landing,
.hero-copy--portal { font-size: var(--text-md-font-size); line-height: var(--text-md-line-height); }

  .page--design-system .library-module .section-heading {
    align-items: flex-start;
    flex-direction: column;
  }

  .page--design-system .library-module .game-body { padding: var(--space-18); }
  .page--design-system .library-module .modal-inner { padding: var(--space-36) var(--space-22) var(--space-28); }

  .page--design-system .library-module .modal-title {
    margin-right: var(--space-42);
    font-size: var(--display-sm-font-size);
    line-height: var(--display-sm-line-height);
  }

  .page--design-system .library-module .modal-actions { padding: var(--space-16) var(--space-22) var(--space-22); }
  .page--design-system .library-module .modal-meta { grid-template-columns: 1fr; }

  .page--game-session .game-result__overview { grid-template-columns: 1fr; }
  .page--game-session .game-result__summary { grid-template-columns: 1fr; }
  .page--game-session .game-result__mood-answer { flex: 1 1 auto; }
  .page--game-session .game-result__section :is(.grid-3, .grid-4) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .page--game-session .session-rail { left: var(--space-16); right: var(--space-16); justify-content: center; }
  .page--game-session .session-exit { bottom: var(--space-80); }

  /* Full width at the top, which is the move the step rail makes at the bottom:
     at this width a 256px dock in the corner leaves the trigger's label nowhere
     to go. The dialog pads clear of it for the same reason it pads clear of the
     rail. */
  .page--game-session .game-switch {
    left: var(--space-16);
    right: var(--space-16);
    width: auto;
  }

  /* Stacked and full width here, so the borderless button is a block like the
     others: the optical pull it takes on a single row would hang it off the
     plate, and the footer pads to its own edges rather than to the row above. */
  .page--game-session .popup__footer {
    padding-inline: var(--space-24);
    flex-direction: column-reverse;
    align-items: stretch;
  }

  .page--game-session .popup__footer .button--tertiary { margin-inline-start: 0; }
  .page--game-session .game-result__actions { flex-direction: column-reverse; }
  .page--game-session .popup__footer .button { width: 100%; }
}


@media (max-width: 520px) {
  .popup-backdrop {
    padding: var(--space-14);
  }

  /* Same recipe as .popup__footer at this width: the primary action ends up
     nearest the thumb, and neither button is a narrow target. */
  .sp-alert__actions,
  .sp-tour__actions {
    flex-direction: column-reverse;
    width: 100%;
  }

  /* Labelled actions only. An icon-only control has no label to justify the
     width, and .button--icon-only's square sizing is the lower-specificity
     rule, so without this exclusion the tour's back chevron becomes a bar. */
  .sp-alert__actions .button:not(.button--icon-only),
  .sp-tour__actions .button:not(.button--icon-only) { width: 100%; }

  .sp-tour__footer {
    align-items: stretch;
    flex-direction: column;
    gap: var(--space-14);
  }

  .page--design-system .auth-canvas:not(.auth-canvas--flush) { padding: var(--space-24) var(--space-14); }
  .page--design-system .auth-code__digit { width: var(--space-40); height: var(--space-48); }
  .page--design-system .auth-meta { align-items: flex-start; flex-direction: column; gap: var(--space-8); }

  .popup__content {
    padding: var(--space-24);
  }

  .popup__footer {
    flex-direction: column-reverse;
  }

  .popup__footer .button {
    width: 100%;
  }

  .page--game-session .game-result__section :is(.grid-3, .grid-4) { grid-template-columns: 1fr; }
  .page--game-session .game-result__mood-answer { --button-height: var(--space-44); }
  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  .page .hero-actions { margin-top: var(--space-24); }
  .page :where( .grid-4, .grid-3, .grid-2) { grid-template-columns: 1fr; }
  .page--design-system .badge-tone-grid { grid-template-columns: 1fr; }

  .page--design-system .portal-library-embed .featured-actions .button,
  .page--design-system .portal-library-embed .hero-actions .button,
  .page--design-system .ui-badge-group,
  .page--portal-games .featured-actions .button,
  .page--portal-games .hero-actions .button {
    width: 100%;
  }

  .page--design-system .ui-badge-group__message { flex: 1; }
  .page--design-system .panel-surface-system { padding: var(--space-14); }

  .page--design-system .panel-surface-contract, .page--design-system .panel-surface-demo__boxes {
    grid-template-columns: 1fr;
  }

.page--design-system .panel-surface-demo__head,
.page--design-system .portal-library-embed .section-heading,
.page--design-system .portal-library-embed .card-footer,
.page--portal .card-footer
{
    align-items: flex-start;
    flex-direction: column;
  }

  [data-skillprint-page^="portal-"] .brand-logo { width: 124px; }

  .page--design-system .portal-library-embed .metric-grid,
  .page--portal-games .metric-grid {
    grid-template-columns: 1fr;
  }

  .page--design-system .portal-library-embed .progress-head,
  .page--portal-games .progress-head {
    align-items: center;
  }

  .page--design-system .portal-library-embed .score-ring,
  .page--design-system .portal-library-embed .score-ring svg,
  .page--portal-games .score-ring,
  .page--portal-games .score-ring svg {
    width: 72px;
    height: 72px;
  }

  .page--design-system .portal-library-embed .featured-actions,
  .page--portal-games .featured-actions {
    width: 100%;
    flex-direction: column;
  }

.page--design-system .portal-library-embed .card-actions,
.page--portal .card-actions
{
    width: 100%;
  }

.page--design-system .portal-library-embed .details-btn,
.page--design-system .portal-library-embed .play-btn,
.page--portal .details-btn,
.page--portal .play-btn
{
    flex: 1;
  }

  .page--design-system .portal-library-embed .modal-inner,
  .page--portal .modal-inner {
    padding: var(--space-32) var(--space-20) var(--space-24);
  }

  /* Steps down with the page title rather than up past it: the base is now
     Display XS, so the old Display SM here would have made the modal heading
     grow by 6px on the narrowest screen. Text XL is the step .portal-head h1
     itself takes on small viewports. */
  .page--design-system .portal-library-embed .modal-title,
  .page--portal .modal-title {
    font-size: var(--text-xl-font-size);
    line-height: var(--text-xl-line-height);
  }

  .page--design-system .portal-library-embed .modal-actions,
  .page--portal .modal-actions {
    padding: var(--space-14) var(--space-20) var(--space-20);
  }

  .page--design-system .portal-library-embed .modal-meta,
  .page--portal .modal-meta {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 440px) {
  .page--design-system .partner-mini-sidebar { display: none; }
  .page--design-system .partner-interface-mini { grid-template-columns: 1fr; }
  .page--design-system .partner-mini-panels { grid-template-columns: 1fr; }
  .page--design-system .deck-interface-mini { padding: var(--space-14); }
  .page--design-system .deck-mini-rail { display: none; }
  .page--design-system .homepage-mini-nav { gap: var(--space-8); }

  .page--design-system .homepage-mini-links {
    display: none;
  }

  .page--design-system .homepage-mini-dimensions { padding-inline: var(--space-16); }

  .page--design-system .docs-mini-links i:nth-child(2),
  .page--design-system .docs-mini-links i:nth-child(3),
  .page--design-system .labs-mini-links {
    display: none;
  }

  .page--design-system .labs-mini-nav > b { margin-left: auto; }
  .page--design-system .interface-preview-link { width: 100%; justify-content: center; }
}

@media print {
  .page--design-system .code-head button,
  .page--design-system .copy-chip,
  .page--design-system .mobile-bar,
  .page--design-system .sidebar {
    display: none !important;
  }

  .page--design-system main { margin: 0; }

  .page--design-system main > .sp-footer > .sp-footer__inner,
  .page--design-system .hero-inner,
  .page--design-system .section-inner {
    width: 92%;
  }

  .page--design-system { background: var(--geistface-grey-25); color: var(--deep-navy-900); }
  .hero--design-system, .page--design-system .section--design-system { border-color: var(--panel-navy-100); }

  .page--design-system .badge-card,
  .page--design-system .component-stage,
  .page--design-system .game-art-card,
  .page--design-system .guideline-card,
  .page--design-system .phase,
  .page--design-system .scale-group,
  .page--design-system .token-card {
    background: var(--geistface-grey-25);
    color: var(--deep-navy-900);
    border-color: var(--panel-navy-100);
  }

  .page--design-system .hero-copy--design-system, .page--design-system .lead, .page--design-system p {
    color: var(--geistface-grey-800) !important;
  }

  .page--seed-deck {
    background: var(--deep);
    overflow: visible;
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  .page--seed-deck .app, .page--seed-deck .deck, .page--seed-deck .deck-shell {
    position: static;
    display: block;
    overflow: visible;
  }

  .topbar--seed-deck,
  .page--seed-deck .counter,
  .page--seed-deck .help,
  .page--seed-deck .progress,
  .page--seed-deck .rail {
    display: none !important;
  }

  .page--seed-deck .slide-wrap {
    width: 16in;
    height: 9in;
    padding: 0;
    display: block;
    page-break-after: always;
    break-after: page;
  }

  .page--seed-deck .slide {
    width: 16in;
    height: 9in;
    aspect-ratio: auto;
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }

  .page--seed-deck [data-animate] { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
  .button[aria-busy="true"]::before {
    animation-duration: 1.4s;
  }
  :is(
    .switch__track,
    .switch__track::after,
    .sp-toggle,
    .sp-toggle__thumb
  ) {
    transition: none;
  }
  :is(
    .sp-progress__fill,
    .sp-progress-ring__value,
    .video-overlay,
    .video-preview
  ) {
    transition: none;
  }
  html { scroll-behavior: auto !important; }
}

/* ---------------------------------------------------------------------------
   Marketing website breakpoints, folded in from the generated stylesheet
   --------------------------------------------------------------------------- */


/* ==========================================================================
   09 - Unique composition · Documentation V2
   Guides plus a templated API reference. The endpoint block is a single
   repeatable template driven by the endpoint model in js/skillprint.js,
   so any endpoint in the OpenAPI spec can be rendered without new markup.
   ========================================================================== */

/* Shell metrics only — the shared documentation token contract lives beside
   the Documentation Application section. */
.page--docs-v2 {
  --sidebar: 288px;
  --topbar: 60px;
  --rail: 212px;
  --code-col: 460px;
  /* Accents as small text. The 500 steps read on the dark panels as borders
     and icons but fall under 4.5:1 as 10-12px text; these are the steps that
     clear it on both surfaces. */
  --docs-violet-text: var(--mindset-violet-300);
  --docs-blue-text: var(--personality-blue-500);
  --docs-green-text: var(--core-green-500);
  --docs-orange-text: var(--skills-orange-500);
  --docs-pink-text: var(--skills-pink-500);
}

html:is([data-theme="light"], [data-surface="light"]) > .page--docs-v2 {
  --docs-violet-text: var(--mindset-violet-600);
  --docs-blue-text: var(--personality-blue-700);
  --docs-green-text: var(--core-green-700);
  --docs-orange-text: var(--skills-orange-700);
  --docs-pink-text: var(--skills-pink-700);
}

html:has(> .page--docs-v2) {
  color-scheme: dark;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--topbar) + 28px);
}

html:has(> .page--docs-v2)[data-theme="light"] {
  color-scheme: light;
}

.page--docs-v2 [hidden] { display: none !important; }


/* --------------------------------------------------------------------------
   Sidebar
   -------------------------------------------------------------------------- */

.page--docs-v2 .sidebar {
  position: fixed;
  inset: 0 auto 0 0;
  z-index: 100;
  width: var(--sidebar);
  height: 100vh;
  border-right: 1px solid var(--border);
}

.page--docs-v2 .brand-row { min-height: var(--topbar); padding: 0 var(--space-18); }

.page--docs-v2 .sidebar-search-wrap { padding: var(--space-16) var(--space-14) var(--space-8); }

.page--docs-v2 .sidebar-search {
  min-height: var(--space-36);
  border-width: 1px;
  border-style: solid;
  cursor: pointer;
}


.page--docs-v2 .sidebar-search kbd { border: 1px solid var(--border-strong); }

.page--docs-v2 .nav-scroll { padding: var(--space-8) var(--space-10) var(--space-24); }

.page--docs-v2 .nav-group { margin-top: var(--space-20); }

.page--docs-v2 .nav-count {
  color: var(--subtle);
  font: 600 var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono);
}

.page--docs-v2 .nav-endpoints { display: grid; gap: 1px; }

.page--docs-v2 .nav-endpoint {
  min-height: var(--space-28);
  padding: 0 var(--space-10);
  display: flex;
  align-items: center;
  gap: var(--space-8);
  border-radius: var(--radius-sm);
  color: var(--muted);
  font-size: var(--text-xxs-font-size);
  text-decoration: none;
  transition: var(--transition-interactive);
}

.page--docs-v2 .nav-endpoint:hover { background: var(--panel-900); color: var(--text-strong); }

.page--docs-v2 .nav-endpoint.is-active {
  border-color: var(--nav-current-border);
  background: var(--nav-current-surface);
  color: var(--nav-current-text);
}

.page--docs-v2 .sidebar-foot { padding: var(--space-12) var(--space-14) var(--space-16); }
.page--docs-v2 .foot-row { grid-template-columns: 1fr auto; }
.page--docs-v2 .version-select { min-height: var(--space-40); cursor: default; }
.page--docs-v2 .version-select:hover { background: var(--surface-box); }

/* The drawer's scrim, drawn only while the drawer is open. */
.page--docs-v2 .sidebar-scrim {
  position: fixed;
  inset: 0;
  z-index: 95;
  display: none;
  border: 0;
  padding: 0;
  background: var(--side-nav-scrim);
}

.page--docs-v2.nav-open .sidebar-scrim { display: block; }

/* A control that just copied says so on itself, not only in the toast. */
.page--docs-v2 .copy-btn.is-copied { color: var(--core-green-500); border-color: transparent; }

/* --------------------------------------------------------------------------
   Topbar and shell
   -------------------------------------------------------------------------- */


.page--docs-v2 .status-link { min-height: var(--space-32); padding: 0 var(--space-10); cursor: default; }

.page--docs-v2 .lang-switch { min-height: var(--space-30); }

.page--docs-v2 .lang-switch .button-group__item {
  min-width: 0;
  min-height: var(--space-28);
  padding: 0 var(--space-10);
  font: 600 var(--text-xs-font-size)/var(--text-xs-line-height) var(--font-mono);
}

/* The two-left-edges fault: past ~1830px a centred cap freezes the column
   while the breadcrumbs and topbar actions keep running full width, so the
   shell and the topbar start at different left edges. The remedy: the cap
   stays (.doc-body is an 880px measure), the box pins to the start, and the
   topbar's gutter moves into padding so it holds at every breakpoint. */
.page--docs-v2 .shell {
  width: min(1560px, 100%);
  margin-inline: 0 auto;
  grid-template-columns: minmax(0, 1fr) var(--rail);
  gap: var(--space-56);
  padding: var(--space-56) var(--space-28) var(--space-96);
}

.page--docs-v2 .shell[data-mode="reference"] { grid-template-columns: minmax(0, 1fr); }
.page--docs-v2 .shell[data-mode="reference"] .rail { display: none; }
.page--docs-v2 .doc-body { max-width: 880px; }
.page--docs-v2 .shell[data-mode="reference"] .doc-body { max-width: none; }

/* --------------------------------------------------------------------------
   Guide typography and blocks
   -------------------------------------------------------------------------- */

.page--docs-v2 .section { padding: var(--space-48) 0; }
.page--docs-v2 .section-head { max-width: 720px; margin-bottom: var(--space-24); }

.page--docs-v2 .doc-eyebrow {
  display: flex;
  align-items: center;
  gap: var(--space-10);
  margin-bottom: var(--space-14);
  color: var(--subtle);
  font: 600 var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.page--docs-v2 .eyebrow-chip {
  padding: 0 var(--space-8);
  display: inline-flex;
  align-items: center;
  color: var(--docs-violet-text);
}

.page--docs-v2 h1 { margin: 0 0 var(--space-12); color: var(--text-strong); font-size: var(--app-display-sm-font-size); line-height: var(--app-display-sm-line-height); font-weight: var(--app-heading-weight); letter-spacing: var(--app-title-tracking); }
.page--docs-v2 h2 { margin: 0 0 var(--space-8); color: var(--text-strong); font-size: var(--app-text-xl-font-size); line-height: var(--app-text-xl-line-height); font-weight: var(--app-heading-weight); letter-spacing: var(--app-heading-tracking); }
.page--docs-v2 h3 { margin: 0 0 var(--space-6); color: var(--text-strong); font-size: var(--app-text-md-font-size); line-height: var(--app-text-md-line-height); font-weight: var(--app-heading-weight); letter-spacing: var(--app-heading-tracking); }

.page--docs-v2 .lead {
  max-width: var(--measure-md);
  margin: 0;
  color: var(--muted);
}

.page--docs-v2 .section-head p { margin: 0; font-size: var(--app-text-md-font-size); line-height: var(--app-text-md-line-height); }
.page--docs-v2 .hero-meta { font-size: var(--text-sm-font-size); line-height: var(--text-sm-line-height); }
.page--docs-v2 .sp-footer--pager { font-size: var(--text-sm-font-size); line-height: var(--text-sm-line-height); }
.page--docs-v2 .card-grid, .page--docs-v2 .auth-grid, .page--docs-v2 .quick-grid { gap: var(--space-14); }

.page--docs-v2 .value-card, .page--docs-v2 .mood-card { gap: var(--space-10); padding: var(--space-20); }
.page--docs-v2 .value-icon, .page--docs-v2 .mood-icon { width: var(--space-40); height: var(--space-40); color: var(--violet-400); margin-bottom: var(--space-4); }
.page--docs-v2 .card-link { padding-top: var(--space-14); font-weight: 650; }

.page--docs-v2 .auth-card { padding: var(--space-20); }
.page--docs-v2 .card-label { display: block; margin-bottom: var(--space-8); font: 600 var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono); letter-spacing: 0.08em; }
.page--docs-v2 .auth-card p { margin: 0; }

.page--docs-v2 .fact-list { list-style: none; margin: var(--space-16) 0 0; gap: var(--space-8); }
.page--docs-v2 .fact-list li { display: flex; gap: var(--space-12); align-items: start; }
.page--docs-v2 .fact-list li::before { flex: 0 0 auto; }


.page--docs-v2 .quick-card { padding: var(--space-18); }
.page--docs-v2 .quick-top { margin-bottom: var(--space-16); }
.page--docs-v2 .quick-number { letter-spacing: 0.06em; }

.page--docs-v2 .external-link { margin-top: var(--space-12); color: var(--docs-violet-text); }
.page--docs-v2 .external-link:hover { text-decoration: underline; text-underline-offset: 3px; }

/* Flow map — the five-step integration overview */
.page--docs-v2 .flow-map { list-style: none; margin: 0; gap: var(--space-10); grid-template-columns: repeat(5, minmax(0, 1fr)); }

.page--docs-v2 .flow-node {
  min-height: 148px;
  align-content: start;
  gap: var(--space-8);
  padding: var(--space-16);
  border-radius: var(--radius-xl);
}

.page--docs-v2 .flow-step { font: 600 var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono); }
.page--docs-v2 .flow-node[data-accent="violet"] { border-color: color-mix(in srgb, var(--violet-400) 52%, var(--border)); }
.page--docs-v2 .flow-node[data-accent="blue"] { border-color: color-mix(in srgb, var(--blue) 48%, var(--border)); }
.page--docs-v2 .flow-node[data-accent="mint"] { border-color: color-mix(in srgb, var(--mint) 45%, var(--border)); }
.page--docs-v2 .flow-node[data-accent="lime"] { border-color: color-mix(in srgb, var(--lime) 45%, var(--border)); }
.page--docs-v2 .flow-node[data-accent="pink"] { border-color: color-mix(in srgb, var(--pink) 45%, var(--border)); }

/* Flow SDK loop map */
.page--docs-v2 .loop-map { grid-template-columns: repeat(2, minmax(0, 1fr)); }

.page--docs-v2 .loop-node {
  align-content: start;
  gap: var(--space-8);
  padding: var(--space-16);
  border-radius: var(--radius-xl);
}

.page--docs-v2 .loop-call { display: flex; align-items: center; gap: var(--space-6); font: 500 var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono); color: var(--subtle); }
.page--docs-v2 .loop-node[data-accent="violet"] { border-color: color-mix(in srgb, var(--violet-400) 52%, var(--border)); }
.page--docs-v2 .loop-node[data-accent="blue"] { border-color: color-mix(in srgb, var(--blue) 48%, var(--border)); }
.page--docs-v2 .loop-node[data-accent="mint"] { border-color: color-mix(in srgb, var(--mint) 45%, var(--border)); }
.page--docs-v2 .loop-node[data-accent="pink"] { border-color: color-mix(in srgb, var(--pink) 45%, var(--border)); }

/* Steps */
.page--docs-v2 .step { grid-template-columns: var(--space-42) minmax(0, 1fr); gap: var(--space-16); padding: var(--space-20); }
.page--docs-v2 .step-num { width: var(--space-36); height: var(--space-36); color: var(--docs-violet-text); font: 650 var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono); }
.page--docs-v2 .step p { margin: 0 0 var(--space-4); }

/* Chips, states and origin pills */
.page--docs-v2 .chip-cloud { margin-top: var(--space-4); }

.page--docs-v2 .dim-chip {
  min-height: var(--space-26);
  padding: 0 var(--space-10);
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  background: var(--panel-900);
  color: var(--muted);
  font: 500 11px/17px var(--font-mono);
}

.page--docs-v2 .dim-chip[data-family="cognition"] { border-color: color-mix(in srgb, var(--mint) 34%, var(--border)); }
.page--docs-v2 .dim-chip[data-family="provider"] { border-color: color-mix(in srgb, var(--violet-400) 34%, var(--border)); }
.page--docs-v2 .origin-chip { font-family: var(--font-mono); font-size: var(--text-xs-font-size); }

.page--docs-v2 .state-flow { margin-top: var(--space-4); }

.page--docs-v2 .state-pill {
  min-height: var(--space-26);
  padding: 0 var(--space-12);
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--panel-900);
  color: var(--text-strong);
  font: 650 var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono);
  letter-spacing: 0.08em;
}

.page--docs-v2 .state-pill[data-state="open"] { border-color: color-mix(in srgb, var(--green) 48%, var(--border)); color: var(--docs-green-text); }
.page--docs-v2 .state-pill[data-state="closed"] { border-color: color-mix(in srgb, var(--orange) 48%, var(--border)); color: var(--docs-orange-text); }
.page--docs-v2 .state-pill[data-state="scored"] { border-color: color-mix(in srgb, var(--violet-400) 52%, var(--border)); color: var(--docs-violet-text); }

/* Tables */
.page--docs-v2 table { min-width: 620px; }
.page--docs-v2 td code, .page--docs-v2 th code { font-family: var(--font-mono); font-size: var(--text-xs-font-size); white-space: nowrap; }


/* Error rows */
.page--docs-v2 .error-row { grid-template-columns: var(--space-56) minmax(0, 1fr); gap: var(--space-14); align-items: start; padding: var(--space-12) var(--space-14); }

.page--docs-v2 .error-code {
  font: 650 12px/19px var(--font-mono);
  color: var(--muted);
}

.page--docs-v2 .error-code[data-tone="ok"] { color: var(--docs-green-text); }
.page--docs-v2 .error-code[data-tone="warn"] { color: var(--docs-orange-text); }
.page--docs-v2 .error-code[data-tone="error"] { color: var(--docs-pink-text); }

.page--docs-v2 .sp-footer--pager { margin-top: var(--space-56); }

/* --------------------------------------------------------------------------
   Method pills — one contract, used in nav, prose and code heads
   -------------------------------------------------------------------------- */

.page--docs-v2 .method {
  min-width: var(--space-48);
  padding: 0 var(--space-6);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  border: 1px solid transparent;
  border-radius: var(--radius-xs);
  font: 700 var(--text-xxs-font-size)/18px var(--font-mono);
  letter-spacing: 0.06em;
}

.page--docs-v2 .method--get { border-color: color-mix(in srgb, var(--blue) 44%, transparent); background: color-mix(in srgb, var(--blue) 14%, transparent); color: var(--docs-blue-text); }
.page--docs-v2 .method--post { border-color: color-mix(in srgb, var(--green) 44%, transparent); background: color-mix(in srgb, var(--green) 14%, transparent); color: var(--docs-green-text); }
.page--docs-v2 .method--put { border-color: color-mix(in srgb, var(--orange) 44%, transparent); background: color-mix(in srgb, var(--orange) 14%, transparent); color: var(--docs-orange-text); }
.page--docs-v2 .method--patch { border-color: color-mix(in srgb, var(--violet-400) 48%, transparent); background: color-mix(in srgb, var(--violet-400) 14%, transparent); color: var(--docs-violet-text); }
.page--docs-v2 .method--delete { border-color: color-mix(in srgb, var(--pink) 44%, transparent); background: color-mix(in srgb, var(--pink) 14%, transparent); color: var(--docs-pink-text); }

.page--docs-v2 .method--mini { min-width: 0; padding: 0 var(--space-4); line-height: 16px; }

/* --------------------------------------------------------------------------
   Code panels — dark in both themes, global language switching
   -------------------------------------------------------------------------- */

.page--docs-v2 .code-panel__head {
  min-height: var(--space-40);
  padding: 0 var(--space-10) 0 var(--space-14);
  border-bottom: 1px solid var(--code-border);
  background: var(--code-surface);
  color: var(--code-muted);
  font: 500 var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono);
}

.page--docs-v2 .code-panel__route { color: var(--code-text); }

.page--docs-v2 .code-panel__body {
  padding: var(--space-18) var(--space-20);
  overflow: auto;
  color: var(--code-text);
  font: 11px/19px var(--font-mono);
  tab-size: 2;
}

.page--docs-v2 .lang-tabs { gap: var(--space-2); }

/* One code-panel tab contract. The status tab differs only in weight. */
.page--docs-v2 .lang-tab,
.page--docs-v2 .status-tab {
  min-height: var(--space-28);
  padding: 0 var(--space-8);
  border: 0;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--code-muted);
  font: 600 var(--text-xs-font-size)/var(--text-xs-line-height) var(--font-mono);
  cursor: pointer;
  transition: var(--transition-interactive);
}

.page--docs-v2 .lang-tab:hover,
.page--docs-v2 .status-tab:hover { color: var(--code-text); }
.page--docs-v2 .lang-tab.is-active { background: var(--code-surface-raised, var(--deep-navy-900)); color: var(--code-text); }

.page--docs-v2 .copy-btn {
  width: var(--space-32);
  height: var(--space-32);
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--code-muted);
  cursor: pointer;
  transition: var(--transition-interactive);
}

.page--docs-v2 .copy-btn:hover { border-color: var(--code-border-strong); color: var(--code-text); }

/* Response status tabs */
.page--docs-v2 .status-tabs { gap: var(--space-2); }

.page--docs-v2 .status-tab { font-weight: 600; }


.page--docs-v2 .status-tab.is-active { background: var(--code-surface-raised, var(--deep-navy-900)); }
.page--docs-v2 .status-tab.is-active[data-tone="ok"] { color: var(--core-green-500); }
.page--docs-v2 .status-tab.is-active[data-tone="warn"] { color: var(--skills-orange-500); }
.page--docs-v2 .status-tab.is-active[data-tone="error"] { color: var(--skills-pink-500); }

/* --------------------------------------------------------------------------
   The endpoint template
   -------------------------------------------------------------------------- */

.page--docs-v2 .api-group-head { max-width: 720px; padding-bottom: var(--space-8); }

.page--docs-v2 .api-base {
  margin-top: var(--space-16);
  padding: var(--space-10) var(--space-14);
  display: inline-flex;
  align-items: center;
  gap: var(--space-10);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--panel-900);
  color: var(--muted);
  font: 500 11px/17px var(--font-mono);
}

.page--docs-v2 .api-base strong { color: var(--text-strong); font-weight: 600; }

.page--docs-v2 .endpoint {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, var(--code-col));
  gap: var(--space-48);
  padding: var(--space-48) 0;
  border-top: 1px solid var(--border);
  scroll-margin-top: calc(var(--topbar) + 24px);
}

.page--docs-v2 .endpoint__doc { min-width: 0; }
.page--docs-v2 .endpoint__code { min-width: 0; position: sticky; top: calc(var(--topbar) + var(--space-24)); align-self: start; display: grid; gap: var(--space-12); }

.page--docs-v2 .endpoint__title { margin: 0 0 var(--space-10); font-size: var(--app-text-lg-font-size); line-height: var(--app-text-lg-line-height); }

.page--docs-v2 .endpoint__route {
  margin-bottom: var(--space-14);
  display: flex;
  align-items: center;
  gap: var(--space-10);
  flex-wrap: wrap;
}

.page--docs-v2 .endpoint__path {
  color: var(--text-strong);
  font: 600 12px/19px var(--font-mono);
  overflow-wrap: anywhere;
}

.page--docs-v2 .endpoint__lead { margin: 0 0 var(--space-18); color: var(--muted); font-size: var(--app-text-md-font-size); line-height: var(--app-text-md-line-height); }

.page--docs-v2 .endpoint__auth {
  margin-bottom: var(--space-20);
  padding: var(--space-8) var(--space-12);
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--panel-900);
  color: var(--muted);
  font-size: var(--text-xs-font-size);
}

.page--docs-v2 .endpoint__auth svg { color: var(--violet-400); flex: 0 0 auto; }
.page--docs-v2 .endpoint__auth strong { color: var(--text-strong); font-weight: 600; }

.page--docs-v2 .param-block { margin-top: var(--space-20); }

.page--docs-v2 .param-block__title {
  margin: 0 0 var(--space-4);
  color: var(--subtle);
  font: 600 var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.page--docs-v2 .param-list { margin: 0; }
.page--docs-v2 .param { padding: var(--space-12) 0; border-bottom: 1px solid var(--border); }
.page--docs-v2 .param:last-child { border-bottom: 0; }

.page--docs-v2 .param__name {
  margin: 0 0 var(--space-4);
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--space-8);
}

.page--docs-v2 .param__key { color: var(--text-strong); font: 600 11px/18px var(--font-mono); }
.page--docs-v2 .param__type { color: var(--docs-blue-text); font: 500 var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono); }

.page--docs-v2 .param__flag {
  padding: 0 var(--space-6);
  border-radius: var(--radius-xs);
  font: 600 var(--text-xxs-font-size)/var(--text-xxs-line-height) var(--font-mono);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.page--docs-v2 .param__flag[data-flag="required"] { background: color-mix(in srgb, var(--pink) 16%, transparent); color: var(--docs-pink-text); }
.page--docs-v2 .param__flag[data-flag="optional"] { background: color-mix(in srgb, var(--geistface-grey-200) 10%, transparent); color: var(--subtle); }
.page--docs-v2 .param__flag[data-flag="read-only"] { background: color-mix(in srgb, var(--violet-400) 16%, transparent); color: var(--docs-violet-text); }

.page--docs-v2 .param__desc { margin: 0; color: var(--muted); font-size: var(--text-xs-font-size); line-height: var(--text-sm-line-height); }
.page--docs-v2 .param__enum { margin-top: var(--space-6); display: flex; flex-wrap: wrap; gap: var(--space-4); }

.page--docs-v2 .param__enum span {
  padding: 0 var(--space-6);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  color: var(--muted);
  font: 500 10px/17px var(--font-mono);
}

.page--docs-v2 .endpoint-errors { margin-top: var(--space-20); display: grid; gap: var(--space-6); }

.page--docs-v2 .endpoint-error {
  display: grid;
  grid-template-columns: var(--space-42) minmax(0, 1fr);
  gap: var(--space-12);
  align-items: baseline;
  padding: var(--space-8) 0;
  border-bottom: 1px solid var(--border);
}

.page--docs-v2 .endpoint-error:last-child { border-bottom: 0; }
.page--docs-v2 .endpoint-error span:first-child { font: 650 11px/18px var(--font-mono); }
.page--docs-v2 .endpoint-error span:last-child { color: var(--muted); font-size: var(--text-xs-font-size); }
.page--docs-v2 .endpoint-error[data-tone="warn"] span:first-child { color: var(--docs-orange-text); }
.page--docs-v2 .endpoint-error[data-tone="error"] span:first-child { color: var(--docs-pink-text); }

/* --------------------------------------------------------------------------
   Right rail
   -------------------------------------------------------------------------- */

.page--docs-v2 .rail { min-width: 0; }

.page--docs-v2 .toc-card { padding: var(--space-14); }

/* --------------------------------------------------------------------------
   Search and toast
   -------------------------------------------------------------------------- */

.page--docs-v2 .search-overlay.is-open { display: grid; }

.page--docs-v2 .search-input-wrap { min-height: var(--space-56); }

.page--docs-v2 .search-result {
  padding: var(--space-10) var(--space-12);
  display: grid;
  grid-template-columns: var(--space-44) 1fr;
  align-items: center;
  cursor: pointer;
}


.page--docs-v2 .result-icon {
  width: var(--space-28);
  height: var(--space-28);
  display: grid;
  justify-self: center;
}


.page--docs-v2 .toast {
  position: fixed;
  left: 50%;
  bottom: var(--space-28);
  z-index: 400;
  padding: var(--space-10) var(--space-16);
  background: var(--text-strong);
  color: var(--deep);
  opacity: 0;
  transform: translate(-50%, var(--space-10));
  pointer-events: none;
  transition: opacity var(--duration-fast) ease, transform var(--duration-fast) ease;
}

.page--docs-v2 .toast.is-visible { opacity: 1; transform: translate(-50%, 0); }

/* --------------------------------------------------------------------------
   Documentation V2 · responsive
   -------------------------------------------------------------------------- */

@media (max-width: 1400px) {
  .page--docs-v2 { --code-col: 400px; }
  .page--docs-v2 .endpoint { gap: var(--space-32); }
}

@media (max-width: 1240px) {
  .page--docs-v2 { --rail: 184px; }
  .page--docs-v2 .shell { gap: var(--space-36); }
  .page--docs-v2 .endpoint { grid-template-columns: minmax(0, 1fr); gap: var(--space-24); }
  .page--docs-v2 .endpoint__code { position: static; }
  .page--docs-v2 .flow-map { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .page--docs-v2 .loop-map { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 1240px) {
  .page--docs-v2 .status-link { display: none; }
}

/* The drawer switches at the grid's tablet tier, where Part A already turns
   .sp-side-nav into an off-canvas panel; the header, the cleared margin and
   the drawer all move together. */
@media (max-width: 1023px) {
  .page--docs-v2 { --sidebar: 0px; --topbar: 56px; }
  .page--docs-v2 .rail { display: none; }
  .page--docs-v2 .shell { grid-template-columns: minmax(0, 1fr); }
  .page--docs-v2 .card-grid, .page--docs-v2 .auth-grid, .page--docs-v2 .quick-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .page--docs-v2 .lang-switch { display: none; }

  .page--docs-v2 .sidebar {
    width: min(288px, calc(100vw - var(--space-48)));
    transform: translateX(-100%);
    transition: transform var(--duration-base) ease, opacity var(--duration-base) ease, visibility var(--duration-base);
    box-shadow: var(--shadow-xl);
  }

  .page--docs-v2.nav-open .sidebar {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transform: translateX(0);
  }

  .page--docs-v2 .sidebar .sp-side-nav__link { min-height: var(--space-44); }
  .page--docs-v2 .nav-endpoint { min-height: var(--space-40); }
  .page--docs-v2 .sidebar .env-badge { display: none; }

  .page--docs-v2 .sidebar-close {
    position: absolute;
    top: var(--space-12);
    right: var(--space-12);
    display: grid;
    place-items: center;
  }

  .page--docs-v2 .mobile-header {
    position: sticky;
    top: 0;
    z-index: 90;
    min-height: var(--topbar);
    padding: 0 var(--space-12);
    display: flex;
    border-bottom: 1px solid var(--border);
    background: color-mix(in srgb, var(--deep) 94%, transparent);
    -webkit-backdrop-filter: blur(16px);
    backdrop-filter: blur(16px);
  }

  .page--docs-v2 .mobile-header .brand-row { min-height: auto; padding: 0; border: 0; }
  .page--docs-v2 .topbar { position: static; padding: 0 var(--space-16); }
  .page--docs-v2 .shell { width: 100%; padding: var(--space-32) var(--space-16) var(--space-80); }
}

@media (max-width: 767px) {
  .page--docs-v2 .flow-map, .page--docs-v2 .loop-map { grid-template-columns: minmax(0, 1fr); }
}

@media (hover: none) {
  .page--docs-v2 .lang-tab, .page--docs-v2 .status-tab { min-height: var(--space-36); }
  .page--docs-v2 .copy-btn { width: var(--space-36); height: var(--space-36); }
}

@media (max-width: 620px) {
  .page--docs-v2 .card-grid, .page--docs-v2 .auth-grid, .page--docs-v2 .quick-grid { grid-template-columns: minmax(0, 1fr); }
  .page--docs-v2 .section { padding: var(--space-36) 0; }
  .page--docs-v2 .code-panel__body { padding: var(--space-14) var(--space-16); }
  .page--docs-v2 .lang-tabs .lang-tab:not(.is-active) { display: none; }
  .page--docs-v2 .step { grid-template-columns: minmax(0, 1fr); gap: var(--space-12); }
  .page--docs-v2 table { min-width: 0; }
  .page--docs-v2 table:has(th:nth-child(3)) { min-width: 480px; }
}

@media (max-width: 360px) {
  .page--docs-v2 .mobile-header .brand-product { display: none; }
}

/* The route is the point of a loop card, so it wraps rather than truncates. */
.page--docs-v2 .loop-node { overflow: hidden; }
.page--docs-v2 .loop-call { min-width: 0; align-items: start; }

.page--docs-v2 .loop-path {
  min-width: 0;
  overflow-wrap: anywhere;
}


/* ==========================================================================
   09 - Unique composition · Mobile Performance Graphic

   The marketing site's closing CTA visual, built here rather than supplied as
   artwork so it regenerates from the design system like the other six shots.
   `python3 scripts/skillprint.py shots` captures it to
   ../skillprint-website/assets/images/skillprint-mobile-performance.webp.

   The screens are drawn at true device size - 390x844, the same type and
   spacing tokens the portal itself uses - and the whole frame is then scaled.
   Building them small and hand-shrinking the type would have produced a mock
   whose proportions no real screen shares.

   The stage is transparent on purpose: the image sits on the dark CTA panel.
   ========================================================================== */

.page--mobile-performance {
  --mp-screen-w   : 390px;
  --mp-screen-h   : 844px;
  --mp-bezel      : 11px;
  --mp-frame-r    : 56px;
  --mp-screen-r   : 46px;
  --mp-frame      : #05081a;
  --mp-frame-edge : color-mix(in srgb, var(--deep-navy-25) 16%, transparent);
  --mp-pad        : var(--space-20);
  background: transparent;
}

.page--mobile-performance .mp-stage {
  width: 900px;
  height: 900px;
  margin-inline: auto;
  background: transparent;
}

.page--mobile-performance .mp-scene {
  width: 900px;
  height: 900px;
}

/* Each phone is placed and scaled here; everything inside is at device size. */
.page--mobile-performance .mp-phone {
  transform-origin: 0 0;
  filter: drop-shadow(0 38px 60px rgb(3 6 20 / 62%)) drop-shadow(0 8px 18px rgb(3 6 20 / 46%));
}

.page--mobile-performance .mp-phone--session { left: 80px; top: 200px; z-index: 1; transform: scale(0.6); }
.page--mobile-performance .mp-phone--scores  { left: 308px; top: 140px; z-index: 3; transform: scale(0.7); }
.page--mobile-performance .mp-phone--mood    { left: 576px; top: 180px; z-index: 2; transform: scale(0.6); }

.page--mobile-performance .mp-phone__frame {
  width: calc(var(--mp-screen-w) + var(--mp-bezel) * 2);
  height: calc(var(--mp-screen-h) + var(--mp-bezel) * 2);
  padding: var(--mp-bezel);
  border: 1px solid var(--mp-frame-edge);
  border-radius: var(--mp-frame-r);
  background: var(--mp-frame);
}

.page--mobile-performance .mp-phone__notch {
  top: calc(var(--mp-bezel) + var(--space-10));
  left: 50%;
  z-index: 2;
  width: 112px;
  height: 30px;
  transform: translateX(-50%);
  border-radius: var(--radius-full);
  background: var(--mp-frame);
}

.page--mobile-performance .mp-screen {
  width: var(--mp-screen-w);
  height: var(--mp-screen-h);
  border-radius: var(--mp-screen-r);
  background: var(--surface-panel);
}

/* The board takes the slack on the session screen, the sheet on the other two,
   so every screen reaches its own bottom edge instead of stopping mid-phone. */
.page--mobile-performance .mp-phone--session .mp-screen { grid-template-rows: auto auto 1fr auto; }
.page--mobile-performance :is(.mp-phone--scores, .mp-phone--mood) .mp-screen { grid-template-rows: auto 1fr; }

/* An eyebrow here is placed by its row, never stacked above a heading. */
.page--mobile-performance .eyebrow { margin-bottom: 0; }

/* --- status bar ---------------------------------------------------------- */

.page--mobile-performance .mp-status {
  padding: var(--space-16) var(--space-28) var(--space-6);
  color: var(--text-default);
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: var(--weight-semibold);
}

.page--mobile-performance .mp-status__icons i {
  display: block;
  border-radius: 2px;
  background: currentColor;
  opacity: 0.85;
}

.page--mobile-performance .mp-status__bars { width: 17px; height: 11px; clip-path: polygon(0 64%, 22% 64%, 22% 100%, 0 100%, 0 64%, 39% 40%, 61% 40%, 61% 100%, 39% 100%, 39% 40%, 78% 0, 100% 0, 100% 100%, 78% 100%); }
.page--mobile-performance .mp-status__wifi { width: 15px; height: 11px; clip-path: polygon(50% 100%, 0 34%, 50% 0, 100% 34%); }
.page--mobile-performance .mp-status__battery { width: 24px; height: 12px; border-radius: 3px; }

/* --- back left: the running session -------------------------------------- */

.page--mobile-performance .mp-hud {
  padding: var(--space-18) var(--mp-pad) var(--space-12);
}

.page--mobile-performance .mp-hud__value {
  font-family: var(--font-mono);
  font-size: 22px;
  font-weight: var(--weight-medium);
  letter-spacing: -0.02em;
}

.page--mobile-performance .mp-board {
  align-content: center;
  justify-items: center;
  gap: var(--space-18);
  text-align: center;
}

.page--mobile-performance .mp-board__hex {
  width: 330px;
  aspect-ratio: 8 / 7;
  clip-path: polygon(50% 0, 100% 25%, 100% 75%, 50% 100%, 0 75%, 0 25%);
  background: linear-gradient(160deg, color-mix(in srgb, var(--mindset-violet-500) 46%, transparent), color-mix(in srgb, var(--personality-mint-500) 16%, transparent));
  opacity: 0.5;
}

.page--mobile-performance .mp-board__wordmark {
  color: var(--text-default);
  font-family: var(--font-display, var(--skillprint-font-ui));
  font-size: 52px;
  font-weight: var(--weight-regular);
  letter-spacing: -0.02em;
}

.page--mobile-performance .mp-board__state,
.page--mobile-performance .mp-status-chip,
.page--mobile-performance .mp-best {
  display: inline-flex;
  align-items: center;
  gap: var(--space-6);
  padding: var(--space-4) var(--space-10);
  border: 1px solid var(--mp-chip-border, var(--border-subtle));
  border-radius: var(--radius-full);
  background: var(--mp-chip-bg, var(--surface-box));
  color: var(--mp-chip-text, var(--text-muted));
}

.page--mobile-performance :is(.mp-board__state, .mp-status-chip, .mp-best) i {
  width: var(--space-6);
  height: var(--space-6);
  flex: 0 0 var(--space-6);
  background: var(--mp-chip-dot, var(--core-green-500));
}

.page--mobile-performance .mp-board__state,
.page--mobile-performance .mp-best {
  --mp-chip-border: color-mix(in srgb, var(--core-green-500) 38%, transparent);
  --mp-chip-bg    : color-mix(in srgb, var(--core-green-500) 12%, transparent);
  --mp-chip-text  : var(--core-green-500);
}

.page--mobile-performance .mp-status-chip--mood {
  --mp-chip-border: color-mix(in srgb, var(--core-lime-500) 38%, transparent);
  --mp-chip-bg    : color-mix(in srgb, var(--core-lime-500) 12%, transparent);
  --mp-chip-text  : var(--core-lime-500);
  --mp-chip-dot   : var(--core-lime-500);
}

.page--mobile-performance .mp-tune {
  gap: var(--space-8);
  margin: 0 var(--mp-pad) var(--space-28);
  padding: var(--space-14) var(--space-16);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-card);
  background: var(--surface-box);
}

.page--mobile-performance .mp-tune__name { color: var(--text-muted); font-size: 14px; }

.page--mobile-performance .mp-tune__value {
  font-family: var(--font-mono);
  font-size: 15px;
  font-weight: var(--weight-medium);
}

/* --- the analysis sheet, shared by the front and right screens ------------ */

.page--mobile-performance .mp-sheet {
  align-content: space-between;
  gap: var(--space-16);
  margin-top: var(--space-10);
  padding: var(--space-16) var(--mp-pad) var(--mp-pad);
  border-top: 1px solid var(--border-subtle);
  border-radius: var(--radius-panel) var(--radius-panel) 0 0;
  background: var(--surface-box);
}

.page--mobile-performance .mp-sheet__grip {
  width: 38px;
  height: 4px;
  border-radius: var(--radius-full);
  background: var(--border-subtle);
}

.page--mobile-performance .mp-title {
  margin: 0;
  font-size: 24px;
  line-height: 1.24;
  font-weight: var(--weight-semibold);
  letter-spacing: -0.01em;
}

.page--mobile-performance .mp-score,
.page--mobile-performance .mp-mood {
  gap: var(--space-6);
  padding: var(--space-16);
  border: 1px solid var(--border-subtle);
  background: var(--surface-raised);
}

.page--mobile-performance .mp-score__value,
.page--mobile-performance .mp-mood__value {
  font-family: var(--font-mono);
  font-size: 44px;
  line-height: 1.1;
  font-weight: var(--weight-medium);
  letter-spacing: -0.03em;
}

.page--mobile-performance .mp-mood__value { font-family: var(--skillprint-font-ui); font-size: 34px; }

.page--mobile-performance .mp-score__note,
.page--mobile-performance .mp-mood__note {
  color: var(--text-muted);
  font-size: 13px;
}

.page--mobile-performance .mp-section { gap: var(--space-10); }

.page--mobile-performance .mp-metric { gap: var(--space-8); }

.page--mobile-performance .mp-metric__name { color: var(--text-muted); font-size: 14px; }

.page--mobile-performance .mp-metric__value {
  font-family: var(--font-mono);
  font-size: 17px;
  font-weight: var(--weight-medium);
  letter-spacing: -0.02em;
}

.page--mobile-performance .mp-metric__total {
  margin-left: 2px;
  color: var(--text-muted);
  font-size: 12px;
}

/* Skill scores read as cognition, the mood pair as mood - the same trait
   colours the design system gives those families everywhere else. */
.page--mobile-performance .mp-section .sp-progress { --progress-colour: var(--mint); }
.page--mobile-performance .mp-metric--pattern    .sp-progress { --progress: 84%; }
.page--mobile-performance .mp-metric--spatial    .sp-progress { --progress: 71%; }
.page--mobile-performance .mp-metric--attention  .sp-progress { --progress: 52%; }
.page--mobile-performance .mp-metric--timing     .sp-progress { --progress: 34%; }
.page--mobile-performance .mp-metric--flow       .sp-progress { --progress: 72%; --progress-colour: var(--lime); }
.page--mobile-performance .mp-metric--confidence .sp-progress { --progress: 58%; --progress-colour: var(--lime); }

.page--mobile-performance .mp-meta {
  padding-top: var(--space-14);
  border-top: 1px solid var(--border-subtle);
}

.page--mobile-performance .mp-meta__value {
  display: block;
  margin-top: 2px;
  font-size: 14px;
  font-weight: var(--weight-semibold);
}

.page--mobile-performance .mp-ask { gap: var(--space-10); }

.page--mobile-performance .mp-ask__title { font-size: 14px; font-weight: var(--weight-semibold); }

.page--mobile-performance .mp-ask__choice {
  padding: var(--space-8) var(--space-14);
  flex: 1 1 0;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-control);
  background: var(--surface-raised);
  color: var(--text-muted);
  font-size: 13px;
  font-weight: var(--weight-medium);
  text-align: center;
}

.page--mobile-performance .mp-ask__choice--picked {
  border-color: color-mix(in srgb, var(--core-lime-500) 46%, transparent);
  background: color-mix(in srgb, var(--core-lime-500) 14%, transparent);
  color: var(--core-lime-500);
}

/* The sheet's own footer. These are static stand-ins for the portal's real
   .button rows - the graphic is a picture of the product, not a live control,
   so it must not put focusable buttons in a page that is only ever captured. */
.page--mobile-performance .mp-actions { margin-top: var(--space-4); }

.page--mobile-performance .mp-btn {
  flex: 1 1 0;
  padding: var(--space-12) var(--space-16);
  border: 1px solid transparent;
  border-radius: var(--radius-control);
  font-size: 14px;
  font-weight: var(--weight-semibold);
  text-align: center;
}

.page--mobile-performance .mp-btn--secondary {
  border-color: var(--border-subtle);
  background: var(--surface-raised);
  color: var(--text-default);
}

.page--mobile-performance .mp-btn--primary {
  background: var(--mindset-violet-500);
  color: var(--deep-navy-25);
}

/* Seven sessions of flow, so the mood read has a history behind it. */
.page--mobile-performance .mp-trend { gap: var(--space-10); }

.page--mobile-performance .mp-trend__plot { height: 54px; }

.page--mobile-performance .mp-trend__bar {
  flex: 1 1 0;
  border-radius: var(--radius-xs);
  background: var(--surface-raised);
}

.page--mobile-performance .mp-trend__bar--a { height: 38%; }
.page--mobile-performance .mp-trend__bar--b { height: 52%; }
.page--mobile-performance .mp-trend__bar--c { height: 45%; }
.page--mobile-performance .mp-trend__bar--d { height: 66%; }
.page--mobile-performance .mp-trend__bar--e { height: 58%; }
.page--mobile-performance .mp-trend__bar--f { height: 74%; }
.page--mobile-performance .mp-trend__bar--g { height: 100%; background: var(--core-lime-500); }

/* Past sessions stay neutral so the session just played is the only thing
   carrying colour - tinting all seven made the row read as one olive block. */

.page--mobile-performance .mp-note {
  color: var(--text-muted);
  font-size: 12px;
  line-height: 1.45;
}


/* ============================================================================
   09 - Unique composition · Marketing Website

   The marketing site's own composition, folded back from the generated
   stylesheet so the master is once again the only place these rules live.

   They had been written straight into ../skillprint-website/css/skillprint.css,
   which turned that generated file into a hand-maintained union: `skillprint.py
   css` could not rebuild it without deleting them, so its guard refused to run
   and no master change could reach the site at all.

   Everything below is scoped by .page--homepage, .page--site or a
   [data-skillprint-page] value. No prototype page carries any of those, so none
   of it reaches the pages in this folder. Responsive rules stay beside the
   composition they modify rather than moving to section 10, which is how the
   site file already held them.
   ========================================================================== */

.sp-nav__route:focus-visible {
  outline: 0;
  box-shadow:
    0 0 0 var(--focus-ring-width) var(--focus-inner),
    0 0 0 calc(var(--focus-ring-width) * 2) var(--focus-default);
}

.sp-nav__endcap[hidden] {
  display: none;
}

/* The drawer's opener is the .icon-button component; the class carries only
   what makes this one different - it is hidden while the full route row is on
   screen, and it sits on a filled surface so it reads as the one control in a
   bar that has lost its routes. The box, the size, hover and focus all come
   from the component. */
.sp-nav__menu {
  /* The ≤1023px block below turns it on as the routes hide, which is the
     state its drawer serves. */
  display: none;
  background: var(--nav-hover-surface);
  box-shadow: var(--shadow-xs);
}

.sp-nav__menu svg {
  width: var(--icon-size-sm);
  height: var(--icon-size-sm);
  fill: none;
  stroke: currentColor;
  stroke-width: var(--icon-stroke-md);
  stroke-linecap: round;
  stroke-linejoin: round;
  --icon-stroke: var(--icon-stroke-20);
}

.sp-nav__drawer {
  position: absolute;
  top: calc(100% + var(--space-8));
  right:
    max(
      var(--grid-padding),
      calc((100vw - var(--grid-container-max)) / 2 + var(--grid-padding))
    );
  z-index: 110;
  width: min(var(--space-320), calc(100vw - var(--space-32)));
  padding: var(--space-10);
  border: 1px solid var(--nav-border);
  border-radius: var(--radius-xl);
  background: var(--nav-drawer-surface);
  box-shadow: var(--nav-shadow);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(calc(var(--space-4) * -1));
  transition: var(--transition-interactive);
}

.sp-nav__drawer[data-open="true"] {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translateY(0);
}

.sp-nav__drawer-routes {
  display: grid;
  gap: var(--space-2);
}

.sp-nav__drawer .sp-nav__route {
  width: 100%;
  min-height: var(--space-40);
  padding-inline: var(--space-12);
}

.sp-nav__drawer-divider {
  height: 1px;
  margin-block: var(--space-8);
  background: var(--nav-border);
}

.sp-nav__drawer-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: var(--space-8);
}

@media (max-width: 1023px) {
  .sp-nav__menu {
        display: grid;
      }

  .sp-nav__tablet-hidden {
        display: none !important;
      }
}

@media (max-width: 767px) {
  .sp-nav__drawer {
        right: var(--space-8);
        left: var(--space-8);
        width: auto;
      }

  .sp-nav__drawer-actions {
        align-items: stretch;
        flex-direction: column;
      }

  .sp-nav__drawer-actions .button {
        width: 100%;
      }
}

.sp-card--strong {
  background: var(--surface-strong);
}

.code-head button:focus-visible {
  outline: 0;
  box-shadow:
    0 0 0 var(--focus-ring-width) var(--focus-inner),
    0 0 0 calc(var(--focus-ring-width) * 2) var(--focus-default);
}

.chart-label {
  fill: var(--surface-text);
}


/* An eyebrow placed by a flex or grid parent - beside a card heading, or in the
   label column of a compare row - is positioned by that parent and must not add
   the stacked eyebrow's bottom margin. */
.page--homepage :is(.compare-head, .compare-row, .intelligence-head, .signal-summary-head) .eyebrow {
  margin-bottom: 0;
}

.page .eyebrow--signal::before {
  content: "";
  width: 8px;
  height: 8px;
  flex: 0 0 8px;
  border-radius: 50%;
  background: var(--green, var(--core-green-500));
  box-shadow: 0 0 0 5px rgb(9 224 143/10%);
}

/* Keep a wide table inside its own scroll box: the page must never scroll
   sideways, and `.clip` would hide the overflowing columns outright. */

.table-scroll {
  overflow-x: auto;
  overscroll-behavior-x: contain;
}

/* External-destination link: the icon sits after the label, never replaces it. */

.link-external {
  display: inline-flex;
  align-items: center;
  gap: var(--spacing-sm);
}

/* .link-external is for an inline text link, where 6px is right beside body
   copy. On a button it was silently overriding the component's own gap - same
   specificity, later in source - so every button that happened to leave the
   site spaced its icon differently from every button that did not. The
   component decides. */
.button.link-external { gap: var(--button-gap); }

.link-external:not(.button) > svg {
  --icon-stroke: var(--icon-stroke-14);
  width: 14px;
  height: 14px;
  flex: 0 0 14px;
  opacity: 0.72;
}

/* ==========================================================================
   02 - SHARED PAGE COMPOSITION
   ========================================================================== */


.hero-copy { font-size: 18px; line-height: 28px; }

/* ---------------------------------------------------------------------------
   Marketing website composition, folded in from the generated stylesheet

   `.page--site` is carried by every marketing page and by no prototype
   page, so it is the scope for anything that must not reach the
   prototypes.
   --------------------------------------------------------------------------- */

/* Wider desktop measure. The system's 1280px container left the marketing
   pages feeling boxed in; text blocks keep their own max-widths, so only the
   grids, media and nav gain the extra room. */

.page--site {
  --grid-container-max: 1440px;
}

/* The body sets a 24px base line-height and the size utilities only change
   font-size, so 10px and 12px labels inherit 24px of leading - roughly double
   what the type scale pairs them with. These pair each small size with its own
   scale line-height, and stand aside wherever the markup sets leading. */

.page--site .font-xs:not([class*="leading-"]) {
  line-height: var(--text-xxs-line-height);
}

.page--site .font-sm:not([class*="leading-"]) {
  line-height: var(--text-xs-line-height);
}

.page--site .skip-link:focus-visible { transform: translateY(0); }

/* Compact hero for the pages below the homepage. */

.hero--page {
  min-height: 0;
  padding: var(--space-144) 0 var(--space-64);
}

.hero--page::before {
  content: "";
  position: absolute;
  width: 460px;
  height: 460px;
  right: -230px;
  top: 60px;
  border-radius: 50%;
  background: rgb(9 224 143 / 5%);
  border: 1px solid rgb(9 224 143 / 12%);
  pointer-events: none;
}

.hero-copy--page > p {
  max-width: 640px;
  font-size: 18px;
  line-height: 28px;
}

/* Fixed-ratio media frame, used for artwork, screenshots and placeholders. */

.media-frame {
  --media-ratio: 4 / 3;
  display: block;
  overflow: hidden;
  aspect-ratio: var(--media-ratio);
  border: 1px solid var(--ui-border);
  border-radius: var(--radius-card);
  background: var(--surface-panel);
}

.media-frame--wide { --media-ratio: 16 / 9; }

/* For a drawing that is square in the first place. The ontology wheel is a
   900x900 disc, and the 16/9 frame was cropping the terms at twelve and six
   o'clock - the same trap .skillprint__wheel avoids by carrying its own
   aspect-ratio. */

.media-frame--square { --media-ratio: 1; }

.media-frame--panel { border-radius: var(--radius-panel); }

.media-frame > img {
  width: 100%;
  height: 100%;
  display: block;
  object-fit: cover;
}

/* Long-form body copy for the legal pages. */

.prose {
  max-width: var(--grid-readable-max);
  color: var(--ui-muted);
  font-size: var(--font-size-base);
  line-height: 26px;
}

.prose > * + * { margin-top: var(--space-16); }

.prose h2 { margin-top: var(--space-48); color: var(--ui-text); }

.prose h3 { margin-top: var(--space-32); color: var(--ui-text); }

.prose > :first-child { margin-top: 0; }

.prose strong { color: var(--ui-text); font-weight: var(--weight-semibold); }

.prose ul { padding-left: var(--space-20); }

.prose li + li { margin-top: var(--space-8); }

/* `.page--homepage button { font: inherit }` used to sit here. It is already
   part of the shared form-control normalisation further up, and keeping a copy
   only put the same declaration in the generated site stylesheet twice. */

.section--homepage { padding: var(--space-96) 0; }

.section-head--homepage {
  grid-template-columns: minmax(220px, 0.7fr) minmax(0, 1.3fr);
  gap: var(--space-72);
  margin-bottom: var(--space-56);
}

.section-head--homepage p {
  max-width: 720px;
  font-size: 18px;
  line-height: 28px;
}

.page--homepage .nav {
  position: fixed;
  inset: 0 0 auto;
  z-index: 100;
  border-bottom: 1px solid transparent;
  transition: background 0.18s, border-color 0.18s, backdrop-filter 0.18s;
}

/* Through the token, not around it. This used to name --deep-navy-900 directly,
   and that literal has no light-mode value - so the bar stayed near-black in
   light mode while its contents took the light theme's dark ink. The current
   route measured 1.04:1, the routes 1.18-1.47:1, and the theme toggle - the one
   control a reader needs in order to leave light mode - 1.65:1. --nav-surface is
   already defined correctly for both themes on this very element; the bar only
   ever had to ask for it. */

.page--homepage .nav.scrolled {
  background: var(--nav-surface);
  border-bottom-color: var(--nav-border);
  backdrop-filter: blur(12px);
}

.page--homepage .nav-inner { min-height: 76px; gap: var(--space-32); }

.page--homepage .logo { width: 168px; }

.hero--homepage { min-height: 920px; padding: var(--space-144) 0 var(--space-80); }

.hero--homepage::before, .page--homepage .hero--homepage::after {
  content: "";
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
}

.hero--homepage::before {
  width: 540px;
  height: 540px;
  right: -270px;
  top: 170px;
  background: color-mix(in srgb, var(--core-green-500) 6%, transparent);
  border: 1px solid color-mix(in srgb, var(--core-green-500) 13%, transparent);
}

.hero--homepage::after {
  width: 360px;
  height: 360px;
  left: -180px;
  bottom: 20px;
  background: color-mix(in srgb, var(--mindset-violet-500) 6%, transparent);
  border: 1px solid color-mix(in srgb, var(--mindset-violet-500) 14%, transparent);
}

.hero-grid--homepage { z-index: 1; grid-template-columns: minmax(0, 0.88fr) minmax(560px, 1.12fr); }

.hero-copy--homepage > p {
  max-width: 650px;
  font-size: 20px;
  line-height: 31px;
}

.page--homepage .hero-note { margin-top: var(--space-22); }

.page--homepage .hero-note i {
  width: 7px;
  height: 7px;
  background: var(--green);
}

.page--homepage .hero-dashboard {
  border: 1px solid var(--border-hover);
  box-shadow: var(--shadow-xl);
}

.page--homepage .dashboard-top { height: 48px; padding: 0 var(--space-8) var(--space-14); }

.page--homepage .window-dots i {
  width: 7px;
  height: 7px;
  background: var(--panel-600);
}

.page--homepage .window-dots i:first-child { background: var(--green); }

.page--homepage .dashboard-body { grid-template-columns: 1.05fr 0.95fr; padding-top: var(--space-12); }

.page--homepage .game-view { min-height: 460px; background: var(--deep-navy-900); }

.game-grid--homepage {
  opacity: 0.48;
  background-size: 40px 40px;
  background-image: url(../assets/images/website/grid-40.svg);
}

.page--homepage .game-orbit {
  width: 250px;
  height: 250px;
  left: 50%;
  top: 47%;
  transform: translate(-50%, -50%);
  border: 1px solid color-mix(in srgb, var(--personality-mint-500) 25%, transparent);
}

.page--homepage .game-orbit::after, .page--homepage .game-orbit::before {
  content: "";
  position: absolute;
  border: 1px solid color-mix(in srgb, var(--personality-mint-500) 16%, transparent);
  border-radius: 50%;
}

.page--homepage .game-orbit::before { inset: 34px; }

.page--homepage .game-orbit::after { inset: 78px; background: color-mix(in srgb, var(--personality-mint-500) 6%, transparent); }

.page--homepage .player {
  left: 50%;
  top: 47%;
  width: 54px;
  height: 54px;
  transform: translate(-50%, -50%) rotate(45deg);
  border: 1px solid var(--mint);
  box-shadow: 0 0 0 8px color-mix(in srgb, var(--personality-mint-500) 6%, transparent);
}

.page--homepage .scan-line {
  left: 0;
  right: 0;
  top: 18%;
  height: 1px;
  background: var(--green);
  box-shadow: 0 0 18px color-mix(in srgb, var(--core-green-500) 70%, transparent);
  opacity: 0.55;
  animation: homepage-scan 4s ease-in-out infinite;
}

.page--homepage .game-event {
  left: 16px;
  right: 16px;
  bottom: 16px;
  grid-template-columns: 1fr auto;
  padding: var(--space-12);
  background: color-mix(in srgb, var(--panel-navy-950) 88%, transparent);
  backdrop-filter: blur(8px);
}

.page--homepage .game-event span { font: 500 10px/16px var(--font-mono); }

.page--homepage .event-score { font: 600 18px/24px var(--font-mono); }

.page--homepage .signal-panel { min-height: 460px; }

.page--homepage .signal-summary-head span { font: 500 10px/16px var(--font-mono); }

.page--homepage .signal-summary-head b { font: 600 11px/16px var(--font-mono); }

.page--homepage .signal-score { align-items: flex-end; margin: var(--space-14) 0 var(--space-16); }

.page--homepage .signal-score strong {
  font: 500 44px/46px var(--font-mono);
  letter-spacing: -0.05em;
}

.page--homepage .signal-score span { padding-bottom: 5px; }

.page--homepage .score-bar { height: 7px; background: var(--panel-700); }

.page--homepage .score-bar i {
  width: 82%;
  height: 100%;
  border-radius: inherit;
  background: var(--green);
}

.page--homepage .signal-list { flex: 1; }

.page--homepage .signal-card {
  grid-template-columns: 38px 1fr auto;
  gap: var(--space-12);
  --card-current-padding: var(--space-12);
}

.page--homepage .signal-icon {
  width: 38px;
  height: 38px;
  color: var(--accent);
}

.page--homepage .signal-icon svg { --icon-stroke: var(--icon-stroke-18); width: 18px; height: 18px; }

.page--homepage .signal-card b { font: 600 13px/18px var(--font-mono); }

.page--homepage .stats-strip {
  z-index: 1;
  margin-top: var(--space-56);
  grid-template-columns: repeat(4, 1fr);
}

.page--homepage .profile-layout {
  grid-template-columns: minmax(420px, 0.95fr) minmax(0, 1.05fr);
  gap: var(--space-80);
}

.page--homepage .profile-visual { min-height: var(--space-384); }

.page--homepage .profile-visual img {
  z-index: 2;
  width: min(100%, 640px);
}

.page--homepage .profile-chip {
  z-index: 3;
  min-width: 190px;
  border: 1px solid var(--border-hover);
  background: color-mix(in srgb, var(--panel-navy-950) 90%, transparent);
  backdrop-filter: blur(12px);
  box-shadow: var(--shadow-md);
}

.page--homepage .profile-chip span { margin-top: 3px; font: 500 10px/16px var(--font-mono); }

.page--homepage .profile-chip.one { left: 0; top: 120px; }

.page--homepage .profile-chip.two { right: 0; bottom: 120px; }

.page--homepage .profile-chip b { float: right; font: 600 13px/18px var(--font-mono); }

.page--homepage .profile-copy > p, .page--homepage .process-copy p, .page--homepage .badge-copy p {
  font-size: 18px;
  line-height: 28px;
}

.page--homepage .feature-list { gap: 0; margin-top: var(--space-30); }

.page--homepage .feature-item { grid-template-columns: 32px 1fr; padding: var(--space-18) 0; }

.page--homepage .feature-item span { margin-top: var(--space-4); }

.page--homepage .dimension-grid { grid-template-columns: repeat(3, 1fr); }

.page--homepage .dimension-icon {
  width: var(--space-32);
  height: var(--space-32);
  border: 1px solid color-mix(in srgb, var(--accent) 36%, var(--border));
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--accent) 10%, var(--panel-900));
  color: var(--accent);
}

.page--homepage .dimension-icon svg { width: 16px; height: 16px;
  --icon-stroke: var(--icon-stroke-24);
}

.page--homepage .dimension-tags { padding-top: var(--space-20); }

.page--homepage .process-wrap { grid-template-columns: 0.86fr 1.14fr; gap: var(--space-72); }

.page--homepage .process-copy { position: sticky; top: 120px; }

.page--homepage .badge-copy .button, .page--homepage .process-copy .button { margin-top: var(--space-28); }

.page--homepage .process-card { grid-template-columns: 54px 1fr auto; gap: var(--space-18); }

/* One marker badge, two sizes. It holds a step numeral or an initial (M/C/P on
   getting-started), which is why it is not named for numbers.

   Corner and type are derived from the size rather than set per size, so both
   keep the same proportions. Stating them separately is what made the pair read
   as two components: 13px in a 54px box left the numeral floating at 24% of the
   box, while 11px in a 28px box filled 39% of it and looked cramped. Deriving
   both fixes the relationship at 30% type and 22% corner whatever the size is.

   The type has a floor because proportion alone stops working at the small end
   - 30% of 32px is 9.6px, which is under the smallest size the scale carries.
   The small badge is therefore 34% rather than 30%, close enough to read as the
   same component and still legible. It is also 32px rather than 28px so it
   fills .feature-item's 32px column exactly. */
.page--homepage .marker-badge {
  --marker-badge-size: var(--space-32);
  width: var(--marker-badge-size);
  height: var(--marker-badge-size);
  border: 1px solid var(--border-hover);
  border-radius: calc(var(--marker-badge-size) * 0.22);
  font-family: var(--font-mono);
  font-size: max(11px, calc(var(--marker-badge-size) * 0.3));
  font-weight: var(--weight-semibold);
  line-height: 1;
}

.page--homepage .marker-badge--sm { --marker-badge-size: 32px; }

.page--homepage .process-card code {
  padding-top: var(--space-8);
  color: var(--quiet);
  font: 500 10px/16px var(--font-mono);
}

.page--homepage .intelligence-panel { padding: var(--space-24); }

.page--homepage .intelligence-head span { font: 500 10px/16px var(--font-mono); }

.page--homepage .chart-wrap { height: 330px; }

.page--homepage .chart-wrap svg { height: 100%; }

.page--homepage .chart-grid { stroke: color-mix(in srgb, var(--geistface-grey-200) 9%, transparent); stroke-width: 1; }

.page--homepage .chart-label { fill: color-mix(in srgb, var(--geistface-grey-200) 48%, transparent); font: 10px var(--font-mono); }

.page--homepage .line-green {
  fill: none;
  stroke: var(--green);
  stroke-width: 3;
}

.page--homepage .line-blue {
  fill: none;
  stroke: var(--blue);
  stroke-width: 2;
  stroke-dasharray: 6 7;
}

.page--homepage .line-violet {
  fill: none;
  stroke: var(--violet);
  stroke-width: 2;
  stroke-dasharray: 2 7;
}

.page--homepage .chart-point {
  fill: var(--green);
  stroke: var(--panel-900);
  stroke-width: 3;
}

.page--homepage .chart-legend { margin-top: var(--space-16); }

.page--homepage .chart-legend i {
  width: 18px;
  height: 2px;
  background: var(--accent);
}

.page--homepage .badge-layout { grid-template-columns: 0.8fr 1.2fr; gap: var(--space-72); }

.page--homepage .badge-stage { min-height: 560px; }

.page--homepage .badge-stage::after, .page--homepage .badge-stage::before {
  content: "";
  position: absolute;
  border-radius: 50%;
  border: 1px solid color-mix(in srgb, var(--geistface-grey-200) 8%, transparent);
}

.page--homepage .badge-stage::before { width: 430px; height: 430px; }

.page--homepage .badge-stage::after { width: 300px; height: 300px; }

.page--homepage .badge-card.a { left: 7%; top: 12%; }

.page--homepage .badge-card.b { right: 6%; top: 15%; }

.page--homepage .badge-card.c { left: 33%; bottom: 7%; }

/* Type, padding and shape come from .eyebrow / .eyebrow--pill. Only the
   featured card's green tint is a real variant, so only it is stated here. */
.page--homepage .compare-card.featured .compare-head .eyebrow--pill {
  border-color: color-mix(in srgb, var(--core-green-500) 32%, transparent);
  background: color-mix(in srgb, var(--core-green-500) 8%, transparent);
}

.page--homepage .compare-row { grid-template-columns: 138px 1fr; padding: 17px var(--space-24); }

/* The compare card is .card--flush, so each section pays for its own inset: the
   head does it with .padding-3xl and .compare-row with the rule above. The
   accordion arrived from the master with `padding: var(--space-16) 0`, correct
   for an accordion that sits in an already-padded parent and wrong here, so its
   questions ran to the card edge while the heading above them was inset 24px.
   Same inset as .compare-row, since index and the three pages still on
   .compare-row have to read as one component. Dividers stay full-bleed - they
   separate card sections, not the text inside them. */
.page--homepage .compare-card :is(.accordion__summary, .accordion__panel) {
  padding-inline: var(--space-24);
}

/* .sp-card already draws the top edge, and the head its own rule below it. */
.page--homepage .compare-card.accordion { border-top: 0; }

/* The last question's rule would otherwise sit across the card's rounded foot. */
.page--homepage .compare-card .accordion__item:last-child { border-bottom: 0; }

/* The open panel needs air under the question, and the card needs air under the
   last answer; the master's accordion gets both from its parent's padding. */
.page--homepage .compare-card .accordion__panel { padding-bottom: var(--space-20); }

.page--homepage .cta-section { padding: var(--space-96) 0; }

.page--homepage .cta-panel {
  min-height: 540px;
  grid-template-columns: 1fr 0.9fr;
  gap: var(--space-30);
  padding: var(--space-64);
}

.page--homepage .cta-copy { z-index: 2; }

.page--homepage .cta-copy h2 { max-width: 660px; }

.page--homepage .cta-copy p {
  max-width: 560px;
  font-size: 18px;
  line-height: 28px;
}

.page--homepage .cta-copy .hero-actions { margin-top: var(--space-30); }

.page--homepage .cta-visual { z-index: 2; align-self: end; }

.page--homepage .cta-visual img {
  max-height: 520px;
  object-fit: contain;
  object-position: center bottom;
  filter: saturate(0.85) contrast(1.02);
}

.footer--homepage { padding: var(--space-64) 0 var(--space-30); }

.page--homepage .footer-top {
  grid-template-columns: 1.2fr repeat(3, 0.6fr);
  gap: var(--space-48);
  padding-bottom: var(--space-48);
}

.page--homepage .footer-col strong { margin-bottom: 15px; }

.page--homepage .footer-col a { margin: 9px 0; }

.page--homepage .footer-bottom { padding-top: var(--space-24); color: var(--quiet); }

.page--homepage .reveal.visible { opacity: 1; transform: none; }

/* The marketing feature cards carried 24-26px in the approved homepage; the
   composition dropped both rules, leaving them at the 16px default and
   noticeably tighter than the 24px used by .stat and .compare-head. */

.page--homepage :is(.dimension-card, .process-card) {
  --card-current-padding: var(--card-padding-md);
}

.page--homepage .dimension-card h3 {
  margin-bottom: var(--space-10);
  font-size: var(--display-xs-font-size);
  line-height: var(--display-xs-line-height);
  letter-spacing: -0.025em;
}

/* The gap under a card's leading element. An icon tile takes the approved
   34px; a media frame needs less, because the image already reads as a block. */

.page--homepage .dimension-icon + h3 { margin-top: var(--space-32); }

/* Mobile_Portal_Graphic.html. Three real portal screens in one frame each, shot
   at 390x844 dpr 3 and never redrawn - the page this replaced drew its own
   approximation of the product in CSS, and the product moved on without it.
   The stage ships transparent, so the phones keep their shadows over the
   marketing site's dark panel instead of landing on an opaque square.

   1200x950, not the 900x900 the old asset used. Its home is a column about
   680px wide under a 520px height cap, and `object-fit: contain` on a square
   asset threw away a third of that width - the phones landed at 124px each and
   nothing on them could be read. A 1.26 canvas fills the cap instead, and the
   same phones arrive around 200px.

   The screen window is 390/810 rather than the captured 390/844: the profile's
   next heading starts at 830, and cropping to 810 ends the frame inside the
   card's own padding rather than through the middle of a word. */

.page--mobile-portal-graphic {
  width: 1200px;
  height: 950px;
  background: transparent;
}

.mpg-stage {
  width: 1200px;
  height: 950px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.mpg-phone {
  --mpg-screen-w: 336px;
  --mpg-bezel: 12px;
  position: relative;
  flex: 0 0 auto;
  width: calc(var(--mpg-screen-w) + var(--mpg-bezel) * 2);
  padding: var(--mpg-bezel);
  border: 1px solid color-mix(in srgb, var(--pure-white, #fff) 16%, transparent);
  border-radius: 54px;
  background: var(--deep-navy-900, #090d27);
  box-shadow: 0 40px 76px -16px rgb(4 6 20 / 68%), 0 0 0 1px rgb(4 6 20 / 40%);
}

.mpg-phone__screen {
  display: block;
  overflow: hidden;
  aspect-ratio: 390 / 810;
  border-radius: 44px;
}

.mpg-phone img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
}

.mpg-phone__island {
  z-index: 2;
  top: calc(var(--mpg-bezel) + 10px);
  left: 50%;
  width: 88px;
  height: 24px;
  transform: translateX(-50%);
  border-radius: 999px;
  background: var(--deep-navy-900, #090d27);
  box-shadow: 0 0 0 1px rgb(255 255 255 / 6%);
}

.mpg-phone--centre {
  z-index: 3;
  --mpg-screen-w: 400px;
}

.mpg-phone--left { z-index: 2; margin-right: -58px; margin-top: 96px; }

.mpg-phone--right { z-index: 2; margin-left: -58px; margin-top: 96px; }


/* The stacked numbered card - a .marker-badge above the title rather than
   beside it, as on how-it-works' "mechanics become actions" row. Its sibling
   .process-card gives the badge its own column and top-aligns it with the
   title, so that pair needs no vertical gap; stacking them left the numeral
   sitting directly on the title with nothing between the two. Same
   relationship as .dimension-icon + h3 above, one step smaller because the
   badge is the smaller mark, and one step more padding because badge, title
   and description all have to breathe in a single column. */

.page--homepage .dimension-grid--steps {
  gap: var(--space-16);
  margin-top: var(--space-48);
}

.page--homepage .dimension-card--step { --card-current-padding: var(--card-padding-lg); }

.page--homepage .dimension-card--step .marker-badge + h3 {
  margin-top: var(--space-24);
  margin-bottom: var(--space-12);
  text-wrap: balance;
}

/* What comes back, homepage. The screen is the subject: two 190px cards used
   to float over it, covering the chart they described and setting mono
   captions against the interface's own type. One caption line under the frame
   carries the same reading without competing with it. */

.page--homepage .profile-figure {
  z-index: 2;
  width: min(100%, 640px);
}

.page--homepage .profile-figure > .media-frame { filter: saturate(0.92) contrast(1.04); }

.page--homepage .profile-figure .hero-note {
  margin-top: var(--space-16);
  justify-content: center;
  text-align: center;
}

.page--homepage .media-frame + h3 { margin-top: var(--space-20); }

.page--homepage .process-card h3 { margin-bottom: var(--space-8); }

@media (max-width: 1120px) {
  .hero-grid--homepage { grid-template-columns: 1fr; }

  .hero-copy--homepage { max-width: 850px; }

  .page--homepage .hero-dashboard { max-width: 820px; }

  .hero--homepage { padding-top: var(--space-128); }

  .page--homepage .badge-layout, .page--homepage .process-wrap, .page--homepage .profile-layout {
        gap: var(--space-42);
      }

  .page--homepage .profile-chip.one { left: 3%; }

  .page--homepage .profile-chip.two { right: 3%; }

  .section-head--homepage { grid-template-columns: 1fr; gap: var(--space-16); }

  .page--homepage .cta-panel { padding: var(--space-48); }
}

@media (max-width: 900px) {
  .page--homepage .stats-strip { grid-template-columns: repeat(2, 1fr); }

  .page--homepage .badge-layout,
      .page--homepage .cta-panel,
      .page--homepage .process-wrap,
      .page--homepage .profile-layout {
        grid-template-columns: 1fr;
      }

  .page--homepage .profile-visual { min-height: var(--space-320); }

  .page--homepage .process-copy { position: static; }

  .page--homepage .dimension-grid { grid-template-columns: 1fr; }

  .page--homepage .badge-stage { min-height: 600px; }

  .page--homepage .cta-visual { max-width: 620px; }

  .page--homepage .footer-top { grid-template-columns: 1fr 1fr; }

  .page--homepage .cta-panel { padding: var(--space-42); }

  .section--homepage { padding: var(--space-96) 0; }
}

@media (max-width: 650px) {
  .page--homepage .nav-inner { min-height: 68px; }

  .page--homepage .logo { width: 148px; }

  .hero--homepage { min-height: auto; padding: var(--space-112) 0 var(--space-56); }

  .hero-copy--homepage > p { font-size: 17px; line-height: 27px; }

  .page--homepage .dashboard-body { grid-template-columns: 1fr; }

  .page--homepage .game-view { min-height: 380px; }

  .page--homepage .signal-panel { min-height: auto; }

  .page--homepage .stats-strip { margin-top: var(--space-36); }

  .page--homepage .profile-visual { min-height: var(--space-256); }

  .page--homepage .profile-chip { min-width: 155px; padding: 11px; }

  .page--homepage .profile-chip.one { top: 60px; }

  .page--homepage .profile-chip.two { bottom: 70px; }

  .page--homepage .process-card { grid-template-columns: 48px 1fr; }

  .page--homepage .process-card code { display: none; }


  .page--homepage .marker-badge--sm { --marker-badge-size: 32px; }

  .page--homepage .intelligence-panel { padding: var(--space-16); }

  .page--homepage .chart-wrap { height: 280px; padding: var(--space-10); }

  .page--homepage .badge-stage { min-height: 760px; }

  .page--homepage .badge-card.a { left: 50%; top: 4%; }

  .page--homepage .badge-card.b { right: 50%; top: 35%; }

  .page--homepage .badge-card.c { left: 50%; bottom: 3%; }

  .page--homepage .compare { grid-template-columns: 1fr; }

  .page--homepage .cta-panel { padding: var(--space-28) var(--space-22); min-height: 0; }

  .page--homepage .cta-copy .button { width: 100%; }

  .page--homepage .footer-top { grid-template-columns: 1fr; }

  .page--homepage .footer-bottom { align-items: flex-start; flex-direction: column; }

  .section--homepage { padding: var(--space-72) 0; }

  .section-head--homepage { margin-bottom: var(--space-36); }

  .page--homepage .signal-list { grid-template-columns: 1fr; }

  .game-grid--homepage { background-image: none; }
}

/* ---------------------------------------------------------------------------
   Marketing website breakpoints, folded in from the generated stylesheet
   --------------------------------------------------------------------------- */
@media (max-width: 900px) {
  .hero--page { padding: 128px 0 var(--space-48); }

  .hero--page::before { display: none; }
}

@media (max-width: 650px) {
  .hero--page { padding: var(--space-104) 0 var(--space-40); }

  .prose h2 { margin-top: var(--space-32); }

  /* The supplied breakpoint centres the badge cards with left/right: 50% but
         no longer carries the matching translate, so they overhang the stage and
         get clipped. Restores the centring that breakpoint already assumes. */
    
      .page--homepage .badge-card.a,
      .page--homepage .badge-card.c { transform: translateX(-50%); }

  .page--homepage .badge-card.b { transform: translateX(50%); }
}

/* ==========================================================================
   WEBSITE ADDITIONS
   The only rules written for this site. Everything above is the project
   master, filtered. Each rule is here because the master does not carry it.
   ========================================================================== */

/* Reveal-on-scroll is an enhancement, never a requirement for reading the page.
   The shared script sets [data-reveal] only when it will actually run the
   reveal pass, so content stays visible with JavaScript off or motion reduced. */
html:not([data-reveal]) .reveal {
  opacity: 1;
  transform: none;
}

/* .profile-chip sits at top/bottom: 120px, which works when the visual beside
   it is an image. home-intelligence puts a panel of labelled rows in that slot,
   where 120px landed the chips on the panel title and on the last row. The
   visual box is taller than the panel, so the clear space above and below keeps
   the floating treatment without covering anything. */
[data-skillprint-page="home-intelligence"] .profile-chip.one { top: var(--space-24); }

[data-skillprint-page="home-intelligence"] .profile-chip.two { bottom: var(--space-24); }

/* The legal pages read as reference documents rather than a narrow editorial
   column, so they take the widest measure on the scale rather than the 720px
   default. Not the full container: at 1440px that ran to 167 characters a
   line, about double what stays readable, and these are long documents. */
.page--site .prose { max-width: var(--measure-lg); }

/* The card head's pill. Shape, size and colour still come from .eyebrow and
   .eyebrow--pill; these are the three deliberate departures.

   - margin-inline: the master's pill is the centred hero one and carries
     `auto`, which inside a flex head absorbs the free space on both sides and
     parked this pill mid-row instead of letting space-between push it to the
     end. It was sitting 169px short of the card edge.
   - padding: the master's is asymmetric (6px lead, 10px tail) to sit around a
     leading dot. This pill has no dot, so the asymmetry only reads as crooked.
   - case: sentence case rather than the eyebrow's uppercase and 0.1em
     tracking. Beside a sentence-case heading these read as captions for the
     card, not as system labels. */
.page--homepage .compare-head .eyebrow--pill {
  margin-inline: 0;
  padding: var(--space-4) var(--space-12);
  text-transform: none;
  letter-spacing: 0.01em;
}


/* ============================================================================
   37 · Marketing site · audience routes, page accents, ambient motion, wheel

   Everything the restructured marketing site needs that Part A and the
   existing .page--homepage compositions do not already provide. Scoped to
   .page--site so none of it can reach the prototypes.

   The audience pages keep .page--homepage as well as their own class: they
   reuse the homepage compositions wholesale, and only the accent changes.
   ========================================================================== */

/* ----------------------------------------------------------------------------
   37.1 · The Product disclosure

   Five sibling routes made the bar read as a list of things rather than as
   three ideas, so the three products moved inside one Product menu. The
   audience is still the prominent line on each row, because "Developers" is
   actionable on sight and "Flow" has to be learned first.
   -------------------------------------------------------------------------- */
.page--site .sp-nav__disclosure { position: relative; }

.page--site .sp-nav__route--menu {
  gap: var(--space-6);
  border: 1px solid transparent;
  cursor: pointer;
}

.page--site .sp-nav__caret {
  --icon-size: var(--icon-size-xs);
  /* .sp-icon--sm carries the 20px stroke with it. Shrinking the icon to 16px
     without restating the weight drew it at 1.4px where the ladder asks for
     1.5px - the one icon on the site off the scale. */
  --icon-stroke: var(--icon-stroke-16);
  transition: transform var(--duration-fast) var(--ease-standard);
}

.page--site [data-nav-menu-toggle][aria-expanded="true"] .sp-nav__caret { transform: rotate(180deg); }

.page--site .sp-nav__menu-panel {
  position: absolute;
  inset-block-start: calc(100% + var(--space-8));
  inset-inline-start: 50%;
  z-index: 30;
  width: var(--space-384);
  padding: var(--space-8);
  display: grid;
  gap: var(--space-2);
  border: 1px solid var(--nav-border);
  border-radius: var(--radius-2xl);
  background: var(--nav-drawer-surface);
  box-shadow: var(--nav-shadow);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translate(-50%, -6px);
  transition: opacity var(--duration-fast) var(--ease-standard),
    transform var(--duration-fast) var(--ease-standard),
    visibility var(--duration-fast) var(--ease-standard);
}

.page--site .sp-nav__menu-panel[data-open="true"] {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: translate(-50%, 0);
}

.page--site .sp-nav__product {
  display: grid;
  grid-template-columns: var(--space-32) minmax(0, 1fr);
  align-items: start;
  gap: var(--space-12);
  padding: var(--space-10) var(--space-12);
  border: 1px solid transparent;
  border-radius: var(--radius-xl);
  color: var(--nav-text);
  transition: var(--transition-interactive);
}

.page--site .sp-nav__product:hover {
  border-color: var(--nav-hover-border);
  background: var(--nav-hover-surface);
}

/* Each product owns one colour, and it is the same colour the product's own
   page is themed in, so the menu is where a visitor first learns the code. */
.page--site .sp-nav__product--augment { --sp-route-accent: var(--personality-blue-500); }
.page--site .sp-nav__product--flow    { --sp-route-accent: var(--core-lime-500); }
.page--site .sp-nav__product--signal  { --sp-route-accent: var(--skills-orange-500); }

/* A 32px tile at --radius-md holding a 16px icon. --radius-card is the panel
   radius and made a 36px box read as a rounded square competing with the two
   lines of text beside it; the row is a label with a mark, not a tile with a
   caption. */
.page--site .sp-nav__product-icon {
  display: grid;
  place-items: center;
  width: var(--space-32);
  height: var(--space-32);
  border: 1px solid color-mix(in srgb, var(--sp-route-accent) 26%, transparent);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--sp-route-accent) 12%, transparent);
  color: var(--sp-route-accent);
}

.page--site .sp-nav__product-text { display: grid; gap: 1px; min-width: 0; }

.page--site .sp-nav__product-audience {
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
  font-weight: var(--weight-semibold);
}

.page--site .sp-nav__product-name {
  color: var(--sp-route-accent);
  font-family: var(--skillprint-font-mono);
  font-size: var(--text-xxs-font-size);
  line-height: var(--text-xxs-line-height);
  font-weight: var(--weight-medium);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.page--site .sp-nav__product-blurb {
  color: var(--nav-muted);
  font-size: var(--font-size-xs);
  line-height: var(--text-xs-line-height);
}

/* In the drawer the panel is permanently open and sits in flow: a disclosure
   inside a disclosure is a maze, so the script disables the button there.

   visibility and pointer-events are inherit, not visible/auto. The base panel
   rule hard codes them to hidden/none, so the drawer copy has to reopen them -
   but stating visible/auto here reopened them even while the drawer itself was
   closed. Both properties are inherited, and a descendant setting them back
   escapes a hidden ancestor, so the four product links stayed hit testable at
   every viewport: an invisible 300px column pinned to the top right of the
   page, swallowing clicks meant for whatever sat underneath. inherit ties both
   back to the drawer's own open state. */
.page--site .sp-nav__drawer .sp-nav__menu-panel {
  position: static;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  box-shadow: none;
  opacity: 1;
  visibility: inherit;
  pointer-events: inherit;
  transform: none;
}

.page--site .sp-nav__drawer .sp-nav__route--menu {
  width: 100%;
  justify-content: flex-start;
  color: var(--nav-muted);
  cursor: default;
}

.page--site .sp-nav__drawer .sp-nav__caret { display: none; }

/* ----------------------------------------------------------------------------
   37.2 · Page accents

   One custom property drives the page's colour. --green is the hook the
   existing components already read (.text-positive resolves through
   --positive-text, and .eyebrow--signal's dot reads --green directly), so
   pointing it at --sp-accent retints an entire page without touching a single
   component rule or adding a class to the markup.

   The .page--site defaults must stay above the audience classes: they share a
   specificity of (0,1,0), so source order is what decides.
   -------------------------------------------------------------------------- */
.page--site {
  --sp-accent  : var(--core-green-500);
  --sp-accent-2: var(--mindset-violet-500);
  --green      : var(--sp-accent);
}

/* Augment · Personality Blue. */
.page--augment {
  --sp-accent  : var(--personality-blue-500);
  --sp-accent-2: var(--personality-mint-500);
}

/* Flow · Core Lime. */
.page--flow {
  --sp-accent  : var(--core-lime-500);
  --sp-accent-2: var(--core-green-500);
}

/* Signal · Skills Orange. */
.page--signal {
  --sp-accent  : var(--skills-orange-500);
  --sp-accent-2: var(--skills-pink-500);
}

/* How it works · Skillprint green. The science page belongs to no audience, so
   it takes the brand colour rather than borrowing one - and since Amplify took
   Personality Mint, keeping mint here would have read as a Players page. */
.page--how-it-works {
  --sp-accent  : var(--core-green-500);
  --sp-accent-2: var(--mindset-violet-400);
}

/* The eyebrow dot's halo is a literal green rgb() in the base rule, which
   leaves an orange dot ringed in green on Augment. Re-mix it from the accent. */
.page--site .eyebrow--signal::before {
  box-shadow: 0 0 0 5px color-mix(in srgb, var(--sp-accent) 12%, transparent);
}

/* ----------------------------------------------------------------------------
   37.3 · Ambient gradients

   Two drifting spotlights per page, mixed from the page accent, plus a fixed
   wash behind the whole document. They sit under everything and never take
   pointer events. Motion is slow enough to read as light changing rather than
   as an animation, and it is off entirely under prefers-reduced-motion.
   -------------------------------------------------------------------------- */
.page--site {
  background-image:
    radial-gradient(70% 55% at 12% 0%,
      color-mix(in srgb, var(--sp-accent) 7%, transparent) 0%, transparent 62%),
    radial-gradient(60% 50% at 92% 14%,
      color-mix(in srgb, var(--sp-accent-2) 8%, transparent) 0%, transparent 58%);
  background-attachment: fixed;
  background-repeat: no-repeat;
}

/* The hero's two decorative discs become soft glows in the page accent. The
   base rule gives them size, position and shape; this only restates paint. */
/* The audience heroes are single column and have no dashboard mock to sit
   beside, so they carry the two spotlights themselves rather than inheriting
   .hero--homepage's. Same sizes, same motion, positioned for a centred hero. */
.page--site .hero--audience::before,
.page--site .hero--audience::after {
  content: "";
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
  filter: blur(2px);
}

.page--site .hero--audience::before {
  width: var(--space-640);
  height: var(--space-640);
  inset-inline-start: -14%;
  inset-block-start: -18%;
  background: radial-gradient(circle,
    color-mix(in srgb, var(--sp-accent) 15%, transparent) 0%, transparent 68%);
}

.page--site .hero--audience::after {
  width: var(--space-560);
  height: var(--space-560);
  inset-inline-end: -12%;
  inset-block-end: -26%;
  background: radial-gradient(circle,
    color-mix(in srgb, var(--sp-accent-2) 15%, transparent) 0%, transparent 68%);
}

@media (prefers-reduced-motion: no-preference) {
  .page--site .hero--audience::before { animation: sp-drift-a 34s var(--ease-standard) infinite alternate; }
  .page--site .hero--audience::after  { animation: sp-drift-b 41s var(--ease-standard) infinite alternate; }
}

/* The closing CTA panel gets the same treatment from the opposite corner, so
   the page opens and closes on the accent. */
@media (prefers-reduced-motion: no-preference) {
  .page--site .hero--homepage::before { animation: sp-drift-a 34s var(--ease-standard) infinite alternate; }
  .page--site .hero--homepage::after  { animation: sp-drift-b 41s var(--ease-standard) infinite alternate; }
}

@keyframes sp-drift-a {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(-52px, 46px, 0) scale(1.12); }
}

@keyframes sp-drift-b {
  from { transform: translate3d(0, 0, 0) scale(1.08); }
  to   { transform: translate3d(44px, -38px, 0) scale(0.94); }
}

/* ----------------------------------------------------------------------------
   37.4 · Audience page hero

   A single column hero for the three product pages: they lead with an argument
   and a screenshot below, not with a dashboard mock beside the copy.
   -------------------------------------------------------------------------- */
.page--site .hero--audience {
  min-height: 0;
  padding: var(--space-144) 0 var(--space-72);
  text-align: center;
}

.page--site .hero--audience .hero-copy {
  max-width: var(--space-768);
  margin-inline: auto;
}

.page--site .hero--audience .hero-actions { justify-content: center; }

.page--site .hero--audience .hero-note {
  width: fit-content;
  margin-inline: auto;
}

/* The audience badge above the H1: the product name at full size, so the page
   still teaches the name the nav introduced. */
.page--site .audience-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-10);
  padding: var(--space-6) var(--space-14);
  border: 1px solid color-mix(in srgb, var(--sp-accent) 28%, transparent);
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--sp-accent) 10%, transparent);
  color: var(--sp-accent);
  font-family: var(--skillprint-font-mono);
  font-size: var(--font-size-xs);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

/* ----------------------------------------------------------------------------
   37.5 · Two path cards

   Flow's two integration routes. A wider, taller sibling of .dimension-card
   that can carry a full argument and its own call to action.
   -------------------------------------------------------------------------- */
.page--site .path-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  gap: var(--space-16);
}

.page--site .path-card {
  --card-current-padding: var(--space-40);
  display: flex;
  flex-direction: column;
  gap: var(--space-16);
  min-width: 0;
}

.page--site .path-card .path-index {
  color: var(--sp-accent);
  font-family: var(--skillprint-font-mono);
  font-size: var(--font-size-xs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

.page--site .path-card .path-actions { margin-block-start: auto; padding-block-start: var(--space-8); }

/* ----------------------------------------------------------------------------
   37.6 · The Skillprint wheel

   The individual Skillprint: twelve measured qualities on a ring, each one a
   strand spiralling into the mark at the centre. Redrawn from the consumer
   profile screen in the brand's own palette.

   Geometry lives in the markup as path data, the way the site's other charts
   already carry it. Everything that is paint lives here. A spoke is one group
   holding strand, node and label, so a single hover lights all three.
   -------------------------------------------------------------------------- */
.page--site .sp-wheel {
  display: block;
  width: 100%;
  max-width: var(--space-640);
  height: auto;
  margin-inline: auto;
  overflow: visible;
}

.page--site .sp-wheel__ring {
  fill: none;
  stroke: var(--sp-wheel-line, color-mix(in srgb, var(--geistface-grey-300) 22%, transparent));
  stroke-width: 1;
}

.page--site .sp-wheel__strand {
  fill: none;
  stroke: var(--sp-wheel-line, color-mix(in srgb, var(--geistface-grey-300) 30%, transparent));
  stroke-width: 1.25;
  stroke-linecap: round;
  transition: stroke var(--duration-slow) var(--ease-standard),
              stroke-width var(--duration-slow) var(--ease-standard);
}

.page--site .sp-wheel__node {
  fill: color-mix(in srgb, var(--geistface-grey-300) 45%, transparent);
  transition: fill var(--duration-slow) var(--ease-standard),
              r var(--duration-slow) var(--ease-standard);
}

.page--site .sp-wheel__label {
  fill: var(--quiet, var(--geistface-grey-400));
  font-family: var(--skillprint-font-ui);
  font-size: 15px;
  font-weight: var(--weight-semibold);
  transition: fill var(--duration-slow) var(--ease-standard);
}

.page--site .sp-wheel__hub {
  fill: color-mix(in srgb, var(--panel-navy-950) 92%, transparent);
  stroke: color-mix(in srgb, var(--sp-accent) 30%, transparent);
  stroke-width: 1;
}

.page--site .sp-wheel__mark { fill: var(--sp-accent); }

/* Hover and keyboard focus both promote a spoke. The current spoke is the one
   the copy is talking about, so it stays lit without any interaction. */
.page--site .sp-wheel__spoke:hover .sp-wheel__strand,
.page--site .sp-wheel__spoke:focus-visible .sp-wheel__strand,
.page--site .sp-wheel__spoke--current .sp-wheel__strand {
  stroke: var(--sp-accent);
  stroke-width: 2;
}

.page--site .sp-wheel__spoke:hover .sp-wheel__node,
.page--site .sp-wheel__spoke:focus-visible .sp-wheel__node,
.page--site .sp-wheel__spoke--current .sp-wheel__node {
  fill: var(--sp-accent);
}

.page--site .sp-wheel__spoke:hover .sp-wheel__label,
.page--site .sp-wheel__spoke:focus-visible .sp-wheel__label,
.page--site .sp-wheel__spoke--current .sp-wheel__label {
  fill: var(--surface-text, var(--geistface-grey-25));
}

.page--site .sp-wheel__spoke:focus-visible { outline: none; }

/* Signal running inward. Each strand brightens in turn, one after another
   around the ring, so the figure reads as live without moving: rotating it
   would carry the labels upside down for half of every cycle, and a wheel
   whose labels cannot be read is a decoration rather than a diagram.

   The order is the source order of the spokes, which is clockwise from the
   top, so the highlight travels the way the eye already reads the ring. The
   current spoke is excluded: it is lit because the copy is about it, and a
   pulse would only make that read as an accident. */
@media (prefers-reduced-motion: no-preference) {
  .page--site .sp-wheel__spoke:not(.sp-wheel__spoke--current) .sp-wheel__strand,
  .page--site .sp-wheel__spoke:not(.sp-wheel__spoke--current) .sp-wheel__node {
    animation: sp-wheel-pulse 11s var(--ease-standard) infinite;
  }

  .page--site .sp-wheel__spoke:nth-of-type(1)  :is(.sp-wheel__strand, .sp-wheel__node) { animation-delay: 0s; }
  .page--site .sp-wheel__spoke:nth-of-type(2)  :is(.sp-wheel__strand, .sp-wheel__node) { animation-delay: 0.4s; }
  .page--site .sp-wheel__spoke:nth-of-type(3)  :is(.sp-wheel__strand, .sp-wheel__node) { animation-delay: 0.8s; }
  .page--site .sp-wheel__spoke:nth-of-type(4)  :is(.sp-wheel__strand, .sp-wheel__node) { animation-delay: 1.2s; }
  .page--site .sp-wheel__spoke:nth-of-type(5)  :is(.sp-wheel__strand, .sp-wheel__node) { animation-delay: 1.6s; }
  .page--site .sp-wheel__spoke:nth-of-type(6)  :is(.sp-wheel__strand, .sp-wheel__node) { animation-delay: 2s; }
  .page--site .sp-wheel__spoke:nth-of-type(7)  :is(.sp-wheel__strand, .sp-wheel__node) { animation-delay: 2.4s; }
  .page--site .sp-wheel__spoke:nth-of-type(8)  :is(.sp-wheel__strand, .sp-wheel__node) { animation-delay: 2.8s; }
  .page--site .sp-wheel__spoke:nth-of-type(9)  :is(.sp-wheel__strand, .sp-wheel__node) { animation-delay: 3.2s; }
  .page--site .sp-wheel__spoke:nth-of-type(10) :is(.sp-wheel__strand, .sp-wheel__node) { animation-delay: 3.6s; }
  .page--site .sp-wheel__spoke:nth-of-type(11) :is(.sp-wheel__strand, .sp-wheel__node) { animation-delay: 4s; }
  .page--site .sp-wheel__spoke:nth-of-type(12) :is(.sp-wheel__strand, .sp-wheel__node) { animation-delay: 4.4s; }

  /* Hover has to win over the pulse, and an animation beats a plain
     declaration, so the hover state stops the animation rather than trying to
     out-specify it. */
  .page--site .sp-wheel__spoke:hover :is(.sp-wheel__strand, .sp-wheel__node),
  .page--site .sp-wheel__spoke:focus-visible :is(.sp-wheel__strand, .sp-wheel__node) {
    animation: none;
  }
}

@keyframes sp-wheel-pulse {
  0%, 68%, 100% { opacity: 0.55; }
  80%           { opacity: 1; }
}

/* Every instance ships its labels. A wheel without them is a decoration; the
   labels are what make it say "these are the things a Skillprint measures".
   In the closing CTA it shares a column with the copy, so it gets a ceiling
   rather than a different component. */
.page--site .cta-visual .sp-wheel { max-width: var(--space-480); }

/* The wheel's frame on How it works: a panel that lets the labels breathe. */
.page--site .wheel-stage {
  display: grid;
  place-items: center;
  padding: var(--space-40) var(--space-56);
}

@media (max-width: 767px) {
  .page--site .wheel-stage { padding: var(--space-16); }
  .page--site .sp-wheel__label { font-size: 13px; }
  .page--site .hero--audience { padding-top: var(--space-112); }
}

/* ----------------------------------------------------------------------------
   37.7 · Redirect stub

   The three retired routes render one centred line while the replacement
   loads, so a stale bookmark never shows a blank page.
   -------------------------------------------------------------------------- */
.page--redirect .redirect-note {
  display: grid;
  place-items: center;
  gap: var(--space-16);
  min-height: 60vh;
  padding: var(--space-40);
  text-align: center;
}


/* ============================================================================
   38 · Marketing site · product cards, the data moat rail, the pull quote and
        the interactive Human × Game ontology map
   ========================================================================== */

/* ----------------------------------------------------------------------------
   38.1 · Product cards

   A product card leads with the interface. On a homepage, a screenshot of the
   real thing outranks any icon we could draw for it, so the shot is the first
   element and the icon is gone. Three cards, three accents, one shape.
   -------------------------------------------------------------------------- */
.page--site .product-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: var(--space-24);
}

.page--site .product-card {
  --card-current-padding: var(--space-24);
  display: flex;
  flex-direction: column;
  gap: var(--space-20);
  min-width: 0;
}

/* The shot bleeds to the card's edges at the top. Its own radius is inherited
   from the card so the two corners agree. */
.page--site .product-card__shot {
  margin: calc(var(--card-current-padding) * -1);
  margin-block-end: 0;
  overflow: hidden;
  border-end-start-radius: 0;
  border-end-end-radius: 0;
  border-start-start-radius: inherit;
  border-start-end-radius: inherit;
  border-block-end: 1px solid var(--border-subtle);
}

.page--site .product-card__shot img {
  display: block;
  width: 100%;
  height: auto;
}

.page--site .product-card__body { display: grid; gap: var(--space-12); min-width: 0; }

.page--site .product-card__audience {
  color: var(--tone, var(--sp-accent));
  font-family: var(--skillprint-font-mono);
  font-size: var(--text-xxs-font-size);
  line-height: var(--text-xxs-line-height);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

.page--site .product-card h3 { margin: 0; }

/* ----------------------------------------------------------------------------
   38.2 · The data moat rail

   Five steps that read as one movement rather than five cards. The connector
   is a border on the step rather than a separate element, so it wraps with it.
   -------------------------------------------------------------------------- */
.page--site .moat-rail {
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-12);
}

.page--site .moat-step {
  display: grid;
  gap: var(--space-8);
  padding: var(--space-24);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-2xl);
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--sp-accent) 7%, transparent) 0%, transparent 70%);
}

.page--site .moat-step b {
  color: var(--sp-accent);
  font-family: var(--skillprint-font-mono);
  font-size: var(--font-size-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.1em;
}

.page--site .moat-step span {
  color: var(--surface-text);
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
  font-weight: var(--weight-medium);
}

/* ----------------------------------------------------------------------------
   38.3 · The pull quote
   -------------------------------------------------------------------------- */
.page--site .sp-quote {
  margin-block-start: var(--space-28);
  padding-inline-start: var(--space-20);
  border-inline-start: 2px solid color-mix(in srgb, var(--sp-accent) 45%, transparent);
}

.page--site .sp-quote p {
  color: var(--surface-text);
  font-family: var(--skillprint-font-display);
  font-size: var(--text-xl-font-size);
  line-height: var(--text-xl-line-height);
}

.page--site .sp-quote cite {
  display: block;
  margin-block-start: var(--space-8);
  color: var(--quiet);
  font-family: var(--skillprint-font-mono);
  font-size: var(--font-size-xs);
  font-style: normal;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

/* ----------------------------------------------------------------------------
   38.4 · The Human × Game ontology map

   Not restated here. The map on How it works is the design system's
   Ontology_Wheel component, not a copy of it: section 17's rules were
   re-scoped to `:is(.page--ontology-wheel, .ontology-root)`, so the marketing
   site and the prototype resolve the same declarations from one place and
   cannot drift apart. The site's container carries .ontology-root.

   Only the frame around it belongs to the site.
   -------------------------------------------------------------------------- */

.page--site .hero--onto { padding-block-start: var(--space-144); }

@media (max-width: 767px) {
  .page--site .hero--onto { padding-block-start: var(--space-112); }
}


/* ============================================================================
   39 · Marketing site · the polish pass

   Typography consistency in the bar, boxes removed from things that were not
   objects, product rows, the FAQ, and footer alignment.
   ========================================================================== */

/* ----------------------------------------------------------------------------
   39.1 · One typography for the top level routes

   Product is a <button> and Home and How It Works are anchors. The site's form
   reset hands buttons the base 16px/400 and beats .sp-nav__route's 12px/600 on
   source order, so the bar was literally showing two type sizes side by side.
   This states one size for all three and stops the reset deciding it.

   That size is .sp-nav__route's own 12px/18px, not the 16px this rule first
   normalised to. The marketing site is one surface in a system - the portal,
   the docs, the labs benchmark and every prototype render this bar at 12px -
   and a visitor moving from the site into the portal was crossing a 4px step
   in the one component that is supposed to be identical everywhere. The
   component decides; the site only stops the form reset from overriding it.
   -------------------------------------------------------------------------- */
.page--site :is(.sp-nav__routes, .sp-nav__drawer-routes) .sp-nav__route {
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: normal;
}

/* The current route is marked by colour alone. Bolding it would reintroduce
   the mixed weight this section exists to remove.

   The colour needs the extra specificity: `.page--homepage a { color: inherit }`
   outranks a plain .sp-nav__route rule, so stating the colour once here is what
   makes the marker visible now that the weight no longer carries it. */
.page--site :is(.sp-nav__routes, .sp-nav__drawer-routes) .sp-nav__route,
.page--site :is(.sp-nav__routes, .sp-nav__drawer-routes) .sp-nav__route:link,
.page--site :is(.sp-nav__routes, .sp-nav__drawer-routes) .sp-nav__route:visited {
  color: var(--nav-muted);
}

.page--site :is(.sp-nav__routes, .sp-nav__drawer-routes) .sp-nav__route:hover {
  color: var(--nav-text);
  font-weight: var(--weight-semibold);
}

/* The current route takes the navigation ink, not the hover ink: the two were
   one selector, so the page you are on and the link under the pointer read
   identically. */
.page--site :is(.sp-nav__routes, .sp-nav__drawer-routes) .sp-nav__route[aria-current="page"] {
  color: var(--nav-current-text);
  font-weight: var(--weight-semibold);
}

/* ----------------------------------------------------------------------------
   39.2 · Supporting lines are text, not objects

   The line under the hero buttons is a caption. A border and a fill around it
   gave it the weight of a card and put it in competition with the buttons it
   is supposed to sit behind. The dot stays: it is the only thing that needs to
   read as a marker.
   -------------------------------------------------------------------------- */
.page--site .hero-note {
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  box-shadow: none;
  color: var(--quiet);
}

/* ----------------------------------------------------------------------------
   39.3 · The proof row

   Five figures on one line at every desktop width, and no panel around them.
   The old grid was four fixed columns, so a fifth stat wrapped and the labels
   broke mid phrase. `auto-fit` with a floor keeps them on one row while there
   is room and collapses in one step when there is not.
   -------------------------------------------------------------------------- */
/* One column per figure, whatever the count. A fixed five column template put
   a fifth stat on a second row on the homepage, left an empty trailing track on
   About's four, and broke six into 5+1 on How it works. `grid-auto-flow: column`
   with equal auto columns gives each strip exactly as many columns as it has
   children, on one row. */
.page--site .stats-strip--bare {
  margin-top: var(--space-56);
  padding: var(--space-28) 0 0;
  grid-template-columns: none;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: var(--space-24);
  border: 0;
  border-top: 1px solid var(--border-subtle);
  border-radius: 0;
  background: none;
}

.page--site .stats-strip--bare .stat {
  padding: 0;
  border: 0;
}

.page--site .stats-strip--bare .stat strong {
  font-size: var(--display-xs-font-size);
  line-height: var(--display-xs-line-height);
  letter-spacing: -0.02em;
}

/* The labels are two and three word phrases; balancing beats a ragged break
   after the first word. */
.page--site .stats-strip--bare .stat span {
  margin-top: var(--space-4);
  text-wrap: balance;
}

/* Below the desktop widths the row is allowed to become a grid again: six
   figures on one line stops being readable long before it stops fitting. */
@media (max-width: 1023px) {
  .page--site .stats-strip--bare {
    grid-auto-flow: row;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-24);
  }
}

@media (max-width: 639px) {
  .page--site .stats-strip--bare { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ----------------------------------------------------------------------------
   39.4 · Product rows

   Argument left, interface right, three times. Every accent on the row comes
   from the row's own --tone, which .tone already publishes as --accent, so the
   eyebrow, the tags and the button all follow the product without a second
   colour being named here.
   -------------------------------------------------------------------------- */
.page--site .product-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-96);
}

.page--site .product-row {
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  gap: var(--space-64);
  min-width: 0;
}

.page--site .product-row__copy { display: grid; gap: var(--space-16); min-width: 0; }

.page--site .product-row__audience {
  color: var(--accent, var(--sp-accent));
  font-family: var(--skillprint-font-mono);
  font-size: var(--text-xxs-font-size);
  line-height: var(--text-xxs-line-height);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

.page--site .product-row h3 {
  margin: 0;
  font-size: var(--display-sm-font-size);
  line-height: var(--display-sm-line-height);
  letter-spacing: -0.02em;
}

.page--site .product-row .button { justify-self: start; margin-top: var(--space-8); }

/* A wash of the product's colour behind its screenshot, so the row is tinted
   without a border being drawn around anything. */
/* The frame takes the asset's ratio rather than a fixed 16:9. The benchmark
   shot is 1440x950, and object-fit: cover was cropping about 15% off it. */
.page--site .product-row__shot { aspect-ratio: auto; }

.page--site .product-row__shot img {
  position: static;
  width: 100%;
  height: auto;
  object-fit: fill;
}

.page--site .product-row__shot {
  position: relative;
  border-color: color-mix(in srgb, var(--accent, var(--sp-accent)) 22%, var(--border-subtle));
  box-shadow: 0 32px 80px color-mix(in srgb, var(--deep-navy-950) 45%, transparent);
}

@media (max-width: 1023px) {
  .page--site .product-grid { gap: var(--space-64); }
  .page--site .product-row { grid-template-columns: minmax(0, 1fr); gap: var(--space-32); }
}

/* ----------------------------------------------------------------------------
   39.5 · FAQs

   One container, not three. The questions were split across two bordered cards
   with their own headings, which made a five item list look like a comparison
   table. Title and a line on the left, every question in one accordion on the
   right, separated by rules rather than boxes.
   -------------------------------------------------------------------------- */
.page--site .faq-layout {
  grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr);
  gap: var(--space-64);
}

.page--site .faq-intro h2 { margin-block: var(--space-12) var(--space-16); }

.page--site .faq-list {
  border-top: 1px solid var(--border-subtle);
}

.page--site .faq-list .accordion__item {
  border-bottom: 1px solid var(--border-subtle);
  background: none;
}

.page--site .faq-list .accordion__summary {
  padding-block: var(--space-22);
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
  font-weight: var(--weight-medium);
}

/* The answer takes the site's own reading colour, not the component default.
   `.accordion__panel` inherits `--surface-text-muted`, which measures 3.97:1 on
   the marketing site's light ground - under AA for body copy - while every
   other muted paragraph on the site runs at 8.2:1 through `--ui-muted`. The
   component is right for the surfaces it was drawn on; this restates it for
   this one. */
.page--site .faq-list .accordion__panel {
  padding-bottom: var(--space-24);
  color: var(--ui-muted);
}

@media (max-width: 1023px) {
  .page--site .faq-layout { grid-template-columns: minmax(0, 1fr); gap: var(--space-32); }
}

/* ----------------------------------------------------------------------------
   39.6 · Footer

   The brand block was three elements with three different gaps sitting below
   the baseline of the columns beside it. It is one block now, and every column
   starts on the same line.
   -------------------------------------------------------------------------- */
.page--site .footer-top { align-items: start; }

/* The mark is 494x100 artwork in a box the stylesheet only gave a width to, so
   it kept the 100px height attribute and letterboxed the logo inside it. That
   phantom 66px was the gap under the wordmark, not a margin, which is why
   tightening the paragraph's margin did nothing. */
.page--homepage .footer-brand img {
  height: auto;
  margin-block: 0;
}

/* Matching .page--homepage's specificity on purpose: the base rule sets 18px
   here and would otherwise win on specificity over a .page--site selector. */
.page--homepage .footer-brand p {
  margin: var(--space-12) 0 0;
  max-width: 34ch;
}

/* Every resource link is its own line at the same rhythm as the other columns,
   with the external mark riding the text rather than pushing the row taller. */
.page--site .footer-col a {
  display: flex;
  align-items: center;
  gap: var(--space-8);
  margin-block: 0;
  padding-block: var(--space-6);
  width: fit-content;
}

.page--site .footer-col strong { margin-bottom: var(--space-10); }

.page--site .footer-col a .sp-icon {
  --icon-size: var(--icon-size-xs);
  flex: 0 0 auto;
  opacity: 0.6;
}

.page--site .footer-col a:hover .sp-icon { opacity: 1; }


/* ----------------------------------------------------------------------------
   39.7 · Accents reach the small parts too

   Brief item 7 asked for the product colours on icons, highlights, accents and
   hover states. Icons and highlights already resolved through --accent, but
   tags and the buttons beside them did not: .ui-tag reads --border-strong and
   --surface-text and never looks at --tone, so 48 tags on the site were the
   same neutral inside a blue card, a lime card and an orange one.

   Tinting rather than filling: a tag is a label, and a row of solid accent
   pills would out-shout the heading above them.
   -------------------------------------------------------------------------- */
.page--site .tone .ui-tag,
.page--site .product-row .ui-tag,
.page--site .path-card .ui-tag {
  border-color: color-mix(in srgb, var(--accent, var(--sp-accent)) 34%, transparent);
  color: color-mix(in srgb, var(--accent, var(--sp-accent)) 78%, var(--surface-text));
  background: color-mix(in srgb, var(--accent, var(--sp-accent)) 7%, transparent);
}

/* The row's own call to action follows its product, so the three Explore
   buttons are no longer identical neutrals in three differently coloured rows. */
.page--site .product-row .button--secondary:hover,
.page--site .path-card .button--secondary:hover {
  border-color: color-mix(in srgb, var(--accent, var(--sp-accent)) 55%, transparent);
  background: color-mix(in srgb, var(--accent, var(--sp-accent)) 12%, var(--button-surface));
}

/* Hovering a product in the nav menu says which product it is: each row is
   tinted with that product's own accent, which is the colour its page is
   themed in, so the menu is where a visitor first learns the code. */
.page--site .sp-nav__product:hover,
.page--site .sp-nav__product:focus-visible {
  border-color: color-mix(in srgb, var(--sp-route-accent) 38%, transparent);
  background: color-mix(in srgb, var(--sp-route-accent) 9%, var(--nav-hover-surface));
}

/* The product you are already on is the exception, and it takes the navigation
   violet rather than its own accent. Current used to share the hover tint,
   which meant two things at once: the row under the pointer and the page you
   were reading looked identical, and on Amplify - whose accent is the green -
   the current row was the one green current-state left in the system. "You are
   here" is one colour everywhere; which product it is, is the row's accent. */
.page--site .sp-nav__product[aria-current="page"] {
  border-color: var(--nav-current-border);
  background: var(--nav-current-surface);
}

/* Flow's second path card was declared tone--green but rendered lime, because
   .path-index reads --sp-accent (the page) rather than --accent (the card).
   Reading the card's own tone first is what makes a per card tone mean
   anything. */
.page--site .path-card .path-index { color: var(--accent, var(--sp-accent)); }

/* .text-positive resolves through --positive-text, which is hard coded to a
   green in the light theme. The dark theme routes it through --green, which the
   page accent already overrides, so the two agree today and would diverge the
   moment a light theme is switched on. State it once against the accent. */
.page--site .text-positive { color: var(--sp-accent); }


/* ----------------------------------------------------------------------------
   39.8 · The routes sit in the centre of the bar

   The bar is a flex row of logo, routes, actions, and .sp-nav__routes carries
   `push-inline-end`, whose `margin-inline-start: auto` shoved the routes up
   against the actions on the right. Giving the logo and the actions an equal
   share of the free space instead leaves the routes centred on the bar, no
   matter that the logo and the action cluster are different widths.

   The drawer is position: absolute, so it takes no part in this.
   -------------------------------------------------------------------------- */
.page--site .sp-nav__brand,
.page--site .sp-nav__actions {
  flex: 1 1 0;
  min-width: 0;
}

/* The brand cell now takes a share of the row rather than hugging its logo,
   and the logo image is `full-width`, so it grew with the cell and burst out
   of the bar. The image keeps its own size; only the cell around it flexes. */
.page--site .sp-nav__brand img {
  width: 168px;
  max-width: 100%;
  height: auto;
}

.page--site .sp-nav__actions { justify-content: flex-end; }

.page--site .sp-nav__routes {
  flex: 0 0 auto;
  margin-inline-start: 0;
  justify-content: center;
}


/* ==========================================================================
   09 - Unique composition · Portal Welcome & Sign In

   The portal's front door, route `/`. One centred auth card over a soft
   mood/cognition wash, then the surfaces that sit behind it once a player is
   in: the two "what would you like to do today" choices, the mood and skill
   vocabularies, and the New Games rail.

   The two first-run overlays the real app shows as floating popovers - the
   game-tile coachmark and the profile-unlocked banner - are modelled here as
   dismissible in-flow panels. A coachmark that hides the interface cannot
   teach it, and neither of them is a decision the reader has to make before
   the page is usable, so neither is a modal.
   ========================================================================== */

.page--portal-welcome main {
  background: var(--page);
}

.page--portal-welcome h1,
.page--portal-welcome h2,
.page--portal-welcome h3,
.page--portal-welcome p {
  margin-top: 0;
}

/* One section rhythm for the whole page
   -------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------
   Auth card

   The real page floats the card over three blurred colour blobs. Here the
   wash is a pair of radial gradients off the mood and cognition ramps - same
   read, no filter cost
   -------------------------------------------------------------------------- */
.page--portal-welcome .auth-card {
  width: 100%;
  max-width: var(--measure-sm);
  gap: var(--spacing-4xl);
  padding: var(--spacing-5xl);
  border-radius: var(--radius-panel);
  box-shadow: var(--shadow-lg);
}

.page--portal-welcome .auth-card__head {
  gap: var(--spacing-md);
  justify-items: center;
}

.page--portal-welcome .auth-card__title {
  font-size: clamp(28px, 4vw, 36px);
  line-height: 1.15;
  letter-spacing: -0.02em;
  font-weight: var(--weight-bold);
}

.page--portal-welcome .auth-card__lede {
  max-width: 34ch;
  font-size: var(--font-size-md);
  line-height: var(--line-height-md);
}

.page--portal-welcome .auth-card__body {
  gap: var(--spacing-xl);
}

.page--portal-welcome .auth-guest {
  gap: var(--spacing-md);
  font-weight: var(--weight-semibold);
}

/* The rule is drawn on the flex children rather than as a border on the row,
   so the label keeps its own background and the two halves stay symmetric. */
.page--portal-welcome .auth-divider__rule {
  flex: 1 1 auto;
  height: 1px;
  background: var(--surface-border);
}

.page--portal-welcome .auth-divider__label {
  letter-spacing: 0.12em;
}

.page--portal-welcome .auth-providers {
  gap: var(--spacing-md);
}

.page--portal-welcome .auth-provider {
  justify-content: center;
  gap: var(--spacing-lg);
}

/* A neutral letterform, deliberately not a brand mark: the prototype must not
   ship a third party's logo, and the button's label already names it. */
.page--portal-welcome .auth-provider__mark {
  width: var(--space-24);
  height: var(--space-24);
  font-family: var(--skillprint-font-mono);
}

.page--portal-welcome .auth-org {
  margin-top: var(--spacing-xs);
  padding-top: var(--spacing-xl);
}

.page--portal-welcome .auth-org__toggle {
  justify-content: center;
  gap: var(--spacing-md);
  color: var(--skillprint-action-violet);
  font-weight: var(--weight-semibold);
}

.page--portal-welcome .auth-org__toggle:hover {
  color: var(--mindset-violet-300);
}

html:is([data-theme="light"], [data-surface="light"]) .page--portal-welcome .auth-org__toggle:hover {
  color: var(--mindset-violet-700);
}

.page--portal-welcome .auth-org__toggle[aria-expanded="true"] {
  display: none;
}

.page--portal-welcome .auth-org__form {
  gap: var(--spacing-lg);
}

.page--portal-welcome .auth-org__form[hidden] {
  display: none;
}

.page--portal-welcome .auth-org__title {
  color: var(--surface-text);
}

.page--portal-welcome .auth-org__actions > * {
  flex: 1 1 0;
}

.page--portal-welcome .auth-org__status:empty {
  display: none;
}

.page--portal-welcome .auth-legal {
  max-width: 40ch;
  margin-inline: auto;
}


/* --------------------------------------------------------------------------
   What would you like to do today?
   Two choices, one per family: the library leans mood, progress leans
   cognition
   -------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------
   Your Profile is Unlocked!  -  dismissible, in flow
   -------------------------------------------------------------------------- */
/* display:grid outranks the UA [hidden] rule
   -------------------------------------------------------------------------- */
.page--portal-welcome .metric--portal {
  display: grid;
}

.page--portal-welcome .status-tab {
  min-height: var(--space-36);
  padding: 0 var(--spacing-2xl);
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--surface-text-muted);
  font-family: var(--skillprint-font-ui);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-sm);
  font-weight: var(--weight-semibold);
  cursor: pointer;
  transition: var(--transition-interactive);
}

.page--portal-welcome .status-tab:hover {
  color: var(--surface-text);
}

.page--portal-welcome .status-tab.is-active,
.page--portal-welcome .status-tab[aria-selected="true"] {
  background: var(--skillprint-action-violet);
  color: var(--white);
}

.page--portal-welcome .mood-card,
.page--portal-welcome .skill-card {
  --card-current-padding: var(--spacing-lg) var(--spacing-xl);
  --card-current-radius : var(--radius-card);

  text-decoration: none;
  color: inherit;
}

.page--portal-welcome .mood-card {
  min-width: var(--space-192);
  flex: 1 1 auto;
}

.page--portal-welcome .mood-icon {
  background: color-mix(in srgb, var(--mood-500) 16%, transparent);
}

.page--portal-welcome .cognition-icon {
  background: color-mix(in srgb, var(--cognition-500) 16%, transparent);
}

/* A horizontal rail, scrolled rather than wrapped
   -------------------------------------------------------------------------- */
/* Each rail tone is its two ramp colours carried down onto deep navy rather
   than used raw. The raw 500 steps are display colours - mint and lime at full
   strength put white body copy at roughly 1.8:1 - and the card has to hold a
   title, a line of description and a NEW flag, all reversed out. Mixed to 44%
   and 32% every tone clears 4.5:1 against `--white` while staying obviously
   itself, and the pair stays fixed across both surfaces because the card is a
   colour object
   -------------------------------------------------------------------------- */
/* The rail's Play control sits on colour, so it inverts rather than using the
   violet primary - Action Violet on a violet card would disappear.

   The ink is `--deep-navy-900` and not `--skillprint-deep-navy`: the semantic
   token is the *deep surface*, and it flips to near-white under
   data-surface="light"
   -------------------------------------------------------------------------- */
.page--portal-welcome .empty-state {
  padding: var(--spacing-5xl) var(--spacing-2xl);
  border: 1px dashed var(--surface-border-hover);
  border-radius: var(--radius-card);
  background: var(--surface-box);
  justify-items: center;
}


/* --------------------------------------------------------------------------
   Responsive
   -------------------------------------------------------------------------- */


@media (max-width: 720px) {

  .page--portal-welcome .auth-card {
    padding: var(--spacing-3xl);
  }
}


/* ==========================================================================
   09 - Unique composition · Skillprint Portal · Skills
   --------------------------------------------------------------------------
   One page, three sections, one grammar. Mood, Cognition and Personality are
   not three destinations - they are the three things a session measures, and
   splitting them across pages made the reader hold the relationship in their
   head. Portal_Moods.html was the last of that split and has been removed -
   its nine moods are the Mood section on this page, and its own block of
   styles went with it. Each section runs the same three beats: the dimension's own bar, one
   featured skill with the games that best develop it, then the full index at
   three cards per row in alphabetical order.

   Colour is the only thing that differs between them. --skill-accent is the
   graphic step - the icon glyph, the frame border, the meter fill, a chip's
   tint - and --skill-ink is the text-safe step for anything that has to be
   read as words. Both are re-valued per section from [data-dimension], so a
   block inherits its dimension's colour by sitting inside it rather than by
   carrying a modifier. Everything that is *not* one of the three dimensions
   and still needs emphasis takes --skill-highlight: Skillprint Green.

   The shared components carry the work wherever one exists - sp-card and
   sp-icon-frame for the boxes, ui-badge for the pills, sp-progress for the
   meters, button for the controls. What is written here is composition,
   the accent plumbing, and the game peek.
   ========================================================================== */

.page--portal,
.page--portal-skills {
  --skill-accent   : var(--mood-500);
  --skill-ink      : var(--mood-500);
  --skill-highlight: var(--core-green-500);
  --skill-on-accent: var(--skillprint-deep-navy);
}

/* --skill-ink-hover is one ramp step further along in the direction that keeps
   the label legible, and it is stated rather than mixed. The mix it replaces
   was color-mix(... var(--skillprint-deep-navy)), and that token is re-valued
   to --deep-navy-25 - a near-white - under html[data-surface="light"]. So the
   "darken on hover" step LIGHTENED the button on the light ground and dropped
   its white label from 6.4:1 to 4.1:1. A name is not a value. */
.page--portal [data-dimension="mood"] {
  --skill-accent    : var(--mood-500);
  --skill-ink       : var(--mood-500);
  --skill-ink-hover : var(--core-lime-600);
}

.page--portal [data-dimension="cognition"] {
  --skill-accent    : var(--cognition-500);
  --skill-ink       : var(--cognition-500);
  --skill-ink-hover : var(--personality-mint-600);
}

.page--portal [data-dimension="personality"] {
  --skill-accent    : var(--personality-500);
  --skill-ink       : var(--personality-500);
  --skill-ink-hover : var(--personality-blue-600);
}

/* The three 500s are drawn for a dark ground. On the portal's near-white one
   Core Lime is 1.2:1 and Personality Mint 1.3:1, so the icons, the meter fills
   and the links all but disappear. Each dimension takes a darker step of its
   own ramp rather than a different hue: the graphic step is the one the shared
   sheet already uses for .sp-icon--mood / --cognition / --personality, and the
   ink step goes far enough down the same ramp to be read as text (Lime 900 is
   6.8:1, Mint 900 6.5:1, Blue 800 5.4:1 on --surface-box). */
html:is([data-theme="light"], [data-surface="light"]) > .page--portal,
html:is([data-theme="light"], [data-surface="light"]) > .page--portal-skills {
  /* The base pair needs the light step as much as the three dimensions below
     do, and only the dimensions were given one - so --skill-accent and
     --skill-ink kept their dark-ground 500s for anything not sitting inside a
     [data-dimension] block. Home's game peek is exactly that: the popover is
     built against the page, not a dimension, so its "Play" cue drew Core Lime
     500 on the near-white card at 1.78:1 - a control, effectively invisible.
     These are the mood values, which is what the base already defaults to in
     the dark declaration above, so the fallback keeps its identity and simply
     becomes readable. */
  --skill-accent   : var(--core-lime-700);
  --skill-ink      : var(--core-lime-900);
  --skill-highlight: var(--core-green-800);
  --skill-on-accent: var(--white);
}

html:is([data-theme="light"], [data-surface="light"]) > .page--portal [data-dimension="mood"] {
  --skill-accent    : var(--core-lime-700);
  --skill-ink       : var(--core-lime-900);
  --skill-ink-hover : var(--core-lime-950);
}

html:is([data-theme="light"], [data-surface="light"]) > .page--portal [data-dimension="cognition"] {
  --skill-accent    : var(--personality-mint-700);
  --skill-ink       : var(--personality-mint-900);
  --skill-ink-hover : var(--personality-mint-950);
}

html:is([data-theme="light"], [data-surface="light"]) > .page--portal [data-dimension="personality"] {
  --skill-accent    : var(--personality-blue-600);
  --skill-ink       : var(--personality-blue-800);
  --skill-ink-hover : var(--personality-blue-900);
}

/* Where each of the two steps is allowed to go. --skill-accent is the mid step
   and reads as the category's hue; it is only ever a large area - a fill, a
   wash, a 4px rule. --skill-ink is the step far enough down the same ramp to be
   read as a shape, and it takes everything small or line-like: glyphs, chevrons,
   accent borders, link text. The mid step measured 2.5:1 on the light ground,
   which is fine behind a meter and not fine as the meter's icon. */


/* --------------------------------------------------------------------------
   A section

   There is no in-page dimension switcher. The rail's .portal-subnav is the
   same three links in the same state, it is on screen the whole way down the
   page rather than only at the top of it, and it is where every other portal
   screen already looks for the current dimension - so a second copy in the
   content column was one control too many.
   -------------------------------------------------------------------------- */

.page--portal-skills .skills-section {
  gap: var(--space-16);
  /* Clears the sticky top bar when a rail link or the browser's own fragment
     jump lands on the section. */
  scroll-margin-top: calc(var(--portal-topbar) + var(--space-20));
}

.page--portal-skills .skills-section__index-title {
  margin: var(--space-12) 0 0;
  color: var(--ui-muted);
  font-weight: var(--weight-semibold);
}

/* The section is about one category, so its title wears that category's icon.
   The glyph is the official category mark from the sprite - never a member
   trait's - and it takes --skill-ink from the [data-dimension] on the section
   itself, so all three headings are one rule rather than three. Sized against
   the title's own cap height instead of a fixed step, so the pair stays
   optically matched if the heading's type scale ever moves. */
.page--portal-skills .portal-section__title--category {
  display: flex;
  align-items: center;
  gap: var(--space-10);
}

.page--portal-skills .portal-section__title--category .sp-icon {
  --icon-size: 1.1em;
  --icon-stroke: var(--icon-stroke-sm);
  color: var(--skill-ink);
  flex: none;
}


/* --------------------------------------------------------------------------
   The featured skill
   -------------------------------------------------------------------------- */

.page--portal-skills .skill-feature {
  --card-current-padding: var(--space-24);
  --card-current-radius : var(--radius-card);
  display: grid;
  gap: var(--space-20);
}

.page--portal-skills .skill-feature__head {
  display: grid;
  gap: var(--space-14);
}

.page--portal-skills .skill-feature__eyebrow {
  margin-bottom: var(--space-2);
}

.page--portal-skills .skill-feature__icon {
  --icon-frame-radius: var(--radius-xl);
  border-color: color-mix(in srgb, var(--skill-ink) 45%, transparent);
  background: color-mix(in srgb, var(--skill-accent) 14%, var(--surface-box));
  color: var(--skill-ink);
  box-shadow: var(--shadow-xs);
}

/* The 48px frame keeps the same glyph-to-box proportion at the design system's
   next step up: 22px on --icon-stroke-22, still a 1.5px line. */
.page--portal-skills .skill-feature__icon .sp-icon {
  width: var(--space-22);
  height: var(--space-22);
  flex-basis: auto;
  --icon-stroke: var(--icon-stroke-22);
}

.page--portal-skills .skill-feature__copy {
  max-width: var(--measure-sm);
  font-size: var(--font-size-md);
  line-height: var(--line-height-md);
}

/* A rule in the dimension's own accent, under the featured subject. */
.page--portal-skills .skill-feature__rule {
  height: var(--space-4);
  border-radius: var(--radius-full);
  background: var(--skill-accent);
}

.page--portal-skills .skill-feature__bar h4 {
  font-size: var(--font-size-md);
  line-height: var(--line-height-md);
  font-weight: var(--weight-semibold);
}

.page--portal-skills .skill-feature__strip {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-12);
}

.page--portal-skills .skill-tile {
  --card-current-radius: var(--radius-compact);
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.page--portal-skills .skill-tile__body { flex: 1 1 auto; }

.page--portal-skills .skill-tile:hover {
  border-color: color-mix(in srgb, var(--skill-ink) 72%, var(--surface-border));
}

/* The whole catalogue ships card artwork now, so the tile shows the card
   itself. The band is 16/9 against the card's 1.81, so `cover` crops a couple
   of pixels off the ends and nothing else. */
.page--portal-skills .skill-tile__art {
  aspect-ratio: 16 / 9;
  border-bottom: 1px solid var(--surface-border);
  background: color-mix(in srgb, var(--skill-accent) 8%, var(--surface-raised));
}

.page--portal-skills .skill-tile__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.page--portal-skills .skill-tile__body { padding: var(--space-14); }

.page--portal-skills .skill-tile__body h4 {
  font-size: var(--font-size-md);
  line-height: var(--line-height-md);
  font-weight: var(--weight-semibold);
}

.page--portal-skills .skill-tile__body p {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
}

.page--portal-skills .skill-tile__meta {
  margin-top: var(--space-2);
  /* The floor under the gap the flex `auto` margin opens: on a tile whose copy
     already fills the card there is no slack for `auto` to distribute, and the
     duration and genre ended up against the button. */
  margin-bottom: var(--space-12);
}

/* The one filled control inside a dimension takes that dimension's colour,
   not the product violet: on this page the accent is what says which of the
   three you are reading. */
/* The fill and the label were retinted off .button--primary; the icon colour
   was not, so the play triangle kept --button-primary-icon (Mindset Violet 200)
   and sat on a lime, mint or blue fill at 1.24:1 - invisible, while the word
   beside it measured 14:1. An icon inside a button is the same mark as its
   label and takes the same colour.

   padding-top used to stand in for the gap above the button; padding grows the
   control instead of separating it, so the button was taller than its size and
   still touching the metadata. The gap now belongs to the metadata. */
.page--portal-skills .skill-tile__play {
  --button-icon-color: var(--skill-on-accent);
  align-self: start;
  margin-top: auto;
  background: var(--skill-ink);
  border-color: transparent;
  color: var(--skill-on-accent);
}

.page--portal-skills .skill-tile__play:hover {
  background: var(--skill-ink-hover);
}


/* --------------------------------------------------------------------------
   The skill index

   Three per row is set one step more specific than the shell's own
   auto-fill rule further down the sheet, which would otherwise decide the
   column count from the available width.
   -------------------------------------------------------------------------- */

:is(.page--portal-skills, .page--portal-game-detail, .page--portal-playbook-detail) .portal-content .skill-grid {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-12);
}

/* Four labels is the one count that breaks in a three-track grid: it sets 3 + 1,
   and a single card alone on the second line reads as a dropped card rather than
   the fourth member of a set - the same fault the game rail's own comment names
   further down this sheet. Two of the fifty games produce four labels (Snake
   Attack, Cat Focus); the other forty-eight produce five, which sets 3 + 2 and
   reads as a set, so this is the only count that needs saying.

   :has() carries the specificity of its argument, so this lands at (0,4,0)
   against the three-track rule's (0,3,0) and wins without !important. The
   phone step below re-states it for the same reason. */
:is(.page--portal-game-detail, .page--portal-playbook-detail) .skill-grid:has(> :last-child:nth-child(4)) {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

:is(.page--portal-skills, .page--portal-game-detail, .page--portal-playbook-detail) .skill-card {
  --card-current-padding: var(--space-18);
  --card-current-radius : var(--radius-compact);
  display: grid;
  gap: var(--space-14);
}

/* head, copy, games, action. Content fills the body of the card and the one
   primary action sits on the floor of it, which is the rule every card in the
   portal follows - the games list is the row that takes up the slack, so the
   buttons line up across a row whatever each card's description and game count
   come to. The action is last in the markup too, so what a keyboard reaches
   last is what the eye reads last. */
.page--portal-skills .skill-card {
  grid-template-rows: auto auto 1fr auto;
}

/* Game Detail draws the same card without the games list, so it has three
   children and the slack row has to move. Left on the four-track template the
   action landed in the 1fr track and stretched to the height of the tallest
   card in the row, and the gap to the empty fourth track drew 14px of dead
   space under every card. Copy takes the slack instead - and takes
   align-content: start with it, for the same reason .skill-card__games declares
   it on the Skills page: a grid in a 1fr track pulls its own rows apart. */
:is(.page--portal-game-detail, .page--portal-playbook-detail) .skill-card {
  grid-template-rows: auto 1fr auto;
}

:is(.page--portal-game-detail, .page--portal-playbook-detail) .skill-card__copy {
  align-content: start;
}

/* 55% of the ink over a hairline that is itself 8% opaque came out at 2.7:1 on
   the light ground - a hover you have to look for. 72% clears 3:1 in both. */
:is(.page--portal-skills, .page--portal-game-detail, .page--portal-playbook-detail) .skill-card:hover {
  border-color: color-mix(in srgb, var(--skill-ink) 72%, var(--surface-border));
}

/* The icon treatment the trait cards in the design system established: a
   40px rounded square, the dimension's colour as the glyph and a tinted
   border, and a surface that stays neutral. Colour identifies; it does not
   fill. */
:is(.page--portal-skills, .page--portal-game-detail, .page--portal-playbook-detail) .skill-card__icon {
  --icon-frame-radius: var(--radius-lg);
  border-color: color-mix(in srgb, var(--skill-ink) 45%, transparent);
  background: color-mix(in srgb, var(--skill-accent) 14%, var(--surface-box));
  color: var(--skill-ink);
  box-shadow: var(--shadow-xs);
}

/* The design system draws this exact composition - a glyph in a 40px rounded
   square - at 18px on --icon-stroke-18, which paints a 1.5px line. .sp-icon--sm
   is 20px on --icon-stroke-20 and paints 1.75px, which is what made these read
   dense. Same treatment, same numbers. */
:is(.page--portal-skills, .page--portal-game-detail, .page--portal-playbook-detail) .skill-card__icon .sp-icon {
  width: var(--space-18);
  height: var(--space-18);
  flex-basis: auto;
  --icon-stroke: var(--icon-stroke-18);
}

/* Mood, Cognition or Personality, on the same line as the mark. Named in
   full and capitalised, every time - it is the dimension's name, not a slug. */
:is(.page--portal-skills, .page--portal-game-detail, .page--portal-playbook-detail) .skill-card__pillar {
  --badge-accent: var(--skill-accent);
  margin-inline-start: auto;
  font-family: var(--font-mono);
  font-size: var(--font-size-xs);
  font-weight: var(--weight-semibold);
}

:is(.page--portal-skills, .page--portal-game-detail, .page--portal-playbook-detail) .skill-card__copy {
  display: grid;
  gap: var(--space-4);
}

:is(.page--portal-skills, .page--portal-game-detail, .page--portal-playbook-detail) .skill-card__desc {
  font-size: var(--font-size-sm);
  line-height: var(--line-height-md);
}

.page--portal-skills .skill-card__games {
  display: grid;
  gap: var(--space-8);
  align-content: start;
}

.page--portal-skills .skill-card__game-list {
  list-style: none;
  padding: 0;
}

/* One game. It goes somewhere, so it is a link; the peek that opens under it
   is a preview of where, not a second control. */
.page--portal-skills .skill-game {
  min-height: var(--space-24);
  padding: var(--space-2) var(--space-10);
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-full);
  background: var(--surface-raised);
  color: var(--surface-text);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-sm);
  font-weight: var(--weight-medium);
  text-decoration: none;
  cursor: pointer;
  transition: var(--transition-interactive);
}

.page--portal-skills .skill-game:hover,
.page--portal-skills .skill-game.is-peeking {
  /* The pill is small and its hover has to be readable at a glance, so the
     outline is the ink at full strength rather than a mix - 72% left the blue
     end of the ramp at 2.9:1 on the light ground. */
  border-color: var(--skill-ink);
  background: color-mix(in srgb, var(--skill-accent) 14%, var(--surface-box));
  color: var(--skill-ink);
}

.page--portal-skills .skill-game:focus-visible {
  outline: 0;
  border-color: color-mix(in srgb, var(--skill-accent) 58%, transparent);
  box-shadow:
    0 0 0 var(--focus-ring-width) var(--focus-inner),
    0 0 0 calc(var(--focus-ring-width) * 2) var(--focus-default);
}

/* --------------------------------------------------------------------------
   The card's one action

   The card ends in the shared .sp-progress-button: one full-width object that
   is both the reading and the way in. The page adds nothing to it - the
   component carries its own two-tone, its own fill and its own value - which
   is the whole reason it was made shared rather than written here.

   The skills page deliberately does NOT retint it to the dimension's colour.
   Every other card on this page is already colour-coded by dimension through
   the mark, the pill and the game pills; the action is the one element that
   means the same thing on all 28, and a violet that shifts per section reads
   as a fourth piece of data rather than as one button.
   -------------------------------------------------------------------------- */

.page--portal-skills .skill-feature__all {
  color: var(--skill-ink);
  text-decoration: none;
}

.page--portal-skills .skill-feature__all:hover {
  text-decoration: underline;
  text-underline-offset: 4px;
}


/* --------------------------------------------------------------------------
   The game peek

   One element, moved and refilled by the script rather than 84 copies in the
   markup, and positioned from the trigger's own rectangle - which is why it
   is fixed rather than absolute: a card in a scrolling column is not a
   containing block the peek could be measured against. It never takes the
   pointer, so hovering it cannot steal focus from the link that opened it.
   -------------------------------------------------------------------------- */

/* The peek is a portal component, not a Skills-page one: its module became
   hook-driven when Home's Play button wanted the same preview, and these rules
   had to follow it. Left scoped to .page--portal-skills the card still built
   on Home and still filled with the right game - it just had no positioning,
   so it laid out in the flow at the foot of the page, 1425px wide. A component
   that moves has to bring its stylesheet with it. */
.page--portal .game-peek {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 70;
  width: 264px;
  max-width: calc(100vw - var(--space-32));
  padding: var(--space-12);
  display: grid;
  gap: var(--space-10);
  border: 1px solid var(--surface-border-hover);
  border-radius: var(--radius-compact);
  background: var(--surface-box);
  box-shadow: var(--shadow-lg);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transform: translateY(var(--space-4));
  transition:
    opacity var(--duration-fast) var(--ease-standard),
    transform var(--duration-fast) var(--ease-standard),
    visibility var(--duration-fast) var(--ease-standard);
}

/* Only the shown card takes the pointer. Hidden it must stay transparent to
   clicks, or a fixed 264px panel would sit over the page catching them. */
.page--portal .game-peek.is-visible {
  opacity: 1;
  visibility: visible;
  transform: none;
  pointer-events: auto;
}

/* The pointer has to cross the 10px gap between the game and the card without
   falling through to the page. The card carries that gap as its own hit area,
   drawn on whichever side it opened. */
.page--portal .game-peek::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: var(--space-12);
}

.page--portal .game-peek[data-side="below"]::before { bottom: 100%; }
.page--portal .game-peek[data-side="above"]::before { top: 100%; }

.page--portal .game-peek__head {
  display: flex;
  align-items: center;
  gap: var(--space-10);
}

.page--portal .game-peek__art {
  width: var(--space-40);
  height: var(--space-40);
  flex: 0 0 var(--space-40);
  display: grid;
  place-items: center;
  overflow: hidden;
  border: 1px solid color-mix(in srgb, var(--skill-ink) 40%, transparent);
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--skill-accent) 10%, var(--surface-raised));
}

.page--portal .game-peek__art img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
}

.page--portal .game-peek__title {
  margin: 0;
  color: var(--surface-text);
  font-size: var(--font-size-md);
  line-height: var(--line-height-md);
  font-weight: var(--weight-semibold);
}

.page--portal .game-peek__summary {
  margin: 0;
  color: var(--ui-muted);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-md);
}

.page--portal .game-peek__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-10);
  padding-top: var(--space-10);
  border-top: 1px solid var(--surface-border);
  color: var(--ui-muted);
  font-size: var(--font-size-xs);
  line-height: var(--line-height-sm);
  font-weight: var(--weight-semibold);
}

.page--portal .game-peek__meta {
  display: inline-flex;
  align-items: center;
  gap: var(--space-6);
}

/* A skill peek has no running time and no genre, and the module already sets
   `hidden` on this row for that case - but an author `display` outranks the
   user-agent's `[hidden] { display: none }`, so the row kept its box and drew
   whatever was inside it. That is how a lone clock ended up hanging in the
   foot of every skill preview on the game page. */
.page--portal .game-peek__meta[hidden] {
  display: none;
}

.page--portal .game-peek__cue {
  display: inline-flex;
  align-items: center;
  gap: var(--space-4);
  color: var(--skill-ink);
  text-decoration: none;
  cursor: pointer;
}

.page--portal .game-peek__cue:hover,
.page--portal .game-peek__cue:focus-visible {
  text-decoration: underline;
  text-underline-offset: var(--space-3);
}

.page--portal .game-peek__cue:hover .sp-icon {
  transform: translateX(var(--space-2));
}

.page--portal .game-peek__cue .sp-icon {
  --icon-size: var(--icon-size-xs);
  --icon-stroke: var(--icon-stroke-xs);
  transition: transform var(--duration-fast) var(--ease-standard);
}

@media (prefers-reduced-motion: reduce) {
  .page--portal .game-peek__cue .sp-icon { transition: none; }
  .page--portal .game-peek__cue:hover .sp-icon { transform: none; }
}


/* --------------------------------------------------------------------------
   The rail · this week's reading
   -------------------------------------------------------------------------- */

.page--portal-skills .skill-panel__head {
  display: grid;
  gap: var(--space-2);
}

.page--portal-skills .skill-panel__week {
  font-family: var(--font-mono);
}

/* One metric per row. Three of them side by side in a 320px rail gave each a
   90px column, which broke every label onto two lines and left the numbers
   too small to be the thing the panel is for. Stacked, the label and its note
   take the width they need and the reading sits at the end of the row at a
   size worth reading. */
.page--portal-skills .skill-panel__metrics {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-8);
}

.page--portal-skills .metric--stacked {
  padding: var(--space-12) var(--space-14);
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  column-gap: var(--space-12);
  border-radius: var(--radius-compact);
  background: var(--surface-box);
}

.page--portal-skills .metric--stacked .metric__label {
  grid-column: 1;
  grid-row: 1;
}

.page--portal-skills .metric--stacked .metric__note {
  grid-column: 1;
  grid-row: 2;
  margin-top: var(--space-2);
  color: var(--skill-highlight);
}

.page--portal-skills .metric--stacked .metric__value {
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: center;
  margin: 0;
  font: var(--weight-semibold) var(--text-xl-font-size) / var(--text-xl-line-height) var(--font-mono);
}

.page--portal-skills .skill-panel__meters {
  display: grid;
  gap: var(--space-14);
  margin-top: var(--space-16);
  padding-top: var(--space-16);
  border-top: 1px solid var(--surface-border);
}

.page--portal-skills .skill-panel__label {
  margin-bottom: var(--space-2);
}

.page--portal-skills .skill-meter {
  display: grid;
  gap: var(--space-6);
}

/* The three readings are the three dimensions, so each one is the only place
   on this panel that is entitled to a dimension colour. */
/* 2.47:1 on the light ground as the mid step. It is a 20px glyph, not a fill. */
.page--portal-skills .skill-meter__icon { color: var(--skill-ink); }

.page--portal-skills .skill-meter__pillar {
  color: var(--skill-ink);
  font-family: var(--font-mono);
}

.page--portal-skills .skill-meter__bar {
  --progress-colour: var(--skill-accent);
}

/* The fill width is a token, not an inline style: one class per sample value
   keeps the prototype's fixed demo data out of the markup. */
.page--portal-skills .skill-meter__bar--78 { --progress: 78%; }
.page--portal-skills .skill-meter__bar--74 { --progress: 74%; }
.page--portal-skills .skill-meter__bar--66 { --progress: 66%; }

.page--portal-skills .skill-panel__streak {
  margin-top: var(--space-16);
  padding-top: var(--space-16);
}

.page--portal-skills .skill-day {
  width: var(--space-24);
  height: var(--space-24);
  border-radius: var(--radius-full);
  background: var(--surface-box);
  font: var(--weight-semibold) var(--font-size-xs) / 1 var(--font-mono);
}

/* A streak belongs to no dimension, so it takes the page's general emphasis. */
.page--portal-skills .skill-day.is-done {
  border-color: transparent;
  background: var(--skill-highlight);
  color: var(--skill-on-accent);
}


/* --------------------------------------------------------------------------
   The rail · My Goal

   The panel used to run head, chips and note at three different rhythms
   inside a card that already had its own padding, so the Edit control sat
   above the copy it edited and the chips touched the note below them. One
   grid, one gap, and the control aligned to the heading it belongs to.
   -------------------------------------------------------------------------- */

.page--portal-skills .skill-goal {
  gap: var(--space-12);
}

.page--portal-skills .skill-goal__head {
  gap: var(--space-12);
}

.page--portal-skills .skill-goal__eyebrow {
  margin-bottom: var(--space-2);
}

.page--portal-skills .skill-goal__copy {
  max-width: var(--measure-sm);
}

.page--portal-skills .skill-goal__chips {
  list-style: none;
  padding: 0;
  margin-top: var(--space-2);
}

.page--portal-skills .skill-goal__chips .ui-badge[data-dimension] {
  --badge-accent: var(--skill-accent);
}

.page--portal-skills .skill-goal__chips .ui-badge[data-dimension] .ui-badge__icon {
  color: var(--skill-ink);
}

.page--portal-skills .skill-goal__add {
  --badge-accent: var(--surface-border-hover);
  border-style: dashed;
  color: var(--ui-muted);
}

.page--portal-skills .skill-goal__note {
  display: flex;
  align-items: flex-start;
  gap: var(--space-8);
  padding-top: var(--space-12);
}

.page--portal-skills .skill-goal__note .sp-icon {
  flex: 0 0 auto;
  margin-top: var(--space-2);
  color: var(--skill-highlight);
}


/* --------------------------------------------------------------------------
   Responsive
   -------------------------------------------------------------------------- */

/* The breakpoints are the shell's own, not a second set. Between 1024 and
   1180 the rail narrows but stays, and what is left of the column cannot
   carry three of anything; at 1023 the rail drops below the column and the
   full width comes back, so three returns until the screen itself is too
   narrow for it. */
@media (max-width: 1180px) {
  :is(.page--portal-skills, .page--portal-game-detail, .page--portal-playbook-detail) .portal-content .skill-grid,
  .page--portal-skills .skill-feature__strip {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* There was a third step here that put the grid back to three tracks below
   1023px, and a fourth at 780px returning it to two. 1023px is where
   .portal-layout drops its rail below the content, handing the column its
   256px back - main goes from 405px to 665px across that one pixel - so the
   three-track step was spending the reclaimed width rather than a mistake in
   itself: cards came out at 214px against 197px just above the breakpoint.

   It still went the wrong way to read. The count ran 3 - 2 - 3 - 2 - 1 as the
   window narrowed, so a narrower tablet showed more columns than a wider one,
   and the ladder only made sense if you already knew the rail collapsed
   underneath it. The count now falls monotonically and the reclaimed width
   goes into the cards instead. Keying on the viewport is deliberate: the
   container genuinely is wider below 1023px, so anything that measures the
   container - auto-fill included - would put the columns back. */

@media (max-width: 560px) {
  :is(.page--portal-skills, .page--portal-game-detail, .page--portal-playbook-detail) .portal-content .skill-grid,
  :is(.page--portal-game-detail, .page--portal-playbook-detail) .skill-grid:has(> :last-child:nth-child(4)),
  .page--portal-skills .skill-feature__strip {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* The peek is a hover affordance; a coarse pointer has no hover, and the link
   underneath already goes to the same place. */
@media (hover: none) {
  .page--portal .game-peek { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .page--portal .game-peek {
    transition: none;
    transform: none;
  }
}


/* ==========================================================================
   09 - Unique composition · Portal Game Detail
   ========================================================================== */

/* ---------------------------------------------------------------------------
   The portal's /game/[slug]/ route, drawn for Hextris.

   The library page owns the game-card composition; a detail page needs a
   different set of parts, so everything here is prefixed gd- and scoped to the
   page. Shared components carry the work wherever one exists: sp-card and
   sp-panel for surfaces, ui-badge for pills, sp-icon-frame for glyph tiles,
   sp-progress for the meters, .metric--portal for the record row, and the
   button family for every control. The tone utility supplies --tone per block
   so accents stay token-driven in both surfaces.
   -------------------------------------------------------------------------- */

:is(.page--portal-game-detail, .page--portal-playbook-detail) {
  --gd-tone: var(--tone, var(--personality-mint-500));

  /* Tone reads as a tint on the dark surface but washes out as type on the
     light one, so text takes an ink derived from the same tone. */
  --gd-ink: var(--gd-tone);
}

html:is([data-surface="light"], [data-theme="light"]) :is(.page--portal-game-detail, .page--portal-playbook-detail) {
  --gd-ink: color-mix(in srgb, var(--gd-tone) 42%, var(--ui-text));
}

/* Custom properties resolve where they are declared, so a block carrying its
   own .tone-- modifier has to restate the pair or every tile inherits the
   page-level mint. */
:is(.page--portal-game-detail, .page--portal-playbook-detail) .tone {
  --gd-tone: var(--tone, var(--personality-mint-500));
  --gd-ink: var(--gd-tone);
}

html:is([data-surface="light"], [data-theme="light"]) :is(.page--portal-game-detail, .page--portal-playbook-detail) .tone {
  --gd-ink: color-mix(in srgb, var(--gd-tone) 42%, var(--ui-text));
}

:is(.page--portal-game-detail, .page--portal-playbook-detail) h1,
:is(.page--portal-game-detail, .page--portal-playbook-detail) h2,
:is(.page--portal-game-detail, .page--portal-playbook-detail) h3 {
  color: var(--ui-text);
}


/* ---------------------------------------------------------------------------
   00 · Portal chrome

   The portal header markup is shared, but its sizing lives under the library
   page's scope, so without this the lockup draws at intrinsic width and the
   mobile route strip never hides. Same declarations
   -------------------------------------------------------------------------- */
:is(.page--portal-game-detail, .page--portal-playbook-detail) .brand-logo {
  display: block;
  width: 142px;
  height: auto;
  overflow: visible;
  stroke: none;
}


/* ---------------------------------------------------------------------------
   02 · Hero — artwork left
   -------------------------------------------------------------------------- */
:is(.page--portal-game-detail, .page--portal-playbook-detail) .gd-hero__grid {
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  gap: var(--space-48);
  align-items: center;
}

.page--portal-game-detail .gd-art {
  aspect-ratio: 380 / 210;
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-panel);
  background: color-mix(in srgb, var(--gd-tone) 10%, var(--surface-box));
  box-shadow: var(--shadow-lg);
}

.page--portal-game-detail .gd-art__layer {
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: opacity var(--duration-slow) var(--ease-standard);
}

.page--portal-game-detail .gd-art__layer--animated {
  opacity: 0;
}

.page--portal-game-detail .gd-art__flag {
  --badge-accent: var(--core-green-500);
  top: var(--space-16);
  right: var(--space-16);
  letter-spacing: 0.08em;
  font-weight: var(--weight-semibold);
}

/* One gap, not four margins. This block was the last one on the page still
   spacing its children with a margin each - .gd-blurb carried 12px above and
   20px below, .gd-target 16px above - where .portal-section, .rail-card,
   .skill-feature and .gd-panel are all a grid with a single gap. It matters
   more now that the block ends in the page's primary control: with margins,
   the space above Play was whatever the element before it happened to declare.
   16px is .gd-panel's own gap, so the hero's rhythm and the panels below it
   are the same number. */
:is(.page--portal-game-detail, .page--portal-playbook-detail) .gd-hero__copy {
  display: grid;
  align-content: center;
  justify-items: start;
  gap: var(--space-16);
}

:is(.page--portal-game-detail, .page--portal-playbook-detail) .gd-blurb {
  max-width: var(--measure-sm);
  margin: 0;
  line-height: var(--line-height-base);
}

:is(.page--portal-game-detail, .page--portal-playbook-detail) .gd-facts {
  list-style: none;
}

:is(.page--portal-game-detail, .page--portal-playbook-detail) .gd-fact {
  padding: var(--space-6) var(--space-12);
  border-radius: var(--radius-pill);
  background: var(--surface-box);
}

:is(.page--portal-game-detail, .page--portal-playbook-detail) .gd-target {
  margin-top: 0;
}

/* Play sits a step further from the facts than the facts sit from each other,
   so the eye reads "here is the game, here is what it reads, now start" rather
   than finding a fourth fact that happens to be a button. --space-8 on top of
   the block's own 16px gap. */
:is(.page--portal-game-detail, .page--portal-playbook-detail) .gd-actions {
  margin-top: var(--space-8);
}


:is(.page--portal-game-detail, .page--portal-playbook-detail) .gd-panel {
  --card-current-padding: var(--space-24);
  --card-current-radius: var(--radius-card);
  display: grid;
  gap: var(--spacing-xl);
  align-content: start;
  border-color: color-mix(in srgb, var(--gd-tone) 26%, var(--surface-border));
}


/* --- How to play · the controls ------------------------------------------
   Two sets side by side, because the question "how do I play this" has two
   answers and which one applies is the device, not the game. They collapse to
   one column at the same step the panel's own reading column does.

   The panel above already carries the rule as prose. This is deliberately not
   prose: an input and what it does is a two-column relation, and setting it as
   a sentence is what made the old one-line version unable to say it at all. */
.page--portal-game-detail .gd-controls {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-20);
}

.page--portal-game-detail .gd-controls__set {
  display: grid;
  gap: var(--space-10);
  align-content: start;
}

/* The set label is a label, not a heading in the page's voice - it names a
   device, and it sits under an h2 that already named the section. Stated here
   rather than left to `.font-xs`, because a utility on a heading loses to
   `.page :where(h3)` on source order. */
.page--portal-game-detail .gd-controls__title {
  margin: 0;
  color: var(--ui-muted);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.page--portal-game-detail .gd-controls__list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--space-8);
}

/* Input left, meaning right, on one baseline. `start` rather than `center`
   because the description wraps to two lines on a narrow column and a centred
   key would then float against a two-line sentence. */
.page--portal-game-detail .gd-control {
  display: flex;
  align-items: start;
  gap: var(--space-10);
}

.page--portal-game-detail .gd-control__input {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  flex: 0 0 auto;
}

.page--portal-game-detail .gd-control__does {
  color: var(--ui-muted);
  font-size: var(--font-size-md);
  line-height: var(--line-height-md);
}

/* A key is a physical thing: it has a top face and an edge under it. The inset
   highlight and the hard bottom border are the whole trick, and they are the
   reason this reads as a key rather than as a tag. */
.page--portal-game-detail .gd-key {
  min-width: var(--space-24);
  padding: var(--space-2) var(--space-6);
  border: 1px solid var(--surface-border-hover);
  border-bottom-width: 2px;
  border-radius: var(--radius-sm);
  background: var(--surface-box);
  color: var(--surface-text);
  font-family: var(--skillprint-font-mono);
  font-size: var(--text-xs-font-size);
  line-height: var(--space-20);
  font-weight: var(--weight-semibold);
  text-align: center;
  white-space: nowrap;
}

/* A gesture is not a key and must not be dressed as one - there is no object
   to press. It takes the pill the rest of the system gives to a named state,
   in the game's own tone so the two columns read as a pair. */
.page--portal-game-detail .gd-gesture {
  padding: var(--space-2) var(--space-10);
  border: 1px solid color-mix(in srgb, var(--gd-tone) 38%, transparent);
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--gd-tone) 14%, transparent);
  color: var(--surface-text);
  font-size: var(--text-xs-font-size);
  line-height: var(--space-20);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
}

@media (max-width: 640px) {
  .page--portal-game-detail .gd-controls {
    grid-template-columns: minmax(0, 1fr);
  }
}


/* ---------------------------------------------------------------------------
   04 · Adapts to You — the two live parameters this game moves
   -------------------------------------------------------------------------- */

.page--portal-game-detail .gd-adapt__panel {
  display: grid;
  gap: var(--spacing-3xl);
  padding: var(--space-32);
  border-color: color-mix(in srgb, var(--gd-tone) 30%, var(--surface-border));
}


.page--portal-game-detail .gd-adapt__copy {
  max-width: var(--measure-md);
}


/* The flow meter. A flow score is read across all three dimensions, so it must
   not wear any one of their colours. It followed --gd-tone, which falls back to
   --personality-mint-500 - the exact value [data-dimension="cognition"] uses -
   so the one figure on the page that belongs to no category was drawn in
   Cognition's mint, beside a Skills section whose cognition cards are drawn in
   the same mint. --skill-highlight is the token this sheet already defines for
   precisely that case: "Everything that is *not* one of the three dimensions
   and still needs emphasis takes --skill-highlight: Skillprint Green." It is
   also the reasoning already applied to .rail-meter, whose default was made a
   plain Core Green after the same fault - a meter reporting all three while
   wearing one dimension's colour.

   The --52/--72/--74 modifiers that used to sit here are gone. Their comment
   claimed "--progress cannot come from a style attribute", but the card went
   data-driven and sets --progress inline from record.last; nothing had asked
   for a modifier by name in a long time. */
.page--portal-game-detail .gd-meter {
  --progress-colour: var(--skill-highlight);
}


.page--portal-game-detail .metric--portal {
  display: grid;
}


/* ---------------------------------------------------------------------------
   Skills · one section, one row per taxonomy pillar
   Was two sibling cards headed "SKILLS DEVELOPED" and "MOODS" in tracked upper
   case. The portal spends that treatment on eyebrows and meta labels, never on
   a heading - those two were the only all-caps card titles on any portal
   screen. One section now, headed by the house .portal-section__title, holding
   a row per pillar in the Mood / Cognition / Personality order Portal_Skills
   sections the taxonomy in.
   -------------------------------------------------------------------------- */


/* ---------------------------------------------------------------------------
   08 · Game Detail · the play control
   The sticky bar this section used to carry is gone: Play now sits in the page
   head beside the way back, which is where Skill Detail puts it, so the same
   control was being drawn twice. What is left is the button's own busy state.
   -------------------------------------------------------------------------- */

.page--portal-game-detail .gd-play[aria-busy="true"] {
  pointer-events: none;
  opacity: 0.72;
}


/* ---------------------------------------------------------------------------
   Responsive — the two-column blocks stack, the rail keeps scrolling
   -------------------------------------------------------------------------- */

@media (max-width: 1023px) {
  :is(.page--portal-game-detail, .page--portal-playbook-detail) .gd-hero__grid {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-32);
  }
}

@media (max-width: 767px) {
  .page--portal-game-detail .gd-adapt__panel {
    padding: var(--space-20);
  }
}

@media (prefers-reduced-motion: reduce) {
  .page--portal-game-detail .gd-art__layer {
    transition: none;
  }
}


/* ==========================================================================
   09 - Unique composition · Portal Playbook Detail

   A playbook is a short, ordered set of games with one job, and this is the
   page behind How to play on the playbook card. It is Game Detail's page,
   not a lookalike: the hero grid, the copy column, the fact pills, the action
   row and the panel are the same rules, widened above to cover both screens,
   so a reader moving between a game and the playbook that contains it sees one
   layout. Only the two things a playbook has and a game does not are new - the
   artwork, and the sequence.

   THE ARTWORK
   A game's hero is its own picture. A playbook has no picture, it has a family
   and a mark, so its hero is the card's own ground with the card's own mark
   standing in the middle of it, drawn large. The values come from the same
   --pb-500 / --pb-ink pair the card reads, generalised onto .pb-tone above, so
   the page and the card cannot end up different pinks.

   NAMING IT
   The badge on the artwork reads Playbook, in the ink rather than in a status
   colour: it is what this thing is, not how it is doing. It is the one label on
   the page whose job is recognition.
   ========================================================================== */

.page--portal-playbook-detail .pb-art {
  aspect-ratio: 380 / 210;
  display: grid;
  place-items: center;
  border: 1px solid color-mix(in srgb, var(--pb-ink) 26%, transparent);
  border-radius: var(--radius-panel);
  background: var(--pb-500);
  color: var(--pb-ink);
  box-shadow: var(--shadow-lg);
}

/* Big, centred, and the only thing on the ground. On the card the same mark is
   a watermark in the corner at 48px; here it is the subject, so it takes a
   share of the panel rather than a fixed size and stays a mark at every width
   the hero column is given. */
.page--portal-playbook-detail .pb-art__mark {
  width: 42%;
  max-width: var(--space-224);
  height: auto;
}

.page--portal-playbook-detail .pb-art__flag {
  top: var(--space-16);
  right: var(--space-16);
  border: 1px solid color-mix(in srgb, var(--pb-ink) 32%, transparent);
  background: color-mix(in srgb, var(--pb-ink) 12%, transparent);
  color: var(--pb-ink);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: var(--weight-semibold);
}

/* The eyebrow says the same word as the badge, for the reader who arrived at
   the copy column first. It carries the family colour, which is the only place
   on this page outside the artwork that does. */
.page--portal-playbook-detail .pb-eyebrow {
  margin: 0;
  color: var(--gd-ink);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* --- the sequence ---
   The one section a game detail has no equivalent of. Three of the shared game
   cards in the order they are meant to be played, each behind its own step
   number - the order IS the playbook, so it is stated as a number rather than
   left to reading direction, which stops being an order the moment the grid
   wraps to one column. */
.page--portal-playbook-detail .pb-sequence {
  display: grid;
  gap: var(--space-24);
  margin: 0;
  padding: 0;
  list-style: none;
  /* auto-fit, not auto-fill. auto-fill keeps the empty tracks it made room for,
     so three steps in a container wide enough for five sat at 232px each with
     two phantom columns of dead ground to their right - the set read as three
     cards that had failed to fill the row rather than as the whole sequence.
     auto-fit collapses the empty tracks and the 1fr maximum then hands their
     width back to the real steps, which is how .skill-grid below fills its own
     row. Still a range rather than a hard repeat(3): a playbook is three games
     today, and a count written into the stylesheet is the kind of thing that
     silently breaks the day it is four. */
  grid-template-columns: repeat(auto-fit, minmax(232px, 1fr));
}

.page--portal-playbook-detail .pb-step {
  display: grid;
  gap: var(--space-12);
  align-content: start;
  min-width: 0;
}

.page--portal-playbook-detail .pb-step__head {
  gap: var(--space-10);
}

/* The number is the step's own mark: a filled disc when the game has been
   played and an outline when it has not, which is the same fill-against-outline
   the card's pips use to say the same thing. */
.page--portal-playbook-detail .pb-step__index {
  flex: none;
  display: grid;
  place-items: center;
  width: var(--space-24);
  height: var(--space-24);
  border: 1px solid color-mix(in srgb, var(--ui-text) 40%, transparent);
  border-radius: var(--radius-full);
  color: var(--ui-muted);
  font-size: var(--text-xs-font-size);
  line-height: 1;
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

.page--portal-playbook-detail .pb-step--done .pb-step__index {
  border-color: var(--core-green-500);
  background: var(--core-green-500);
  color: var(--deep-navy-950);
}

.page--portal-playbook-detail .pb-step__label {
  min-width: 0;
  color: var(--ui-muted);
}

/* --- the progress card ---
   The rail's card, in the rail's own geometry. It answers the one question the
   card on Games asks and cannot fit: which of the three is left. */
.page--portal .pb-progress__count {
  margin: 0;
  font-variant-numeric: tabular-nums;
}

.page--portal .pb-progress__pips {
  gap: var(--space-6);
  margin: var(--space-12) 0 0;
  padding: 0;
  list-style: none;
}

.page--portal .pb-progress__pip {
  flex: 1;
  height: var(--space-6);
  border: 1px solid color-mix(in srgb, var(--ui-text) 40%, transparent);
  border-radius: var(--radius-full);
}

.page--portal .pb-progress__pip--played {
  border-color: var(--core-green-500);
  background: var(--core-green-500);
}

.page--portal .pb-progress__next {
  margin: var(--space-16) 0 0;
}

@media (max-width: 1023px) {
  .page--portal-playbook-detail .pb-art {
    aspect-ratio: 16 / 9;
  }
}


/* ==========================================================================
   09 - Unique composition · Portal Pre-Game Interstitial

   The screen between choosing a game and playing it. One centred column,
   one job: say what the session targets, show the calibration running and
   hand over to Play. Everything structural is borrowed - .sp-card, .sp-panel,
   .sp-progress, .ui-badge, .sp-icon-frame and the shared adjustment banner.
   What is written here is only the column, the target pair, the step list and
   the two placements the borrowed pieces need on this route.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Nav chrome

   A launch screen is not a browse screen. The route links go, the brand and
   the account controls stay, and .sp-nav__actions keeps its auto start-margin
   so the cluster still sits at the far end of the bar.
   -------------------------------------------------------------------------- */
.page--portal-game-interstitial .sp-nav__routes {
  display: none;
}

.page--portal-game-interstitial .brand-logo {
  display: block;
  width: var(--space-144);
  height: auto;
  overflow: visible;
  stroke: none;
}

/* --------------------------------------------------------------------------
   The column
   -------------------------------------------------------------------------- */
/* The top inset is the banner's: the calibration notice is fixed below the
   nav, so the column starts underneath it rather than behind it. */
.page--portal-game-interstitial .interstitial {
  max-width: var(--space-640);
  padding-block: var(--space-96) var(--space-96);
  display: grid;
  gap: var(--space-24);
}

.page--portal-game-interstitial .interstitial-art {
  width: var(--space-160);
  height: var(--space-160);
  border: 1px solid color-mix(in srgb, var(--mindset-violet-500) 26%, transparent);
  background: color-mix(in srgb, var(--mindset-violet-500) 10%, transparent);
}

.page--portal-game-interstitial .interstitial-art__img {
  width: var(--space-112);
  height: auto;
  max-width: 100%;
  border-radius: var(--radius-md);
}

/* .eyebrow is width: max-content
   -------------------------------------------------------------------------- */
.page--portal-game-interstitial .interstitial-facts {
  margin-top: var(--space-4);
}

.page--portal-game-interstitial .interstitial-fact dt {
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   Blocks

   Every card below the identity carries the same head-then-body rhythm.
   -------------------------------------------------------------------------- */
.page--portal-game-interstitial .interstitial-block {
  display: grid;
  gap: var(--space-12);
}

/* `.page :where(h2)` sets a fluid display size; a card head is not a display
   heading, so each block title takes the body scale back. */
.page--portal-game-interstitial .interstitial-block__title,
.page--portal-game-interstitial .interstitial-loading__title {
  color: var(--surface-text);
  font-size: var(--text-md-font-size);
  line-height: var(--text-md-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.01em;
}

/* --------------------------------------------------------------------------
   What the session targets

   A mood and a skill, read as a pair. Each card takes its accent from the
   pillar it belongs to: mood surfaces lean violet, cognition leans mint.
   -------------------------------------------------------------------------- */
.page--portal-game-interstitial .interstitial-targets {
  gap: var(--space-12);
}

.page--portal-game-interstitial .interstitial-target {
  --target-accent: var(--mindset-violet-500);

  display: grid;
  align-content: start;
  gap: var(--space-8);
  border-color: color-mix(in srgb, var(--target-accent) 30%, var(--surface-border));
  background: color-mix(in srgb, var(--target-accent) 6%, var(--surface-box));
}

.page--portal-game-interstitial .interstitial-target--skill {
  --target-accent: var(--personality-mint-500);
}

.page--portal-game-interstitial .interstitial-target .sp-icon-frame {
  border-color: color-mix(in srgb, var(--target-accent) 42%, transparent);
  background: color-mix(in srgb, var(--target-accent) 14%, var(--surface-box));
}

.page--portal-game-interstitial .interstitial-target__label {
  margin-top: var(--space-4);
}

.page--portal-game-interstitial .interstitial-target__name {
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.01em;
}

@media (max-width: 560px) {
  .page--portal-game-interstitial .interstitial-targets {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* --------------------------------------------------------------------------
   Calibration

   The loading state, the progress the engine reports and the four things it
   does before the game can start. The bar is .sp-progress; only its starting
   value and colour are set here, and the page script moves --progress from
   the same custom property.
   -------------------------------------------------------------------------- */
.page--portal-game-interstitial .interstitial-loading {
  display: grid;
  gap: var(--space-16);
}

.page--portal-game-interstitial .interstitial-spinner {
  width: var(--space-40);
  height: var(--space-40);
  border-radius: var(--radius-full);
  border: 1px solid color-mix(in srgb, var(--mindset-violet-500) 38%, transparent);
  background: color-mix(in srgb, var(--mindset-violet-500) 12%, transparent);
  color: var(--violet);
}

.page--portal-game-interstitial .interstitial-spinner svg {
  width: var(--space-20);
  height: var(--space-20);
  animation: interstitial-spin var(--duration-slowest) linear infinite;
}

@keyframes interstitial-spin {
  to { transform: rotate(360deg); }
}

/* When the run reaches 100% the spinner stops and takes the product's green,
   so "finished" is legible without reading the percentage. */
.page--portal-game-interstitial .interstitial[data-state="ready"] .interstitial-spinner {
  border-color: color-mix(in srgb, var(--core-green-500) 46%, transparent);
  background: color-mix(in srgb, var(--core-green-500) 14%, transparent);
  color: var(--core-green-500);
}

.page--portal-game-interstitial .interstitial[data-state="ready"] .interstitial-spinner svg {
  animation: none;
}

.page--portal-game-interstitial .interstitial-loading__percent {
  margin-inline-start: auto;
  color: var(--violet);
  font-family: var(--skillprint-font-mono);
  font-variant-numeric: tabular-nums;
}

.page--portal-game-interstitial .interstitial-progress {
  --progress: 68%;
  --progress-colour: var(--violet);
}

.page--portal-game-interstitial .interstitial-steps {
  display: grid;
  gap: var(--space-8);
  list-style: none;
}

.page--portal-game-interstitial .interstitial-step {
  display: flex;
  align-items: center;
  gap: var(--space-10);
  color: var(--surface-text-muted);
}

.page--portal-game-interstitial .interstitial-step__mark {
  width: var(--space-20);
  height: var(--space-20);
  flex: 0 0 var(--space-20);
  border: 1px solid var(--surface-border);
  background: var(--surface-raised);
  color: transparent;
}

.page--portal-game-interstitial .interstitial-step__mark svg {
  width: var(--space-12);
  height: var(--space-12);
}

.page--portal-game-interstitial .interstitial-step.is-done {
  color: var(--surface-text);
}

.page--portal-game-interstitial .interstitial-step.is-done .interstitial-step__mark {
  border-color: transparent;
  background: var(--core-green-500);
  color: var(--deep-navy-900);
}

.page--portal-game-interstitial .interstitial-step.is-active {
  color: var(--surface-text);
  font-weight: var(--weight-semibold);
}

.page--portal-game-interstitial .interstitial-step.is-active .interstitial-step__mark {
  border-color: var(--mindset-violet-500);
  background: color-mix(in srgb, var(--mindset-violet-500) 22%, transparent);
  animation: interstitial-step-pulse var(--duration-slowest) var(--ease-standard) infinite alternate;
}

@keyframes interstitial-step-pulse {
  to { box-shadow: 0 0 0 var(--space-4) color-mix(in srgb, var(--mindset-violet-500) 16%, transparent); }
}

.page--portal-game-interstitial .interstitial-actions {
  margin-top: var(--space-4);
}

.page--portal-game-interstitial .interstitial-play {
  min-width: var(--space-192);
}

/* --------------------------------------------------------------------------
   What adapts, what it develops, how it is played
   -------------------------------------------------------------------------- */
.page--portal-game-interstitial .interstitial-chip {
  --badge-accent: var(--violet);
}

.page--portal-game-interstitial .interstitial-skill-list {
  list-style: none;
}

.page--portal-game-interstitial .interstitial-skill {
  padding: var(--space-6) var(--space-12) var(--space-6) var(--space-6);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-full);
  background: var(--surface-raised);
}

/* --------------------------------------------------------------------------
   The shared adjustment banner on this route

   The component is fixed to the top of the viewport, which on a game canvas
   is empty space and here is the sticky nav. It clears the bar instead. It is
   also the one thing on this screen that is genuinely live, so unlike the
   in-game toast it stays put; only its parameter and value change.
   -------------------------------------------------------------------------- */
.page--portal-game-interstitial .sp-adjustment {
  top: calc(var(--space-64) + var(--space-16));
}

/* --------------------------------------------------------------------------
   Reduced motion

   The calibration is decoration on a static prototype; nothing here needs to
   move for the page to be readable, including the banner's own pulse.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .page--portal-game-interstitial .interstitial-spinner svg,
  .page--portal-game-interstitial .interstitial-step.is-active .interstitial-step__mark,
  .page--portal-game-interstitial .sp-adjustment.is-visible .sp-adjustment__icon {
    animation: none;
  }
}


/* ==========================================================================
   09 - Unique composition · Portal Profile

   The portal's /profile/ route: the player's own Skillprint, the trends
   chart, the cognitive orbit, the game-specific insight board, the skill
   breakdown, the session history, the goal cards and the settings block.

   Everything here is layout and paint for markup the shared components do
   not already cover. The charts are built by the page module in
   js/skillprint.js; that module writes geometry only, and every colour a
   mark carries is declared below off a token, so a theme change is a
   stylesheet concern rather than a redraw of hard-coded values.
   ========================================================================== */

/* The rank ramp the skill breakdown reads, declared on the page so it inherits
   to every [data-cognition-ramp] row. Same two ends as the skill progression
   page, so a skill that is second-strongest is the same colour on both
   screens. */
.page--portal-profile {
  --skills-ramp-from: var(--personality-blue-300);
  --skills-ramp-to: var(--personality-mint-300);
}

:is(.page--portal-profile, .page--portal-settings) .pp-section {
  padding-block: var(--spacing-6xl);
}

/* --------------------------------------------------------------------------
   Your Skillprint - one section, not four cards

   The drawing, the figures at a glance, how to read it and the six group
   strengths are four answers to one question, and they used to be four
   sp-cards stacked down the column with the print among them. Four containers
   in a row read as four unrelated modules; a reader had no way to tell that
   the middle one was the subject and the other three were its legend.

   They are one panel now. One head with the player and the two actions, then
   the print on the left at the size it needs to be explored and the reading
   beside it, then the provenance under both. Inside the panel nothing is a
   card: a subsection is a heading and a hairline, which is all the separation
   anything needs once it is already in the same container.

   The drawing is still the component from Ontology_Wheel.html, unrestyled and
   at full instrument - every term hoverable, the key band under the disc, the
   same PNG export. That is why this section takes the whole content width
   rather than sitting in the column beside the summary rail: split two ways
   inside the main column the print fell to about 370px, and 87 radiating terms
   at that size are unreadable. Full width, the left track holds the print at
   roughly the width it had when it was the only thing in the column.
   -------------------------------------------------------------------------- */

/* No max-width: the panel sits in the main column now, beside the summary
   rail, so the column is the constraint. */
.page--portal-profile .pp-print {
  padding: 0;
  overflow: hidden;
}

.page--portal-profile .pp-print__bar {
  padding: var(--spacing-2xl) var(--spacing-3xl);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-xl);
  border-block-end: 1px solid var(--surface-border);
}

.page--portal-profile .pp-print__who {
  display: block;
  font-size: var(--text-md-font-size);
  line-height: var(--text-md-line-height);
  font-weight: var(--weight-semibold);
}

.page--portal-profile .pp-print__caption {
  display: block;
  margin-block-start: var(--spacing-xs);
  color: var(--ui-muted);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
}

/* The print takes the larger track. A drawing sized to fit beside its own
   legend is a decoration of the legend. */
.page--portal-profile .pp-print__body {
  display: grid;
  grid-template-columns: minmax(0, 65fr) minmax(0, 35fr);
}

.page--portal-profile .pp-print__figure {
  min-width: 0;
  padding: var(--spacing-3xl);
  display: grid;
  gap: var(--spacing-xl);
  align-content: start;
  justify-items: center;
  border-inline-end: 1px solid var(--surface-border);
}

/* Capped, and centred in whatever track is left. Uncapped the drawing simply
   took the column: on a 1960px window it reached 883px and made the panel
   1062px tall, which pushed the reading beside it - the figures, how to read
   it, the group strengths - past the bottom of the viewport and left 330px of
   dead space under them. The point of putting them side by side was that you
   can see both at once, so the print stops growing at the size it needs to
   stay explorable and the section stays one screen. */
.page--portal-profile .pp-print__figure > .ontology-visual {
  width: 100%;
}


/* --- the print has to be readable --------------------------------------------
   The wheel on Profile was drawing 88 terms at 10.5px in --surface-text-muted,
   which is the ink for a caption, not for the only label a skill gets. At the
   size the figure column allows it read as texture rather than as words - the
   drawing was legible and the thing it was naming was not.

   Scoped to Profile, not to the wheel everywhere: Ontology_Wheel.html is a page
   whose whole surface is the drawing and its type is tuned against that width.
   This is the same drawing inside a 65% column of a card.

   Three changes, all on the label: one step up in size, one step up in weight,
   and off the muted ink. The geometry is untouched - no term is dropped and
   nothing is re-laid-out to make room, which is what the brief rules out. */
.page--portal-profile .ontology-root {
  --ontology-leaf-size: 12px;
}

.page--portal-profile .leaf-label {
  fill: color-mix(in srgb, var(--surface-text) 82%, transparent);
  font-weight: var(--weight-semibold);
}

/* 24px of padding around a drawing that is already capped inside its column
   was 48px off the width the wheel had to work with, on the one element in the
   card whose legibility is a function of its size. */
.page--portal-profile .pp-print__figure {
  padding: var(--space-16);
}


/* --- every skill stays named, on a phone too -------------------------------
   Below 900px the shared wheel hides its terms unless [data-terms="on"], and
   the reasoning above that rule is sound: 87 terms around a 375px ring is
   ~13px of arc each, so the whole disc and readable terms cannot both be had.
   Its answer was to keep the disc and put the terms behind a toggle.

   Profile takes the other side of that trade. This wheel is the reader's own
   print - the terms are the content, and a disc with no words on it says
   nothing about them - so the terms are on by default here and the disc
   scrolls sideways at the width they need to stay legible. The dedicated
   Ontology_Wheel page keeps its own default; this is scoped to Profile.

   Nothing is dropped and nothing is re-laid-out: same 87 terms, same geometry,
   at the size the shared rule already sized them for when opted in. */
@media (max-width: 900px) {
  .page--portal-profile .pp-print__figure .ontology-visual .leaf-label {
    display: block;
  }

  .page--portal-profile .pp-print__figure .ontology-visual__stage {
    overflow-x: auto;
    overscroll-behavior-x: contain;
  }

  /* 560, not 720. At 720 the disc was 780px tall on a phone - most of a screen
     for one figure - and it took 439px of sideways travel to cross. 560 gives
     back a fifth of both while still holding the terms apart.

     The type is raised to pay for it. Label size is in the drawing's own units
     and the whole drawing is scaled to fit, so shrinking the disc shrinks the
     words with it; 12 units at 720 and 15 at 560 land at the same size on
     glass, which is the number the reader actually cares about. */
  .page--portal-profile .pp-print__figure .skillprint__wheel {
    min-width: var(--space-560);
    width: var(--space-560);
  }

  .page--portal-profile .ontology-root {
    --ontology-leaf-size: 15px;
  }
}

.page--portal-profile .pp-print__note {
  margin: 0;
  color: var(--ui-muted);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

.page--portal-profile .pp-print__read {
  min-width: 0;
  display: grid;
  align-content: start;
}

/* A subsection inside the panel: a heading, its content, and a hairline from
   the one above it. No border, no second background, no radius - those are
   what made these read as separate cards. */
/* 16, not 24. These blocks are a title and a short list of readings, and at
   24 the padding was a third of the height of the thing it surrounded - the
   statistics section could show four tiles where the card had room for more. */
.page--portal-profile .pp-read {
  min-width: 0;
  padding: var(--space-16);
  display: grid;
  gap: var(--spacing-lg);
  align-content: start;
}

/* An 80px floor on a tile holding one number and one short label was buying
   air, not legibility. The type is untouched; only the box around it moves. */
.page--portal-profile .ontology-metric {
  min-height: var(--space-64);
  padding: var(--space-10);
}

/* Tighter again on a phone. This has to sit after the rule above rather than
   inside the earlier media block, because a media query buys no specificity -
   the desktop values are written later in the file and were simply winning. */
@media (max-width: 900px) {
  .page--portal-profile .pp-read {
    padding: var(--space-12);
  }

  .page--portal-profile .ontology-metric {
    min-height: var(--space-56);
    padding: var(--space-8);
  }

  .page--portal-profile .ontology-metrics {
    gap: var(--space-8);
  }
}

.page--portal-profile .pp-print__read .pp-read + .pp-read {
  border-block-start: 1px solid var(--surface-border);
}

.page--portal-profile .pp-read__title {
  margin: 0;
  font-size: var(--text-md-font-size);
  line-height: var(--text-md-line-height);
  font-weight: var(--weight-semibold);
}

.page--portal-profile .pp-print__foot {
  border-block-start: 1px solid var(--surface-border);
}

/* The statistics sit under the drawing rather than at the foot of the reading
   column. In the column they were the last of four blocks and inherited its
   padding twice over, which left them floating in a tall gap; under the wheel
   they fill the height the drawing does not use and the panel loses a row. */
.page--portal-profile .pp-print__figure > .pp-read {
  padding: 0;
}

.page--portal-profile .pp-print__figure .ontology-metrics {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

@media (max-width: 1280px) {
  .page--portal-profile .pp-print__figure .ontology-metrics {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* The insight belongs to the section, not inside the panel. */
.page--portal-profile .pp-print + .chart-insight,
.page--portal-profile #print > .chart-insight {
  margin-block: var(--spacing-xl) 0;
}

/* Stacked, in the order the reading is done: print, at a glance, how to read
   it, strength by group. The print keeps the full width it had in the row, so
   stacking makes it bigger rather than smaller. */
@media (max-width: 1180px) {
  .page--portal-profile .pp-print__body {
    grid-template-columns: minmax(0, 1fr);
  }

  .page--portal-profile .pp-print__figure {
    border-inline-end: 0;
    border-block-end: 1px solid var(--surface-border);
  }

  .page--portal-profile .pp-print__read .pp-read:first-child {
    border-block-start: 0;
  }
}

:is(.page--portal-profile, .page--portal-settings) .pp-section + .pp-section {
  padding-block-start: 0;
}

/* --------------------------------------------------------------------------
   The two halves of the profile

   This page answers two questions and used to interleave them: the print,
   the trends, a card that was only a route out to the progression page, the
   orbit, the game insights, the breakdown, the ledger. Nothing told a reader
   which of those described their skills and which described the play
   underneath, so the page had to be read end to end to be understood.

   The sections themselves are unchanged. This is a grouping - one label and
   one line above each half - not a rebuild, and it is deliberately not a set
   of tabs or an in-page nav: a profile that has to be navigated is the thing
   being fixed.
   -------------------------------------------------------------------------- */

:is(.page--portal-profile, .page--portal-settings) .pp-group__head {
  max-width: var(--measure-md);
  padding-block: var(--spacing-6xl) var(--spacing-2xl);
  display: grid;
  gap: var(--spacing-md);
}

/* The rule belongs to the second group and every group after it, so the first
   heading on the page does not open with a line above it. */
:is(.page--portal-profile, .page--portal-settings) .pp-group + .pp-group .pp-group__head {
  max-width: none;
  border-block-start: 1px solid var(--surface-border);
}

/* One step above the section headings under it - 24px against their 20px -
   because that gap is the only thing carrying the hierarchy. Two headings the
   same size in one column read as two peers, whatever the markup says. */
:is(.page--portal-profile, .page--portal-settings) .pp-group__title {
  margin: 0;
  max-width: var(--measure-md);
  font-size: var(--display-xs-font-size);
  line-height: var(--display-xs-line-height);
  font-weight: var(--weight-semibold);
}

:is(.page--portal-profile, .page--portal-settings) .pp-group__lede {
  margin: 0;
  max-width: var(--measure-md);
  color: var(--ui-muted);
}

:is(.page--portal-profile, .page--portal-settings) .pp-group__head + .pp-section {
  padding-block-start: 0;
}

/* Arriving on #skill-breakdown or #sessions from another screen should land
   below the drawer's bar with a little air, not flush against it. */
:is(.page--portal-profile, .page--portal-settings) .pp-section {
  scroll-margin-top: calc(var(--portal-topbar) + var(--space-20));
}

:is(.page--portal-profile, .page--portal-settings) .pp-head {
  margin-block-end: var(--spacing-3xl);
}

:is(.page--portal-profile, .page--portal-settings) .section-head-copy {
  min-width: 0;
  max-width: var(--measure-md);
  display: grid;
  gap: var(--spacing-md);
}

:is(.page--portal-profile, .page--portal-settings) .section-head-copy h2 {
  margin: 0;
  font-size: var(--text-xl-font-size);
  line-height: var(--text-xl-line-height);
  font-weight: var(--weight-semibold);
}

.page--portal-profile .status-badge {
  --badge-surface: color-mix(in srgb, var(--core-green-500) 14%, var(--surface-box));
  gap: var(--spacing-md);
  border-color: color-mix(in srgb, var(--core-green-500) 38%, var(--surface-border));
  background: var(--badge-surface);
  color: var(--surface-text);
}

.page--portal-profile .status-badge i {
  width: var(--space-8);
  height: var(--space-8);
  flex: 0 0 var(--space-8);
  background: var(--core-green-500);
}

.page--portal-profile .pp-unlock__step.is-done {
  background: var(--skillprint-action-violet);
}

.page--portal-profile .progress-panel {
  align-self: start;
}

.page--portal-profile .progress-head h2 {
  margin: 0;
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
  font-weight: var(--weight-semibold);
}

.page--portal-profile .week-label {
  display: block;
  margin-block-end: var(--spacing-xs);
  font-size: var(--font-size-xs);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.08em;
}

.page--portal-profile .metric-grid {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--spacing-lg);
}

.page--portal-profile .metric--portal {
  border-radius: var(--radius-compact);
  background: var(--surface-box);
}

.page--portal-profile .metric--portal strong {
  margin-block-start: var(--spacing-sm);
  font-family: var(--skillprint-font-mono);
  font-size: var(--space-24);
  line-height: var(--space-32);
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
}

.page--portal-profile .metric--portal small {
  margin-block-start: var(--spacing-xs);
  color: var(--core-green-500);
}

.page--portal-profile .streak-row {
  margin-block-start: var(--spacing-2xl);
  padding-block-start: var(--spacing-xl);
}

.page--portal-profile .day {
  width: var(--space-28);
  height: var(--space-28);
  font-size: var(--font-size-xs);
  font-weight: var(--weight-semibold);
}

.page--portal-profile .day.done {
  border-color: transparent;
  background: var(--core-green-500);
}

/* --------------------------------------------------------------------------
   Tab and switch groups - the pillar tri-tab and the chart-type switch
   -------------------------------------------------------------------------- */

.page--portal-profile :is(.status-tabs, .chart-view-switch) {
  padding: var(--space-4);
  align-items: center;
  gap: var(--space-2);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-pill);
  background: var(--surface-panel);
  /* One row, always. Four pills in a bordered track inside a head that is also
     holding a title and a paragraph: when the width ran out the track wrapped
     and the pills became tall two-line lozenges in a pill-shaped border, which
     is the one shape this control cannot make. It scrolls now instead, at the
     same fade width the session toolbar uses - short, because what is being
     revealed is a 70px pill and not a 200px card. */
  flex-wrap: nowrap;
  max-width: 100%;
  overflow-x: auto;
  overscroll-behavior-inline: contain;
  scrollbar-width: none;
  --scroll-fade: var(--space-24);
}

.page--portal-profile :is(.status-tabs, .chart-view-switch)::-webkit-scrollbar {
  display: none;
}

/* A pill is a label, never a paragraph. */
.page--portal-profile :is(.status-tab, .chart-view-switch button) {
  flex: 0 0 auto;
  white-space: nowrap;
}

.page--portal-profile :is(.status-tab, .chart-view-switch button) {
  min-height: var(--space-28);
  padding-inline: var(--spacing-lg);
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--surface-text-muted);
  font-family: var(--skillprint-font-ui);
  font-size: var(--font-size-sm);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
  cursor: pointer;
  transition: var(--transition-interactive);
}

.page--portal-profile :is(.status-tab, .chart-view-switch button):hover {
  color: var(--surface-text);
}

.page--portal-profile .status-tab[aria-pressed="true"],
.page--portal-profile .chart-view-switch button.is-active {
  background: var(--skillprint-action-violet);
  color: var(--geistface-grey-25);
}

.page--portal-profile .pp-compare {
  grid-auto-flow: column;
  align-items: center;
  gap: var(--spacing-md);
}

.page--portal-profile .pp-compare label {
  color: var(--surface-text-muted);
  font-weight: var(--weight-medium);
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   Skillprint orbit card
   -------------------------------------------------------------------------- */

.page--portal-profile .chart-card-head {
  flex-wrap: wrap;
}

/* --------------------------------------------------------------------------
   The cognitive orbit

   The wheel is drawn at its own size and centred, rather than stretched to the
   column. .chart-frame svg sets width:100% for the charts that genuinely span
   their frame - the bars, the lines - and the radar is the one that does not:
   it is a fixed-radius drawing, so filling a 930px card meant 480px of empty
   canvas either side of it. width:auto lets the svg take the width and height
   the script states on it, max-width brings it back down on a phone, and the
   auto margins put the leftover room on both sides instead of inside the
   drawing.
   -------------------------------------------------------------------------- */
.page--portal-profile [data-pp-orbit] > .pp-orbit__svg {
  width: auto;
  max-width: 100%;
  height: auto;
  margin-inline: auto;
}

/* A node is a control, so it says so: a bigger target than the 5.5px circle it
   draws, and the same focus ring every other control on the page uses. */
.page--portal-profile .pp-orbit__node {
  cursor: pointer;
  stroke-width: 2;
  transition:
    r var(--duration-fast) var(--ease-standard),
    stroke-width var(--duration-fast) var(--ease-standard);
}

.page--portal-profile .pp-orbit__node:hover {
  r: 7;
  stroke-width: 3;
}

/* The selected node wears a halo in its own series colour, drawn as a wide
   translucent stroke behind the fill. paint-order is what makes that read as
   a ring rather than as a fatter dot: without it the stroke is painted over
   the fill and simply swallows it. */
.page--portal-profile .pp-orbit__node[data-selected="true"] {
  r: 6;
  stroke: var(--pp-series, var(--mindset-violet-500));
  stroke-width: 10;
  stroke-opacity: 0.3;
  paint-order: stroke fill;
}

/* An outline on an SVG circle is drawn round its bounding box, so the focus
   ring came out as a hard black rectangle with square corners sitting behind
   a round node - the "weird box" this replaces. The same halo carries focus,
   in the focus colour and at full opacity so it is unmistakable. */
.page--portal-profile .pp-orbit__node:focus-visible {
  outline: none;
  r: 6;
  stroke: var(--focus-default);
  stroke-width: 8;
  stroke-opacity: 1;
  paint-order: stroke fill;
}

/* --- the reading behind the node ----------------------------------------- */

/* The reading under the wheel is its own block inside the card: a rule above
   it, then even space around everything in it. It used to take 16px of top
   padding and 8px between its parts while the card around it worked in 24s,
   so the name, the three figures and the button were packed tighter than
   anything else on the page. */
.page--portal-profile .pp-orbit-detail {
  margin-inline: var(--spacing-3xl);
  padding-block: var(--spacing-2xl);
  border-block-start: 1px solid var(--surface-border);
}

.page--portal-profile .pp-orbit-detail__body {
  display: grid;
  gap: var(--spacing-xl);
}

/* display:grid outranks the UA [hidden] rule, so the reading for the selected
   node was drawn underneath the "select a node" line that says there isn't
   one - an empty avatar, three orphaned labels and a dead Open progression
   button, all at once. Same trap the goal card's three states fell into. */
.page--portal-profile .pp-orbit-detail__body[hidden] {
  display: none;
}

.page--portal-profile .pp-orbit-detail__name {
  font-size: var(--font-size-md);
  line-height: var(--line-height-md);
}

.page--portal-profile .pp-orbit-detail__pillar {
  --badge-accent: var(--pp-series, var(--violet));
}

/* Three readings on one line, each a label over its figure. They wrap to two
   columns before they wrap their own labels. */
.page--portal-profile .pp-orbit-detail__stats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--spacing-lg) var(--spacing-4xl);
}

.page--portal-profile .pp-orbit-detail__stats > div {
  display: grid;
  gap: var(--space-2);
}

.page--portal-profile .pp-orbit-detail__stats dd {
  font-size: var(--font-size-md);
  line-height: var(--line-height-md);
  font-variant-numeric: tabular-nums;
}

.page--portal-profile .pp-orbit-detail [data-orbit-link] {
  justify-self: start;
}

/* The insight is a footnote to the whole card, so it clears the block above
   it rather than sitting on its edge. */
.page--portal-profile .pp-orbit-detail + .chart-insight {
  margin-block-start: var(--spacing-xl);
}

/* The Skills rail's goal card edits in place, like the profile's. Its three
   states are the same three, so they take the same guard: display:grid on a
   state beats the UA [hidden] rule and would draw all three at once. */
.page--portal-skills :is(.skill-goal__view, .skill-goal__empty, .skill-goal__edit) {
  display: grid;
  gap: var(--space-12);
}

.page--portal-skills :is(.skill-goal__view, .skill-goal__empty, .skill-goal__edit)[hidden] {
  display: none;
}

.page--portal-skills .skill-goal__empty {
  justify-items: center;
}

/* The picker scrolls, so its height has to land on a row boundary. A tag is
   32px tall on a 12px gap, making the row pitch 44px, so four rows read as
   32 + 44 x 3. The old flat 160px sliced the fourth row through the middle,
   which is what made the card look mispadded. The inline-end padding is the
   gutter for the scrollbar, so tags stop clear of it instead of running under. */
.page--portal-skills .skill-goal .pp-goal__picker {
  max-height: calc(var(--space-32) + var(--space-44) * 3);
  overflow-y: auto;
  padding-inline-end: var(--space-8);
}

.page--portal-skills .skill-goal .pp-goal__foot {
  padding-block-start: var(--space-12);
  justify-content: flex-end;
}

.page--portal-profile .pp-node-row {
  margin-inline: var(--spacing-3xl);
  padding-block-start: var(--spacing-xl);
  align-items: center;
  gap: var(--spacing-xl);
}

.page--portal-profile .pp-node-group {
  color: var(--surface-text-muted);
  font-weight: var(--weight-medium);
}

.page--portal-profile .pp-node-box {
  margin-block-start: var(--spacing-lg);
  padding: var(--spacing-md) var(--spacing-lg);
  display: grid;
  gap: var(--spacing-xs);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-control);
  background: var(--surface-panel);
}

.page--portal-profile .pp-node-line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--spacing-lg);
  color: var(--surface-text-muted);
}

.page--portal-profile .pp-node-line .font-mono,
.page--portal-profile .pp-node-line .weight-semibold {
  color: var(--surface-text);
}

.page--portal-profile .pp-node-box .button {
  margin-block-start: var(--spacing-md);
}

.page--portal-profile .pp-recent__dots i {
  width: var(--space-8);
  height: var(--space-8);
  background: var(--surface-raised);
}

.page--portal-profile .pp-recent__dots i.is-on {
  background: var(--mindset-violet-400);
}

/* --------------------------------------------------------------------------
   Metric filter bar
   -------------------------------------------------------------------------- */

.page--portal-profile .filter-panel {
  margin-block-end: var(--spacing-2xl);
  padding: var(--spacing-xl);
  display: grid;
  gap: var(--spacing-lg);
  border-radius: var(--radius-card);
}

.page--portal-profile .filter-label {
  display: block;
  margin-block-end: var(--spacing-md);
}

.page--portal-profile .chart-chip {
  gap: var(--spacing-sm);
  cursor: pointer;
}

.page--portal-profile .chart-chip .sp-icon {
  flex: 0 0 auto;
}

/* A metric chip's icon takes its pillar's colour, the same three #trait-system
   publishes. It comes off the group the chip sits in rather than a class on
   each of the twenty-eight chips: the group already declares which pillar it
   holds, so the icon cannot end up labelled one pillar and coloured another. */
.page--portal-profile [data-pp-pillar-group="Mood"] .chart-chip .sp-icon { color: var(--mood-500); }
.page--portal-profile [data-pp-pillar-group="Cognition"] .chart-chip .sp-icon { color: var(--cognition-500); }
.page--portal-profile [data-pp-pillar-group="Personality"] .chart-chip .sp-icon { color: var(--personality-500); }

html:is([data-theme="light"], [data-surface="light"]) .page--portal-profile [data-pp-pillar-group="Mood"] .chart-chip .sp-icon { color: var(--core-lime-700); }
html:is([data-theme="light"], [data-surface="light"]) .page--portal-profile [data-pp-pillar-group="Cognition"] .chart-chip .sp-icon { color: var(--personality-mint-700); }
html:is([data-theme="light"], [data-surface="light"]) .page--portal-profile [data-pp-pillar-group="Personality"] .chart-chip .sp-icon { color: var(--personality-blue-600); }

.page--portal-profile .pp-metric-note {
  border-block-start: 1px solid var(--surface-border);
  padding-block-start: var(--spacing-lg);
}

/* --------------------------------------------------------------------------
   Game-specific insights
   -------------------------------------------------------------------------- */

.page--portal-profile .pp-game-filters {
  margin-block-end: var(--spacing-2xl);
  gap: var(--spacing-md);
}

/* The per-page tag gap that used to sit here is now on .ui-tag itself, in the
   shared-component section of Part B - Games and Skills draw the same tag and
   were never named by it. */

.page--portal-profile button.ui-tag {
  cursor: pointer;
}

.page--portal-profile .pp-insight-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
  gap: var(--spacing-2xl);
  align-items: start;
}

.page--portal-profile .metric-card {
  padding: var(--spacing-2xl);
  display: grid;
  gap: var(--spacing-lg);
  border-radius: var(--radius-card);
}

.page--portal-profile .metric-value {
  font-family: var(--skillprint-font-mono);
  font-size: var(--space-24);
  line-height: var(--space-32);
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
}

/* The game board draws one row per selected game, so its frame follows the
   chart rather than holding a fixed floor. */
/* No height floor on any of the three frames. The shared .chart-frame reserves
   384px so a frame waiting on its chart does not collapse, but every chart on
   this page is now measured and brings its own height - the orbit is 256px tall
   on a phone - so the floor only showed as dead space between the drawing and
   its key. */
.page--portal-profile .chart-frame {
  min-height: 0;
}

.page--portal-profile [data-pp-game-chart] {
  padding-block: var(--spacing-2xl);
}

.page--portal-profile .empty-state {
  min-height: var(--space-192);
  display: grid;
  place-content: center;
  gap: var(--spacing-md);
  justify-items: center;
  padding: var(--spacing-4xl);
}

.page--portal-profile .empty-state[hidden] {
  display: none;
}

/* --------------------------------------------------------------------------
   Game insights - one box

   The selected games, the three figures they produce and the chart of those
   figures were three containers in a row: a head, a column of metric cards
   and a chart card beside them. They are one reading of one selection, so
   they are one panel, in the order the question is asked - which games, what
   they add up to, and how that moved.
   -------------------------------------------------------------------------- */

.page--portal-profile .pp-insight {
  display: grid;
  gap: var(--spacing-2xl);
  padding: var(--spacing-3xl);
  border-radius: var(--radius-card);
}

.page--portal-profile .pp-insight__head {
  display: grid;
  gap: var(--spacing-xs);
  max-width: var(--measure-md);
}

.page--portal-profile .pp-insight__title {
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
  font-weight: var(--weight-semibold);
}

.page--portal-profile .pp-insight__note {
  margin: 0;
  color: var(--ui-muted);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

.page--portal-profile .pp-insight__stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--spacing-lg);
}

@media (max-width: 720px) {
  .page--portal-profile .pp-insight__stats {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* --------------------------------------------------------------------------
   Game Detail - the reader's own column

   Everything on this page describes the game. Your record describes the
   reader, and read as a band between How to play and More like this there was
   nothing to say so - same width, same rhythm, same heading weight as the
   game's own facts. So it is a card in a column of its own, tinted and sticky.

   That column is now the portal's own .portal-layout / .portal-rail, not a
   private copy of it. The copy had drifted: a 384px aside against the rail's
   320px, a 32px gap against 28px, a collapse at 1180px against 1023px, and no
   response at all to the rail's 320 -> 256px narrowing step. All of that lives
   in the shell, so all that is left here is the one thing about this card that
   is genuinely its own - the violet wash - plus the two internals a 320px
   column needs. Everything geometric is inherited.
   -------------------------------------------------------------------------- */

/* Tinted with the brand's own accent rather than the game's tone: the game's
   colour is on the artwork above, and this card is deliberately not part of
   that. A modifier on .rail-card, so the padding, radius and gap stay the
   contract every other rail card follows and only the surface differs. */
/* Flat, not washed. "The portal draws no gradients - hierarchy is flat
   surfaces, type, spacing, subtle borders and the brand accents" (AGENTS.md),
   and the front door's own section records its washes being removed "with the
   rest of the portal's gradients". This card kept one anyway. The tint is the
   radial's own violet held at one value across the card instead of ramping
   from 14% to nothing, so the card still reads as the brand's rather than the
   game's - which is the whole point the comment above argues - without drawing
   a corner-lit box no other surface in the portal has. */
.page--portal .rail-card--record {
  border-color: color-mix(in srgb, var(--mindset-violet-500) 32%, var(--surface-border));
  background: color-mix(in srgb, var(--mindset-violet-500) 7%, var(--surface-box));
}

/* Two across, so the four figures read as a list rather than being crushed
   four abreast in a 320px card. */
.page--portal .rail-card--record .gd-record__figures {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--spacing-md);
}

/* The head stacks in a column this narrow. It held a title, a hint and a
   "Week 29 · N-day streak" label; the label is gone - the week number told the
   reader nothing and the streak is one of the four figures immediately below -
   so what stacks now is the title over its hint. .rail-card__head is a flex row
   everywhere else and stays that way: this is a modifier, not a redefinition. */
.page--portal .rail-card--record .rail-card__head {
  display: grid;
  gap: var(--space-4);
}

/* One step up from the rail's other card titles, and only here. This is the
   lead card of the column - the reader's own figures, with the badge and
   anything else in the rail subordinate to it - and at the shared 14px it sat
   at exactly the weight of the cards under it, so the column read as three
   equal boxes rather than a section with a heading. 16px is the smallest step
   that says which one is first without turning the card into a page heading.
   Scoped to the modifier, because .rail-card__title is a contract the rest of
   the portal shares.

   Four classes because the rail pins every heading inside a card to 14px from
   `.page--portal .portal-rail .rail-card :is(h2, h3)` - three classes and an
   element, which outweighs the obvious three-class rule and silently swallowed
   this one. Naming the rail as well is what carries it, and it stays a
   specificity match rather than an !important. */
.page--portal .portal-rail .rail-card--record .rail-card__title {
  font-size: var(--font-size-base);
  line-height: var(--line-height-base);
}

.page--portal .rail-card--record .rail-card__hint {
  margin: 0;
  color: var(--ui-muted);
}

/* The card carries its own ground, so the panels inside it do not need a
   second one. min-width: 0 lets these shrink to the card - without it a long
   primary label inside sets the flex item's min-content width and the panel
   runs past the card's own edge, visible as a dashed box hanging off the
   right. */
.page--portal .rail-card--record :is(.gd-record__feed, .gd-record__empty) {
  padding: var(--spacing-xl);
  min-width: 0;
  /* The figures and the flow score are two readings, not one block. This was
     0 - the panel's own border was the only thing between the bottom row of
     figures and the "Last run flow score" heading, so on a 320px card the two
     sat against each other and read as one crowded table. The gap is stated
     here rather than left to the base rule further down the sheet, which this
     selector outranks by a class. */
  margin-block-start: var(--spacing-2xl);
}

.page--portal .rail-card--record :is(.gd-record__feed, .gd-record__empty) .button {
  white-space: normal;
  min-width: 0;
}

/* Arrived here from a session row. The card is sticky and already in view, so
   nothing scrolls - this is what tells the reader the link landed. Two seconds,
   then it releases. */
.page--portal .rail-card--record.is-arrived {
  border-color: var(--mindset-violet-500);
  box-shadow: 0 0 0 var(--space-4) color-mix(in srgb, var(--mindset-violet-500) 18%, transparent);
}

@media (prefers-reduced-motion: no-preference) {
  .page--portal .rail-card--record {
    transition: border-color var(--duration-base) var(--ease-standard),
      box-shadow var(--duration-base) var(--ease-standard);
  }
}

/* Nothing logged yet is a state worth drawing, not a paragraph: the card says
   so plainly and the primary control is the way out of it. */
.page--portal .gd-record__empty {
  border-style: dashed;
}

/* --------------------------------------------------------------------------
   Skill breakdown
   -------------------------------------------------------------------------- */

/* The breakdown is contained and scrolled rather than laid out as a grid that
   grows with the taxonomy. Twenty-eight rows in two columns is a screen and a
   half of profile; the same rows in a panel that scrolls is a section. It is
   the treatment the session ledger already uses, one section down. */
.page--portal-profile .pp-breakdown {
  border-radius: var(--radius-card);
}

/* The overall reading, above the scroll rather than in a card beside it. This
   section used to carry a button out to a separate progression page, which
   sent a reader looking at their progression to look at their progression.
   The button is gone and this took its place: the same fact the destination
   would have opened with, in the section that already holds the detail. */
.page--portal-profile .pp-breakdown__head {
  padding: var(--spacing-xl);
  display: grid;
  gap: var(--spacing-md);
}

.page--portal-profile .pp-breakdown__scroll {
  max-height: var(--space-480);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.page--portal-profile .pp-breakdown__label {
  position: sticky;
  inset-block-start: 0;
  z-index: 1;
  margin: 0;
  padding: var(--spacing-md) var(--spacing-xl);
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--spacing-lg);
  border-block-end: 1px solid var(--surface-border);
  background: var(--surface-box);
  color: var(--pp-tone);
}

/* Every row is a member of one pillar, so it carries that pillar's colour -
   the trait tokens from index.html, not a rank ramp. Colour identifies the
   group; the number still does the measuring. */
.page--portal-profile .pp-breakdown__group[data-pillar="mood"] { --pp-tone: var(--mood-500); }
.page--portal-profile .pp-breakdown__group[data-pillar="cognition"] { --pp-tone: var(--cognition-500); }
.page--portal-profile .pp-breakdown__group[data-pillar="personality"] { --pp-tone: var(--personality-500); }

.page--portal-profile .pp-skill {
  padding: var(--spacing-md) var(--spacing-xl);
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) var(--space-80) var(--space-32) auto;
  align-items: center;
  gap: var(--spacing-lg);
  border-block-end: 1px solid var(--surface-border);
  color: var(--surface-text);
  text-decoration: none;
  transition: var(--transition-interactive);
}

.page--portal-profile .pp-breakdown__group:last-child .pp-skill:last-child {
  border-block-end: 0;
}

.page--portal-profile .pp-skill:hover {
  background: var(--surface-raised);
}

.page--portal-profile .pp-skill:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-default);
  outline-offset: calc(var(--focus-ring-width) * -1);
}

.page--portal-profile .pp-skill__icon {
  color: var(--pp-tone, var(--cognition-500));
}

.page--portal-profile .pp-skill__track {
  height: var(--space-6);
  overflow: hidden;
  background: var(--surface-raised);
}

/* The length comes from the shared meter module in js/skillprint.js: the row
   carries [data-score] and the track reads --track-fill. */
.page--portal-profile .pp-skill__track i {
  display: block;
  width: var(--track-fill, 0%);
  height: 100%;
  background: var(--pp-tone, var(--cognition-500));
}

.page--portal-profile .pp-skill__value {
  font-family: var(--skillprint-font-mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* Needs play is a state, not a disabled row: same colour, same hover, same
   destination. The skill has a meaning and games behind it either way, and it
   is the row a reader is being asked to act on. The label takes the width the
   track and the figure share on a scored row, so both kinds of row put their
   last column in the same place. */
.page--portal-profile .pp-skill--empty {
  grid-template-columns: auto minmax(0, 1fr) calc(var(--space-80) + var(--space-32) + var(--spacing-lg)) auto;
}

.page--portal-profile .pp-skill__state {
  text-align: right;
  white-space: nowrap;
}

/* A session row opens that game's record. The ledger and the rail's last five
   both name a game and a score, which is the question "how am I doing at this
   one" - and Game Detail's Your record section is where that is answered, so
   the row goes straight there rather than to the top of the page. */
/* Identical to the row it replaced - same padding, same tracks, same radius -
   with one column added for the cue that appears on hover. */
:is(.page--portal-profile, .page--portal-settings) .pp-session__link {
  padding: var(--spacing-lg) var(--spacing-xl);
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--spacing-xl);
  border-radius: var(--radius-compact);
  color: inherit;
  text-decoration: none;
  transition: var(--transition-interactive);
}

:is(.page--portal-profile, .page--portal-settings) .pp-session__link:hover {
  border-color: var(--surface-border-hover);
  background: var(--surface-raised);
}

:is(.page--portal-profile, .page--portal-settings) .pp-session__cue {
  opacity: 0;
  transition: var(--transition-interactive);
}

:is(.page--portal-profile, .page--portal-settings) .pp-session__link:hover .pp-session__cue,
:is(.page--portal-profile, .page--portal-settings) .pp-session__link:focus-visible .pp-session__cue {
  opacity: 1;
}

.page--portal .rail-list__link {
  display: contents;
  color: inherit;
  text-decoration: none;
}

.page--portal .rail-list li:hover .rail-list__name {
  color: var(--ui-text);
}

/* --------------------------------------------------------------------------
   Game sessions
   -------------------------------------------------------------------------- */

.page--portal-profile .pp-sessions {
  padding: var(--spacing-3xl);
  display: grid;
  gap: var(--spacing-2xl);
  border-radius: var(--radius-card);
}

.page--portal-profile .pp-sessions__head {
  padding-block-end: var(--spacing-2xl);
}

.page--portal-profile .pp-sessions__head h3 {
  margin: 0 0 var(--spacing-xs);
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
  font-weight: var(--weight-semibold);
}

.page--portal-profile .pp-played {
  padding: var(--spacing-md) var(--spacing-xl);
  border: 1px solid color-mix(in srgb, var(--skillprint-action-violet) 32%, var(--surface-border));
  background: color-mix(in srgb, var(--skillprint-action-violet) 12%, var(--surface-box));
  color: var(--surface-text);
}

.page--portal-profile .pp-played strong {
  font-family: var(--skillprint-font-mono);
  font-size: var(--text-md-font-size);
  font-variant-numeric: tabular-nums;
}

.page--portal-profile .pp-session-list {
  max-height: var(--space-384);
  padding: 0;
  overflow-y: auto;
  list-style: none;
}

.page--portal-profile .pp-session-list[hidden] {
  display: none;
}

/* The row is the link now, so the <li> carries nothing: left with its own
   padding and its own three-column grid it was laying that grid out around a
   single child, which put the whole row in the first column and left the rest
   of the width empty. */
.page--portal-profile .pp-session {
  display: block;
}

/* The artwork is 380x210 and this frame is square, and an <img> holding an SVG
   scales by the SVG's own preserveAspectRatio - so the drawing sat letterboxed
   with the frame's tint showing through as a band above and below it, at a size
   where the band was most of the icon. `cover` crops the sides instead, which
   is what .rail-thumb on this same page already does with the same artwork in a
   32px square. The tint stays as the ground for a game whose art fails to load. */
.page--portal-profile .pp-session__icon {
  width: var(--space-44);
  height: var(--space-44);
  object-fit: cover;
  background: color-mix(in srgb, var(--skillprint-action-violet) 14%, var(--surface-panel));
  color: var(--violet);
}

.page--portal-profile .pp-session__score {
  text-align: right;
}

.page--portal-profile .pp-session__score strong {
  color: var(--violet);
  font-family: var(--skillprint-font-mono);
  font-size: var(--text-md-font-size);
  font-variant-numeric: tabular-nums;
}

.page--portal-profile .pp-session-empty {
  border: 1px dashed var(--surface-border-hover);
  border-radius: var(--radius-compact);
  background: var(--surface-panel);
}

/* --------------------------------------------------------------------------
   Goals
   -------------------------------------------------------------------------- */

:is(.page--portal-profile, .page--portal-settings) .pp-goal-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--spacing-2xl);
}

:is(.page--portal-profile, .page--portal-settings) .pp-goal {
  min-height: var(--space-224);
  padding: var(--spacing-3xl);
  display: grid;
  align-content: start;
  gap: var(--spacing-xl);
  border-radius: var(--radius-card);
}

:is(.page--portal-profile, .page--portal-settings) .pp-goal h3 {
  margin: 0;
  font-size: var(--text-md-font-size);
  line-height: var(--text-md-line-height);
  font-weight: var(--weight-semibold);
}

:is(.page--portal-profile, .page--portal-settings) .pp-goal__empty {
  display: grid;
  gap: var(--spacing-xl);
  justify-items: center;
  padding-block: var(--spacing-2xl);
}

:is(.page--portal-profile, .page--portal-settings) .pp-goal__edit {
  display: grid;
  gap: var(--spacing-lg);
}

:is(.page--portal-profile, .page--portal-settings) .pp-goal__picker {
  max-height: calc(var(--space-32) + var(--space-44) * 3);
  overflow-y: auto;
  padding-inline-end: var(--space-8);
}

:is(.page--portal-profile, .page--portal-settings) .pp-goal__foot {
  padding-block-start: var(--spacing-lg);
  justify-content: flex-end;
}

:is(.page--portal-profile, .page--portal-settings)
  :is(.pp-goal__view, .pp-goal__empty, .pp-goal__edit)[hidden] {
  display: none;
}

/* --------------------------------------------------------------------------
   Settings
   -------------------------------------------------------------------------- */

:is(.page--portal-profile, .page--portal-settings) .pp-settings {
  padding: var(--spacing-3xl);
  display: grid;
  gap: var(--spacing-2xl);
  border-radius: var(--radius-card);
}

:is(.page--portal-profile, .page--portal-settings) .pp-setting {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-xl);
}

:is(.page--portal-profile, .page--portal-settings) .pp-setting.separator-bottom {
  padding-block-end: var(--spacing-2xl);
}

:is(.page--portal-profile, .page--portal-settings) .pp-setting--stack {
  display: grid;
  gap: var(--spacing-xl);
  /* .pp-setting is a flex row with `justify-content: space-between` and
     `align-items: center`, and switching display to grid does not clear either.
     `space-between` is a grid property too: it distributes the column tracks
     inside the container, and with one max-content column that left the whole
     credentials block sized to its contents - 663px inside a 1055px row, with
     392px of dead ground down the right while every other row on the card ran
     the full width. Reset both; a stacked block wants its children stretched. */
  justify-content: normal;
  align-items: normal;
}

/* Level-agnostic on purpose. A setting row is a major region of the Settings
   page and an h2 there, but the same component nested inside a titled group
   wants an h3; the row should look the same either way rather than force
   the document outline to suit the stylesheet. */
:is(.page--portal-profile, .page--portal-settings) .pp-setting :is(h2, h3) {
  margin: 0 0 var(--spacing-xs);
  display: flex;
  align-items: center;
  gap: var(--spacing-md);
  font-size: var(--text-md-font-size);
  line-height: var(--text-md-line-height);
  font-weight: var(--weight-medium);
}

:is(.page--portal-profile, .page--portal-settings) .pp-setting__fields {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--spacing-xl);
}

/* Settings runs the full content width.

   It was capped at --measure-lg, on the reasoning that a label-left,
   control-right form stretched across 1400px puts the two ends of every row a
   screen apart. That reading is sound about very wide screens and was wrong
   about the common one: the cap is a max-width on a left-aligned column, so on
   anything between the cap and the rail's own width the panel simply stopped
   short and left a band of empty ground down the right-hand side - 150px of it
   at 1280. A page that ends before its container does reads as broken, which is
   a worse fault than a wide row.

   If the wide-screen stretch does become a problem, the fix is a cap on the
   row's own control side rather than on the page column, so the panel still
   ends where the page does. No rule follows: the column inherits the portal's
   own width, which is the whole point. */

/* Midnight is the portal's third appearance option: dark, one rung deeper.
   It rides on the shared dark theme rather than replacing it, so only the
   canvas and the panels move. The control that sets it lives on Settings now,
   so both pages answer to the attribute. */
:is(.page--portal-profile, .page--portal-settings)[data-appearance="midnight"] {
  background: var(--deep-navy-950);
}

:is(.page--portal-profile, .page--portal-settings)[data-appearance="midnight"] :is(.sp-panel, .sp-card) {
  --surface-panel: var(--panel-navy-950);
  --surface-box: var(--deep-navy-900);
}

/* --------------------------------------------------------------------------
   Chart paint - the page module writes geometry, these rules carry colour
   -------------------------------------------------------------------------- */

.page--portal-profile .pp-chart__grid {
  fill: none;
  stroke: var(--surface-border);
  stroke-width: 1;
}

/* The charts are drawn at true pixel size now, so this is the size the axis
   actually reads at - it used to arrive on screen a third larger, magnified
   along with everything else in the viewBox. */
.page--portal-profile .pp-chart__label {
  fill: var(--surface-text-muted);
  font-family: var(--skillprint-font-ui);
  font-size: var(--font-size-sm);
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
}

/* The radar sets .is-tight on itself once the column is too narrow for the
   full label ring. One step down the type scale, which is what buys the rim
   back the radius the labels would otherwise take. Charts are drawn at one
   unit per pixel here, so this is a real 12px rather than a scaled one. */
.page--portal-profile .is-tight .pp-chart__label {
  font-size: var(--font-size-xs);
}

.page--portal-profile .pp-chart__label--strong {
  fill: var(--surface-text);
  font-weight: var(--weight-semibold);
}

.page--portal-profile .pp-chart__hub {
  fill: var(--surface-panel);
  stroke: var(--surface-border);
  stroke-width: 1;
}

.page--portal-profile .pp-chart__compare {
  fill: none;
  stroke: var(--surface-text-muted);
  stroke-width: 1.4;
  stroke-dasharray: 5 6;
  stroke-linecap: round;
}

.page--portal-profile .pp-chart__baseline {
  fill: none;
  stroke: var(--core-green-500);
  stroke-width: 2.2;
  stroke-linecap: round;
}

.page--portal-profile :is(.pp-chart__bar, .pp-chart__area, .pp-chart__dot, .pp-chart__node) {
  fill: var(--pp-series, var(--mindset-violet-500));
  stroke: var(--pp-series, var(--mindset-violet-500));
}

.page--portal-profile .pp-chart__line {
  fill: none;
  stroke: var(--pp-series, var(--mindset-violet-500));
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.page--portal-profile .pp-chart__shape {
  fill: var(--pp-series, var(--mindset-violet-500));
  stroke: var(--pp-series, var(--mindset-violet-500));
  stroke-width: 2.2;
  stroke-linejoin: round;
}

.page--portal-profile .pp-chart__dot {
  stroke: var(--surface-box);
  stroke-width: 2;
}

/* The legend swatch takes the same series colour the mark does; the shared
   .key-item rule points at --chart-tone, which this page does not set. */
.page--portal-profile .chart-key .key-item[data-series] i {
  background: var(--pp-series);
}

.page--portal-profile .pp-key-compare {
  width: var(--space-16);
  height: 0;
  border-block-start: 2px dashed var(--surface-text-muted);
  border-radius: 0;
  background: none;
}

/* [hidden] has to beat the component's own display, which is why the goal
   editor, the session list and the edit button each name it. */
.page--portal-profile .button[hidden] {
  display: none;
}

/* 0-3 are the four metric slots of one trend chart. They are a categorical
   sequence within a single pillar - four moods, or four cognitive abilities -
   so they carry no pillar meaning and stay a plain four-colour rotation. */
.page--portal-profile [data-series="0"] { --pp-series: var(--mindset-violet-500); }
.page--portal-profile [data-series="1"] { --pp-series: var(--personality-blue-500); }
.page--portal-profile [data-series="2"] { --pp-series: var(--personality-mint-500); }
.page--portal-profile [data-series="3"] { --pp-series: var(--skills-orange-500); }

/* These three DO name a pillar, so they take the canonical pillar colours
   published in #trait-system - Mood Lime, Cognition Mint, Personality Blue -
   through the same --mood-500 / --cognition-500 / --personality-500 tokens the
   rest of the system reads. They used to be violet / blue / pink, which put a
   third vocabulary of pillar colour in front of the player. Baseline stays
   green: it is the comparison line, not a pillar. */
.page--portal-profile [data-series="mood"] { --pp-series: var(--mood-500); }
.page--portal-profile [data-series="cognition"] { --pp-series: var(--cognition-500); }
.page--portal-profile [data-series="personality"] { --pp-series: var(--personality-500); }
.page--portal-profile [data-series="baseline"] { --pp-series: var(--core-green-500); }

/* Light steps one rung down each ramp, the rule the sheet applies wherever a
   500 sits on a bright surface. */
html:is([data-theme="light"], [data-surface="light"]) .page--portal-profile [data-series="0"] { --pp-series: var(--mindset-violet-600); }
html:is([data-theme="light"], [data-surface="light"]) .page--portal-profile [data-series="1"] { --pp-series: var(--personality-blue-600); }
html:is([data-theme="light"], [data-surface="light"]) .page--portal-profile [data-series="2"] { --pp-series: var(--personality-mint-700); }
html:is([data-theme="light"], [data-surface="light"]) .page--portal-profile [data-series="3"] { --pp-series: var(--skills-orange-600); }
html:is([data-theme="light"], [data-surface="light"]) .page--portal-profile [data-series="mood"] { --pp-series: var(--core-lime-700); }
html:is([data-theme="light"], [data-surface="light"]) .page--portal-profile [data-series="cognition"] { --pp-series: var(--personality-mint-700); }
html:is([data-theme="light"], [data-surface="light"]) .page--portal-profile [data-series="personality"] { --pp-series: var(--personality-blue-600); }
html:is([data-theme="light"], [data-surface="light"]) .page--portal-profile [data-series="baseline"] { --pp-series: var(--core-green-700); }
html:is([data-theme="light"], [data-surface="light"]) .page--portal-profile .pp-chart__baseline { stroke: var(--core-green-700); }
html:is([data-theme="light"], [data-surface="light"]) .page--portal-profile .day.done { background: var(--core-green-700); }
html:is([data-theme="light"], [data-surface="light"]) .page--portal-profile .pp-recent__dots i.is-on { background: var(--mindset-violet-500); }
html:is([data-theme="light"], [data-surface="light"]) .page--portal-profile .pp-session__score strong,
html:is([data-theme="light"], [data-surface="light"]) .page--portal-profile .pp-session__icon { color: var(--mindset-violet-600); }
html:is([data-theme="light"], [data-surface="light"]) .page--portal-profile .metric--portal small { color: var(--core-green-700); }
html:is([data-theme="light"], [data-surface="light"]) .page--portal-profile .status-badge i { background: var(--core-green-700); }

/* The rank ramp takes the darker half of both ends on the light ground, the
   same step-down the skill progression page makes for the same reason: the 300
   steps are drawn for panel navy and go to roughly 1.1:1 on a light panel. */
html:is([data-theme="light"], [data-surface="light"]) > .page--portal-profile {
  --skills-ramp-from: var(--personality-blue-700);
  --skills-ramp-to: var(--personality-mint-900);
}

/* --------------------------------------------------------------------------
   Responsive
   -------------------------------------------------------------------------- */

@media (max-width: 1080px) {
  .page--portal-profile .pp-insight-grid {
    grid-template-columns: minmax(0, 1fr);
  }

  .page--portal-profile .pp-insight-stats {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (max-width: 860px) {
  :is(.page--portal-profile, .page--portal-settings) .pp-section {
    padding-block: var(--spacing-4xl);
  }

  /* Settings was scoped out of this when the block moved off the Profile page:
     the .pp-section rule above it and the .pp-setting rule at 640 both name
     both pages, this one still named only Profile. The result was two
     credential columns held at 375px, where the User ID input came out 34px
     wide and the API Key 26px - a monospace token in a box too narrow for
     three characters. */
  :is(.page--portal-profile, .page--portal-settings) :is(.pp-goal-grid, .pp-setting__fields) {
    grid-template-columns: minmax(0, 1fr);
  }

  .page--portal-profile .pp-node-row {
    margin-inline: var(--spacing-xl);
  }

}

@media (max-width: 640px) {
  .page--portal-profile .metric-grid,
  .page--portal-profile .pp-insight-stats {
    grid-template-columns: minmax(0, 1fr);
  }

  .page--portal-profile .pp-skill {
    grid-template-columns: auto minmax(0, 1fr) var(--space-32) auto;
  }

  .page--portal-profile .pp-skill--empty {
    grid-template-columns: auto minmax(0, 1fr) auto auto;
  }

  .page--portal-profile .pp-skill__track {
    display: none;
  }

  :is(.page--portal-profile, .page--portal-settings) .pp-setting {
    align-items: flex-start;
    flex-direction: column;
  }
}


/* ==========================================================================
   09 - Unique composition · Portal Skill Progression

   One page for every skill, in two states. It replaced four files -
   Portal_Profile_Skills, Portal_Profile_Moods, Portal_Stat_Skill and
   Portal_Stat_Mood - which were the same composition drawn twice: progression
   and statistics split by data type rather than by question, then each half
   split again by pillar. The section grammar below is the statistics pages'
   (hero rail, trend, percentile, ledger, games); what has gone with them is
   the distribution histogram, the target sidebar and the yearly table, each of
   which said something the trend or the ledger already said.

   Colour is the one thing that is no longer fixed. The old pages hard-coded an
   accent family per file - blue for skills, violet for moods - so every skill
   on a page wore the same hue whatever pillar it belonged to. Here the accent
   is the trait token for the selected skill's pillar, switched by data-pillar
   on <body>: Mood is lime, Cognition mint, Personality blue, exactly as
   index.html publishes them. Positive and inactive stay semantic - green for
   an improving reading, grey for a low band - so the trait colour identifies
   the skill without colouring the whole instrument.
   ========================================================================== */

.page--portal-skill-progression {
  --canvas: var(--deep-navy-900);
  --surface-panel: var(--panel-navy-950);
  --surface-box: var(--panel-navy-900);
  --surface-raised: var(--panel-navy-800);
  --surface-strong: var(--panel-navy-700);
  --border-interactive: rgb(248 250 252 / 18%);
  --logo-word: var(--geistface-grey-25);
  --customer: var(--core-green-500);

  --skills-ramp-from: var(--personality-blue-300);
  --skills-ramp-to: var(--personality-mint-300);
  --mood-pillar: var(--mindset-violet-300);
  --skill-accent: var(--skills-ramp-from);

  min-width: var(--space-320);
  background: var(--canvas);
  font-size: var(--text-md-font-size);
}

html:is([data-surface="light"], [data-theme="light"]) > .page--portal-skill-progression {
  --canvas: var(--deep-navy-25);
  --surface-panel: var(--panel-navy-50);
  --surface-box: var(--geistface-grey-25);
  --surface-raised: var(--geistface-grey-100);
  --surface-strong: var(--panel-navy-100);
  --border-interactive: color-mix(in srgb, var(--deep-navy-900) 20%, transparent);
  --logo-word: var(--deep-navy-950);

  --skills-ramp-from: var(--personality-blue-700);
  --skills-ramp-to: var(--personality-mint-900);
  --mood-pillar: var(--mindset-violet-600);
}

html:has(> .page--portal-skill-progression) {
  scroll-behavior: smooth;
  scroll-padding-top: var(--space-72);
}

/* The trait accent, switched by pillar.

   --mood-500, --cognition-500 and --personality-500 are declared on .page and
   already follow the mode (lime and mint step to 600 on a light ground), so
   pointing --stat-accent at them is all the theming this page needs. They are
   read here as fills, bars, markers and chart strokes - never as body text -
   which is the tier those tokens are drawn for.

   --stat-accent-strong and --stat-accent-low stay out of the trait system on
   purpose: green means the reading improved and grey means it sat low, and
   neither of those is a fact about which pillar you are looking at. */
.page--portal-skill-progression {
  --stat-accent: var(--cognition-500);
  --stat-accent-soft: var(--personality-mint-300);
  --stat-accent-strong: var(--core-green-400);
  --stat-accent-low: var(--geistface-grey-400);
  --stat-track: var(--surface-raised);
  --stat-tint: color-mix(in srgb, var(--stat-accent) 12%, transparent);
  --stat-hairline: color-mix(in srgb, var(--stat-accent) 26%, var(--surface-border));
}

.page--portal-skill-progression[data-pillar="mood"] {
  --stat-accent: var(--mood-500);
  --stat-accent-soft: var(--core-lime-300);
}

.page--portal-skill-progression[data-pillar="cognition"] {
  --stat-accent: var(--cognition-500);
  --stat-accent-soft: var(--personality-mint-300);
}

.page--portal-skill-progression[data-pillar="personality"] {
  --stat-accent: var(--personality-500);
  --stat-accent-soft: var(--personality-blue-300);
}

html:is([data-surface="light"], [data-theme="light"]) > .page--portal-skill-progression {
  --stat-accent-strong: var(--core-green-800);
  --stat-accent-low: var(--geistface-grey-500);
}

html:is([data-surface="light"], [data-theme="light"]) > .page--portal-skill-progression[data-pillar="mood"] {
  --stat-accent-soft: var(--core-lime-700);
}

html:is([data-surface="light"], [data-theme="light"]) > .page--portal-skill-progression[data-pillar="cognition"] {
  --stat-accent-soft: var(--personality-mint-700);
}

html:is([data-surface="light"], [data-theme="light"]) > .page--portal-skill-progression[data-pillar="personality"] {
  --stat-accent-soft: var(--personality-blue-700);
}

.page--portal-skill-progression .brand-logo {
  display: block;
  width: 142px;
  height: auto;
  overflow: visible;
  stroke: none;
}

.page--portal-skill-progression h1 {
  max-width: var(--space-768);
  margin: 0 0 var(--space-12);
  color: var(--text-strong);
}

.page--portal-skill-progression h2 {
  margin: 0;
  color: var(--text-strong);
  font-size: var(--text-xl-font-size);
  line-height: var(--text-xl-line-height);
}

/* The pillar reads on the badge beside the title, which is a chip and takes
   the fill tier the trait tokens are built for. */
.page--portal-skill-progression .progression-pillar {
  --badge-accent: var(--stat-accent);
}

/* --------------------------------------------------------------------------
   Skill selector

   Twenty-eight members in three groups, and it has to stay a control rather
   than become the page. Chips are the shared .ui-tag, so hover, focus and
   pressed all come from the component; only the pillar colouring and the
   has-data marker are added here.

   The Games library filters its grid with this same control, so the selector
   names both pages rather than the picker being written a second time under
   .page--portal-games. Two differences travel on the markup, not on a
   variation: the library adds a lead row whose chips carry their own
   data-pillar, and it leaves the has-data dot off, because "started" is a
   question about a player rather than about a game.
   -------------------------------------------------------------------------- */

:is(.page--portal-skill-progression, .page--portal-games) .progression-picker {
  padding: var(--spacing-xl) var(--spacing-2xl);
  display: grid;
  gap: var(--spacing-lg);
}

:is(.page--portal-skill-progression, .page--portal-games) .progression-picker__bar {
  row-gap: var(--spacing-sm);
}

:is(.page--portal-skill-progression, .page--portal-games) .progression-picker__groups {
  gap: var(--spacing-lg);
}

:is(.page--portal-skill-progression, .page--portal-games) .progression-picker__group {
  display: grid;
  grid-template-columns: var(--space-96) minmax(0, 1fr);
  align-items: start;
  gap: var(--spacing-lg);
}

/* The accent is read off whichever element carries the pillar. A group sets it
   for the row it labels; a chip sets it for itself, which is what lets the
   library's lead row hold three pillars side by side. --pick-ink travels with
   it because the pressed chip is filled with the accent: the three trait
   colours are bright and take dark type, and the violet "all" does not. */
:is(.page--portal-skill-progression, .page--portal-games)
  :is(.progression-picker__group, .progression-chip)[data-pillar="mood"] { --pick-accent: var(--mood-500); --pick-ink: var(--deep-navy-950); }
:is(.page--portal-skill-progression, .page--portal-games)
  :is(.progression-picker__group, .progression-chip)[data-pillar="cognition"] { --pick-accent: var(--cognition-500); --pick-ink: var(--deep-navy-950); }
:is(.page--portal-skill-progression, .page--portal-games)
  :is(.progression-picker__group, .progression-chip)[data-pillar="personality"] { --pick-accent: var(--personality-500); --pick-ink: var(--deep-navy-950); }
:is(.page--portal-skill-progression, .page--portal-games)
  :is(.progression-picker__group, .progression-chip)[data-pillar="all"] { --pick-accent: var(--mindset-violet-500); --pick-ink: var(--geistface-grey-25); }

:is(.page--portal-skill-progression, .page--portal-games) .progression-picker__label {
  padding-block-start: var(--space-8);
  color: var(--pick-accent);
}

/* The group label names a category, so it carries that category's mark. Both
   pickers are drawn from the same markup - this page's is hand-written, the
   progression page's comes from buildPicker() - so the rule is shared exactly
   like the selector above it. The eyebrow is uppercase and tracked, so the
   glyph is aligned on the text box rather than the baseline. */
:is(.page--portal-skill-progression, .page--portal-games) .progression-picker__label {
  display: flex;
  align-items: center;
  gap: var(--space-6);
}

:is(.page--portal-skill-progression, .page--portal-games) .progression-picker__label .sp-icon {
  color: var(--pick-accent);
  flex: none;
}

:is(.page--portal-skill-progression, .page--portal-games) .progression-chip {
  gap: var(--space-6);
  cursor: pointer;
}

:is(.page--portal-skill-progression, .page--portal-games) .progression-chip .sp-icon {
  color: var(--pick-accent);
}

/* A dot, not a disabled state: a skill you have not started is still a skill
   you can read and play. The marker says which rows have a history behind
   them, and nothing says the others are out of reach. */
:is(.page--portal-skill-progression, .page--portal-games) .progression-chip[data-has-data="true"]::before,
:is(.page--portal-skill-progression, .page--portal-games) .progression-key[data-has-data="true"] {
  content: "";
  width: var(--space-6);
  height: var(--space-6);
  border-radius: var(--radius-full);
  background: var(--pick-accent, var(--stat-accent));
  flex: 0 0 var(--space-6);
}

:is(.page--portal-skill-progression, .page--portal-games) .progression-key {
  width: var(--space-6);
  height: var(--space-6);
  background: var(--border-strong);
}

:is(.page--portal-skill-progression, .page--portal-games) .progression-chip[aria-pressed="true"] {
  background: var(--pick-accent);
  border-color: var(--pick-accent);
  color: var(--pick-ink, var(--deep-navy-950));
}

:is(.page--portal-skill-progression, .page--portal-games) .progression-chip[aria-pressed="true"] .sp-icon,
:is(.page--portal-skill-progression, .page--portal-games) .progression-chip[aria-pressed="true"]::before {
  color: var(--pick-ink, var(--deep-navy-950));
  background: var(--pick-ink, var(--deep-navy-950));
}

:is(.page--portal-skill-progression, .page--portal-games) .progression-chip[aria-pressed="true"] .sp-icon {
  background: none;
}

/* --------------------------------------------------------------------------
   First run

   The state a skill is in before anything has been scored against it. It is a
   finished page, not a populated one with the data missing: no chart frame, no
   zeroed score, no empty table. What it does carry is the reason to play.
   -------------------------------------------------------------------------- */

.page--portal-skill-progression .progression-start {
  padding: var(--spacing-4xl);
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: var(--spacing-2xl);
  border: 1px solid var(--stat-hairline);
  /* Flat, like every other tinted portal surface: --stat-tint is already the
     pillar accent at 12%, so it is mixed into the panel at one value rather
     than ramped away down the card. The hairline above carries the edge. */
  background: color-mix(in srgb, var(--stat-accent) 6%, var(--surface-panel));
}

.page--portal-skill-progression .progression-start__glyph {
  color: var(--stat-accent);
  background: var(--surface-raised);
}

.page--portal-skill-progression .progression-start__copy {
  max-width: var(--space-640);
}

.page--portal-skill-progression .progression-unlocks {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--spacing-lg);
  margin-block-start: var(--spacing-2xl);
  list-style: none;
}

.page--portal-skill-progression .progression-unlock {
  padding: var(--spacing-xl);
  border-radius: var(--radius-compact);
}

.page--portal-skill-progression .progression-unlock__glyph {
  color: var(--stat-accent);
  background: var(--surface-raised);
}

/* --------------------------------------------------------------------------
   Recommended game

   One card in the grid is the answer to "what should I play next", so it is
   marked rather than merely first.
   -------------------------------------------------------------------------- */

.page--portal-profile [data-cognition-ramp] { --skill-accent: var(--skills-ramp-from); }
.page--portal-profile [data-cognition-ramp="2"] { --skill-accent: color-mix(in srgb, var(--skills-ramp-to) 12%, var(--skills-ramp-from)); }
.page--portal-profile [data-cognition-ramp="3"] { --skill-accent: color-mix(in srgb, var(--skills-ramp-to) 25%, var(--skills-ramp-from)); }
.page--portal-profile [data-cognition-ramp="4"] { --skill-accent: color-mix(in srgb, var(--skills-ramp-to) 37%, var(--skills-ramp-from)); }
.page--portal-profile [data-cognition-ramp="5"] { --skill-accent: color-mix(in srgb, var(--skills-ramp-to) 50%, var(--skills-ramp-from)); }
.page--portal-profile [data-cognition-ramp="6"] { --skill-accent: color-mix(in srgb, var(--skills-ramp-to) 62%, var(--skills-ramp-from)); }
.page--portal-profile [data-cognition-ramp="7"] { --skill-accent: color-mix(in srgb, var(--skills-ramp-to) 75%, var(--skills-ramp-from)); }
.page--portal-profile [data-cognition-ramp="8"] { --skill-accent: color-mix(in srgb, var(--skills-ramp-to) 87%, var(--skills-ramp-from)); }
.page--portal-profile [data-cognition-ramp="9"] { --skill-accent: var(--skills-ramp-to); }

/* --- statistics components, inherited from the pages this one replaced ----- */

/* --- shared section rhythm ------------------------------------------------ */

/* This rhythm is not theme-dependent, and for a while it silently was. An
   `html[data-surface="light"]` selector whose declaration block had been
   deleted was left stranded here, directly above this rule and with a section
   divider comment sitting between the two - so CSS read the pair as one
   selector and this became a descendant of a light-only ancestor. The portal's
   own toggle writes data-surface="dark" (js/skillprint.js, applyTheme), so
   every head on this page lost its margin the moment a player chose dark.

   The stranded comment described a light-mode step for the blue ramp. That
   step is not lost with it: --skills-ramp-from and the [data-pillar]
   --stat-accent-soft rules above both already carry blue to 700 on a light
   ground, which is the "one more step" it was arguing for. */
.page--portal-skill-progression .stat-section__head {
  margin-block-end: var(--spacing-md);
}

.page--portal-skill-progression .stat-section__head .eyebrow {
  margin-block-end: var(--spacing-sm);
}

.page--portal-skill-progression .stat-section__lede {
  max-width: var(--measure-md);
  margin-block-end: var(--spacing-3xl);
}

.page--portal-skill-progression .stat-count {
  white-space: nowrap;
}

/* --- hero ----------------------------------------------------------------- */


/* The head's shared 6px grid gap is tuned for an eyebrow - a label belonging to
   the title under it. This head opens with a route out instead, which is a
   different kind of thing and was reading as part of the title block. The row
   gap opens to 10px and the link takes another 10 below it, so navigation sits
   clear of the identity while the title and its line stay together. */
/* Both classes are on <body>, so this outranks the shared
   `.page--portal .portal-head { gap: var(--space-6) }` wherever that sits in
   the file - matching on specificity rather than on source order.

   Game Detail is in here for the same reason, and had been left out: it opens
   its head with the same .stat-hero__back link and takes the same 10px
   margin-bottom from the rule below, but sat on the 6px eyebrow gap - so the
   route out was 16px clear of the title where Skill Progression's is 20px. The
   argument in the comment above names the back link, not the page. */
.page--portal:is(.page--portal-skill-progression, .page--portal-game-detail) .portal-head {
  row-gap: var(--space-10);
}

.page--portal .stat-hero__back {
  margin-bottom: var(--space-10);
  color: var(--muted);
  text-decoration: none;
  justify-self: start;
}

.page--portal .stat-hero__back:hover,
.page--portal .stat-hero__back:focus-visible {
  color: var(--text-primary);
}


.page--portal-skill-progression .stat-hero__glyph {
  flex: 0 0 auto;
  color: var(--stat-accent);
  background: var(--stat-tint);
  border: 1px solid var(--stat-hairline);
}

/* --- summary rail --------------------------------------------------------- */

.page--portal-skill-progression .stat-rail {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--spacing-lg);
}

.page--portal-skill-progression .stat-rail__metric strong {
  font-variant-numeric: tabular-nums;
}

.page--portal-skill-progression .stat-note {
  padding: var(--spacing-lg);
  border: 1px solid var(--stat-hairline);
  background: var(--stat-tint);
}

.page--portal-skill-progression .stat-note .sp-icon {
  flex: 0 0 auto;
  margin-block-start: var(--space-2);
  color: var(--stat-accent);
}

/* --- sessions table ------------------------------------------------------- */

.page--portal-skill-progression .stat-table {
  border-color: var(--stat-hairline);
}

.page--portal-skill-progression .stat-table__table {
  min-width: var(--space-720);
}

.page--portal-skill-progression .stat-table__table :is(th, td) {
  padding: var(--spacing-lg) var(--spacing-xl);
}

.page--portal-skill-progression .stat-table__table th {
  font-size: var(--font-size-xs);
  line-height: var(--line-height-sm);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
}

.page--portal-skill-progression .stat-table__table tbody tr:last-child :is(td) {
  border-block-end: 0;
}

.page--portal-skill-progression .stat-table__table tbody tr:hover {
  background: var(--stat-tint);
}

.page--portal-skill-progression .stat-table__table tbody tr[hidden] {
  display: none;
}

.page--portal-skill-progression .stat-table__date {
  font-variant-numeric: tabular-nums;
}

.page--portal-skill-progression .stat-table__game .sp-icon {
  color: var(--muted);
}

.page--portal-skill-progression .stat-flow {
  min-width: var(--space-128);
}

.page--portal-skill-progression .stat-flow__value {
  min-width: var(--space-28);
  font-variant-numeric: tabular-nums;
}

.page--portal-skill-progression .stat-flow__bar {
  --progress-colour: var(--stat-accent);
}

.page--portal-skill-progression .stat-flow__bar[data-band="low"] { --progress-colour: var(--stat-accent-low); }
.page--portal-skill-progression .stat-flow__bar[data-band="high"] { --progress-colour: var(--stat-accent-strong); }


.page--portal-skill-progression .stat-delta[data-trend="up"] { color: var(--stat-accent-strong); }
.page--portal-skill-progression .stat-delta[data-trend="down"] { color: var(--skills-orange-400); }

/* The falling-trend amber needed the same step: 700 sat at 3.2:1 on the card. */
html[data-surface="light"] .page--portal-skill-progression .stat-delta[data-trend="down"] {
  color: var(--skills-orange-800);
}

.page--portal-skill-progression .stat-table__foot {
  padding: var(--spacing-lg) var(--spacing-xl);
}

/* --- percentile + components --------------------------------------------- */

.page--portal-skill-progression .stat-breakdown {
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
}

.page--portal-skill-progression :is(.stat-rank, .stat-components) {
  display: grid;
  gap: var(--spacing-xl);
  border-color: var(--stat-hairline);
}

/* Both headline figures on this page are measurements - a percentile here, a
   signed change over the period below - and both were set in Recoleta. That
   breaks the rule this sheet already states over .portal-eyebrow: the mono
   family is reserved for technical information, poster numbers, data, code and
   metadata, and a percentile is exactly that. It also broke mechanically.
   Recoleta ships a single face at 400, so the `bolder` these <strong> elements
   take from the browser had no cut to resolve to and the figure was drawn as a
   synthesised bold - a faked weight, in a serif, over a number, two panels away
   from rail metrics set in real Geist Mono. Mono is the family every measured
   figure in the portal already uses, and 600 is the weight every mono figure on
   this page uses: the rail metrics above, the facet scores in the panel beside
   this one, the weekly session counts, the dates in the ledger. At 20/30 that is
   the same treatment .page--portal-skills .metric--stacked .metric__value gives
   the same kind of figure. The weight is stated rather than left to the element,
   for the reason already written over .ontology-metric strong: `bolder` lands
   off the 400/500/600/700 scale. Recoleta is also licensed per project and is
   left out of the Tailwind handoff, so product data set in it reaches the
   receiving team in Georgia. */
.page--portal-skill-progression .stat-rank__value {
  font-family: var(--font-mono);
  font-size: var(--text-xl-font-size);
  line-height: var(--text-xl-line-height);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

.page--portal-skill-progression .stat-rank__badge {
  --badge-accent: var(--stat-accent-strong);
}

.page--portal-skill-progression .stat-rank__meter {
  position: relative;
  padding-block-end: var(--spacing-md);
}

.page--portal-skill-progression .stat-rank__track {
  display: block;
  height: var(--space-12);
  overflow: hidden;
  background: var(--stat-track);
}

.page--portal-skill-progression .stat-rank__fill {
  display: block;
  width: var(--rank, 0%);
  height: 100%;
  background: var(--stat-accent);
}

.page--portal-skill-progression .stat-rank__marker {
  position: absolute;
  inset-block-start: calc(var(--space-12) * -0.5);
  left: var(--rank, 0%);
  width: var(--space-4);
  height: var(--space-24);
  transform: translateX(-50%);
  border-radius: var(--radius-full);
  background: var(--stat-accent-strong);
  box-shadow: 0 0 0 var(--space-2) var(--surface-panel);
}

.page--portal-skill-progression .stat-rank__note {
  max-width: var(--measure-sm);
}

.page--portal-skill-progression .stat-components__list {
  list-style: none;
}

.page--portal-skill-progression .stat-component__frame {
  flex: 0 0 auto;
  color: var(--stat-accent);
  background: var(--stat-tint);
}

.page--portal-skill-progression .stat-component__value {
  font-variant-numeric: tabular-nums;
}

.page--portal-skill-progression .stat-component .sp-progress {
  --progress-colour: var(--stat-accent);
}

/* The length comes from the meter contract in js/skillprint.js - [data-score]
   in, --progress out - which the page module re-applies after every render.
   Only the band stays here, and it is now an attribute rather than a list of
   the four scores one page happened to publish: any skill's weakest facets
   draw in the low accent without the sheet knowing their numbers. */
.page--portal-skill-progression .stat-component[data-band="low"] .sp-progress {
  --progress-colour: var(--stat-accent-low);
}

/* --- momentum ------------------------------------------------------------- */

.page--portal-skill-progression .stat-trends {
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
}

.page--portal-skill-progression :is(.stat-trend, .stat-weeks) {
  display: grid;
  gap: var(--spacing-xl);
  align-content: start;
  border-color: var(--stat-hairline);
}

/* The change over the period is the same kind of figure as the percentile in
   the section above, at the same step of the scale, so it takes the same
   treatment rather than a second one. The weight is stated here for the same
   reason it is stated there: this is a <strong>, and the `bolder` it inherits
   lands off the 400/500/600/700 scale. */
.page--portal-skill-progression .stat-trend__value {
  font-family: var(--font-mono);
  font-size: var(--text-xl-font-size);
  line-height: var(--text-xl-line-height);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

.page--portal-skill-progression .stat-trend__badge {
  --badge-accent: var(--stat-accent-strong);
}

.page--portal-skill-progression .stat-trend__chart {
  min-height: 0;
}

.page--portal-skill-progression .stat-trend__svg {
  width: 100%;
  height: auto;
  display: block;
  overflow: visible;
}

/* Every width in this block used to arrive on screen multiplied. The chart is
   drawn into a fixed 320-unit viewBox and then stretched to fill the panel -
   about 560px wide on a 1440 screen and over 820px on a 1920 one - so the
   browser scaled the strokes along with the geometry and the same rule
   rendered at between 1.5 and 2.6 times the number written here. That is what
   made a 2.5 line read as five or six pixels: the values were never wrong, the
   scale was. non-scaling-stroke lifts stroke width out of that transform, so
   each width below is the rendered pixel count at any column width. It buys
   the same guarantee the profile page's charts get by measuring their host and
   drawing one unit to one pixel, without this module needing to redraw on
   every resize. */
.page--portal-skill-progression .stat-trend__rules line {
  stroke: var(--surface-border);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.page--portal-skill-progression .stat-trend__area {
  fill: var(--stat-tint);
  stroke: none;
}

/* 2.4 is what .pp-chart__line draws at on Portal_Profile.html, and that chart
   is already drawn at true pixel size, so its number is the one worth copying
   rather than inventing a second house weight for the same mark. */
.page--portal-skill-progression .stat-trend__line {
  fill: none;
  stroke: var(--stat-accent);
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

/* The ring stays 2, which is what .pp-chart__dot uses. The circle it sits on
   is small now - see the radii in js/skillprint.js - so most of a dot's weight
   is this stroke, which is the part that holds still across column widths. */
.page--portal-skill-progression .stat-trend__dots circle {
  fill: var(--surface-panel);
  stroke: var(--stat-accent);
  stroke-width: 2;
  vector-effect: non-scaling-stroke;
}

/* The last reading is the point the whole panel is about, and it was not
   getting the accent it asks for: the rule above names an element as well as
   two classes, so it outranked this one whatever the order, and every dot came
   out the same knockout ring. Naming the circle here puts this rule in front,
   which is what finally fills the closing dot in the strong green and leaves
   the series reading as gridline, then line, then the point you arrived at. */
.page--portal-skill-progression .stat-trend__dots circle.stat-trend__dot--last {
  fill: var(--stat-accent-strong);
  stroke: var(--stat-accent-strong);
}

.page--portal-skill-progression .stat-weeks__list {
  list-style: none;
}

.page--portal-skill-progression .stat-week__bar {
  display: block;
  height: var(--space-8);
  overflow: hidden;
  background: var(--stat-track);
}

.page--portal-skill-progression .stat-week__bar i {
  display: block;
  width: var(--week-w, 0%);
  height: 100%;
  background: var(--stat-accent);
}

.page--portal-skill-progression .stat-weeks__foot {
  padding-block-start: var(--spacing-xl);
}

/* --- related games -------------------------------------------------------- */

/* These are the library's cards now, so the grid is the library's grid - the
   card is drawn for a 300px column and was being asked to hold a full body,
   Skills Developed and two actions in 256. */
.page--portal-skill-progression .stat-games {
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: var(--space-16);
}

/* The score is the one place the pillar colour is read as words rather than
   drawn as a mark, and --stat-accent is a graphic step: on the light card Mint
   600 measures 1.67:1, so the figure each row exists to report was the least
   readable thing in it. --stat-ink is the text-safe rung of the same ramp -
   the distinction --skill-ink already draws against --skill-accent on the
   Skills screens - so the number keeps its pillar and becomes legible. Dark
   keeps the accent, where the 500s are drawn to be read. */
.page--portal-skill-progression .stat-game__score {
  color: var(--stat-ink, var(--stat-accent));
  font-variant-numeric: tabular-nums;
}

html:is([data-surface="light"], [data-theme="light"]) > .page--portal-skill-progression {
  --stat-ink: var(--personality-mint-900);
}

html:is([data-surface="light"], [data-theme="light"]) > .page--portal-skill-progression[data-pillar="mood"] {
  --stat-ink: var(--core-lime-900);
}

html:is([data-surface="light"], [data-theme="light"]) > .page--portal-skill-progression[data-pillar="cognition"] {
  --stat-ink: var(--personality-mint-900);
}

html:is([data-surface="light"], [data-theme="light"]) > .page--portal-skill-progression[data-pillar="personality"] {
  --stat-ink: var(--personality-blue-800);
}

.page--portal-skill-progression .stat-cta {
  margin-block-start: var(--spacing-3xl);
  padding-block-start: var(--spacing-2xl);
}

.page--portal-skill-progression .stat-cta p {
  max-width: var(--measure-sm);
}

/* --- responsive ----------------------------------------------------------- */

@media (max-width: 1100px) {
  .page--portal-skill-progression .stat-breakdown,
  .page--portal-skill-progression .stat-trends {
    grid-template-columns: minmax(0, 1fr);
  }

  .page--portal-skill-progression .stat-rail {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 640px) {
  .page--portal-skill-progression .stat-rail {
    grid-template-columns: minmax(0, 1fr);
  }}

/* --------------------------------------------------------------------------
   Responsive
   -------------------------------------------------------------------------- */

@media (max-width: 900px) {
  :is(.page--portal-skill-progression, .page--portal-games) .progression-picker__group {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--spacing-sm);
  }

  :is(.page--portal-skill-progression, .page--portal-games) .progression-picker__label {
    padding-block-start: 0;
  }

  .page--portal-skill-progression .progression-unlocks {
    grid-template-columns: minmax(0, 1fr);
  }
}

@media (max-width: 720px) {
  .page--portal-skill-progression .progression-start {
    padding: var(--spacing-2xl);
    grid-template-columns: minmax(0, 1fr);
  }

  /* Twenty-eight chips wrapped onto a phone is most of a screen, and the
     selector is not the page. Each pillar keeps one line and scrolls sideways
     instead, so the three groups stay readable as three rows and the
     progression itself is still the first thing below the head. */
  /* .scrollbar-none in rule form: the hide only applies at this width, and a
     class on the element cannot be conditional on a breakpoint. */
  :is(.page--portal-skill-progression, .page--portal-games) .progression-picker__row {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    padding-block-end: var(--space-4);
    scroll-snap-type: x proximity;
  }

  :is(.page--portal-skill-progression, .page--portal-games) .progression-picker__row::-webkit-scrollbar {
    display: none;
  }

  :is(.page--portal-skill-progression, .page--portal-games) .progression-chip {
    scroll-snap-align: start;
  }
}

/* ==========================================================================
   09 - Unique composition · Portal Skill Detail
   The narrative drill-down for one cognitive skill (/profile/skill/[name]/).
   Its numbers-forward sibling is Portal_Skill_Progression.html; everything
   here is built to read as prose with evidence attached, not as a stat sheet.
   ========================================================================== */

/* One series colour for the whole page - the score ring, the per-game signal
   bars and the trend chart are the same measurement, so they are one colour.

   WHICH colour is a fact about the skill, not about the file. This page fixed
   the series at Personality blue while rendering Focus, which is a Mood skill
   (js/skillprint.js: `pillar:'mood', key:'mood.focus'`, and the page's own
   eyebrow and badge both say Mood) - so every instrument on it wore another
   pillar's hue. That is exactly the fault the Progression rewrite removed:
   "the old pages hard-coded an accent family per file - blue for skills,
   violet for moods - so every skill on a page wore the same hue whatever
   pillar it belonged to". This is the same repair, in the same shape: the
   accent is the trait token for the pillar named on <body>. */
.page--portal-skill-detail {
  --skill-series: var(--cognition-500);
  --skill-series-soft: color-mix(in srgb, var(--cognition-500) 20%, transparent);
}

.page--portal-skill-detail[data-pillar="mood"] {
  --skill-series: var(--mood-500);
  --skill-series-soft: color-mix(in srgb, var(--mood-500) 20%, transparent);
}

.page--portal-skill-detail[data-pillar="cognition"] {
  --skill-series: var(--cognition-500);
  --skill-series-soft: color-mix(in srgb, var(--cognition-500) 20%, transparent);
}

.page--portal-skill-detail[data-pillar="personality"] {
  --skill-series: var(--personality-500);
  --skill-series-soft: color-mix(in srgb, var(--personality-500) 20%, transparent);
}

/* Lime and mint step to 600 on a light ground on .page, so Mood and Cognition
   need nothing here. Blue is the one pillar ramp that does not step, which is
   what the page's original blue-700 override was actually solving: Blue 500 is
   too bright to carry a filled bar on white. Only Personality keeps a step. */
html:is([data-surface="light"], [data-theme="light"]) > .page--portal-skill-detail[data-pillar="personality"] {
  --skill-series: var(--personality-blue-700);
  --skill-series-soft: color-mix(in srgb, var(--personality-blue-700) 16%, transparent);
}


/* --------------------------------------------------------------------------
   09.1 · Hero — identity
   -------------------------------------------------------------------------- */
.page--portal-skill-detail .skill-score {
  display: grid;
  align-content: start;
  gap: var(--space-20);
}

.page--portal-skill-detail .skill-score h2 {
  margin: var(--space-4) 0 0;
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
}

.page--portal-skill-detail .skill-score__ring {
  --progress-ring-size: var(--space-96);
  --progress-colour: var(--skill-series);
}

.page--portal-skill-detail .skill-score__ring strong {
  font-family: var(--skillprint-font-mono);
  font-variant-numeric: tabular-nums;
}

.page--portal-skill-detail .skill-score__ring[data-skill-ring="74"] {
  --progress-value: 74;
}

.page--portal-skill-detail .skill-metric strong {
  margin-top: var(--space-4);
  font-size: var(--space-24);
  line-height: var(--space-32);
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
}

.page--portal-skill-detail .skill-metric small {
  margin-top: var(--space-2);
}

.page--portal-skill-detail .skill-score__streak {
  padding-top: var(--space-16);
}

.page--portal-skill-detail .skill-day {
  width: var(--space-24);
  height: var(--space-24);
  font-size: var(--font-size-xs);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-semibold);
}

.page--portal-skill-detail .skill-day.is-done {
  border-color: transparent;
  background: var(--skill-series);
  color: var(--deep-navy-900);
}


/* --------------------------------------------------------------------------
   09.3 · What Focus measures — the three weighted facets
   -------------------------------------------------------------------------- */

.page--portal-skill-detail .skill-facet {
  gap: var(--space-12);
}

.page--portal-skill-detail .skill-facet h3 {
  margin: var(--space-4) 0 0;
}

.page--portal-skill-detail .skill-facet .ui-badge {
  margin-top: auto;
  align-self: flex-start;
}

.page--portal-skill-detail .skill-facet .sp-icon-frame {
  background: var(--surface-raised);
}

.page--portal-skill-detail .skill-callout {
  align-items: center;
}

.page--portal-skill-detail .skill-callout .sp-icon {
  margin-top: var(--space-2);
}

.page--portal-skill-detail .skill-callout__action {
  margin-left: auto;
}


/* --------------------------------------------------------------------------
   09.4 · What it predicts
   -------------------------------------------------------------------------- */

.page--portal-skill-detail .skill-predict h3 {
  margin: 0;
}

.page--portal-skill-detail .skill-predict__proof {
  margin-top: auto;
  padding-top: var(--space-16);
}


/* --------------------------------------------------------------------------
   09.5 · Trend — same chart language as the profile
   -------------------------------------------------------------------------- */

/* The trend card and its key dot draw the page's own series. They used to ask
   for it by carrying .tone--cognition, which Part A maps to --blue - a third
   pillar's name resolving to a second pillar's colour, on a page rendering a
   Mood skill. --chart-tone inherits, so declaring it once on the section
   reaches both without either of them naming a pillar at all. The baseline key
   overrides it just below with the muted grey of the dashed line it names. */
.page--portal-skill-detail .skill-trend {
  --chart-tone: var(--skill-series);
}

.page--portal-skill-detail .skill-trend__key-baseline {
  --chart-tone: var(--surface-text-muted);
}

.page--portal-skill-detail .skill-trend .insight-dot {
  background: var(--skill-series);
}

.page--portal-skill-detail .skill-trend__compare {
  gap: var(--space-4);
  min-width: var(--space-160);
}

.page--portal-skill-detail .skill-trend__compare label {
  font-size: var(--font-size-xs);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--surface-text-muted);
}

.page--portal-skill-detail .skill-trend__compare select {
  min-height: var(--space-32);
}

/* The chart is drawn markup, not a script mount: the prototype has to read
   correctly from file:// with nothing running. */
.page--portal-skill-detail .skill-chart__grid line {
  stroke: var(--surface-border);
  stroke-width: 1;
}

.page--portal-skill-detail .skill-chart__base {
  stroke: var(--surface-text-muted);
  opacity: 0.5;
}

.page--portal-skill-detail .skill-chart__axis text,
.page--portal-skill-detail .skill-chart__labels text {
  fill: var(--surface-text-muted);
  font-size: var(--space-12);
  font-weight: var(--weight-semibold);
}

.page--portal-skill-detail .skill-chart__bar {
  fill: var(--skill-series);
  opacity: 0.7;
}

.page--portal-skill-detail .skill-chart__bar.is-current {
  opacity: 1;
}

.page--portal-skill-detail .skill-chart__fill {
  fill: var(--skill-series-soft);
}

.page--portal-skill-detail .skill-chart__line {
  fill: none;
  stroke: var(--skill-series);
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.page--portal-skill-detail .skill-chart__point {
  fill: var(--surface-box);
  stroke: var(--skill-series);
  stroke-width: 2.5;
}

.page--portal-skill-detail .skill-chart__point.is-current {
  fill: var(--skill-series);
}

.page--portal-skill-detail .skill-chart__baseline {
  fill: none;
  stroke: var(--surface-text-muted);
  stroke-width: 2;
  stroke-dasharray: 6 6;
  stroke-linecap: round;
  opacity: 0.8;
}

/* Bars is the default view, so it survives with the script absent. */
.page--portal-skill-detail [data-skill-frame] .skill-chart__area {
  display: none;
}

.page--portal-skill-detail [data-skill-frame][data-view="area"] .skill-chart__bars {
  display: none;
}

.page--portal-skill-detail [data-skill-frame][data-view="area"] .skill-chart__area {
  display: block;
}


/* --------------------------------------------------------------------------
   09.6 · Games that train Focus
   -------------------------------------------------------------------------- */

.page--portal-skill-detail .skill-game__media {
  aspect-ratio: 16 / 10;
  background: var(--surface-raised);
}

.page--portal-skill-detail .skill-game__art {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Only six games in the library ship artwork. The rest get a cognition icon
   tile rather than a broken image. */
.page--portal-skill-detail .skill-game__media--icon {
  background: var(--skill-series-soft);
  color: var(--skill-series);
}

.page--portal-skill-detail .skill-game__body {
  padding: var(--space-20);
  gap: var(--space-14);
  flex: 1 1 auto;
}

.page--portal-skill-detail .skill-game__body h3 {
  margin: 0;
}

.page--portal-skill-detail .skill-game__bar {
  --progress-colour: var(--skill-series);
}

.page--portal-skill-detail .skill-game__bar .sp-progress__track {
  height: var(--space-6);
}

.page--portal-skill-detail [data-skill-signal="92"] { --progress: 92%; }
.page--portal-skill-detail [data-skill-signal="88"] { --progress: 88%; }
.page--portal-skill-detail [data-skill-signal="81"] { --progress: 81%; }
.page--portal-skill-detail [data-skill-signal="76"] { --progress: 76%; }
.page--portal-skill-detail [data-skill-signal="70"] { --progress: 70%; }
.page--portal-skill-detail [data-skill-signal="64"] { --progress: 64%; }
.page--portal-skill-detail [data-skill-signal="58"] { --progress: 58%; }

.page--portal-skill-detail .skill-game__foot {
  margin-top: auto;
  padding-top: var(--space-14);
}


/* --------------------------------------------------------------------------
   09.7 · Session breakdown
   -------------------------------------------------------------------------- */

.page--portal-skill-detail .skill-sessions__wrap {
  overflow-x: auto;
  background: var(--surface-box);
}

.page--portal-skill-detail .skill-sessions__wrap .sp-table {
  min-width: var(--space-640);
}

.page--portal-skill-detail .skill-sessions .font-mono {
  font-variant-numeric: tabular-nums;
}

.page--portal-skill-detail .skill-sessions tr.is-current td {
  background: var(--surface-raised);
}

.page--portal-skill-detail .skill-sessions__empty {
  padding: var(--space-32) 0 0;
}


/* --------------------------------------------------------------------------
   09.8 · Play control and the route out
   -------------------------------------------------------------------------- */

.page--portal-skill-detail .skill-next__card h2 {
  margin: var(--space-4) 0 var(--space-12);
  max-width: var(--measure-sm);
}

.page--portal-skill-detail .skill-next__card p {
  max-width: var(--measure-md);
}

.page--portal-skill-detail .skill-next__status:empty {
  display: none;
}

.page--portal-skill-detail .skill-link {
  text-decoration: none;
  align-items: center;
}

.page--portal-skill-detail .skill-link strong {
  margin-bottom: var(--space-4);
}


/* --------------------------------------------------------------------------
   09.9 · Responsive
   -------------------------------------------------------------------------- */

@media (max-width: 1024px) {
  .page--portal-skill-detail .skill-facets,
  .page--portal-skill-detail .skill-predicts__grid,
  .page--portal-skill-detail .skill-games__grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 767px) {

  .page--portal-skill-detail .skill-facets,
  .page--portal-skill-detail .skill-predicts__grid,
  .page--portal-skill-detail .skill-games__grid,
  .page--portal-skill-detail .skill-next__links,
  .page--portal-skill-detail .skill-score__metrics {
    grid-template-columns: minmax(0, 1fr);
  }

  .page--portal-skill-detail .skill-callout {
    flex-wrap: wrap;
  }

  .page--portal-skill-detail .skill-callout__action {
    margin-left: 0;
  }

  .page--portal-skill-detail .skill-next__actions .button {
    flex: 1 1 auto;
  }
}


/* ==========================================================================
   09 - Unique composition · Portal Embeddable Profile Card
   Mirrors /profile/embed/. The route is a widget, not a page: it is iframed
   into a partner surface at a fixed 420x560. This prototype frames the widget
   the way Mobile_Performance frames its phone screens - the card is drawn at
   true embed size and the page around it explains the contract.
   ========================================================================== */

.page--portal-profile-embed {
  --embed-accent: var(--skillprint-action-violet);
  --embed-width: 420px;
  --embed-height: 560px;
}

.page--portal-profile-embed .embed-section {
  padding-block: var(--spacing-5xl);
}

/* --- the stage ------------------------------------------------------------ */

.page--portal-profile-embed .embed-stage {
  align-items: start;
}

.page--portal-profile-embed .embed-figure {
  flex: 0 0 auto;
  width: var(--embed-width);
  max-width: 100%;
}

.page--portal-profile-embed .embed-figure__caption {
  max-width: var(--embed-width);
}

/* Drawn at the real embed size so the prototype measures right. */
.page--portal-profile-embed .embed-card {
  width: 100%;
  min-height: var(--embed-height);
  grid-template-rows: auto 1fr auto;
  gap: 0;
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-card);
  background: var(--surface-raised);
}

.page--portal-profile-embed .embed-card__head {
  padding: var(--spacing-lg) var(--spacing-xl);
  border-block-end: 1px solid var(--surface-border);
}

.page--portal-profile-embed .embed-card__mark {
  display: block;
  width: 20px;
  height: 20px;
}

.page--portal-profile-embed .embed-card__body {
  padding: var(--spacing-xl);
  align-content: start;
}

.page--portal-profile-embed .embed-card__action {
  margin: 0 var(--spacing-xl) var(--spacing-xl);
  width: auto;
}

/* --- flow score ----------------------------------------------------------- */

/* The card leads with the print, the way the profile it links to does: this is
   the surface a player's Skillprint travels on, so the drawing is the thing
   that travels. It replaced a plain ring holding the flow figure - one circle
   standing in for the identity, beside nothing - and the flow figure moved down
   into the stat row with the other two totals.

   Same renderer, same still-figure mode, same crop and same reason as the
   profile hero: at 96px no term could be read, so none is drawn. */
.page--portal-profile-embed .embed-print {
  width: 128px;
  flex: 0 0 auto;
}

.page--portal-profile-embed .embed-print .ontology-visual {
  border-radius: var(--radius-xl);
}

.page--portal-profile-embed .embed-print .leaf-label {
  display: none;
}

.page--portal-profile-embed .embed-print .skillprint__wheel {
  display: block;
  width: 140%;
  max-width: none;
  min-width: 0;
  margin: -20%;
  height: auto;
  aspect-ratio: 1;
}

.page--portal-profile-embed .embed-print__lede {
  font-size: var(--font-size-md);
  line-height: var(--text-md-line-height);
}

.page--portal-profile-embed .embed-score__trend {
  color: var(--embed-accent);
  margin-block: 2px;
}

/* --- stats and traits ----------------------------------------------------- */

.page--portal-profile-embed .embed-stat strong {
  margin-block-start: 2px;
  font-size: var(--font-size-md);
}

.page--portal-profile-embed .embed-traits {
  list-style: none;
}

/* Cognition, not Action Violet. #trait-system's first rule is that action and
   meaning stay separate: violet is for global actions, focus and selection, and
   a pillar colour is what says which pillar a reading belongs to. These three
   bars are cognitive abilities, so they take Cognition Mint - the same colour
   their icons take through .sp-icon--cognition, and the same one the profile's
   own skill breakdown ramps from. The flow ring above keeps the accent: it is
   the composite score, not a pillar. */
.page--portal-profile-embed .embed-trait .sp-progress__fill {
  background: var(--cognition-500);
}

html:is([data-theme="light"], [data-surface="light"]) .page--portal-profile-embed .embed-trait .sp-progress__fill {
  background: var(--personality-mint-700);
}

/* The top three of the eight skills the profile publishes, at the figures it
   publishes them at. */
.page--portal-profile-embed .embed-trait[data-score="82"] .sp-progress__fill { inline-size: 82%; }
.page--portal-profile-embed .embed-trait[data-score="76"] .sp-progress__fill { inline-size: 76%; }
.page--portal-profile-embed .embed-trait[data-score="71"] .sp-progress__fill { inline-size: 71%; }

/* --- error state ---------------------------------------------------------- */

.page--portal-profile-embed .embed-empty {
  align-content: center;
  justify-items: center;
  padding-inline: var(--spacing-2xl);
}

.page--portal-profile-embed .embed-empty__glyph {
  color: var(--skills-orange-500);
  background: color-mix(in srgb, var(--skills-orange-500) 12%, transparent);
}

/* Retrying dims the card without collapsing it, so the iframe never jumps. */
.page--portal-profile-embed .embed-card.is-retrying {
  opacity: 0.55;
}

/* --- embedding notes ------------------------------------------------------ */

.page--portal-profile-embed .embed-notes {
  list-style: none;
}

.page--portal-profile-embed .embed-howto .code-block {
  min-width: 0;
  overflow-x: auto;
}

@media (max-width: 900px) {
  .page--portal-profile-embed .embed-howto { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 520px) {
  .page--portal-profile-embed { --embed-width: 100%; }
  .page--portal-profile-embed .embed-stage { gap: var(--spacing-3xl); }
}


/* ============================================================================
   40 · Marketing site · the V4 consolidation

   The fourth pass folds three iterations into one site. The narrative moved
   from "a foundation model for symbiotic human+AI performance" to "build your
   strengths through play", and the three products became four audiences.

   The site ships dark. It was built light first and turned back, so everything
   below that is light-specific is scoped to [data-surface="light"] and is
   inert today - kept, not deleted, because it is what makes the surface a
   one-attribute switch rather than a rebuild. Flipping the site is
   data-surface on each page plus SHOT_THEME=light on the capture script.

   Everything here is scoped to .page--site, so none of it reaches a prototype.
   Nothing in sections 37-39 was edited: this section only adds what the new
   compositions need, and restates a value only where the older rule was
   written for a narrower case.
   ========================================================================== */

/* ----------------------------------------------------------------------------
   40.1 · Two more page accents, and one changed pairing

   Four audiences need four colours. Players take the brand green, so the
   consumer page reads as Skillprint's own; Enterprise keeps Personality Blue
   from the old Augment page, so the colour a returning visitor learned still
   points at the same audience.

   Flow's secondary moves from Core Green to Mindset Violet. It was green
   beside a lime primary, which is the pairing Players now owns outright, and
   two adjacent pages washed in the same two greens is how a colour code stops
   coding anything.
   -------------------------------------------------------------------------- */

/* Players · Core Green, the brand's own. */
.page--amplify {
  --sp-accent  : var(--core-green-500);
  --sp-accent-2: var(--personality-mint-500);
}

/* Enterprise · Personality Blue, inherited from Augment. */
.page--enterprise {
  --sp-accent  : var(--personality-blue-500);
  --sp-accent-2: var(--personality-mint-500);
}

.page--flow {
  --sp-accent-2: var(--mindset-violet-400);
}

/* Contact · Mindset Violet. The one page that belongs to no audience, so it
   takes the system colour rather than borrowing an audience's. */
.page--contact {
  --sp-accent  : var(--mindset-violet-500);
  --sp-accent-2: var(--personality-blue-500);
}

/* The nav menu rows carry the same four colours, which is where a visitor
   first meets the code. */
.page--site .sp-nav__product--amplify    { --sp-route-accent: var(--core-green-500); }
.page--site .sp-nav__product--enterprise { --sp-route-accent: var(--personality-blue-500); }

/* The Products button stays lit while one of its four pages is open. The rows
   inside carry aria-current, but the closed button showed nothing, so a
   visitor on Enterprise saw no marked route in the bar at all. */
.page--site [data-nav-menu-current="true"] .sp-nav__route--menu {
  color: var(--nav-text);
}

/* ----------------------------------------------------------------------------
   40.2 · The light surface

   Inert while the site is dark. Three things were drawn for the dark surface
   and need restating whenever the light one is switched on.
   -------------------------------------------------------------------------- */

/* The wordmark ships as a pair and the shared .brand-logo rules decide which
   one is shown. The nav cell sizes the logo; the footer's is a wider lockup. */
.page--site .sp-nav__brand .brand-logo { width: 168px; height: auto; }
.page--site .footer-brand .brand-logo { width: 176px; height: auto; }

/* The wheel hub is a dark disc with an accent ring, drawn to sit on the deep
   navy panel. On a light panel the disc has to become the panel. */
html:is([data-surface="light"], [data-theme="light"]) .page--site .sp-wheel__hub {
  fill: color-mix(in srgb, var(--surface-box) 92%, transparent);
}

/* Product row shots carried a heavy dark drop shadow for the same reason. */
html:is([data-surface="light"], [data-theme="light"]) .page--site .product-row__shot {
  box-shadow: 0 24px 56px color-mix(in srgb, var(--deep-navy-900) 10%, transparent);
}

/* `.page--homepage` sets two tokens from the pale end of the greyscale because
   it was written for the deep navy surface: --quiet is a 50% grey-200, and
   --border-hover a 20% deep-navy-25. Both are near-white, so on the light
   surface the quiet type disappeared into the panel and the hover border
   stopped being a border. The block has no light counterpart anywhere, so it
   is added here rather than edited, and it inverts the same two mixes. */
html:is([data-surface="light"], [data-theme="light"]) .page--site {
  /* 84%, not the 50% the dark rule uses. --quiet lands on 12px footer type,
     the hero's supporting line and the pull-quote attribution, and the mix has
     to clear 4.5:1 against the darkest surface it sits on - the panel, not the
     page. 62% measured 3.03 there; 84% measured 5.1 on the panel and 5.4 on the
     page, so the tone still reads as quieter than body copy without dropping
     the small text below AA. */
  --quiet: color-mix(in srgb, var(--geistface-grey-800) 84%, transparent);
  --border-hover: color-mix(in srgb, var(--deep-navy-900) 28%, transparent);
}

/* The primary button's label is white by contract (--button-text-strong), but
   `.page--homepage a { color: inherit }` is (0,1,1) and the component's own
   `color` is (0,1,0), so every button that happens to be an anchor took the
   colour of whatever it sat in. On the dark surface that inherited near-white
   and looked correct by accident; on the light surface the bar's dark grey
   landed on the violet fill at about 1.4:1, which is the global "Talk to us"
   call to action on every page. Stated at matching specificity so the
   component decides its own foreground again. */
.page--site a.button--primary,
.page--site a.button--primary:link,
.page--site a.button--primary:visited {
  color: var(--button-text-strong);
}

/* The secondary and tertiary buttons take the ordinary text colour, which is
   what `inherit` happened to give them - but only while the ancestor happened
   to be body text. Inside a tinted card it was the card's colour. */
.page--site a:is(.button--secondary, .button--tertiary),
.page--site a:is(.button--secondary, .button--tertiary):link,
.page--site a:is(.button--secondary, .button--tertiary):visited {
  color: var(--button-text);
}

/* ----------------------------------------------------------------------------
   40.3 · Product cards, four across

   Section 39.4 redefined .product-grid as a single column with a 96px gap,
   because three full-width rows were the homepage's product section. Four
   audiences do not read as four full-width rows: the section becomes the page.
   The modifier restores the auto-fit track set for the card presentation, and
   the row presentation is untouched.
   -------------------------------------------------------------------------- */
/* Two across, not auto-fit. `auto-fit` with a 300px floor resolves to three
   tracks at the container's real width, which leaves the fourth audience
   alone on a second row - and the odd one out reads as the least important,
   which is the one thing a four-audience section must not say. Two rows of
   two also give each screenshot twice the width. */
.page--site .product-grid--cards {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-24);
  align-items: stretch;
}

@media (max-width: 767px) {
  .page--site .product-grid--cards { grid-template-columns: minmax(0, 1fr); }
}

.page--site .product-grid--cards .product-card {
  --card-current-padding: var(--space-24);
  height: 100%;
}

/* The shot is the first thing in the card and bleeds to its edges, so the
   frame's own ratio governs rather than the media-frame default. */
.page--site .product-card__shot {
  aspect-ratio: 16 / 9;
}

.page--site .product-card__shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
}

.page--site .product-card__body { gap: var(--space-12); }

.page--site .product-card__body h3 {
  font-size: var(--text-xl-font-size);
  line-height: var(--text-xl-line-height);
  letter-spacing: -0.01em;
}

/* The card's call to action sits on the floor of the card, so four cards of
   different copy lengths still line their buttons up. */
.page--site .product-card__body .button {
  justify-self: start;
  margin-block-start: auto;
}

.page--site .product-card__body .dimension-tags { margin-block-start: var(--space-4); }

/* ----------------------------------------------------------------------------
   40.4 · The three-beat flow

   Play a session, Skillprint reads it, see yourself in it. Three steps with a
   connector, used on the homepage and again on Contact for what happens after
   you write in. The connector is drawn on the step so it wraps with it.
   -------------------------------------------------------------------------- */


/* ----------------------------------------------------------------------------
   40.5 · Icon-led step cards

   One shape serving the future-of-work rail, the four things play reveals and
   the six surfaces Skillprint can sit inside. All three are an icon, a
   heading and a line, and giving them one rule is what stops three
   near-identical components existing.
   -------------------------------------------------------------------------- */
.page--site :is(.work-rail, .behaviour-grid) {
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-16);
}

.page--site .behaviour-grid { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }

.page--site :is(.work-step, .behaviour-card) {
  grid-template-columns: var(--space-32) minmax(0, 1fr);
  gap: var(--space-20);
}

.page--site :is(.work-step, .behaviour-card) h3 {
  margin: 0 0 var(--space-6);
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
}

.page--site .work-icon {
  width: var(--space-32);
  height: var(--space-32);
  border: 1px solid color-mix(in srgb, var(--accent, var(--sp-accent)) 26%, transparent);
  border-radius: var(--radius-card);
  background: color-mix(in srgb, var(--accent, var(--sp-accent)) 12%, transparent);
  color: var(--accent, var(--sp-accent));
}

/* A "coming soon" marker on a heading, so an idea can be shown without being
   claimed as shipped. */
.page--site .ui-badge--soon {
  --badge-accent: var(--violet);
  margin-inline-start: var(--space-8);
  font-family: var(--skillprint-font-mono);
  font-size: var(--text-xxs-font-size);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  vertical-align: middle;
}

/* ----------------------------------------------------------------------------
   40.6 · Signal tiles and surface tiles

   The six things a session gives up, and the six places Skillprint can live.
   Both are a label, a claim and a line, in a wrapping grid.
   -------------------------------------------------------------------------- */
.page--site :is(.signal-matrix, .surface-grid) {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--space-16);
}

.page--site :is(.signal-tile, .surface-tile) {
  display: grid;
  gap: var(--space-8);
  align-content: start;
}

.page--site .signal-tile__label {
  color: var(--accent, var(--sp-accent));
  font-family: var(--skillprint-font-mono);
  font-size: var(--text-xxs-font-size);
  line-height: var(--text-xxs-line-height);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

.page--site :is(.signal-tile, .surface-tile) strong {
  color: var(--surface-text);
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
  font-weight: var(--weight-regular);
}

.page--site .surface-tile__icon {
  display: grid;
  place-items: center;
  width: var(--space-40);
  height: var(--space-40);
  margin-block-end: var(--space-4);
  border: 1px solid color-mix(in srgb, var(--accent, var(--sp-accent)) 26%, transparent);
  border-radius: var(--radius-card);
  background: color-mix(in srgb, var(--accent, var(--sp-accent)) 12%, transparent);
  color: var(--accent, var(--sp-accent));
}

/* ----------------------------------------------------------------------------
   40.7 · Route cards

   Brief item 21: informational pages end by routing into the products rather
   than with another technical explanation. The whole card is the link, so the
   target is the card and not a four-word phrase inside it.
   -------------------------------------------------------------------------- */
.page--site .route-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--space-16);
}

.page--site .route-card {
  --card-current-padding: var(--space-28);
  display: grid;
  gap: var(--space-8);
  align-content: start;
  color: inherit;
}

/* The call to action is the same secondary button the homepage product rows
   use, so the two places that route into a product look like one pattern. It
   sits on the floor of the card, which lines four of them up regardless of how
   long each blurb runs. */
.page--site .route-card .button {
  justify-self: start;
  margin-block-start: var(--space-12);
}

/* Same proportions as the nav's product tile: a 32px box at --radius-md
   holding a 16px icon. --radius-card is the panel radius and made these read
   as rounded squares rather than as marks beside a heading. */
.page--site .route-card__icon {
  display: grid;
  place-items: center;
  width: var(--space-32);
  height: var(--space-32);
  margin-block-end: var(--space-8);
  border: 1px solid color-mix(in srgb, var(--accent, var(--sp-accent)) 26%, transparent);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--accent, var(--sp-accent)) 12%, transparent);
  color: var(--accent, var(--sp-accent));
}

.page--site .route-card__audience {
  color: var(--accent, var(--sp-accent));
  font-family: var(--skillprint-font-mono);
  font-size: var(--text-xxs-font-size);
  line-height: var(--text-xxs-line-height);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

.page--site .route-card__title {
  color: var(--surface-text);
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
  font-weight: var(--weight-regular);
}

.page--site .route-card__blurb {
  font-size: var(--text-md-font-size);
  line-height: var(--text-md-line-height);
}


/* The arrow travels on hover. The card already lightens its border from
   .sp-card--interactive; this is the only thing added. */


/* ----------------------------------------------------------------------------
   40.8 · Badge chips

   The animal and achievement badges are the part of the product people
   actually screenshot, so they appear at their own size rather than inside a
   card.
   -------------------------------------------------------------------------- */
.page--site .badge-row { gap: var(--space-24); }

.page--site .badge-chip {
  display: grid;
  justify-items: center;
  gap: var(--space-10);
  width: var(--space-128);
  text-align: center;
}

.page--site .badge-chip img { width: var(--space-96); height: auto; }

.page--site .badge-chip figcaption {
  color: var(--text-primary);
  font-size: var(--font-size-xs);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-semibold);
}

/* The talent is the badge; the animal underneath is its identity, one step
   quieter so the pair reads as one caption rather than two labels. */

.page--site .badge-chip figcaption span {
  margin-top: var(--space-2);
  color: var(--quiet);
  font-weight: var(--weight-medium);
}

/* ----------------------------------------------------------------------------
   40.9 · Contact

   Brief item 7: Contact is a conversion destination, not an afterthought. The
   form takes the wider column and the routed enquiries sit beside it, so a
   visitor who knows which team they want never has to use the form at all.
   -------------------------------------------------------------------------- */
.page--site .contact-form-panel { padding: var(--space-40); }

.page--site .contact-form-panel h2 {
  font-size: var(--display-xs-font-size);
  line-height: var(--display-xs-line-height);
  letter-spacing: -0.02em;
}

.page--site .contact-form-intro { margin: var(--space-8) 0 var(--space-28); }

.page--site .contact-form { gap: var(--space-20); }

.page--site .contact-form__row {
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--space-20);
}

.page--site .contact-form__actions { margin-block-start: var(--space-4); }

.page--site .contact-form__note { color: var(--quiet); }

/* The field error sits in the .field grid, so it lands under its own control
   at the stack's own gap. Pink is the tone [aria-invalid="true"] already puts
   on the border, so the message and the field it belongs to read as one. */
.field__error { color: var(--skills-pink-500); }

/* The status line under the send. `critical` is the validation summary,
   `positive` the confirmation; any other tone falls through to the inherited
   colour on purpose, so a neutral progress message needs no rule of its own. */
.page--site .contact-form__status[data-tone="critical"] { color: var(--skills-pink-500); }

.page--site .contact-form__status[data-tone="positive"] { color: var(--positive-text); }

.page--site .contact-routes { gap: var(--space-16); align-content: start; }

.page--site .contact-route {
  --card-current-padding: var(--space-24);
  display: grid;
  gap: var(--space-8);
  align-content: start;
}

.page--site .contact-route h3 {
  margin: 0;
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
}

.page--site .contact-route__icon {
  display: grid;
  place-items: center;
  width: var(--space-40);
  height: var(--space-40);
  margin-block-end: var(--space-4);
  border: 1px solid color-mix(in srgb, var(--accent, var(--sp-accent)) 26%, transparent);
  border-radius: var(--radius-card);
  background: color-mix(in srgb, var(--accent, var(--sp-accent)) 12%, transparent);
  color: var(--accent, var(--sp-accent));
}

.page--site .contact-route__link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
  margin-block-start: var(--space-4);
  color: var(--accent, var(--sp-accent));
  font-size: var(--text-md-font-size);
  font-weight: var(--weight-semibold);
  overflow-wrap: anywhere;
}

/* The hero above the form is the page's whole introduction, so the section
   under it does not repeat the top padding a section normally carries. */
.page--site .page--contact-form-section { padding-block-start: 0; }

@media (max-width: 1023px) {
  .page--site .contact-form-panel { padding: var(--space-28); }
}

/* ----------------------------------------------------------------------------
   40.10 · The live session in the hero

   Brief item 4: the decision window has to read as something being watched
   right now, not as a caption. The script drives every value in the mock from
   one timer, so the number, the label, the focus score and the ping in the
   game view all move on the same beat - which is what makes it read as one
   observation rather than four animations.

   Paint only. The values and their cadence live in js/skillprint.js.
   -------------------------------------------------------------------------- */

/* The event chip lifts for a moment as a new reading lands. */
.page--site .game-event {
  transition: border-color var(--duration-slow) var(--ease-standard),
              background-color var(--duration-slow) var(--ease-standard);
}

.page--site [data-live-event].is-reading {
  border-color: color-mix(in srgb, var(--sp-accent) 42%, transparent);
  background: color-mix(in srgb, var(--sp-accent) 10%, var(--surface-box));
}

/* The millisecond figure is the thing the eye should land on, so it is fixed
   width: a proportional 96 next to a 184 makes the whole chip jump sideways
   every time the value changes, which reads as a layout bug rather than as a
   live reading. */
.page--site .event-score {
  font-family: var(--skillprint-font-mono);
  font-variant-numeric: tabular-nums;
  transition: opacity var(--duration-fast) var(--ease-standard);
}

.page--site [data-live-event].is-reading .event-score { opacity: 1; }

/* `.page--homepage .game-event span` sets every span in the chip to 10px mono,
   which is right for the caption under the label and wrong for the reading
   itself. Same specificity, stated later, so the value keeps the score's size. */
.page--site .event-score [data-live-ms] {
  display: inline-block;
  min-width: 3ch;
  font: inherit;
  text-align: right;
}

.page--site :is([data-live-clock], [data-live-frame]) { font-variant-numeric: tabular-nums; }

/* The ping marks the moment on the game view itself: it expands once from the
   target and fades, so the number in the chip has a visible cause.

   It is a child of .target rather than a sibling because the target is on its
   own keyframe animation across the view. Positioned independently it fired
   wherever the markup put it, which was next to the player and never where
   the decision was - a ripple with no relationship to the thing that caused
   it. Inside the target it inherits the movement for free. */
.page--site .decision-ping {
  inset-block-start: 50%;
  inset-inline-start: 50%;
  width: var(--space-28);
  height: var(--space-28);
  margin: calc(var(--space-28) / -2) 0 0 calc(var(--space-28) / -2);
  border: 1px solid var(--sp-accent);
  opacity: 0;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .page--site [data-live-session].is-reading .decision-ping {
    animation: sp-decision-ping 900ms var(--ease-standard) 1;
  }
}

@keyframes sp-decision-ping {
  from { opacity: 0.9; transform: scale(0.6); }
  to   { opacity: 0;   transform: scale(4.2); }
}

/* The focus score counts rather than jumps, so the bar under it has to follow
   at the same rate. */
.page--site .score-bar > i {
  transition: width var(--duration-slow) var(--ease-standard);
}

.page--site [data-live-focus] { font-variant-numeric: tabular-nums; }

/* ----------------------------------------------------------------------------
   40.11 · A heading that is an h3 doing an h2's job

   Inside a section that already has its own h2, the second-level heading is
   still the visual heading of its column. This keeps the document outline
   honest without shrinking the type.
   -------------------------------------------------------------------------- */
.page--site .display-heading {
  margin: var(--space-12) 0 var(--space-16);
  font-size: var(--display-xs-font-size);
  line-height: var(--display-xs-line-height);
  letter-spacing: -0.02em;
}

@media (max-width: 767px) {
  .page--site .badge-row { gap: var(--space-16); }
  .page--site .badge-row { gap: var(--space-12); }

  .page--site .badge-chip { width: var(--space-104); }

  .page--site .badge-chip img { width: var(--space-88); }
  /* Text XL, not the --text-2xl-* this asked for: no such token has ever
     existed in the scale, so both declarations were invalid at computed-value
     time and these headings inherited their parent's size on a phone instead
     of stepping down. The base here is Display XS 24/32, and Text XL 20/30 is
     the step below it - the same step .portal-head h1 and .modal-title take on
     a small viewport. */
  .page--site .contact-form-panel h2,
  .page--site .display-heading {
    font-size: var(--text-xl-font-size);
    line-height: var(--text-xl-line-height);
  }
}


/* ==========================================================================
   09 - Unique composition · Mobile Behaviours
   The specimen page for everything that only exists on a phone: the touch
   target floor, the safe area, the viewport unit that lies, the hover state
   that never arrives, and the six gestures the portal is allowed to use.
   Every other page in the system can be judged on a desktop; this one is
   built to be held, so it measures the device it is opened on rather than
   describing one in prose.

   It is also the only page that has to practise what it documents. The rules
   below use dvh rather than vh, pad off the safe area, gate every hover
   behind a hover-capable pointer, and put a 44px floor under their own
   controls - a specimen that failed its own audit would be worse than no
   specimen at all.
   ========================================================================== */

.page--mobile-behaviours {
  /* The gesture constants live here as well as in the script. The script is
     the authority - it prints these numbers into section 05 at runtime - but
     the transforms below need them at paint time, and a threshold that is a
     token rather than a literal can be retuned in one place. */
  --mb-commit-ratio  : .25;
  --mb-pull-trigger  : 64px;
  --mb-pull-cap      : 96px;
  --mb-press-ms      : 500ms;
  --mb-target-floor  : var(--space-44);

  /* --sp-accent is declared only on the marketing site's page classes, so it
     resolves to nothing here and every declaration that named it was dropped
     as invalid - which is why the hit-area rings painted no border at all.
     A page that borrows the hook has to declare it. Violet is the portal's
     action colour, so the demos read as product rather than decoration. */
  --sp-accent        : var(--mindset-violet-500);
  --sp-accent-2      : var(--core-green-500);

  /* Set by the script while a finger is down. Nothing here transitions on
     these: what a finger is holding must track one to one. */
  --mb-swipe-x       : 0px;
  --mb-pull-y        : 0px;
  --mb-pull-progress : 0;
  --mb-sheet-y       : 0px;
  --mb-press-progress: 0;

  /* --deep and --text, not --deep-navy and --primary-text: the first pair are
     the theme-aware aliases the light override remaps, the second pair are
     the dark literals. Naming the literals painted a permanently dark page
     under a light-themed heading colour and made the h1 invisible. */
  background: var(--deep);
  color: var(--text);
}

/* --- shell and rhythm ----------------------------------------------------- */

.page--mobile-behaviours .mb-shell {
  gap: var(--spacing-5xl);
  padding-block: var(--spacing-5xl) var(--spacing-7xl);
  /* The page can be read in landscape on a notched device, where the inline
     insets are the ones that bite. */
  padding-inline: max(var(--spacing-2xl), env(safe-area-inset-left))
                  max(var(--spacing-2xl), env(safe-area-inset-right));
}

/* The wordmark carries no intrinsic size, so every page that mounts .sp-nav
   states one. Left unset it lays out at the SVG's natural 494px and takes the
   whole nav with it - which is how this page came to fail the very overflow
   check it documents. */
.page--mobile-behaviours .sp-nav__brand .brand-logo { width: 158px; }

.page--mobile-behaviours .mb-intro { gap: var(--spacing-lg); }

.page--mobile-behaviours .mb-title {
  font-size: clamp(32px, 7vw, 56px);
  line-height: 1.08;
  letter-spacing: -0.02em;
  max-width: 18ch;
}

.page--mobile-behaviours .mb-lead { max-width: 62ch; }

.page--mobile-behaviours .mb-section-head { gap: var(--spacing-sm); }

.page--mobile-behaviours .mb-module {
  display: grid;
  gap: var(--spacing-2xl);
  padding: var(--spacing-3xl);
}

/* A grid item defaults to min-width: auto, so it refuses to shrink below its
   content. The snap rail is seven buttons wide intrinsically, which sized the
   whole column to 533px and pushed the page into horizontal scroll - the one
   failure this page exists to argue against. Every scrolling child needs the
   floor removed, not just the rail. */
.page--mobile-behaviours .mb-module > * { min-width: 0; }

.page--mobile-behaviours .mb-module__head { display: grid; gap: var(--spacing-sm); }

.page--mobile-behaviours .mb-module__title {
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
  letter-spacing: -0.01em;
  max-width: 30ch;
}

.page--mobile-behaviours .mb-module__title--sm {
  font-size: var(--text-md-font-size);
  line-height: var(--text-md-line-height);
}

.page--mobile-behaviours .mb-module__note { max-width: 68ch; }

.page--mobile-behaviours .mb-module code {
  font-family: var(--font-mono);
  font-size: .92em;
  padding: 1px 5px;
  border-radius: var(--radius-xs);
  background: var(--surface-raised);
}

/* The live readout under each demo. It is deliberately the plainest thing on
   the page: a gesture is judged by its numbers, and the numbers should not
   have to compete with the specimen for attention. */
.page--mobile-behaviours .mb-meter {
  padding: var(--spacing-xs) var(--spacing-sm);
  border-radius: var(--radius-xs);
  background: var(--surface-raised);
  font-variant-numeric: tabular-nums;
}

.page--mobile-behaviours .mb-demo__controls { gap: var(--spacing-md); }

/* --- 01 environment readout ---------------------------------------------- */

.page--mobile-behaviours .mb-readout {
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}

.page--mobile-behaviours .mb-readout__item {
  padding: var(--spacing-lg);
  align-content: start;
}

.page--mobile-behaviours .mb-readout__value {
  font-size: var(--text-md-font-size);
  line-height: var(--text-md-line-height);
  font-variant-numeric: tabular-nums;
  word-break: break-word;
}

.page--mobile-behaviours .mb-readout__item[data-verdict="warn"] {
  border-color: color-mix(in srgb, var(--skills-orange-500) 45%, var(--surface-border));
  background: color-mix(in srgb, var(--skills-orange-500) 8%, transparent);
}

html:is([data-theme="light"], [data-surface="light"])
  .page--mobile-behaviours .mb-readout__item[data-verdict="warn"] {
  border-color: color-mix(in srgb, var(--skills-orange-600) 45%, var(--surface-border));
  background: color-mix(in srgb, var(--skills-orange-600) 8%, transparent);
}

.page--mobile-behaviours .mb-readout__item[data-verdict="ok"] {
  border-color: color-mix(in srgb, var(--success) 40%, var(--surface-border));
}

/* --- 02 touch targets ----------------------------------------------------- */

.page--mobile-behaviours .mb-toggle-row { padding: var(--spacing-lg); }
.page--mobile-behaviours .mb-toggle-copy { cursor: pointer; }

.page--mobile-behaviours .mb-targets__group { align-content: start; }

/* Each specimen is drawn at the height it ships at, so the row is a true
   sample rather than an illustration. */
.page--mobile-behaviours .mb-target {
  position: relative;
  display: inline-flex;
  align-items: center;
  padding-inline: var(--spacing-md);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-control);
  background: var(--surface-raised);
  color: inherit;
  font: 500 var(--text-sm-font-size) / 1 var(--font-ui);
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: var(--transition-interactive);
}

.page--mobile-behaviours .mb-target--22 { height: 22px; }
.page--mobile-behaviours .mb-target--24 { height: 24px; }
.page--mobile-behaviours .mb-target--32 { height: 32px; }
.page--mobile-behaviours .mb-target--36 { height: 36px; }
.page--mobile-behaviours .mb-target--38 { height: 38px; }
.page--mobile-behaviours .mb-target--40 { height: 40px; }
.page--mobile-behaviours .mb-target--42 { height: 42px; }
.page--mobile-behaviours .mb-target--44 { height: var(--space-44); }
.page--mobile-behaviours .mb-target--48 { height: var(--space-48); }

.page--mobile-behaviours .mb-target.is-hit {
  border-color: var(--sp-accent);
  background: color-mix(in srgb, var(--sp-accent) 16%, transparent);
}

/* The floor, exactly as it would ship: a centred pseudo-element that raises
   the hit area to 44px without touching the drawn box. Growing the element
   itself would relayout every row it appears in, which is why none of these
   were fixed by simply setting a min-height.

   The dashed edge is on the pseudo-element rather than an outline on the
   button, because outline-offset cannot take a percentage and the offset
   needed here is half the difference between two heights. Drawing the real
   hit area is also the more honest illustration. */
.page--mobile-behaviours .mb-targets.is-floored .mb-target::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  translate: 0 -50%;
  min-height: var(--mb-target-floor);
  height: 100%;
  border: 1px dashed color-mix(in srgb, var(--sp-accent) 55%, transparent);
  border-radius: var(--radius-control);
}

/* --- 03 hover versus tap -------------------------------------------------- */

.page--mobile-behaviours .mb-hover-pair {
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
}

.page--mobile-behaviours .mb-hover-card { overflow: hidden; }

.page--mobile-behaviours .mb-hover-card__stage {
  aspect-ratio: 16 / 10;
  display: grid;
  background: var(--surface-raised);
}

.page--mobile-behaviours .mb-hover-card__static,
.page--mobile-behaviours .mb-hover-card__live {
  grid-area: 1 / 1;
  color: var(--surface-text-muted);
  transition: opacity var(--duration-base) var(--ease-standard);
}

.page--mobile-behaviours .mb-hover-card__live {
  opacity: 0;
  color: var(--deep-navy);
  background:
    repeating-linear-gradient(
      135deg,
      color-mix(in srgb, var(--sp-accent) 85%, transparent) 0 10px,
      color-mix(in srgb, var(--sp-accent) 55%, transparent) 10px 20px);
}

.page--mobile-behaviours .mb-hover-card.is-art-animating .mb-hover-card__static { opacity: 0; }
.page--mobile-behaviours .mb-hover-card.is-art-animating .mb-hover-card__live { opacity: 1; }

/* The whole point of the pair: this rule only exists where hover does. On a
   phone the left card has no way in at all, which is what the demo is for. */
@media (hover: hover) and (pointer: fine) {
  .page--mobile-behaviours .mb-hover-card:hover .mb-hover-card__static { opacity: 0; }
  .page--mobile-behaviours .mb-hover-card:hover .mb-hover-card__live { opacity: 1; }
}

.page--mobile-behaviours .mb-hover-card__peek {
  position: absolute;
  right: var(--spacing-sm);
  bottom: var(--spacing-sm);
}

/* The tap path is only worth showing where hover is missing. Where a mouse
   exists the hover reveal already covers it, and a second control would be
   noise. */
@media (hover: hover) and (pointer: fine) {
  .page--mobile-behaviours .mb-hover-card__peek { display: none; }
}

.page--mobile-behaviours .mb-hover-card__body { padding: var(--spacing-lg); }
.page--mobile-behaviours .mb-hover-card__verdict { justify-self: start; }

/* --- 04.1 swipe to action ------------------------------------------------- */

.page--mobile-behaviours .mb-swipe { background: var(--surface-raised); }

.page--mobile-behaviours .mb-swipe__actions {
  position: absolute;
  inset: 0;
  left: auto;
}

.page--mobile-behaviours .mb-swipe__action {
  width: 88px;
  padding-inline: var(--spacing-sm);
  text-align: center;
  color: var(--deep-navy);
}

.page--mobile-behaviours .mb-swipe__action--save { background: var(--sp-accent-2); }
.page--mobile-behaviours .mb-swipe__action--hide {
  background: color-mix(in srgb, var(--danger) 82%, white);
  color: white;
}

.page--mobile-behaviours .mb-swipe__card {
  position: relative;
  padding: var(--spacing-lg);
  background: var(--surface-panel);
  translate: var(--mb-swipe-x) 0;
  touch-action: pan-y;
  -webkit-tap-highlight-color: transparent;
  cursor: grab;
}

/* Only the release is animated. While .is-dragging is on, the card is
   wherever the finger is, with no transition to lag behind it. */
.page--mobile-behaviours .mb-swipe__card.is-settling {
  transition: translate var(--duration-base) var(--ease-standard);
}

.page--mobile-behaviours .mb-swipe__art {
  width: 44px;
  height: 44px;
  background:
    radial-gradient(circle at 30% 30%,
      color-mix(in srgb, var(--sp-accent) 90%, transparent),
      color-mix(in srgb, var(--violet-400) 70%, transparent));
}

/* --- 04.2 long press ------------------------------------------------------ */

.page--mobile-behaviours .mb-press {
  padding: var(--spacing-4xl) var(--spacing-2xl);
  justify-items: center;
  gap: var(--spacing-sm);
  background: var(--surface-raised);
  text-align: center;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}

.page--mobile-behaviours .mb-press__ring {
  width: 56px;
  height: 56px;
  border-radius: var(--radius-full);
  border: 2px solid var(--surface-border);
  overflow: hidden;
}

/* The ring fills in step with the timer rather than on a fixed animation, so
   a press that is released early leaves the fill exactly where the finger
   left it - the cancel is visible, not just silent. */
.page--mobile-behaviours .mb-press__fill {
  width: 100%;
  height: 100%;
  background: var(--sp-accent);
  transform-origin: 50% 100%;
  scale: 1 var(--mb-press-progress);
}

.page--mobile-behaviours .mb-press.is-armed { border-color: var(--sp-accent); }

/* --- 04.3 pull to refresh ------------------------------------------------- */

.page--mobile-behaviours .mb-pull { background: var(--surface-raised); }

.page--mobile-behaviours .mb-pull__spinner {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: var(--mb-pull-trigger);
  color: var(--surface-text-muted);
  /* Driven by a unitless ratio the script sets, not by dividing --mb-pull-y
     with --mb-pull-trigger: calc() cannot divide one length by another, and
     the whole declaration would be dropped. */
  opacity: var(--mb-pull-progress, 0);
}

.page--mobile-behaviours .mb-pull.is-refreshing .mb-pull__spinner .sp-icon {
  animation: mb-spin var(--duration-slowest) linear infinite;
}

.page--mobile-behaviours .mb-pull__panel {
  position: relative;
  padding: var(--spacing-2xl);
  background: var(--surface-panel);
  translate: 0 var(--mb-pull-y);
  touch-action: pan-x;
}

.page--mobile-behaviours .mb-pull__panel.is-settling {
  transition: translate var(--duration-base) var(--ease-standard);
}

.page--mobile-behaviours .mb-pull__stats { gap: var(--spacing-2xl); }
.page--mobile-behaviours .mb-pull__stat strong { font-size: var(--text-lg-font-size); }

@keyframes mb-spin { to { rotate: 360deg; } }

/* --- 04.4 the sheet ------------------------------------------------------- */

.page--mobile-behaviours .mb-sheet-backdrop,
.page--mobile-behaviours .mb-peek-backdrop {
  background: rgb(9 13 39 / 55%);
  opacity: 0;
  transition: opacity var(--duration-base) var(--ease-standard);
  z-index: 60;
}

.page--mobile-behaviours .mb-sheet-backdrop.is-open,
.page--mobile-behaviours .mb-peek-backdrop.is-open { opacity: 1; }

.page--mobile-behaviours .mb-sheet {
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 61;
  gap: var(--spacing-md);
  max-height: 88dvh;
  padding: var(--spacing-sm) var(--spacing-2xl)
           max(var(--spacing-2xl), env(safe-area-inset-bottom));
  border-radius: var(--radius-panel) var(--radius-panel) 0 0;
  background: var(--surface-panel);
  border-top: 1px solid var(--surface-border);
  box-shadow: 0 -18px 48px rgb(9 13 39 / 32%);
  translate: 0 100%;
  align-content: start;
  overflow: hidden;
}

.page--mobile-behaviours .mb-sheet.is-open { translate: 0 var(--mb-sheet-y); }

.page--mobile-behaviours .mb-sheet.is-settling {
  transition: translate var(--duration-slow) var(--ease-standard);
}

/* The grab handle is a real button so the sheet can be resized and dismissed
   from the keyboard as well as the thumb, and it carries the 44px floor in
   padding rather than in the bar people actually see. */
.page--mobile-behaviours .mb-sheet__grab {
  display: grid;
  place-items: center;
  width: 100%;
  min-height: var(--space-44);
  padding: 0;
  border: 0;
  background: none;
  cursor: grab;
  touch-action: none;
  -webkit-tap-highlight-color: transparent;
}

.page--mobile-behaviours .mb-sheet__grab-bar {
  width: 40px;
  height: 4px;
  background: var(--surface-border-hover);
}

.page--mobile-behaviours .mb-sheet__body { overflow-y: auto; overscroll-behavior: contain; }

/* --- the long-press peek -------------------------------------------------- */

.page--mobile-behaviours .mb-peek {
  left: 50%;
  bottom: max(var(--spacing-2xl), env(safe-area-inset-bottom));
  z-index: 61;
  width: min(420px, calc(100vw - var(--spacing-3xl)));
  translate: -50% 8px;
  padding: var(--spacing-2xl);
  border-radius: var(--radius-panel);
  background: var(--surface-panel);
  border: 1px solid var(--surface-border);
  box-shadow: 0 -18px 48px rgb(9 13 39 / 32%);
  opacity: 0;
  transition:
    opacity var(--duration-base) var(--ease-standard),
    translate var(--duration-base) var(--ease-standard);
}

.page--mobile-behaviours .mb-peek.is-open { opacity: 1; translate: -50% 0; }

/* --- 04.5 paged panels ---------------------------------------------------- */

.page--mobile-behaviours .mb-pager__tabs { justify-self: start; }

.page--mobile-behaviours .mb-pager { background: var(--surface-raised); }

.page--mobile-behaviours .mb-pager__track {
  translate: calc(var(--mb-pager-index, 0) * -100%) 0;
  touch-action: pan-y;
}

.page--mobile-behaviours .mb-pager__track.is-settling {
  transition: translate var(--duration-base) var(--ease-standard);
}

.page--mobile-behaviours .mb-pager__panel {
  flex: 0 0 100%;
  min-width: 100%;
  padding: var(--spacing-2xl);
  align-content: start;
}

.page--mobile-behaviours .mb-score__track {
  display: block;
  height: 6px;
  background: var(--surface-border);
  overflow: hidden;
}

.page--mobile-behaviours .mb-score__fill {
  display: block;
  height: 100%;
  background: var(--sp-accent);
}

.page--mobile-behaviours .mb-score__fill--34 { width: 34%; }
.page--mobile-behaviours .mb-score__fill--52 { width: 52%; }
.page--mobile-behaviours .mb-score__fill--58 { width: 58%; }
.page--mobile-behaviours .mb-score__fill--71 { width: 71%; }
.page--mobile-behaviours .mb-score__fill--72 { width: 72%; }
.page--mobile-behaviours .mb-score__fill--84 { width: 84%; }

/* --- 04.6 the snap rail --------------------------------------------------- */

/* Everything this demo needs is CSS. The mask is what turns "clipped" into
   "scrollable" - without it the row ends on a hard edge and reads as a
   layout mistake rather than an invitation. */
.page--mobile-behaviours .mb-rail__track {
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: var(--spacing-2xl);
  padding-block: var(--spacing-xs);
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-inline: contain;
  mask-image: linear-gradient(
    to right, transparent 0, black 18px,
    black calc(100% - 28px), transparent 100%);
}

.page--mobile-behaviours .mb-rail__track > .filter-button {
  scroll-snap-align: start;
  flex: 0 0 auto;
}

/* --- 05 the numbers ------------------------------------------------------- */

.page--mobile-behaviours .mb-table-scroll {
  overflow-x: auto;
  overscroll-behavior-inline: contain;
}

.page--mobile-behaviours .mb-table {
  width: 100%;
  min-width: 520px;
  border-collapse: collapse;
  text-align: left;
}

.page--mobile-behaviours .mb-table__caption {
  text-align: left;
  padding-bottom: var(--spacing-sm);
}

.page--mobile-behaviours .mb-table :is(th, td) {
  padding: var(--spacing-md) var(--spacing-lg) var(--spacing-md) 0;
  border-bottom: 1px solid var(--surface-border);
  vertical-align: top;
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

.page--mobile-behaviours .mb-table thead th {
  font-weight: 600;
  color: var(--surface-text-muted);
  font-size: var(--text-xs-font-size, 12px);
  text-transform: uppercase;
  letter-spacing: .06em;
}

.page--mobile-behaviours .mb-table tbody th { font-weight: 500; white-space: nowrap; }

.page--mobile-behaviours .mb-foot {
  padding-top: var(--spacing-2xl);
  border-top: 1px solid var(--surface-border);
}

/* --- the floor this page puts under itself -------------------------------- */

/* Section 02 argues for this rule; the page has to be subject to it. */
@media (pointer: coarse) {
  .page--mobile-behaviours :is(.button, .button-group__item) {
    min-height: var(--space-44);
  }

  /* A switch is a fixed 44x24 by design, so raising its min-height turned it
     into a 44px circle. It gets the floor the way section 02 recommends
     instead - a centred pseudo-element over the drawn control - which leaves
     the switch itself exactly the size the component intends. */
  .page--mobile-behaviours .sp-toggle {
    position: relative;
  }

  .page--mobile-behaviours .sp-toggle::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    translate: 0 -50%;
    height: var(--space-44);
  }
}

/* --- responsive ----------------------------------------------------------- */

@media (max-width: 767px) {
  .page--mobile-behaviours .mb-shell {
    gap: var(--spacing-4xl);
    padding-block: var(--spacing-3xl) var(--spacing-5xl);
  }
  .page--mobile-behaviours .mb-module { padding: var(--spacing-2xl); }
  .page--mobile-behaviours .sp-nav__brand .brand-logo { width: 124px; }
  .page--mobile-behaviours .mb-nav-note { display: none; }
  .page--mobile-behaviours .mb-toggle-row { align-items: flex-start; }
  .page--mobile-behaviours .mb-pull__stats { gap: var(--spacing-xl); }
}

@media (max-width: 480px) {
  .page--mobile-behaviours .mb-readout { grid-template-columns: 1fr; }
  .page--mobile-behaviours .mb-hover-pair { grid-template-columns: 1fr; }
  .page--mobile-behaviours .mb-swipe__action { width: 76px; }
}

/* --- reduced motion ------------------------------------------------------- */

/* A gesture is an input, not an effect. Everything here still commits with
   motion turned off; only the settle animation and the spinner go away. */
@media (prefers-reduced-motion: reduce) {
  .page--mobile-behaviours :is(
    .mb-swipe__card,
    .mb-pull__panel,
    .mb-pager__track,
    .mb-sheet,
    .mb-peek,
    .mb-hover-card__static,
    .mb-hover-card__live,
    .mb-sheet-backdrop,
    .mb-peek-backdrop).is-settling,
  .page--mobile-behaviours :is(
    .mb-swipe__card,
    .mb-pull__panel,
    .mb-pager__track,
    .mb-sheet,
    .mb-peek,
    .mb-hover-card__static,
    .mb-hover-card__live,
    .mb-sheet-backdrop,
    .mb-peek-backdrop) {
    transition-duration: var(--duration-instant);
  }
  .page--mobile-behaviours .mb-pull.is-refreshing .mb-pull__spinner .sp-icon {
    animation: none;
  }
}

/* ==========================================================================
   Icon library — Icon_Library.html
   Badge and game icons: 79 marks in outline, solid and colour. Outline
   and solid arrive as black artwork, so they are inverted on dark surfaces
   rather than shipped twice.
   ========================================================================== */

/* .brand-logo carries no default width, so each nav page states its own.
   158px is what the other .sp-nav pages use; 124px below the routes' 768px
   breakpoint, matching Mobile_Behaviours. */
.page--icon-library .sp-nav__brand .brand-logo {
  width: 158px;
}

@media (max-width: 767px) {
  .page--icon-library .sp-nav__brand .brand-logo { width: 124px; }
}

.page--icon-library .badge-main {
  padding-block: var(--spacing-4xl) var(--spacing-6xl);
}

.page--icon-library .badge-hero .lead {
  max-width: 64ch;
}

.page--icon-library .badge-legend,
.page--icon-library .badge-notes {
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: var(--spacing-xl);
  margin-block-start: var(--spacing-2xl);
}

.page--icon-library .badge-legend__item {
  display: grid;
  gap: var(--spacing-xs);
}

.page--icon-library .badge-notes .sp-card {
  display: grid;
  gap: var(--spacing-sm);
  align-content: start;
}

/* The table is the only thing on the page allowed to scroll sideways. */
.page--icon-library .badge-table__scroll {
  overflow-x: auto;
  margin-block-start: var(--spacing-2xl);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-card);
  background: var(--surface-panel);
}

.page--icon-library .badge-table {
  width: 100%;
  min-width: 46rem;
  border-collapse: collapse;
}

.page--icon-library .badge-table :is(th, td) {
  padding: var(--spacing-md) var(--spacing-lg);
  text-align: left;
  vertical-align: middle;
  border-block-end: 1px solid var(--surface-border);
}

.page--icon-library .badge-table thead th {
  font-size: var(--font-size-xs);
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--surface-text-muted);
  white-space: nowrap;
}

.page--icon-library .badge-table tbody tr:last-child :is(th, td) {
  border-block-end: 0;
}

.page--icon-library .badge-num {
  width: 1%;
  font-variant-numeric: tabular-nums;
}

.page--icon-library .badge-name {
  font-weight: 600;
  white-space: nowrap;
}

.page--icon-library .badge-animal,
.page--icon-library .badge-tint {
  white-space: nowrap;
}

.page--icon-library .badge-note {
  margin-inline-start: var(--spacing-xs);
  font-size: var(--font-size-xs);
  color: var(--surface-text-muted);
}

.page--icon-library .badge-cell {
  width: 5.5rem;
  text-align: center;
}

.page--icon-library .badge-art {
  width: var(--spacing-4xl);
  height: var(--spacing-4xl);
  display: inline-block;
}

/* Outline and solid artwork is black; flip it for the dark surface and leave
   it alone on light. The colour set is already on-brand and never inverts. */
.page--icon-library .badge-ink {
  filter: invert(1);
}

html:is([data-theme="light"], [data-surface="light"]) .page--icon-library .badge-ink {
  filter: none;
}

/* ==========================================================================
   Badge library — Badge_Library.html
   The complete achievement collection: 29 animals, one assigned colour each,
   across six ranked polygons. The artwork is fully outlined vector with its
   own field colour baked in, so unlike the icon library nothing here inverts
   between surfaces — a badge looks the same on either ground.
   ========================================================================== */

.page--badge-library .sp-nav__brand .brand-logo { width: 158px; }

@media (max-width: 767px) {
  .page--badge-library .sp-nav__brand .brand-logo { width: 124px; }
}

.page--badge-library .bl-main { padding-bottom: var(--space-96); }
.page--badge-library .bl-hero .lead { max-width: 74ch; }

.page--badge-library .bl-legend,
.page--badge-library .bl-notes {
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: var(--space-16);
  margin-top: var(--space-32);
}

.page--badge-library .bl-legend__item { display: grid; gap: var(--space-6); }

/* The rank ladder. Sized so the six sit on one line on a laptop and wrap in
   pairs below it, rather than shrinking to where the polygon stops reading. */
.page--badge-library .bl-ranks {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: var(--space-20);
  margin-top: var(--space-32);
}

.page--badge-library .bl-rank { margin: 0; display: grid; gap: var(--space-10); }
.page--badge-library .bl-rank__art { width: 100%; height: auto; aspect-ratio: 1; display: block; }
.page--badge-library .bl-rank figcaption { display: grid; gap: 2px; text-align: center; }
.page--badge-library .bl-rank strong { font-size: var(--font-size-sm); text-transform: capitalize; }
.page--badge-library .bl-rank span { font: 500 var(--font-size-xs)/1.3 var(--font-mono); }

/* One row per animal: identity on the left, its six ranks on the right. */
.page--badge-library .bl-collection { display: grid; margin-top: var(--space-24); }

.page--badge-library .bl-row {
  display: grid;
  grid-template-columns: 210px minmax(0, 1fr);
  gap: var(--space-24);
  align-items: center;
  padding: var(--space-14) 0;
  border-bottom: 1px solid var(--border-subtle);
}

.page--badge-library .bl-row:last-child { border-bottom: 0; }
.page--badge-library .bl-row__id { display: grid; gap: 2px; align-content: center; }
.page--badge-library .bl-row__title { font-size: var(--font-size-md); font-weight: 600; }
.page--badge-library .bl-row__animal { font-size: var(--font-size-sm); }

.page--badge-library .bl-swatch {
  display: inline-flex;
  align-items: center;
  gap: var(--space-6);
  margin-top: var(--space-4);
  color: var(--text-muted);
  font: 500 var(--font-size-xs)/1.4 var(--font-mono);
}

.page--badge-library .bl-swatch i {
  width: 10px;
  height: 10px;
  border-radius: var(--radius-xs);
  border: 1px solid var(--border-subtle);
}

/* The eight badge-family tints, named after the collection's colour slugs.
   Shared by the badge library's swatch rows and the design system's family
   dots, so the hex lives here once instead of inline per instance. */
:is(.page--badge-library, .page--design-system) .family-tint--cool-green { background: var(--core-green-500); }
:is(.page--badge-library, .page--design-system) .family-tint--cool-lime { background: var(--core-lime-500); }
:is(.page--badge-library, .page--design-system) .family-tint--mindset-violet { background: var(--mindset-violet-500); }
:is(.page--badge-library, .page--design-system) .family-tint--mindset-magenta { background: var(--mindset-magenta-500); }
:is(.page--badge-library, .page--design-system) .family-tint--personality-blue { background: var(--personality-blue-500); }
:is(.page--badge-library, .page--design-system) .family-tint--personality-mint { background: var(--personality-mint-500); }
:is(.page--badge-library, .page--design-system) .family-tint--skills-pink { background: var(--skills-pink-500); }
:is(.page--badge-library, .page--design-system) .family-tint--skills-orange { background: var(--skills-orange-500); }

.page--badge-library .bl-row__set {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: var(--space-12);
}

.page--badge-library .bl-badge { margin: 0; display: grid; gap: var(--space-6); }
.page--badge-library .bl-badge__art { width: 100%; height: auto; aspect-ratio: 1; display: block; }

.page--badge-library .bl-badge__cap {
  text-align: center;
  text-transform: capitalize;
  color: var(--text-muted);
  font: 500 var(--font-size-xs)/1.3 var(--font-mono);
}

/* Colour groups. Each family is three or four animals, shown at rank 6. */
.page--badge-library .bl-colours { display: grid; gap: var(--space-8); margin-top: var(--space-24); }

.page--badge-library .bl-colour {
  padding: var(--space-18) 0;
  border-bottom: 1px solid var(--border-subtle);
}

.page--badge-library .bl-colour:last-child { border-bottom: 0; }
.page--badge-library .bl-colour__head { display: grid; gap: 2px; margin-bottom: var(--space-14); }

.page--badge-library .bl-colour__items {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(124px, 1fr));
  gap: var(--space-18);
}

.page--badge-library .bl-swatchcard { margin: 0; display: grid; gap: var(--space-8); }
.page--badge-library .bl-swatchcard__art { width: 100%; height: auto; aspect-ratio: 1; display: block; }
.page--badge-library .bl-swatchcard figcaption { display: grid; gap: 1px; text-align: center; }
.page--badge-library .bl-swatchcard strong { font-size: var(--font-size-xs); line-height: 1.3; }
.page--badge-library .bl-swatchcard span { font-size: var(--font-size-xs); line-height: 1.3; }

@media (max-width: 1023px) {
  .page--badge-library .bl-ranks { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .page--badge-library .bl-row { grid-template-columns: 1fr; gap: var(--space-12); }
}

@media (max-width: 639px) {
  .page--badge-library .bl-ranks { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .page--badge-library .bl-row__set { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* The score, in the corner, the way the portal's badge cards carry it. */
.page--badge-library :is(.bl-rank, .bl-badge) { position: relative; }

.page--badge-library .bl-rank__points,
.page--badge-library .bl-badge__points {
  position: absolute;
  top: 0;
  right: 0;
  z-index: 1;
  display: grid;
  place-items: center;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--mindset-violet-500) 18%, var(--panel));
  border: 1px solid color-mix(in srgb, var(--mindset-violet-500) 40%, transparent);
  color: var(--violet);
  font-family: var(--font-mono);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

.page--badge-library .bl-rank__points {
  min-width: var(--space-24);
  height: var(--space-24);
  font-size: var(--font-size-xs);
}

.page--badge-library .bl-badge__points {
  min-width: var(--space-18);
  height: var(--space-18);
  font-size: 9px;
}

.page--badge-library .bl-rank__award {
  color: var(--violet);
  font-weight: var(--weight-semibold);
  font-size: var(--font-size-xs);
}

/* --------------------------------------------------------------------------
   Section 37 · Achievement badges — the collection gallery and rank ladder

   The gallery runs six across so a row carries the whole rank ladder while the
   colour changes, which is the comparison the section exists to make. It steps
   down 6 -> 4 -> 3 before the existing 900px and 620px rules take it to two.
   -------------------------------------------------------------------------- */

.page--design-system .badge-card__meta {
  display: flex;
  align-items: center;
  gap: var(--space-6);
}

.page--design-system .badge-card__dot {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full);
  border: 1px solid var(--border-subtle);
  flex: none;
}

.page--design-system .badge-rank-ladder { grid-template-columns: repeat(6, minmax(0, 1fr)); }

.page--design-system .badge-rank-step { padding: var(--space-16) var(--space-12) var(--space-14); }
.page--design-system .badge-rank-step img { aspect-ratio: 1; object-fit: contain; }

.page--design-system .badge-rank-step figcaption {
  gap: 2px;
  padding: var(--space-10) 0 0;
  text-align: center;
}

@media (max-width: 1200px) {
  .page--design-system .badge-gallery,
  .page--design-system .badge-rank-ladder { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

@media (max-width: 1000px) {
  .page--design-system .badge-gallery,
  .page--design-system .badge-rank-ladder { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* The rank ladder carries its own score. The number sits in the step's corner
   the way it sits in a badge card's corner in the portal, so the two teach the
   same thing in the same place. */
.page--design-system .badge-rank-step { position: relative; }

.page--design-system .badge-rank-step__points {
  position: absolute;
  top: var(--space-10);
  right: var(--space-10);
  min-width: var(--space-24);
  height: var(--space-24);
  display: grid;
  place-items: center;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--mindset-violet-500) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--mindset-violet-500) 38%, transparent);
  color: var(--violet);
  font: var(--weight-semibold) var(--text-xs-font-size)/1 var(--font-mono);
  font-variant-numeric: tabular-nums;
}

.page--design-system .badge-rank-step__award {
  color: var(--violet);
  font-weight: var(--weight-semibold);
}

.page--design-system .badge-points-table { margin-top: var(--space-28); }

/* Narrower than the page's 760px table floor: four short columns do not need
   a scroll bar on a laptop. */
.page--design-system .badge-points { min-width: 480px; width: 100%; }

.page--design-system .badge-points :is(th, td) {
  padding: var(--space-10) var(--space-16);
  border-bottom: 1px solid var(--ui-border);
  text-align: left;
  vertical-align: middle;
}

.page--design-system .badge-points thead th {
  font: var(--weight-semibold) var(--text-xxs-font-size)/1.2 var(--font-mono);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.page--design-system .badge-points tbody tr:last-child :is(th, td) { border-bottom: 0; }
.page--design-system .badge-points td.mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

/* --------------------------------------------------------------------------
   Section 38 · Game artwork — the catalogue galleries and the animated pairs

   The galleries ride the shared .grid-4 track set, so only the drawing's own
   ratio and the caption live here. The pair stack trades the still for the
   animated variant on hover or keyboard focus; reduced motion never shows
   the animated layer, so the still stays the artwork of record.
   -------------------------------------------------------------------------- */

.page--design-system .game-art-card img { aspect-ratio: 380 / 210; object-fit: cover; }

.page--design-system .game-art-card figcaption {
  gap: var(--space-2);
  padding: var(--space-10) var(--space-12) var(--space-12);
}

.page--design-system .game-art-card__meta {
  display: flex;
  align-items: center;
  gap: var(--space-6);
}

.page--design-system .game-art-card__dot {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full);
  border: 1px solid var(--border-subtle);
  flex: none;
}

.page--design-system .game-art-stack { position: relative; aspect-ratio: 380 / 210; }

.page--design-system .game-art-stack img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.page--design-system .game-art-stack .art-animated {
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-standard);
}

@media (hover: hover) {
  .page--design-system .game-art-pair:hover .art-animated { opacity: 1; }
}

.page--design-system .game-art-pair:focus-visible .art-animated { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .page--design-system .game-art-stack .art-animated { display: none; }
}

/* ----------------------------------------------------------------------------
   40.12 · Accent ink · the product colours as text on a light surface

   The category colours are 500-level and were drawn to sit on deep navy, which
   is where they are today. Used as *text* on the light surface they measure
   between 1.1:1 and 2.7:1 - lime at 10px was 1.17:1, which is not a contrast
   problem so much as an invisible label. Left unhandled, switching the surface
   would quietly delete every audience label, tag, eyebrow and section number
   on the page, so the ink is defined now and waits.

   Darkening the accent outright is the wrong fix: the brief keeps the bright
   colours as texture, and they are correct wherever they are a fill, a border,
   a tint or a glow - an icon tile at 12% opacity, the eyebrow dot, the wheel,
   the hero's spotlights. Only the ink has to change.

   So each accent gains a second token. --sp-accent-ink is the same colour
   stepped down its own ramp to the first value that clears 4.5:1 against the
   darkest surface it lands on (the panel, not the page). --sp-accent-ink-lg
   stops one step earlier: display type only has to clear 3:1, and the extra
   step buys back the chroma that makes the headline read as Skillprint green
   rather than as a dark teal.

   On the dark surface both resolve to the accent itself, so nothing changes
   there and no rule needs a light-specific duplicate below.
   -------------------------------------------------------------------------- */
.page--site {
  --sp-accent-ink   : var(--sp-accent);
  --sp-accent-ink-lg: var(--sp-accent);
}

/* --sp-route-ink is deliberately not defaulted here. --sp-route-accent is
   declared per row, not on the page, so `--sp-route-ink: var(--sp-route-accent)`
   at this level resolves against a property that does not exist yet, computes
   to the guaranteed-invalid value, and inherits down as empty - which took the
   colour off all four product names and their icons at once. The fallback
   belongs at the point of use, where the row's own accent is in scope. */

.page--site .tone { --accent-ink: var(--accent); }

html:is([data-surface="light"], [data-theme="light"]) .page--site {
  --sp-accent-ink   : var(--core-green-900);
  --sp-accent-ink-lg: var(--core-green-800);
}

html:is([data-surface="light"], [data-theme="light"]) .page--amplify {
  --sp-accent-ink   : var(--core-green-900);
  --sp-accent-ink-lg: var(--core-green-800);
}

html:is([data-surface="light"], [data-theme="light"]) .page--enterprise {
  --sp-accent-ink   : var(--personality-blue-800);
  --sp-accent-ink-lg: var(--personality-blue-700);
}

html:is([data-surface="light"], [data-theme="light"]) .page--flow {
  --sp-accent-ink   : var(--core-lime-900);
  --sp-accent-ink-lg: var(--core-lime-800);
}

html:is([data-surface="light"], [data-theme="light"]) .page--signal {
  --sp-accent-ink   : var(--skills-orange-900);
  --sp-accent-ink-lg: var(--skills-orange-800);
}

html:is([data-surface="light"], [data-theme="light"]) .page--how-it-works {
  --sp-accent-ink   : var(--core-green-900);
  --sp-accent-ink-lg: var(--core-green-800);
}

/* Violet is the one family already dark enough to read as text unchanged. */
html:is([data-surface="light"], [data-theme="light"]) .page--contact {
  --sp-accent-ink   : var(--mindset-violet-500);
  --sp-accent-ink-lg: var(--mindset-violet-500);
}

/* A card carries its own tone, so it needs its own ink rather than the page's:
   four differently toned cards in one grid all read their label from --accent. */
html:is([data-surface="light"], [data-theme="light"]) .page--site {
  --tone-ink-green : var(--core-green-900);
  --tone-ink-lime  : var(--core-lime-900);
  --tone-ink-mint  : var(--personality-mint-900);
  --tone-ink-blue  : var(--personality-blue-800);
  --tone-ink-violet: var(--mindset-violet-500);
  --tone-ink-pink  : var(--skills-pink-700);
  --tone-ink-orange: var(--skills-orange-900);
}

html:is([data-surface="light"], [data-theme="light"]) .page--site .tone--green  { --accent-ink: var(--tone-ink-green); }
html:is([data-surface="light"], [data-theme="light"]) .page--site .tone--lime   { --accent-ink: var(--tone-ink-lime); }
html:is([data-surface="light"], [data-theme="light"]) .page--site .tone--mint   { --accent-ink: var(--tone-ink-mint); }
html:is([data-surface="light"], [data-theme="light"]) .page--site .tone--blue   { --accent-ink: var(--tone-ink-blue); }
html:is([data-surface="light"], [data-theme="light"]) .page--site .tone--violet,
html:is([data-surface="light"], [data-theme="light"]) .page--site .tone--violet-soft,
html:is([data-surface="light"], [data-theme="light"]) .page--site .tone--violet-light { --accent-ink: var(--tone-ink-violet); }
html:is([data-surface="light"], [data-theme="light"]) .page--site .tone--pink   { --accent-ink: var(--tone-ink-pink); }
html:is([data-surface="light"], [data-theme="light"]) .page--site .tone--orange { --accent-ink: var(--tone-ink-orange); }

/* The four product rows in the nav menu. */
html:is([data-surface="light"], [data-theme="light"]) .page--site .sp-nav__product--amplify    { --sp-route-ink: var(--core-green-900); }
/* 900, not 800: on the current row the panel now carries a tint of the row's
   own accent, and blue-800 measured exactly 4.50 against it - a pass by
   rounding is not a pass worth shipping. */
html:is([data-surface="light"], [data-theme="light"]) .page--site .sp-nav__product--enterprise { --sp-route-ink: var(--personality-blue-900); }
html:is([data-surface="light"], [data-theme="light"]) .page--site .sp-nav__product--flow       { --sp-route-ink: var(--core-lime-900); }
html:is([data-surface="light"], [data-theme="light"]) .page--site .sp-nav__product--signal     { --sp-route-ink: var(--skills-orange-900); }

/* --------------------------------------------------------------------------
   Every rule below already existed above; each is restated here against the
   ink rather than the accent. Same selector, same specificity, later in source
   order. Nothing that paints a fill, a border or a glow appears in this list.
   -------------------------------------------------------------------------- */

.page--site .sp-nav__product-icon,
.page--site .sp-nav__product-name { color: var(--sp-route-ink, var(--sp-route-accent)); }

.page--site .audience-badge { color: var(--sp-accent-ink); }

.page--site .path-card .path-index { color: var(--accent-ink, var(--sp-accent-ink)); }

.page--site .product-card__audience { color: var(--accent-ink, var(--sp-accent-ink)); }

.page--site .product-row__audience { color: var(--accent-ink, var(--sp-accent-ink)); }

.page--site .moat-step b { color: var(--sp-accent-ink); }


.page--site :is(.work-icon, .surface-tile__icon, .route-card__icon, .dimension-icon, .signal-icon) {
  color: var(--accent-ink, var(--sp-accent-ink));
}

.page--site .signal-tile__label,
.page--site .route-card__audience,
.page--site .contact-route__link {
  color: var(--accent-ink, var(--sp-accent-ink));
}

/* The tag keeps its tinted fill and its bright border - those are the texture.
   Only the label steps down. */
.page--site .tone .ui-tag,
.page--site .product-row .ui-tag,
.page--site .path-card .ui-tag {
  color: var(--accent-ink, var(--sp-accent-ink));
}

/* .text-positive is the accent as running text, and it appears at both ends of
   the scale: a 72px hero span and an 11px status line in the same page. The
   small-text ink is the safe default; a heading opts into the brighter step,
   because at display size 3:1 is the bar and the chroma is worth keeping. */
.page--site .text-positive { color: var(--sp-accent-ink); }

.page--site :is(h1, h2) .text-positive { color: var(--sp-accent-ink-lg); }

/* ----------------------------------------------------------------------------
   40.13 · The game view is a dark island

   The hero's left pane is a game screen and stays deep navy whatever the page
   around it does - a game does not turn white because the marketing site did.
   On the light surface every token inside it still resolved from the page, so
   the frame counter, the decision-window label and the millisecond reading all
   came out dark-on-dark: the meta line measured 2.2:1 and the reading itself
   2.7:1, in the one component the whole hero is built to draw the eye to.

   The panel therefore carries the dark surface's own text tokens rather than
   inheriting the page's. --sp-accent-ink goes back to the plain accent here for
   the same reason: the ink was stepped down to stay legible on white, and on
   this panel it is the bright green that is legible.
   -------------------------------------------------------------------------- */
html:is([data-surface="light"], [data-theme="light"]) .page--site .game-view {
  color-scheme: dark;

  /* --ui-text and --ui-muted are declared once on `.page` as a chain of
     fallbacks, which means they are *resolved* there and inherit down as a
     finished value. Redefining an upstream link deeper in the tree does not
     recompute them - the panel has to restate the --ui-* tokens themselves,
     which is what the utilities actually read. */
  --ui-text: var(--geistface-grey-25);
  --ui-muted: color-mix(in srgb, var(--geistface-grey-200) 72%, transparent);
  --ui-border: rgb(248 250 252 / 12%);
  --ui-border-strong: rgb(248 250 252 / 18%);

  /* Tokens alone do not move anything that inherits its colour rather than
     asking for one: the decision-window label is a bare <strong>, so it took
     the body's ink until the panel set its own. */
  color: var(--ui-text);

  --text: var(--text-primary);
  --text-strong: var(--deep-navy-25);
  --muted: var(--text-muted);
  --surface-text: var(--geistface-grey-25);
  --surface-text-muted: var(--text-muted);
  --sp-accent-ink: var(--sp-accent);
  --sp-accent-ink-lg: var(--sp-accent);
}

/* ----------------------------------------------------------------------------
   40.14 · The last of the light-surface contrast

   Four tokens mixed for the dark surface that land between 3.2:1 and 4.1:1 on
   the light one. Each is small type - a nav route, a menu blurb, a legend
   count, a set of avatar initials - so 4.5:1 is the bar, and each is only a
   few percent short. The mixes are restated rather than the components
   rewritten.
   -------------------------------------------------------------------------- */

/* The bar's inactive routes and the product blurbs under them. 72% measured
   4.03 and 3.76 against the near-white bar; 84% clears with room and still
   reads as quieter than the current route, which is what marks the page. */
html:is([data-surface="light"], [data-theme="light"]) .page--site .sp-nav {
  --nav-muted: color-mix(in srgb, var(--geistface-grey-800) 84%, transparent);
}

/* The ontology legend's leaf count and the avatar initials both take
   --surface-text-muted, which is geistface-grey-600 on light: correct beside
   16px body copy, thin at 10px on a tinted chip. */
html:is([data-surface="light"], [data-theme="light"]) .page--site .ontology-legend__count,
html:is([data-surface="light"], [data-theme="light"]) .page--site .sp-avatar,
html:is([data-surface="light"], [data-theme="light"]) .page--site .sp-avatar__initials {
  color: var(--geistface-grey-800);
}

/* The ontology legend's tags sit on the branch colour at 10px, so they take
   the same ink treatment as every other tag on the site. */
html:is([data-surface="light"], [data-theme="light"]) .page--site .ontology-root .ui-tag {
  color: var(--geistface-grey-800);
}

/* The API reference tables label their columns with a plain <th>, which takes
   --surface-text-muted: 4.07:1 at 12px on the light surface. Same fix as the
   legend count above, applied where a table header is the label. */
html:is([data-surface="light"], [data-theme="light"]) .page--site th {
  color: var(--geistface-grey-800);
}

/* ----------------------------------------------------------------------------
   40.15 · The dark surface has its own two gaps

   The ink work in 40.12 was written for the light surface, where six of the
   seven families are too bright to read as text. On deep navy the problem
   inverts and is much smaller: five families are bright enough already, and
   only Mindset Violet - the one colour that needed no help on white - is too
   dark to read on navy at 2.31:1.

   Both fixes are the tokens the site already has, given their dark-surface
   values. Nothing new is introduced, and neither is scoped to a surface
   attribute: these are the defaults, and the light block overrides them.
   -------------------------------------------------------------------------- */

/* Violet as text on navy. The fill stays violet-500 - it is the action colour
   and correct as a button, a glow or a 12% tile - and only the ink steps up
   the ramp. 300 measured 6.7:1 against the darkest panel it lands on. */
.page--contact {
  --sp-accent-ink   : var(--mindset-violet-300);
  --sp-accent-ink-lg: var(--mindset-violet-300);
}

.page--site .tone--violet,
.page--site .tone--violet-soft,
.page--site .tone--violet-light {
  --accent-ink: var(--mindset-violet-300);
}

/* --quiet is mixed at 50% for the dark surface, which lands the pull-quote
   attribution at 3.0:1, the footer's legal line at 4.31:1 and the hero's
   supporting line at 4.43:1 - all of them 10px and 12px, all of them short of
   4.5. This has been the case since the marketing pages were first written; it
   is fixed here rather than left because the same token was already being
   corrected for the light surface a few sections up, and a launch should not
   ship one surface accessible and the other not. 66% measures 5.7:1 against
   the lightest panel and still reads as clearly quieter than body copy. */
.page--site {
  --quiet: color-mix(in srgb, var(--geistface-grey-200) 66%, transparent);
}

/* ============================================================================
   41 · Marketing site · the polish pass

   Consistency work across the finished site: one hierarchy for every product
   block, the product colour pulled back to the places that carry meaning, and
   the few layout primitives the tightened sections need.
   ========================================================================== */

/* ----------------------------------------------------------------------------
   41.1 · Amplify moves to Personality Mint

   Players and Enterprise are two routes into one product, so Amplify needs a
   colour of its own rather than the brand green every page already uses for
   its accent. Enterprise keeps Personality Blue: it is the same product, but
   the audience colour is what a returning visitor navigates by.
   -------------------------------------------------------------------------- */
.page--amplify {
  --sp-accent  : var(--personality-mint-500);
  --sp-accent-2: var(--personality-blue-500);
}

.page--site .sp-nav__product--amplify { --sp-route-accent: var(--personality-mint-500); }

html:is([data-surface="light"], [data-theme="light"]) .page--amplify {
  --sp-accent-ink   : var(--personality-mint-900);
  --sp-accent-ink-lg: var(--personality-mint-800);
}

html:is([data-surface="light"], [data-theme="light"]) .page--site .sp-nav__product--amplify {
  --sp-route-ink: var(--personality-mint-900);
}

/* ----------------------------------------------------------------------------
   41.2 · One hierarchy for a product block

   Eyebrow, heading, one paragraph, one call to action. The row already had the
   shape; what it did not have was a call to action that says which product it
   belongs to. The icon carries the colour, so the button itself can stay the
   neutral secondary used everywhere else.
   -------------------------------------------------------------------------- */
.page--site :is(.product-row, .product-card) .button svg {
  --button-icon-color: var(--accent-ink, var(--sp-accent-ink));
}

/* The paragraph is the whole supporting message now, so it gets a measure
   rather than running the full width of the copy column. */
.page--site .product-row__copy p { max-width: 46ch; }

/* A wash of the product's colour behind the screenshot, not a frame around it.
   22% read as a coloured border on four rows in a row; 12% reads as the shot
   belonging to the product. */
.page--site .product-row__shot {
  border-color: color-mix(in srgb, var(--accent, var(--sp-accent)) 12%, var(--border-subtle));
}

/* ----------------------------------------------------------------------------
   41.3 · Two layout primitives the tightened sections need
   -------------------------------------------------------------------------- */

/* An even split, for a section whose left column is an argument and whose
   right column is the evidence for it rather than a longer list. */
.page--site .process-wrap--even { grid-template-columns: repeat(2, minmax(0, 1fr)); }

/* .process-card is a three column grid ending in an `auto` track for its
   trailing <code> label. Without the label the track collapses but its gap
   does not, leaving the text short of the card's right edge. */
.page--site .process-card--plain { grid-template-columns: 54px minmax(0, 1fr); }

@media (max-width: 1023px) {
  .page--site .process-wrap--even { grid-template-columns: minmax(0, 1fr); }
}

/* ----------------------------------------------------------------------------
   41.4 · The closing product block

   How it works ends on the same shape the homepage uses for a product: eyebrow,
   heading, one paragraph, one call to action. It is the page's closing heading
   so it is an h2 rather than the row's usual h3, and the row has to size it the
   same way for the two to read as one pattern.
   -------------------------------------------------------------------------- */
.page--site .product-row :is(h2, h3) {
  margin: 0;
  font-size: var(--display-sm-font-size);
  line-height: var(--display-sm-line-height);
  letter-spacing: -0.02em;
}

/* ----------------------------------------------------------------------------
   41.5 · Contact

   Two columns and nothing else: an argument on the left, the form on the right.
   The routed enquiry cards and the "what happens next" rail were a second and
   third way of saying the same thing on a page whose only job is one message.
   -------------------------------------------------------------------------- */
.page--site .hero--contact { padding-block-start: var(--space-144); }

.page--site .contact-layout {
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr);
  gap: var(--space-64);
}

.page--site .contact-intro { position: sticky; top: 120px; }

.page--site .contact-intro h1 {
  margin-block: var(--space-18) var(--space-20);
  font-size: var(--display-md-font-size);
  line-height: var(--display-md-line-height);
  letter-spacing: -0.03em;
}

.page--site .contact-intro > p { max-width: 46ch; }

.page--site .contact-intro__direct { margin-block-start: var(--space-24); }

@media (max-width: 1023px) {
  .page--site .contact-layout { grid-template-columns: minmax(0, 1fr); gap: var(--space-32); }
  .page--site .contact-intro { position: static; }
  .page--site .hero--contact { padding-block-start: var(--space-112); }
}

/* ----------------------------------------------------------------------------
   41.6 · Secondary navigation cards

   Where to go next on the Developers page is a signpost, not a second
   conversion moment. The route card it used carried an accent icon tile, an
   accent label and an accent call to action, which made it compete with the
   page's actual call to action. This is the same card with the colour taken
   out: a title, a line, and the whole card as the target.
   -------------------------------------------------------------------------- */
.page--site .link-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--space-16);
}

.page--site .link-card {
  --card-current-padding: var(--space-28);
  display: grid;
  gap: var(--space-8);
  align-content: start;
  color: inherit;
  transition: var(--transition-interactive);
}

.page--site .link-card:hover,
.page--site .link-card:focus-visible {
  border-color: color-mix(in srgb, var(--sp-accent) 32%, var(--border-subtle));
}

.page--site .link-card__title {
  display: flex;
  align-items: center;
  gap: var(--spacing-sm);
  color: var(--surface-text);
  font-size: var(--text-md-font-size);
  line-height: var(--text-md-line-height);
  font-weight: var(--weight-regular);
}

.page--site .link-card__blurb {
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

/* ----------------------------------------------------------------------------
   41.7 · The research visual

   About's research section is about six years of published work, not about the
   diagram. The ontology map supports that story at a size that leaves the copy
   the louder half of the row.
   -------------------------------------------------------------------------- */
.page--site .research-visual {
  display: grid;
  place-items: center;
  padding: var(--space-8);
}

/* The map is the evidence for the claim beside it, so it takes the column it
   is given. 384px left it floating in a half-width cell with more padding than
   diagram, which read as a thumbnail rather than as the model. */
.page--site .research-visual img {
  width: 100%;
  max-width: var(--space-560);
  height: auto;
}

/* The research figure is a live Skillprint rather than a flat export: the same
   renderer the ontology page uses, in its still-figure mode. Drawn rather than
   photographed, so it is sharp at any size, follows the type and colour tokens,
   and cannot fall out of date with the data behind it.

   It keeps the plate treatment because the ink is tuned for a dark ground - the
   .ontology-visual class it carries pins those tokens - and it is clipped to the
   card radius so the disc sits in a rounded plate rather than bleeding to a
   square edge. */
.page--site .research-visual .skillprint-figure {
  width: 100%;
  max-width: var(--space-560);
  padding: var(--space-16);
  border-radius: var(--radius-4xl);
  overflow: hidden;
}

/* min-width is reset on purpose. The ontology page gives the wheel a 720px floor
   below 900px so 87 radiating terms stay legible and the stage scrolls sideways
   instead of shrinking them to nothing. That is right for an instrument the
   reader works with and wrong for a figure in a page column: it forced this one
   to 720px inside a 274px cell, and the clip turned the disc into a crop. Here
   the drawing may shrink, because nobody is being asked to read a term off it. */
.page--site .research-visual .skillprint__wheel {
  display: block;
  width: 100%;
  min-width: 0;
  height: auto;
  aspect-ratio: 1;
}

@media (max-width: 767px) {
  .page--site .research-visual { padding: 0; }
  .page--site .research-visual img { max-width: var(--space-384); }
  .page--site .research-visual .skillprint-figure {
    max-width: var(--space-384);
    padding: var(--space-12);
  }
}

/* ----------------------------------------------------------------------------
   41.8 · The product menu's icons

   .sp-icon with no modifier is 24px at a 2px stroke, which is the size for an
   icon standing on its own. Inside a 36px tile in a menu row it was heavier
   than the label beside it and read as the loudest thing in the panel.

   .sp-side-nav__link is the design system's own navigation row and draws its
   icon at --icon-size-sm; this matches it, and takes --icon-stroke-20 with it
   so the weight follows the size the way the ladder asks.
   -------------------------------------------------------------------------- */
.page--site .sp-nav__product-icon .sp-icon {
  --icon-size: var(--icon-size-xs);
  --icon-stroke: var(--icon-stroke-16);
}


/* ==========================================================================
   09 - Unique composition · Portal screen switcher
   The review affordance in the portal header. It is the shared .sp-dropdown,
   so the menu, groups, items and open state all come from Part A; these rules
   only place it in the nav and let the long grouped list scroll.
   ========================================================================== */

[data-skillprint-page] .screen-switch {
  position: relative;
  flex: 0 0 auto;
  margin-inline-end: var(--spacing-md);
}

/* The trigger sits beside icon buttons, so it stays compact and the supporting
   line is dropped below the widest breakpoint rather than wrapping the bar. */
[data-skillprint-page] .screen-switch__trigger {
  max-width: var(--space-256);
}

[data-skillprint-page] .screen-switch__menu {
  right: 0;
  left: auto;
  width: max(100%, var(--space-320));
  max-height: min(70vh, var(--space-480));
}

@media (max-width: 900px) {
  [data-skillprint-page] .screen-switch__trigger .sp-dropdown__copy span {
    display: none;
  }
}

@media (max-width: 640px) {
  [data-skillprint-page] .screen-switch {
    order: -1;
    margin-inline-end: 0;
  }
}


/* ==========================================================================
   09 - Unique composition · Dark popups on a light page
   .popup--dark declares its own dark ground, but it only sets colour and
   background - it never re-declares the button tokens. html[data-surface=
   "light"] points --button-text at the light surface's ink, so a button placed
   inside a dark popup on a light page drew dark-on-dark: the post-game
   analysis panel's "Back to games" measured 1.2:1. The popup restates the
   tokens its own ground implies, so any component inside one resolves against
   the popup rather than the page. .popup is Part A and stays untouched.
   ========================================================================== */

.popup--dark {
  --surface-box: var(--panel-navy-900);
  --surface-raised: var(--panel-navy-800);
  --surface-panel: var(--panel-navy-950);
  --surface-text: var(--geistface-grey-200);
  --surface-text-muted:
    color-mix(in srgb, var(--geistface-grey-200) 72%, transparent);
  --surface-border:
    color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
  --surface-border-hover:
    color-mix(in srgb, var(--deep-navy-25) 20%, transparent);

  /* The ink and button tokens have to be restated rather than left to follow
     the surface ones above. A custom property is substituted where it is
     declared, not where it is used: html[data-surface="light"] declares
     --button-text as var(--surface-text) on the html element, and the light
     .page rule declares --text-strong there, so redefining --surface-text
     deeper in the tree cannot reach either. These are the dark values from
     :root and .page, so a dark popup on a light page reads exactly like the
     same popup on a dark page. */
  --text: var(--text-primary);
  --text-strong: var(--deep-navy-25);
  --muted: var(--text-muted);
  --border: var(--border-subtle);
  --border-strong: rgb(248 250 252 / 18%);
  --border-hover: rgb(248 250 252 / 28%);
  --panel-800: var(--panel-navy-800);
  --panel-700: var(--panel-navy-700);
  --panel-600: var(--panel-navy-600);

  /* The leaf tokens components actually read, restated for the same reason.
     .page declares --ui-text as var(--text-strong, ...), so it resolves there
     against the page's ink; setting --text-strong above cannot reach it. This
     is what headings inside a dark popup were reading - h1 to h4 take
     --ui-text, which is why they drew near-black on the dark panel. */
  --ui-text: var(--deep-navy-25);
  --ui-muted: var(--text-muted);
  --ui-border: var(--border-subtle);

  --button-text: var(--geistface-grey-200);
  --button-text-strong: var(--geistface-grey-25);
  --button-text-muted:
    color-mix(in srgb, var(--geistface-grey-200) 72%, transparent);
  --button-text-disabled:
    color-mix(in srgb, var(--geistface-grey-200) 42%, transparent);
  --button-surface: var(--panel-navy-900);
  --button-surface-hover: var(--panel-navy-800);
  --button-surface-disabled:
    color-mix(in srgb, var(--panel-navy-900) 62%, transparent);

  /* The edge, the focus ground and the whole button-group family, restated
     for exactly the reason the fills above are. :root declares each of these
     as var(--surface-*) or var(--border-*) on the html element, so the
     surface tokens redefined at the top of this block cannot reach them.
     Without these a dark popup on a light page drew a navy border on a navy
     button - an edge that vanishes - and gave the button group a pale
     --button-group-border picked straight off the light page it floats over.
     Every value below is :root's own definition, so a dark popup reads the
     same on either ground.

     --button-border-disabled and --button-focus-inner take literals rather
     than var(--border-subtle) / var(--focus-inner): those two are not among
     the tokens this block restates, so pointing at them would re-enter the
     same trap one level down. */
  --button-border: var(--border-strong);
  --button-border-disabled:
    color-mix(in srgb, var(--deep-navy-25) 10%, transparent);
  --button-focus-inner: var(--panel-navy-950);
  --button-shadow:
    0 1px 2px
      color-mix(in srgb, var(--deep-navy-950) 22%, transparent),
    inset 0 0 0 1px
      color-mix(in srgb, var(--geistface-grey-25) 6%, transparent),
    inset 0 -2px 0
      color-mix(in srgb, var(--deep-navy-950) 16%, transparent);
  --button-group-shadow: var(--button-shadow);
  --button-group-surface: var(--surface-box);
  --button-group-current: var(--surface-raised);
  --button-group-text: var(--surface-text);
  --button-group-border: var(--surface-border-hover);
  --button-group-icon:
    color-mix(in srgb, var(--surface-text) 62%, transparent);
  --button-group-icon-strong: var(--surface-text);
  --button-group-disabled:
    color-mix(in srgb, var(--surface-text) 42%, transparent);
}


/* ==========================================================================
   07 - Shared component · dropdown menu surface
   Part A gives .sp-dropdown__menu its position, padding and shadow but never a
   ground, and nothing else in the sheet supplied one - so every dropdown in the
   project, including the two on the design system reference itself, painted its
   items straight onto whatever it happened to float over. These are the same
   three values .sp-card resolves to, so a menu reads as the surface it already
   casts a shadow like. Part A is untouched; this completes it from Part B.
   ========================================================================== */

.sp-dropdown__menu {
  background: var(--surface-box);
  border: 1px solid var(--surface-border);
  border-radius: var(--card-current-radius, var(--radius-2xl));
  color: var(--surface-text);
}

/* The item title has the same gap Part A left on the menu's ground: it is a
   raw <strong>, no rule anywhere gives it a weight, so the browser's `bolder`
   lands on the button's semibold and produces 900 - every titled item in
   every menu, selected or not, shouts over its own description. The rail's
   foot already corrected its two triggers to medium; this is that correction
   made once, at the component, for the copy block menus and headers share.
   Semibold is the item's own label weight, so the title stops competing and
   the muted xs description line carries the distinction, as Part A drew it. */
.sp-dropdown__copy > strong,
.sp-dropdown__header-copy > strong {
  font-weight: var(--weight-semibold);
}


/* ==========================================================================
   07 - Shared component · tag, icon gap
   Part A makes .ui-tag an inline-flex box and centres its children but never
   gives it a gap, so a tag that carries a glyph drew the icon flush against
   its own label - no space at all, at any size, on every surface that uses one.

   It had been fixed once, as `.page--portal-profile .ui-tag { gap }`. Three
   portal screens render icon-bearing tags from the same markup - Profile 38,
   Games 31, Skills 17 - so the two that were not named stayed broken, which is
   the usual sign a rule belongs to the component rather than to a page. The
   per-page rule is gone and this replaces it at the same 6px, so Profile is
   unchanged and the other two catch up.

   Global rather than scoped to .page--portal: gap is inert on a tag with no
   second child, so this cannot reach a tag that does not want it, and the
   marketing site draws the same component. Part A is untouched; this completes
   it from Part B, the way the dropdown surface above does.
   ========================================================================== */

.ui-tag {
  gap: var(--spacing-sm);
}


/* ==========================================================================
   07 - Shared component · chart chip, hover
   Part A opens with `.ui-tag, .chart-chip` sharing one base - the same height,
   padding, border, radius, type and transition - and then hands the hover to
   `button.ui-tag` alone. The chip is a button too, and a well-used one: Profile
   draws twenty-eight of them as the metric picker and Skill Detail two more as
   its chart view switch, every one of them `cursor: pointer`. None
   acknowledged the pointer at all, so selected was the only state they had.

   This is the tag's own hover applied to the half of the base rule that was
   left without one, so the pair behaves alike. :not(.is-active) keeps it off a
   chip that is already selected - that state paints its own violet ground, and
   a `button.chart-chip:hover` at (0,2,1) would otherwise outrank the
   `.chart-chip.is-active` (0,2,0) that draws it. Part A is untouched; this
   completes it from Part B, the way the dropdown surface and the tag gap do.
   ========================================================================== */

button.chart-chip:not(.is-active):hover {
  border-color: color-mix(in srgb, var(--surface-text) 28%, var(--surface-border));
  color: var(--surface-text);
}


/* ----------------------------------------------------------------------------
   41.9 · One rule for every accent tile

   The nav's product tile was corrected on its own, which left the route cards,
   the work rail and the surface grid still drawing a 24px icon at a 2px stroke
   inside a small box. These are the same object in four places, so they take
   the same treatment: the icon is half the tile and the stroke follows the
   size the way the ladder asks.
   -------------------------------------------------------------------------- */
.page--site :is(.route-card__icon, .work-icon, .surface-tile__icon) .sp-icon {
  --icon-size: var(--icon-size-xs);
  --icon-stroke: var(--icon-stroke-16);
}


/* ==========================================================================
   09 - Unique composition · Core Green as text on a light ground
   Core Green identifies the product, and at --core-green-500 it is a bright
   mint: 1.7:1 against the metric card, so "+2 this week" was effectively
   unreadable on the light surface it has always been drawn on. The icons
   already solve this by stepping the hue down the ramp so the stroke holds on
   white; text takes the same step. Dark surfaces keep the 500, where it sings.
   Only the two text uses move - the mood and insight icons stay on the accent.

   The interstitial's finished spinner is here for the same reason and was
   missed: its comment says "'finished' is legible without reading the
   percentage", but the glyph was drawn in raw --core-green-500 on the portal's
   light ground at about 1.7:1, so it was the one thing on that screen that
   was not legible. Its ring and fill are alpha decoration and stay as they
   are; only the stroke steps. The reason it needs naming at all is that
   --green, unlike --lime, --mint and --violet, is not re-valued for a light
   ground on .page - the ramp step has to be asked for.
   ========================================================================== */

html:is([data-surface="light"], [data-theme="light"]) .metric--portal small,
html:is([data-surface="light"], [data-theme="light"]) .modal-category,
html:is([data-surface="light"], [data-theme="light"])
  .page--portal-game-interstitial .interstitial[data-state="ready"] .interstitial-spinner {
  color: var(--core-green-800);
}


/* ==========================================================================
   06 - Shared repairs · light mode tokens that were never given a value

   Four tokens in the sheet are declared once, for a dark ground, and never
   re-valued when the surface flips. Nothing announces it: a colour that
   resolves to near-white on white is not an error, it is an invisible word.
   These four are the ones a page can actually reach, so they are repaired
   here rather than page by page.

   --text-primary and --text-muted are the dark-surface ink pair. In light
   they resolved to Geistface Grey 200 and a 72% wash of it, which is why the
   statistics back-link turned white on hover the moment it was hovered.
   --border-subtle is a white hairline, invisible on a light card. --subtle
   was never defined on .page at all, so `.text-subtle` was an invalid
   declaration that inherited full ink - the de-emphasis silently did nothing
   on three portal screens.

   --shadow-lg had the same shape of fault: it exists only inside
   .page--design-system, so every `box-shadow: var(--shadow-lg)` elsewhere
   computed to none. It is completed here beside its own ladder.
   ========================================================================== */

.page {
  --subtle: color-mix(in srgb, var(--muted) 88%, transparent);
  --shadow-lg:
    0 var(--space-12) var(--space-24)
      color-mix(in srgb, var(--deep-navy-950) 22%, transparent),
    0 var(--space-4) var(--space-8)
      color-mix(in srgb, var(--deep-navy-950) 16%, transparent);
}

html:is([data-surface="light"], [data-theme="light"]) > .page {
  --text-primary: var(--geistface-grey-900);
  --text-muted: color-mix(in srgb, var(--geistface-grey-800) 82%, transparent);
  --border-subtle: color-mix(in srgb, var(--deep-navy-900) 10%, transparent);
  --subtle: color-mix(in srgb, var(--geistface-grey-700) 92%, transparent);
  --shadow-lg:
    0 var(--space-12) var(--space-24)
      color-mix(in srgb, var(--deep-navy-950) 8%, transparent),
    0 var(--space-2) var(--space-6)
      color-mix(in srgb, var(--deep-navy-950) 5%, transparent);
}

/* The two accent aliases that are read as text. The brand 500s are drawn for
   a dark ground and measure 3.0:1 on white, so the text roles step down their
   own ramp exactly the way --positive-text and --accent-text already do.
   The fills keep the 500 - only the reading colour moves. */
html:is([data-surface="light"], [data-theme="light"]) .page .text--danger {
  color: var(--skills-pink-700);
}

html:is([data-surface="light"], [data-theme="light"]) .page .text--warning {
  color: var(--skills-orange-800);
}

/* Hovering a back-link should not erase it. --text-primary is repaired above,
   which covers this, but the rule is restated on the strong ink so the
   statistics screens do not depend on that repair to stay legible. */
html:is([data-surface="light"], [data-theme="light"]) .page--portal .stat-hero__back:hover,
html:is([data-surface="light"], [data-theme="light"]) .page--portal .stat-hero__back:focus-visible {
  color: var(--ui-text);
}


/* ==========================================================================
   09 - Unique composition · Games Portal · the light ground

   The portal is light-first and it is the only surface in the project a
   player sees, so this is where "light" has to actually mean light. The
   sheet's light theme was built as a value swap for a system whose home is
   dark, and three things followed from that which read as heavy here:

   1. Elevation was inverted. --surface-panel resolved to #eef0f6 against a
      #f8f9fc canvas holding #fcfcfd cards, so the largest container on the
      screen was also the darkest thing on it - a grey slab with no shadow.
      Here panel and card are the same near-white and separation is carried
      by a hairline and a shadow, which is the order a light dashboard reads.

   2. The ink was near-black saturated navy (#090d27) and it was the default
      for body copy, not just for headings, with two more inks competing
      under it. One ink ladder now: strong, default, muted, subtle.

   3. Every button carried `inset 0 -2px 0` at 16% black. That bottom bar is
      a dark-surface affordance; on a white button it is the chunky edge the
      brief asks to remove. The light recipe keeps a single 1px lift.

   Why the tokens are restated as literals rather than chained: a custom
   property's var() is substituted where it is *declared*. --button-surface
   is declared on <html> as var(--surface-box), so re-valuing --surface-box
   down here cannot reach it. This is the same reason .popup--dark restates
   its own leaf tokens, and it is why the block below looks repetitive.
   ========================================================================== */

html:is([data-surface="light"], [data-theme="light"]) > .page--portal {
  color-scheme: light;

  /* --- the ground ------------------------------------------------------- */
  --page: var(--geistface-grey-50);
  --deep: var(--geistface-grey-50);
  --canvas: var(--geistface-grey-50);
  --surface: var(--geistface-grey-25);
  --surface-canvas: var(--geistface-grey-50);
  --surface-panel: var(--geistface-grey-25);
  --surface-box: var(--geistface-grey-25);
  --surface-raised:
    color-mix(in srgb, var(--panel-navy-50) 45%, var(--geistface-grey-25));
  --surface-strong:
    color-mix(in srgb, var(--panel-navy-100) 55%, var(--geistface-grey-25));

  --panel-950: var(--geistface-grey-25);
  --panel-900: var(--geistface-grey-25);
  --panel-800:
    color-mix(in srgb, var(--panel-navy-50) 45%, var(--geistface-grey-25));
  --panel-700:
    color-mix(in srgb, var(--panel-navy-100) 55%, var(--geistface-grey-25));
  --panel-600: var(--geistface-grey-300);

  /* --- one ink ladder --------------------------------------------------- */
  --text-strong: var(--deep-navy-800);
  --text: var(--geistface-grey-900);
  --text-primary: var(--geistface-grey-900);
  --surface-text: var(--geistface-grey-900);
  --muted: var(--geistface-grey-700);
  --surface-text-muted: var(--geistface-grey-700);
  --text-muted: color-mix(in srgb, var(--geistface-grey-800) 82%, transparent);
  /* 94%, not 88%. --subtle is the third rung of this ladder and it is real
     text - the footer's copyright, the breakdown legend, "Needs play", the
     empty-history line, Settings' token readout - so it is held to 4.5:1 like
     any other. At 88% it measured 4.40:1 on a card and 4.28:1 on the canvas,
     failing on both. 94% reads 5.02 and 4.86 and still sits a clear step below
     --muted's 5.75/5.55, which is the only reason the rung exists. */
  --subtle: color-mix(in srgb, var(--geistface-grey-700) 94%, transparent);

  /* --- hairlines, not rules --------------------------------------------- */
  --border: color-mix(in srgb, var(--deep-navy-900) 8%, transparent);
  --border-subtle: color-mix(in srgb, var(--deep-navy-900) 8%, transparent);
  --border-strong: color-mix(in srgb, var(--deep-navy-900) 13%, transparent);
  --border-hover: color-mix(in srgb, var(--deep-navy-900) 18%, transparent);
  --surface-border: color-mix(in srgb, var(--deep-navy-900) 8%, transparent);
  --surface-border-hover:
    color-mix(in srgb, var(--deep-navy-900) 15%, transparent);
  --skillprint-border-hover:
    color-mix(in srgb, var(--deep-navy-900) 18%, transparent);

  /* --- elevation drawn for a white ground ------------------------------- */
  --shadow-xs: 0 1px 2px color-mix(in srgb, var(--deep-navy-950) 5%, transparent);
  --shadow-sm:
    0 1px 2px color-mix(in srgb, var(--deep-navy-950) 5%, transparent),
    0 1px 3px color-mix(in srgb, var(--deep-navy-950) 4%, transparent);
  --shadow-md:
    0 var(--space-2) var(--space-4)
      color-mix(in srgb, var(--deep-navy-950) 5%, transparent),
    0 var(--space-6) var(--space-12)
      color-mix(in srgb, var(--deep-navy-950) 6%, transparent);
  --shadow-lg:
    0 var(--space-8) var(--space-20)
      color-mix(in srgb, var(--deep-navy-950) 7%, transparent),
    0 var(--space-2) var(--space-6)
      color-mix(in srgb, var(--deep-navy-950) 5%, transparent);
  --shadow-xl:
    0 var(--space-16) var(--space-40)
      color-mix(in srgb, var(--deep-navy-950) 9%, transparent),
    0 var(--space-4) var(--space-10)
      color-mix(in srgb, var(--deep-navy-950) 5%, transparent);

  /* --- the leaf tier the utilities actually read ------------------------ */
  --ui-page: var(--geistface-grey-50);
  --ui-surface: var(--geistface-grey-25);
  --ui-surface-raised:
    color-mix(in srgb, var(--panel-navy-50) 45%, var(--geistface-grey-25));
  --ui-text: var(--deep-navy-800);
  --ui-muted: var(--geistface-grey-700);
  --ui-border: color-mix(in srgb, var(--deep-navy-900) 8%, transparent);
  --ui-border-strong: color-mix(in srgb, var(--deep-navy-900) 13%, transparent);
  --ui-border-hover: color-mix(in srgb, var(--deep-navy-900) 18%, transparent);

  --card-surface: var(--geistface-grey-25);
  --card-surface-raised:
    color-mix(in srgb, var(--panel-navy-50) 45%, var(--geistface-grey-25));
  --card-text: var(--deep-navy-800);
  --card-border: color-mix(in srgb, var(--deep-navy-900) 8%, transparent);
  --card-border-hover:
    color-mix(in srgb, var(--deep-navy-900) 15%, transparent);

  --icon-bg: color-mix(in srgb, var(--mindset-violet-500) 8%, transparent);
  --icon-fg: var(--mindset-violet-600);
  --pill: var(--geistface-grey-700);

  /* --- controls: subtle, never raised ----------------------------------- */
  --button-shadow:
    0 1px 2px color-mix(in srgb, var(--deep-navy-950) 6%, transparent);
  --button-surface: var(--geistface-grey-25);
  --button-surface-hover:
    color-mix(in srgb, var(--panel-navy-50) 45%, var(--geistface-grey-25));
  --button-surface-disabled:
    color-mix(in srgb, var(--geistface-grey-50) 70%, transparent);
  --button-border: color-mix(in srgb, var(--deep-navy-900) 13%, transparent);
  --button-border-disabled:
    color-mix(in srgb, var(--deep-navy-900) 8%, transparent);
  --button-text: var(--geistface-grey-900);
  --button-text-strong: var(--geistface-grey-25);
  --button-text-muted: var(--geistface-grey-700);
  --button-text-disabled:
    color-mix(in srgb, var(--geistface-grey-900) 40%, transparent);
  --button-secondary: var(--geistface-grey-25);
  --button-secondary-border:
    color-mix(in srgb, var(--deep-navy-900) 13%, transparent);
  --button-secondary-hover:
    color-mix(in srgb, var(--panel-navy-50) 45%, var(--geistface-grey-25));
  --button-ghost-text: var(--geistface-grey-700);
  --button-group-surface: var(--geistface-grey-25);
  --button-group-current:
    color-mix(in srgb, var(--panel-navy-50) 45%, var(--geistface-grey-25));
  --button-group-text: var(--geistface-grey-900);
  --button-group-border:
    color-mix(in srgb, var(--deep-navy-900) 13%, transparent);
  --button-group-icon: var(--geistface-grey-700);
  --button-group-icon-strong: var(--geistface-grey-900);
  --button-group-shadow: none;

  /* --- the two navigations --------------------------------------------- */
  --nav-surface: var(--geistface-grey-25);
  --nav-drawer-surface: var(--geistface-grey-25);
  --nav-border: color-mix(in srgb, var(--deep-navy-900) 8%, transparent);
  --nav-text: var(--geistface-grey-900);
  --nav-muted: var(--geistface-grey-700);
  --nav-hover-surface:
    color-mix(in srgb, var(--panel-navy-50) 45%, var(--geistface-grey-25));
  --nav-hover-border: transparent;
  --side-nav-surface: var(--geistface-grey-25);
  --side-nav-border: color-mix(in srgb, var(--deep-navy-900) 8%, transparent);
  --side-nav-text: var(--deep-navy-800);
  --side-nav-muted: var(--geistface-grey-700);
  --side-nav-hover:
    color-mix(in srgb, var(--panel-navy-50) 52%, var(--geistface-grey-25));
  --side-nav-active: var(--nav-current-surface);
  --side-nav-active-border: var(--nav-current-border);
  --side-nav-accent: var(--violet);
  --side-nav-scrim: color-mix(in srgb, var(--deep-navy-900) 32%, transparent);
}

/* .sp-panel and .sp-card now share a ground, so the panel needs the one thing
   that still separates it: a shadow. Cards stay flat and take the hairline,
   which keeps a grid of them quiet. */
html:is([data-surface="light"], [data-theme="light"]) .page--portal .sp-panel {
  box-shadow: var(--shadow-sm);
}

/* Icon frames were a grey square on every card, and a screen of them adds
   more grey mass than the icons add meaning. They become a brand tint. */
html:is([data-surface="light"], [data-theme="light"]) .page--portal .sp-icon-frame {
  border-color: transparent;
  background: color-mix(in srgb, var(--mindset-violet-500) 8%, transparent);
}

/* Inputs sat on the same near-white as the card they were inside, so only the
   border said a field was there. The field takes the recessed tint instead
   and the border relaxes, which is the light-mode read. */
html:is([data-surface="light"], [data-theme="light"]) .page--portal .field :is(input, select, textarea),
html:is([data-surface="light"], [data-theme="light"]) .page--portal :is(input, select, textarea) {
  border-color: color-mix(in srgb, var(--deep-navy-900) 11%, transparent);
  background: color-mix(in srgb, var(--panel-navy-50) 38%, var(--geistface-grey-25));
}

html:is([data-surface="light"], [data-theme="light"]) .page--portal .field :is(input, select, textarea):hover,
html:is([data-surface="light"], [data-theme="light"]) .page--portal :is(input, select, textarea):hover {
  border-color: color-mix(in srgb, var(--deep-navy-900) 16%, transparent);
}

html:is([data-surface="light"], [data-theme="light"]) .page--portal .field :is(input, select, textarea):focus,
html:is([data-surface="light"], [data-theme="light"]) .page--portal :is(input, select, textarea):focus {
  background: var(--geistface-grey-25);
}

/* Dark mode keeps the system's own values; only the shell tokens that have no
   dark counterpart are named here. */
html:is([data-surface="dark"], [data-theme="dark"]) > .page--portal {
  --side-nav-active: var(--nav-current-surface);
  --side-nav-active-border: var(--nav-current-border);
  --side-nav-accent: var(--violet);
  --icon-bg: color-mix(in srgb, var(--mindset-violet-400) 16%, transparent);
  --icon-fg: var(--mindset-violet-200);
}


/* ==========================================================================
   09 - Unique composition · Games Portal · the shell

   Left navigation and the content beside it. There is one navigation system
   and it is the rail: the routes, then Profile, Settings and Theme in its
   foot. The bar that used to run over the content carried a search field, a
   Home button, a theme toggle, a Settings link and an avatar - a second place
   to look for the same destinations, and the reason a reader had to learn two
   maps of one product. It is gone above the drawer breakpoint; below it the
   same element survives as the drawer's opener and nothing else.

   The sidebar is fixed rather than sticky and the main column is offset by
   its width. Sticky needs the grid track to outlive the content to behave,
   and a short screen - the sign-in front door, an empty state - is exactly
   where that fails. Fixed is what the documentation shell settled on too.
   ========================================================================== */

/* --portal-topbar is the height the content has to clear, so on a desktop it
   is zero: every sticky offset and fragment scroll-margin in the portal is
   written against it, and they all follow from this one value rather than
   from a list of rules to remember. The drawer breakpoint restores it. */
.page--portal {
  --portal-sidebar: var(--space-256);
  --portal-topbar: 0px;
  /* 56, not 64. The bar is sized by the tallest thing in it - the 44px drawer
     opener - plus its padding, so at 64 the padding was doing the extra 20 and
     the chrome sat taller than the content asked for. 6px each side keeps the
     44px touch floor intact and takes 8px of dead height off the top of every
     phone screen. Everything that follows the bar follows this one value:
     --portal-topbar below 900px, every sticky offset and fragment
     scroll-margin written against it, and the signed-out .welcome-bar. */
  --portal-bar-height: var(--space-56);
  --portal-rail: var(--space-320);
  /* 24, not 32. The portal is a rail plus a content column that is already
     capped for reading width; the extra 8px on each side was not buying
     measure, it was pushing the cards inward from a shell that had room. */
  --portal-gutter: var(--space-24);
  background: var(--ui-page);
}

.page--portal .portal-app {
  min-height: 100dvh;
}

/* --- the rail ------------------------------------------------------------ */

/* Part A turns .sp-side-nav into a hidden drawer below 1023px - opacity,
   visibility, pointer-events, a translate off-screen and a drawer shadow -
   because that is the width the documentation shell stops carrying a
   permanent rail at. The portal's rail is permanent down to 900px and only
   becomes a drawer under it, so between those two widths the rail was
   translated off-screen and invisible while the content column still held
   256px open for it. Every one of those five is restored here and spent again
   at the portal's own breakpoint; Part A stays untouched.

   This mattered less when a top bar carried Home, Settings, Profile and the
   theme toggle: a laptop at 1000px still had somewhere to click. With the
   rail as the only navigation there is nothing to fall back to, so it has to
   be right at every width. */
.page--portal .portal-sidebar {
  position: fixed;
  inset-block: 0;
  inset-inline-start: 0;
  z-index: 90;
  width: var(--portal-sidebar);
  height: 100dvh;
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transform: none;
  box-shadow: none;
}

.page--portal .portal-brand {
  display: grid;
  gap: var(--space-8);
  padding: var(--space-20) var(--space-20) var(--space-16);
  text-decoration: none;
  color: inherit;
}

.page--portal .portal-brand .brand-logo {
  display: block;
  width: 132px;
  height: auto;
  overflow: visible;
  stroke: none;
}

/* The product line sits under the wordmark rather than beside it, so the two
   read as one lockup: whose product, then which part of it. */
.page--portal .portal-brand__product {
  color: var(--side-nav-muted);
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-medium);
  letter-spacing: 0.06em;
}

.page--portal .portal-sidebar__scroll {
  flex: 1 1 auto;
  padding-inline: var(--space-12);
}

/* Three routes do not need a group heading above them. */
.page--portal .portal-sidebar .sp-side-nav__link {
  min-height: var(--space-40);
  gap: var(--space-12);
  border-radius: var(--radius-lg);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

.page--portal .portal-sidebar .sp-side-nav__link:hover {
  border-color: transparent;
}

/* The current row is marked by its tinted ground and its violet leading icon,
   and by nothing else: "Side (.sp-side-nav) navigation marks the current row
   with a subtle tinted background and a violet leading icon; the label never
   goes bold, and there is no underline" (AGENTS.md).

   Part A steps the label to semibold and this rule restated it, so the rule the
   contract states was broken in the portal's own stylesheet - and the type
   shifted under the reader as they moved between routes, which is the thing a
   weight change on selection always costs. Part A is read-only, so medium has
   to be asked for here rather than removed there. The subnav directly below
   already gets this right and says so in its comment. */
.page--portal .portal-sidebar .sp-side-nav__link.is-active,
.page--portal .portal-sidebar .sp-side-nav__link[aria-current="page"] {
  border-color: transparent;
  color: var(--nav-current-text);
  font-weight: var(--weight-medium);
}

.page--portal .portal-sidebar .sp-side-nav__link svg {
  --icon-stroke: var(--icon-stroke-sm);
}

/* The secondary rows under the routes - the skill categories once Skills is
   the current route. Indented to the label above them, never bold. */
.page--portal .portal-subnav {
  display: grid;
  gap: var(--space-2);
  margin: var(--space-4) 0 var(--space-4) var(--space-24);
  padding-inline-start: var(--space-12);
  border-inline-start: 1px solid var(--side-nav-border);
}

.page--portal .portal-subnav__link {
  min-height: var(--space-32);
  padding-inline: var(--space-10);
  display: flex;
  align-items: center;
  gap: var(--space-8);
  border-radius: var(--radius-md);
  color: var(--side-nav-muted);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-medium);
  text-decoration: none;
  transition: var(--transition-interactive);
}

.page--portal .portal-subnav__link:hover {
  background: var(--side-nav-hover);
  color: var(--side-nav-text);
}

.page--portal .portal-subnav__link.is-active,
.page--portal .portal-subnav__link[aria-current="page"] {
  background: var(--side-nav-active);
  color: var(--side-nav-text);
}

/* A skill category's colour is the thing it names, so it is worth nothing as
   decoration on a row you are not standing in. These glyphs took
   --side-nav-accent - one violet, on all three, in every state - which said
   "navigation" where the reader needed "which dimension", and disagreed with
   the Skills page the row leads to. Inactive now matches Home and Games
   exactly; only the current row spends its dimension's colour, and it takes
   it from [data-dimension] on the row itself, so this stays one rule rather
   than three. --skill-ink is the text-safe step of each ramp and already
   flips for the light ground. */
.page--portal .portal-subnav__link .sp-icon {
  --icon-size: var(--icon-size-xs);
  --icon-stroke: var(--icon-stroke-xs);
  flex: 0 0 auto;
  color: currentColor;
}

.page--portal .portal-subnav__link.is-active .sp-icon,
.page--portal .portal-subnav__link[aria-current="page"] .sp-icon {
  color: var(--skill-ink);
}

/* The foot carries what the top bar used to: Profile, Settings and Theme, in
   that order, under the routes rather than opposite them. They are the same
   .sp-side-nav__link the routes are - one row shape, one active treatment -
   because a destination that looks like a utility is a destination a reader
   has to be told about twice. Theme is the one row that is a control rather
   than a route, which is why it is a <button>; it still reads as a row. */
.page--portal .portal-sidebar__foot {
  padding: var(--space-12);
  display: grid;
  gap: var(--space-8);
  border-top: 1px solid var(--side-nav-border);
}

.page--portal .portal-sidebar__utilities {
  padding-block-end: var(--space-8);
  border-block-end: 1px solid var(--side-nav-border);
}

/* A <button> in a row of links: the cursor and the text alignment are the two
   things the element does not bring with it. */
.page--portal .portal-sidebar__utilities button.sp-side-nav__link {
  cursor: pointer;
  text-align: start;
}

/* --- the main column ----------------------------------------------------- */

.page--portal .portal-main {
  min-width: 0;
  margin-inline-start: var(--portal-sidebar);
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
}

/* Nothing but the drawer's opener lives here now, so above the drawer
   breakpoint the bar does not exist: display: none rather than an empty
   64px band with a hairline under it. Everything it needs to be a bar is
   declared here and the breakpoint only turns it on. */
.page--portal .portal-topbar {
  position: sticky;
  top: 0;
  z-index: 60;
  min-height: var(--portal-bar-height);
  /* One value on all four sides. The bar took the page gutter horizontally and
     6px vertically, so the logo sat 16px in from the edge with 6px of air above
     it - the mark read as inset while the bar read as tight, and the two
     paddings were answering different questions. The header is chrome, not a
     card, so it is not bound to the gutter the cards line up on; balanced
     around its own contents is what it needs.

     max() against the safe area is for a landscape notch, which is the one
     case where the physical edge is not where the screen edge is. */
  padding: var(--space-8);
  padding-inline:
    max(var(--space-8), env(safe-area-inset-left))
    max(var(--space-8), env(safe-area-inset-right));
  display: none;
  align-items: center;
  gap: var(--space-12);
  border-bottom: 1px solid var(--ui-border);
  background: color-mix(in srgb, var(--ui-page) 88%, transparent);
  backdrop-filter: blur(16px);
}

/* The bar reads brand-first: the full lockup on the leading edge, the drawer's
   opener pushed to the trailing one. Below 900px the rail is closed, so this is
   the only Skillprint mark on screen and the bare favicon glyph it used to
   carry was the one place the product did not say its own name.

   It is still not a link. The rail's lockup remains the way home, and a second
   route to it in the bar is the second navigation the shell exists to avoid -
   that half of the argument the old mark made is unchanged.

   The lockup is 132px, the width the rail draws its own at, so the brand is one
   size across the product rather than one per breakpoint. Height stays auto
   against a 494x100 viewBox: the ratio is the artwork's, never the box's. */
.page--portal .portal-topbar__brand {
  display: block;
  min-width: 0;
}

.page--portal .portal-topbar .brand-logo {
  width: 132px;
  height: auto;
  overflow: visible;
  stroke: none;
}

/* Which of the pair is drawn is asked here in the same shape the rail asks it,
   and for the same reason: a single `display: block` on `.portal-topbar
   .brand-logo` would sit at (0,3,0), out-specify the global
   `.brand-logo--light { display: none }` pair, and draw both logos at once. */
.page--portal .portal-topbar .brand-logo--dark { display: block; }
.page--portal .portal-topbar .brand-logo--light { display: none; }

html:is([data-surface="light"], [data-theme="light"]) .page--portal .portal-topbar .brand-logo--dark { display: none; }
html:is([data-surface="light"], [data-theme="light"]) .page--portal .portal-topbar .brand-logo--light { display: block; }

/* The opener takes the trailing edge. `margin-inline-start: auto` rather than
   a `justify-content` on the bar, so the bar keeps one rule if anything is
   ever added between the two. */
.page--portal .portal-topbar .portal-nav-toggle {
  margin-inline-start: auto;
}

/* The content runs the width the topbar runs. A measure cap here used to
   centre the column while the bar above it kept the plain gutter, so past
   ~1400px the page read as two different left edges - search at one gutter
   from the nav, the page title a hundred-odd pixels further in. The gutter is
   the only inset either of them takes now; the rail and the copy measures
   inside do the narrowing that the cap was standing in for. */
/* The tail used to be 64px, which sat under the last section on top of the
   foot's own 20px and read as a band of nothing before the rule. */
.page--portal .portal-content {
  flex: 1 1 auto;
  min-width: 0;
  width: 100%;
  padding: var(--space-32) var(--portal-gutter) var(--space-40);
}

/* --- the utility footer -------------------------------------------------- */

.page--portal .portal-foot {
  padding: var(--space-20) var(--portal-gutter) var(--space-28);
  width: 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-16);
  border-top: 1px solid var(--ui-border);
  color: var(--subtle);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-sm);
}

.page--portal .portal-foot nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-16);
}

.page--portal .portal-foot a {
  color: inherit;
  text-decoration: none;
}

.page--portal .portal-foot a:hover {
  color: var(--ui-text);
  text-decoration: underline;
  text-underline-offset: var(--space-2);
}

.page--portal .portal-foot__legal {
  margin-inline-start: auto;
}

/* --- the drawer, below the rail's breakpoint ----------------------------- */

.page--portal .portal-nav-toggle,
.page--portal .portal-sidebar__close {
  display: none;
}

.page--portal .portal-scrim {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: none;
  border: 0;
  padding: 0;
  background: var(--side-nav-scrim);
}


/* ==========================================================================
   09 - Unique composition · Games Portal · page furniture

   The parts every portal screen builds from once the shell is around it: the
   page head, the two-column dashboard, the section bar, and the game tile
   that carries the artwork. They are here rather than in each page's own
   section because Home, Games, Skills and Profile all draw them, and a tile
   that is a different object on each screen is how a portal stops reading as
   one product.
   ========================================================================== */

/* 32, matching the rhythm between sections, because the head is separated from
   the page's first section by whichever of the two is larger and the portal has
   both kinds of screen. A screen built on .portal-layout zeroes the layout's own
   margin-top, so the head's margin was the whole gap and measured 28; a screen
   that stacks sections straight into .portal-content got the shell's 32 instead
   and the head's margin collapsed under it. Identical heads, 4px apart,
   depending on a structural choice further down the page. At 32 both routes
   agree and the number is one the ladder already uses. */
.page--portal .portal-head {
  display: grid;
  gap: var(--space-6);
  margin-bottom: var(--space-32);
}

.page--portal .portal-head__row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-20);
}

.page--portal .portal-head h1 {
  margin: 0;
  font-size: var(--display-xs-font-size);
  line-height: var(--display-xs-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.01em;
}

.page--portal .portal-head p {
  margin: 0;
  max-width: var(--measure-md);
  color: var(--ui-muted);
  font-size: var(--font-size-base);
  line-height: var(--line-height-base);
}

/* --------------------------------------------------------------------------
   Two labels, and only two

   The portal had grown about thirty spellings of "small grey label", all of
   them written inline: font-2xs (9px - a size that appears on no scale in this
   sheet) plus weight-semibold or weight-bold, plus text-uppercase, plus
   tracking-label. Applied to a stat caption, a skill name, a filter group
   heading and a section eyebrow alike, so every small thing on a page shouted
   at exactly the same volume and nothing about the type said which was which.

   There are two now.

   .portal-eyebrow is the section eyebrow, and capitals belong to it alone -
   that is the one place the design system spends them (.section-index on the
   design system's own pages is Geist, semibold, uppercase, muted). It is Geist
   here too now, not Geist Mono: the sheet reserves the mono family for
   technical information - poster numbers, data, code, metadata - and the word
   "Profile" over a page title is none of those.

   .ui-label is everything else: the caption under a figure, the name of a
   metric, the heading over a group of chips. Sentence case, one step of the
   real scale, medium weight, muted. It sits with .ui-badge and .ui-tag as the
   third member of that family.
   -------------------------------------------------------------------------- */

.page--portal .portal-eyebrow,
.page--portal .rail-card__label {
  color: var(--ui-muted);
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.page--portal .ui-label {
  color: var(--ui-muted);
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-medium);
  letter-spacing: normal;
  text-transform: none;
}

/* On a dark plate the muted step is the plate's own, not the page's. */
.page--portal .ontology-visual .ui-label {
  color: var(--ui-muted);
}

/* --- the dashboard: content, then the column that stays with it ---------- */

.page--portal .portal-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--portal-rail);
  align-items: start;
  gap: var(--space-28);
}

.page--portal .portal-layout__main {
  min-width: 0;
  display: grid;
  gap: var(--space-32);
}

.page--portal .portal-rail {
  position: sticky;
  top: calc(var(--portal-topbar) + var(--space-20));
  min-width: 0;
  display: grid;
  gap: var(--space-16);
}

/* --- section bar --------------------------------------------------------- */

.page--portal .portal-section {
  display: grid;
  gap: var(--space-14);
  min-width: 0;
}

.page--portal .portal-section__bar {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-16);
  min-width: 0;
}

.page--portal .portal-section__title {
  margin: 0;
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
  font-weight: var(--weight-semibold);
}

.page--portal .portal-section__hint {
  margin: var(--space-2) 0 0;
  color: var(--ui-muted);
  font-size: var(--font-size-md);
  line-height: var(--line-height-md);
}

.page--portal .portal-section__link {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: var(--space-6);
  color: var(--ui-muted);
  font-size: var(--font-size-md);
  font-weight: var(--weight-semibold);
  text-decoration: none;
}

.page--portal .portal-section__link:hover { color: var(--ui-text); }

.page--portal .portal-section__link .sp-icon {
  --icon-size: var(--icon-size-xs);
  --icon-stroke: var(--icon-stroke-xs);
}

/* --- the game tile ------------------------------------------------------- */

.page--portal .game-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(232px, 1fr));
  gap: var(--space-16);
}

/* A rail rather than a grid where the row is a shortlist and its length is
   the point - continue, recommended, recently played. It scrolls with a snap
   so a half tile at the edge reads as "there is more", not as a broken grid. */
.page--portal .game-rail {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(232px, 1fr);
  gap: var(--space-16);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  padding-bottom: var(--space-4);
}

.page--portal .game-rail > * { scroll-snap-align: start; }

.page--portal .play-card {
  --card-current-padding: 0;
  --card-current-radius: var(--radius-2xl);
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  overflow: hidden;
  text-decoration: none;
  color: inherit;
}

.page--portal .play-card__art {
  position: relative;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: color-mix(in srgb, var(--mindset-violet-500) 7%, transparent);
}

.page--portal .play-card__art img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The animated artwork is the same drawing with its own keyframes, so it can
   sit on top of the static one and simply be revealed. Opacity rather than a
   src swap keeps the first frame identical and never flashes empty. */
.page--portal .play-card__art .art-animated {
  opacity: 0;
  transition: opacity var(--duration-base) ease;
}

@media (hover: hover) {
  .page--portal .play-card:hover .art-animated,
  .page--portal .play-card:focus-within .art-animated { opacity: 1; }
}

/* .play-card__flag was the homepage's own third treatment for a status - a
   white pill in the top-left corner. Both tiles now carry .media-badge, so
   there is nothing left to style here. */

/* Title, meta, then the foot takes the slack. Every tile carries exactly those
   three children, and the third row is the one that grows, so the two buttons
   sit on the floor of the tile rather than wherever the meta happens to end.
   The rail looks aligned today only because every meta is one line; the moment
   one wraps - and at rail width they do - the foot dropped 24px below its
   neighbours while the cards stayed the same height. */
.page--portal .play-card__body {
  flex: 1 1 auto;
  display: grid;
  grid-template-rows: auto auto 1fr;
  align-content: start;
  gap: var(--space-4);
  padding: var(--space-14) var(--space-16) var(--space-16);
}

.page--portal .play-card__title {
  margin: 0;
  font-size: var(--font-size-base);
  line-height: var(--line-height-base);
  font-weight: var(--weight-semibold);
  color: var(--ui-text);
}

.page--portal .play-card__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-6);
  color: var(--ui-muted);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-sm);
}

.page--portal .play-card__meta i {
  width: var(--space-2);
  height: var(--space-2);
  border-radius: var(--radius-full);
  background: currentColor;
  opacity: 0.6;
}

.page--portal .play-card__foot {
  /* the .push-block-end pattern the library card's .card-footer already uses;
     the old fixed margin becomes padding so the minimum gap survives */
  margin-top: auto;
  padding-top: var(--space-12);
  align-self: end;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-8);
}

/* The progress line on a game already in play. Two ends of the same token so
   a resumed tile and a fresh one are the same object with one part filled. */
.page--portal .play-card__progress {
  height: var(--space-4);
  border-radius: var(--radius-full);
  background: var(--surface-strong);
  overflow: hidden;
}

.page--portal .play-card__progress > i {
  display: block;
  height: 100%;
  width: var(--meter, 0%);
  border-radius: inherit;
  background: var(--mindset-violet-500);
}

/* --- the one big tile: continue where you left off ----------------------- */

.page--portal .continue-card {
  --card-current-padding: 0;
  --card-current-radius: var(--radius-3xl);
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 42%);
  align-items: stretch;
  overflow: hidden;
}

.page--portal .continue-card__copy {
  display: grid;
  align-content: center;
  gap: var(--space-10);
  padding: var(--space-28) var(--space-32);
  min-width: 0;
}

.page--portal .continue-card__title {
  margin: 0;
  font-size: var(--display-xs-font-size);
  line-height: var(--display-xs-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.01em;
}

.page--portal .continue-card__lede {
  margin: 0;
  max-width: 46ch;
  color: var(--ui-muted);
  font-size: var(--font-size-base);
  line-height: var(--line-height-base);
}

.page--portal .continue-card__actions {
  margin-top: var(--space-6);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-10);
}

.page--portal .continue-card__art {
  position: relative;
  min-height: var(--space-224);
  overflow: hidden;
  background: color-mix(in srgb, var(--mindset-violet-500) 7%, transparent);
}

.page--portal .continue-card__art img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* --- the right column's cards -------------------------------------------- */

.page--portal .rail-card {
  --card-current-padding: var(--space-18);
  --card-current-radius: var(--radius-2xl);
  display: grid;
  gap: var(--space-14);
}

.page--portal .rail-card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-12);
}

.page--portal .rail-card__title {
  margin: 0;
  font-size: var(--font-size-md);
  line-height: var(--line-height-md);
  font-weight: var(--weight-semibold);
  color: var(--ui-text);
}

.page--portal .rail-stats {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-8);
}

.page--portal .rail-stat {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-10) var(--space-12);
  border-radius: var(--radius-lg);
  background: var(--surface-raised);
  min-width: 0;
}

.page--portal .rail-stat dt {
  color: var(--ui-muted);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-medium);
  letter-spacing: normal;
  text-transform: none;
}

.page--portal .rail-stat dd {
  margin: 0;
  color: var(--ui-text);
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

.page--portal .rail-list {
  display: grid;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.page--portal .rail-list li {
  display: flex;
  align-items: center;
  gap: var(--space-10);
  padding: var(--space-8) 0;
  min-width: 0;
  font-size: var(--font-size-md);
  line-height: var(--line-height-md);
}

.page--portal .rail-list li + li {
  border-top: 1px solid var(--ui-border);
}

.page--portal .rail-list__name {
  min-width: 0;
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ui-text);
}

.page--portal .rail-list__value {
  flex: 0 0 auto;
  color: var(--ui-muted);
  font-variant-numeric: tabular-nums;
}

.page--portal .rail-thumb {
  width: var(--space-32);
  height: var(--space-32);
  flex: 0 0 auto;
  border-radius: var(--radius-md);
  object-fit: cover;
  background: color-mix(in srgb, var(--mindset-violet-500) 8%, transparent);
}

/* The bar a skill score is read from. The track is the recessed tint, so a
   row of them at different fills still reads as one instrument. */
.page--portal .rail-meter {
  height: var(--space-6);
  border-radius: var(--radius-full);
  background: var(--surface-strong);
  overflow: hidden;
}

.page--portal .rail-meter > i {
  display: block;
  height: 100%;
  width: var(--meter, 0%);
  border-radius: inherit;
  background: var(--meter-tone, var(--mindset-violet-500));
}

/* --- the skill category switcher ----------------------------------------- */

.page--portal .skills-switch {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
  padding: var(--space-4);
  border: 1px solid var(--ui-border);
  border-radius: var(--radius-full);
  background: var(--ui-surface);
  width: fit-content;
  max-width: 100%;
}

.page--portal .skills-switch__item {
  min-height: var(--space-32);
  padding-inline: var(--space-14);
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
  border: 0;
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--ui-muted);
  font-family: var(--skillprint-font-ui);
  font-size: var(--font-size-md);
  font-weight: var(--weight-semibold);
  text-decoration: none;
  cursor: pointer;
  transition: var(--transition-interactive);
}

.page--portal .skills-switch__item .sp-icon {
  --icon-size: var(--icon-size-xs);
  --icon-stroke: var(--icon-stroke-xs);
}

.page--portal .skills-switch__item:hover {
  background: var(--surface-raised);
  color: var(--ui-text);
}

.page--portal .skills-switch__item.is-active,
.page--portal .skills-switch__item[aria-selected="true"],
.page--portal .skills-switch__item[aria-current="page"] {
  background: var(--mindset-violet-500);
  color: var(--geistface-grey-25);
}

.page--portal .skills-switch__item.is-active .sp-icon,
.page--portal .skills-switch__item[aria-selected="true"] .sp-icon,
.page--portal .skills-switch__item[aria-current="page"] .sp-icon {
  color: currentColor;
}

/* --- a compact toolbar for the library ----------------------------------- */

.page--portal .portal-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-10);
}

.page--portal .portal-toolbar__count {
  margin-inline-start: auto;
  color: var(--ui-muted);
  font-size: var(--font-size-md);
  font-weight: var(--weight-semibold);
}

.page--portal .filter-chip {
  min-height: var(--space-32);
  padding-inline: var(--space-12);
  display: inline-flex;
  align-items: center;
  gap: var(--space-6);
  border: 1px solid var(--ui-border);
  border-radius: var(--radius-full);
  background: var(--ui-surface);
  color: var(--ui-muted);
  font-family: var(--skillprint-font-ui);
  font-size: var(--font-size-md);
  font-weight: var(--weight-semibold);
  cursor: pointer;
  transition: var(--transition-interactive);
}

.page--portal .filter-chip:hover {
  background: var(--surface-raised);
  color: var(--ui-text);
}

.page--portal .filter-chip.is-active,
.page--portal .filter-chip[aria-pressed="true"] {
  border-color: transparent;
  background: var(--mindset-violet-500);
  color: var(--geistface-grey-25);
}

/* --- responsive ---------------------------------------------------------- */

@media (max-width: 1180px) {
  .page--portal {
    --portal-rail: var(--space-256);
  }
}

@media (max-width: 1023px) {
  .page--portal .portal-layout {
    grid-template-columns: minmax(0, 1fr);
  }

  .page--portal .portal-rail {
    position: static;
    grid-auto-flow: row;
  }

  .page--portal .continue-card {
    grid-template-columns: minmax(0, 1fr);
  }

  .page--portal .continue-card__art {
    order: -1;
    min-height: var(--space-160);
  }
}

/* Below this the rail becomes a drawer, and the bar comes back to hold the
   button that opens it - the only thing it carries. The scrim is a real
   button so the dismiss is reachable from the keyboard. --portal-topbar goes
   back to a real height here, which is what puts the sticky rail and every
   fragment jump below the bar again. */
@media (max-width: 900px) {
  .page--portal {
    --portal-gutter: var(--space-20);
    --portal-topbar: var(--portal-bar-height);
  }

  .page--portal .portal-topbar {
    display: flex;
  }

  .page--portal .portal-main {
    margin-inline-start: 0;
  }

  .page--portal .portal-sidebar {
    transform: translateX(-100%);
    transition: transform var(--duration-base) var(--ease-standard);
    box-shadow: none;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
  }

  /* inline-grid, not inline-flex. .icon-button centres its glyph with
     `place-items: center` on a grid, and `justify-items` is a no-op in flex -
     so flipping the display here left the mark on the flex line's start edge:
     1px from the left of the 44px box and 23px from the right, while
     align-items kept it honest vertically. The button only ever needed to stop
     being `display: none`; it did not need a different layout model. The
     `flex: 0 0 auto` that rode along is already on .icon-button. */
  .page--portal .portal-nav-toggle {
    display: inline-grid;
  }

  .page--portal .portal-sidebar__close {
    position: absolute;
    inset-block-start: var(--space-14);
    inset-inline-end: var(--space-12);
    display: inline-flex;
  }

  .page--portal.nav-open .portal-sidebar {
    transform: translateX(0);
    box-shadow: var(--shadow-xl);
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
  }

  .page--portal.nav-open .portal-scrim {
    display: block;
  }
}

@media (max-width: 640px) {
  .page--portal .portal-content {
    padding-block: var(--space-24) var(--space-48);
  }

  .page--portal .portal-head h1 {
    font-size: var(--text-xl-font-size);
    line-height: var(--text-xl-line-height);
  }

  .page--portal .game-grid {
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  }

  .page--portal .rail-stats {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .page--portal .portal-foot {
    gap: var(--space-10);
  }

  .page--portal .portal-foot__legal {
    margin-inline-start: 0;
    flex-basis: 100%;
  }
}

@media (prefers-reduced-motion: reduce) {
  .page--portal .portal-sidebar { transition: none; }
  .page--portal .play-card__art .art-animated { transition: none; }
}

/* The switcher is a review affordance, not a route. In the rail's foot it is
   the quietest control on the screen: tertiary, full width, one line of copy
   instead of two, and its menu opens upward out of the foot.

   The treatment belongs to the foot rather than to the switcher: anything
   review-only that ends up down here is answering the same question, so Home's
   state preview is named alongside it instead of restating the same four
   declarations under its own class. Without this it kept the raw <strong> the
   dropdown ships with - 900 against the switcher's 500, directly above it. */
:is(.page--portal .portal-sidebar__foot .screen-switch,
    .page--portal .portal-sidebar__foot .portal-preview__switch) {
  margin-inline-end: 0;
  width: 100%;
  /* The switcher takes order: -1 below 640px, from the days it sat in a page
     header beside other controls and wanted to lead them. In the rail's foot
     it sits under Profile, Settings and Theme, and it stays there: a review
     affordance that jumps above the player's own routes on a phone is the
     loudest thing in the rail on the smallest screen. */
  order: 0;
}

:is(.page--portal .portal-sidebar__foot .screen-switch__trigger,
    .page--portal .portal-sidebar__foot .portal-preview__switch .sp-dropdown__trigger) {
  max-width: none;
  justify-content: space-between;
  font-size: var(--font-size-sm);
  font-weight: var(--weight-medium);
  color: var(--side-nav-muted);
}

:is(.page--portal .portal-sidebar__foot .screen-switch__trigger,
    .page--portal .portal-sidebar__foot .portal-preview__switch .sp-dropdown__trigger) strong {
  font-weight: var(--weight-medium);
}

:is(.page--portal .portal-sidebar__foot .screen-switch__menu,
    .page--portal .portal-sidebar__foot .portal-preview__menu) {
  inset-block-end: calc(100% + var(--space-8));
  inset-block-start: auto;
  inset-inline: 0 auto;
  width: var(--space-320);
  max-height: min(64vh, var(--space-480));
}

/* A card that is a link is still a card: the underline belongs to running
   text, and on a tile it drew a line under every label in the grid. */
.page--portal a.sp-card,
.page--portal a.sp-card:hover {
  text-decoration: none;
  color: inherit;
}

/* Two up, for the rows whose items are a label and a count rather than a
   piece of artwork. At the auto-fill measure those left an orphan on the
   second line; this keeps the row square at every width the rail allows. */
.page--portal .game-grid--pairs {
  grid-template-columns: repeat(auto-fill, minmax(196px, 1fr));
}

/* A rail that runs past its right edge fades out there, so the last card
   reads as continuing rather than as sliced off by the column. The class is
   set only while there is actually more to scroll, so the fade is never over
   the final card once you have reached it.

   Keyed off the same two hooks the rail layout above is keyed off, not off
   .game-rail: More like this on Game Detail is a [data-gd-related] list that
   already scrolled horizontally and already had every other rail behaviour,
   and matching on the class alone was the one thing it did not inherit. */
/* The trailing edge only. A leading fade was tried and pulled: it dims the
   first card in the row, and the first card is the one that has to read whole -
   a row you have scrolled into should still show you a complete card at its
   left edge, not a half-faded one. The end fade is the one that carries
   information anyway; the left edge already tells you there is more behind it
   by the fact that you scrolled.

   --scroll-fade is the one width these share, so the session toolbar, the game
   rows and the pillar pills all fade over the same distance. */
.page--portal {
  --scroll-fade: var(--space-64);
}

.page--portal :is([data-game-rail], [data-gd-related], [data-scroll-fade]).is-clipped-end {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - var(--scroll-fade)), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - var(--scroll-fade)), transparent);
}


/* ==========================================================================
   09 - Unique composition · Games Portal · the front door

   Signing in is the one portal screen with no rail: there is nothing to
   navigate to yet. So it keeps a single bar carrying the lockup and the theme
   control, one card, and the same small utility footer every other screen
   ends with.

   The card used to float over three radial washes off the brand ramps. They
   are gone with the rest of the portal's gradients - on a light ground the
   wash was the only thing on the screen doing any shouting, and the card now
   separates from the canvas the way every other card does: a hairline and a
   shadow.
   ========================================================================== */

.page--portal-welcome .welcome-shell {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

/* The signed-out screen has no rail, so this bar is the only chrome it has and
   it is drawn at every width. It takes the bar height directly rather than
   --portal-topbar, which is now zero wherever the rail is on screen. */
.page--portal-welcome .welcome-bar {
  min-height: var(--portal-bar-height);
  padding: var(--space-12) var(--portal-gutter);
  display: flex;
  align-items: center;
  gap: var(--space-12);
  border-bottom: 1px solid var(--ui-border);
}

.page--portal-welcome .welcome-bar__brand {
  display: grid;
  gap: var(--space-2);
  text-decoration: none;
  color: inherit;
}

.page--portal-welcome .welcome-bar__brand .brand-logo {
  display: block;
  width: 132px;
  height: auto;
  overflow: visible;
  stroke: none;
}

.page--portal-welcome .welcome-bar__brand .brand-logo--light { display: none; }

html:is([data-surface="light"], [data-theme="light"]) .page--portal-welcome .welcome-bar__brand .brand-logo--dark { display: none; }
html:is([data-surface="light"], [data-theme="light"]) .page--portal-welcome .welcome-bar__brand .brand-logo--light { display: block; }

.page--portal-welcome .welcome-bar__actions {
  margin-inline-start: auto;
  display: flex;
  align-items: center;
  gap: var(--space-8);
}

.page--portal-welcome .welcome-main {
  flex: 1 1 auto;
  display: grid;
  place-items: center;
  padding: var(--space-48) var(--portal-gutter);
}

/* --- the card ------------------------------------------------------------ */

.page--portal-welcome .auth-card {
  --card-current-padding: var(--space-40);
  --card-current-radius: var(--radius-4xl);
  width: 100%;
  max-width: var(--space-480);
  display: grid;
  gap: var(--space-28);
  box-shadow: var(--shadow-lg);
}

.page--portal-welcome .auth-card__head {
  display: grid;
  gap: var(--space-8);
  text-align: center;
  justify-items: center;
}

/* The title was clamped up to 36px, which on a 480px card is a marketing
   headline rather than the label on a form. It comes back to the system's own
   display step. */
.page--portal-welcome .auth-card__title {
  font-size: var(--display-xs-font-size);
  line-height: var(--display-xs-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.01em;
}

.page--portal-welcome .auth-card__lede {
  max-width: 38ch;
  font-size: var(--font-size-md);
  line-height: var(--line-height-md);
}

.page--portal-welcome .auth-card__body {
  display: grid;
  gap: var(--space-20);
}

.page--portal-welcome .auth-divider__rule {
  flex: 1 1 auto;
  height: 1px;
  background: var(--ui-border);
}

.page--portal-welcome .auth-divider__label { letter-spacing: 0.1em; }

.page--portal-welcome .auth-providers { gap: var(--space-10); }

/* Both provider buttons are one row: mark, then label, centred as a pair, so
   the two rows align on the same optical centre rather than each centring its
   own contents. */
.page--portal-welcome .auth-provider {
  justify-content: center;
  gap: var(--space-10);
}

/* The real marks. A prototype that draws a letter G where Google's mark
   belongs is testing a button that will never ship; the artwork is the
   vendors' own, kept unrecoloured and unrotated, and sized to the button's
   own icon step so it sits on the label's cap height. */
.page--portal-welcome .auth-provider__mark {
  width: var(--icon-size-sm);
  height: var(--icon-size-sm);
  flex: 0 0 auto;
  display: block;
}

.page--portal-welcome .auth-org {
  padding-top: var(--space-16);
  border-top: 1px solid var(--ui-border);
}

.page--portal-welcome .auth-org__toggle {
  justify-content: center;
  gap: var(--space-8);
  color: var(--mindset-violet-600);
  font-weight: var(--weight-semibold);
}

.page--portal-welcome .auth-org__toggle:hover { color: var(--mindset-violet-700); }

html:is([data-surface="dark"], [data-theme="dark"]) .page--portal-welcome .auth-org__toggle {
  color: var(--mindset-violet-300);
}

.page--portal-welcome .auth-org__toggle[aria-expanded="true"] { display: none; }

/* The disclosure is a swap, not a stack. The toggle removes itself above, but
   the guest CTA, the divider and the two providers stayed, so the open card
   stacked both sign-in paths and stood 814px tall. On a 936px viewport that is
   taller than the screen can hold once the 72px bar, the 96px of main padding
   and the 67px foot are counted, and the legal line and the footer both fell
   below the fold. These are the other half of what the link replaces; Cancel
   and Escape put them back, and both already return focus to the toggle. */
.page--portal-welcome .auth-card__body:has(.auth-org__toggle[aria-expanded="true"])
  :is(.auth-guest, .auth-divider, .auth-providers) {
  display: none;
}

/* With nothing above it, the rule that divided the org link from the providers
   divides nothing. */
.page--portal-welcome .auth-card__body:has(.auth-org__toggle[aria-expanded="true"]) .auth-org {
  padding-top: 0;
  border-top: 0;
}

.page--portal-welcome .auth-org__form {
  gap: var(--space-12);
  margin-top: var(--space-4);
}

.page--portal-welcome .auth-org__form[hidden] { display: none; }

/* The markup asks for this in the small step (`font-sm leading-md
   weight-semibold`) and none of it was landing: `.font-sm` and
   `.page :where(h2)` are both specificity (0,1,0), and the heading rule is the
   later of the two in this file, so it won. The panel label was rendering at
   the section-heading step - clamp(32px, 4vw, 48px) - which is 111px of a
   480px card spent on two words, and the second largest reason the open card
   did not fit. Stated here rather than left to the utility, the way
   `.auth-card__title` next door already states its own size.

   14px rather than the 12px `font-sm` would have given it: the form's own
   field labels are 12px/600, so at the literal value the panel title would
   have been the same size as the labels beneath it and the hierarchy would
   read flat. This is one step up from them and well clear of the 24px card
   title. Tracking comes back to normal - the -0.035em on the heading rule is
   display tracking and reads as a typo at label size. */
.page--portal-welcome .auth-org__title {
  font-size: var(--font-size-md);
  line-height: var(--line-height-md);
  font-weight: var(--weight-semibold);
  letter-spacing: normal;
}

.page--portal-welcome .auth-org__actions > * { flex: 1 1 0; }

.page--portal-welcome .auth-org__status:empty { display: none; }

.page--portal-welcome .auth-legal { max-width: 40ch; margin-inline: auto; }

@media (max-width: 640px) {
  .page--portal-welcome .auth-card { --card-current-padding: var(--space-24); }
  .page--portal-welcome .welcome-main { padding-block: var(--space-24); }
}

/* ==========================================================================
   09 - Unique composition · Games Portal · the library tile, tightened

   The library tile stays the information-rich one - the Games screen is where
   a player is asking what a game measures, and mood and cognition are the
   answer - but it was drawn for a three-up grid on a full-width page and the
   rail took that width away. Both selectors move together: the design system
   documents this tile from the same markup, and a tile that reads differently
   in the reference than in the product is two tiles.
   ========================================================================== */

.page--design-system .portal-library-embed .game-grid--portal,
.page--portal-games .game-grid--portal {
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: var(--space-16);
}

/* The duration is two numbers and a unit; it never wraps. Where the row runs
   out of space the actions drop under it instead of breaking the label. */
.page--design-system .portal-library-embed .card-footer,
.page--portal .card-footer
{
  flex-wrap: wrap;
  row-gap: var(--space-12);
  padding-top: var(--space-16);
}

.page--design-system .portal-library-embed .duration,
.page--portal .duration
{
  white-space: nowrap;
}

.page--design-system .portal-library-embed .game-description,
.page--portal .game-description
{
  /* Two lines reserved, not three. The floor exists so a row of cards puts its
     durations and its "Skills developed" labels at the same height - but it was
     set to the ceiling rather than to the common case. 45 of the 50 library
     blurbs wrap to two lines, so nine cards in ten reserved a third line they
     never used and stood 28px of dead air between the description and the
     duration. Two lines is what the copy actually is.

     The ceiling stays at three, so the five genuinely long blurbs still print
     in full rather than being clipped a line early; those five are the only
     cards whose duration sits a line lower than its neighbours', and the Play
     button is pinned to the card floor regardless, so a row of them still ends
     level. This is the third of three min-heights on this element - the two
     above it are dead, being earlier in the file at equal specificity. */
  min-height: calc(var(--space-22) * 2);
  margin: var(--space-6) 0 var(--space-14);
  font-size: var(--font-size-md);
  line-height: var(--line-height-md);
}

.page--design-system .portal-library-embed .trait-group + .trait-group,
.page--portal .trait-group + .trait-group
{
  margin-top: var(--space-8);
}

.page--design-system .portal-library-embed .trait-group,
.page--portal .trait-group
{
  gap: var(--space-4);
}

/* The switcher in the front door's bar takes the same quiet treatment it has
   in the rail's foot: it is a way to review the set of screens, not a route,
   and a bold label beside a wordmark reads as one. */
.page--portal-welcome .welcome-bar .screen-switch__trigger {
  color: var(--ui-muted);
  font-weight: var(--weight-medium);
}

.page--portal-welcome .welcome-bar .screen-switch__trigger strong {
  font-weight: var(--weight-medium);
}

.page--portal-welcome .welcome-bar .screen-switch__trigger .sp-dropdown__value {
  margin-inline-end: var(--space-4);
}

.page--portal-welcome .auth-legal { max-width: 46ch; }

/* ==========================================================================
   09 - Unique composition · Games Portal · one type scale

   The portal was assembled screen by screen, and each screen picked its own
   size for the same job: a section heading was Display SM on the profile, a
   panel heading beside it was Display SM again, and a card title inside that
   panel was Text XL. Three levels of hierarchy all drawn at roughly one size
   is the "oversized" read the brief names - nothing is large because
   everything is.

   One ladder for the whole portal, taken from the system's own scale:

     page title          Display XS   24 / 32   .portal-head h1
     section heading     Text LG      18 / 28   .portal-section__title, h2
     card / panel title  Text MD      16 / 24   h3, .rail-card__title
     body                Text SM      14 / 20   .portal-section__hint, p
     label               Text XS      12 / 18   eyebrows, meta, units

   Every rule below is a section heading or smaller being brought onto that
   ladder. Nothing gains a size it did not have; several lose one.
   ========================================================================== */

.page--portal .portal-content :is(.pp-section, .portal-section, .skill-feature) h2,
.page--portal .portal-content .section-head-copy h2 {
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: 0;
}

.page--portal .portal-content .section-head-copy p {
  font-size: var(--font-size-md);
  line-height: var(--line-height-md);
}

/* The featured skill led with a 30px name inside a card whose own section
   heading was the same size, so the card out-shouted the section it sat in. */
.page--portal .skill-feature__identity h2,
.page--portal .skill-feature__identity h3 {
  font-size: var(--text-xl-font-size);
  line-height: var(--text-xl-line-height);
  font-weight: var(--weight-semibold);
}

.page--portal .skill-feature__icon {
  --icon-frame-size: var(--space-48);
}

/* Card titles across the portal settle on the body size, semibold. A tile in
   a grid of twenty is a label, not a headline. */
.page--portal .portal-content :is(.skill-card__title, .index-card h3, .sp-card h3, .chart-card-title strong) {
  font-size: var(--font-size-base);
  line-height: var(--line-height-base);
  font-weight: var(--weight-semibold);
}

/* A chart's own labels sit under the section heading that already named the
   subject, so they are metadata rather than a second heading: the eyebrow
   above the chart, the chart's name, and the sentence under it all step down
   one. "Cognitive orbit / Your pillars this week" was reading at the same
   weight as "Performance trends" three lines above it. */
/* A block inside a section, not a section of its own. Skill breakdown used to
   carry an <h2> beside Performance trends' own, which is the two-heading
   structure this page had everywhere: a reader could not tell which was the
   subject and which was a part of it. */
:is(.page--portal-profile, .page--portal-settings) .pp-subhead {
  margin-block: var(--spacing-4xl) var(--spacing-2xl);
}

:is(.page--portal-profile, .page--portal-settings) .pp-subhead .section-head-copy h3 {
  margin: 0;
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
  font-weight: var(--weight-semibold);
}

.page--portal-profile .chart-card-title .theme-label {
  color: var(--ui-muted);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-medium);
  letter-spacing: normal;
  text-transform: none;
}

.page--portal-profile .portal-content .chart-card-title strong {
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
  font-weight: var(--weight-semibold);
}

.page--portal-profile .chart-card-title > span {
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
}

/* The pillar and metric switches beside a chart are controls on it, not
   headings of their own. */
.page--portal-profile :is(.status-tabs, .chart-view-switch) button,
.page--portal-profile .chart-chip,
.page--portal-profile .chart-key {
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
}

.page--portal .skill-card__games-label {
  font-size: var(--text-xxs-font-size);
  line-height: var(--text-xxs-line-height);
  letter-spacing: 0.06em;
}

/* Inside the rail nothing is a section heading: the card is the section. */
.page--portal .portal-rail :is(h2, h3) {
  font-size: var(--font-size-md);
  line-height: var(--line-height-md);
  font-weight: var(--weight-semibold);
  letter-spacing: 0;
}

.page--portal .portal-rail .rail-card p {
  font-size: var(--font-size-md);
  line-height: var(--line-height-md);
}

.page--portal .portal-rail :is(.space-bottom-2xl, .space-bottom-3xl) {
  margin-bottom: var(--space-12);
}

.page--portal .portal-rail .skill-panel__metrics {
  gap: var(--space-8);
}

/* Section rhythm. The stack was a mix of 24, 32 and 48px gaps depending on
   which screen wrote the rule; the dashboard uses one.

   The reset leaves margin-block-start alone on purpose: the rhythm is written
   at (0,2,0) on `> * + *` further down, so a `margin: 0` here at (0,3,0) would
   win and flatten the stack to nothing.

   The third selector is Settings. It is the one screen whose .pp-section sits
   straight in .portal-content rather than inside .portal-layout__main, so the
   two selectors above never reached it and it kept the legacy 48px padding-
   block that `:is(.page--portal-profile, .page--portal-settings) .pp-section`
   still declares - 80px of air between the page head and the first control,
   against 32 on every other screen, and a 48px dead band above the content
   tail. Profile has been overridden to 0 here for so long that the legacy rule
   now only ever landed where it was wrong. */
.page--portal .portal-layout__main > .pp-section,
.page--portal .portal-layout__main > .portal-section,
.page--portal .portal-content > .pp-section {
  margin-inline: 0;
  margin-block-end: 0;
  padding: 0;
  border: 0;
}

.page--portal .portal-content .pp-section {
  display: grid;
  gap: var(--space-14);
}

.page--portal .portal-content .section-head {
  gap: var(--space-16);
}

.page--portal .portal-content .section-head-copy {
  display: grid;
  gap: var(--space-2);
}

/* The dimension switcher is the head of the page's content, not part of the
   title block, so it takes the head's own spacing below it. */
.page--portal .skills-switch {
  margin-bottom: var(--space-28);
}

/* The skill index states its own column count in the Skills page's section:
   it is composed three-up, not fitted to the available width. */

/* ==========================================================================
   09 - Unique composition · Games Portal · shell corrections

   Four faults the migration surfaced, each a rule reaching further than it
   meant to.
   ========================================================================== */

/* 1. The lockup drew both wordmarks on a dark surface. .sp-side-nav hides the
      wrong one at (0,2,0); the brand rule below it forced display: block at
      (0,3,0) and won. The geometry stays on .brand-logo, and which of the two
      is drawn goes back to the pair that owns that question. */
.page--portal .portal-brand .brand-logo {
  width: 132px;
  height: auto;
  overflow: visible;
  stroke: none;
}

.page--portal .portal-brand .brand-logo--dark { display: block; }
.page--portal .portal-brand .brand-logo--light { display: none; }

html:is([data-surface="light"], [data-theme="light"]) .page--portal .portal-brand .brand-logo--dark { display: none; }
html:is([data-surface="light"], [data-theme="light"]) .page--portal .portal-brand .brand-logo--light { display: block; }

/* 2. Rhythm between top-level sections. .portal-content is a plain block, so a
      screen that stacks sections straight into it got none, and three pages
      kept a page-local padding rule to compensate. The shell owns it now. */
.page--portal .portal-content > * + *,
.page--portal .portal-layout__main > * + * {
  margin-top: var(--space-32);
}

.page--portal .portal-layout__main {
  gap: 0;
}

.page--portal .portal-content > .portal-layout {
  margin-top: 0;
}

/* 3. A rule between sections needs air above the heading that follows it.
      .portal-section carries no block padding, so separator-top drew the line
      flush against the title. */
.page--portal .portal-section.separator-top,
.page--portal .portal-content > section.separator-top {
  padding-top: var(--space-28);
}

/* 4. The stack of section paddings written for the old full-bleed pages used to
      compound with the shell's own rhythm here, and was cancelled by a rule
      that reached one class further than the shell's own. Both are gone: the
      page-local `section { padding-block: 48px }` and `main { padding-bottom:
      40px }` are deleted at source rather than overridden, so .portal-section
      carries no block padding on this page for the same reason it carries none
      on any other, and `.portal-section.separator-top { padding-top: 28px }`
      is free to put air above the two headings that follow a hairline - which
      the cancelling rule had been silently zeroing.

      The tail went with them. .portal-content had padding-bottom: 64px here,
      which is the exact value the shell's comment records the portal being
      corrected away from: "The tail used to be 64px, which sat under the last
      section on top of the foot's own 20px and read as a band of nothing
      before the rule." The shell's 40px applies now. */

/* The interstitial's column was 640px wide and pinned left once .sp-container
   stopped centring it, with 96px of dead space above. The shell supplies both
   the measure and the gutter; the wrapper keeps only its section rhythm. */
.page--portal-game-interstitial .portal-content .interstitial {
  max-width: none;
  padding-block: 0;
}

/* The adjustment banner is position: fixed at left: 50%, which was the centre
   of the page before a 256px rail took the left of it. */
.page--portal .sp-adjustment {
  top: calc(var(--portal-topbar) + var(--space-16));
}

@media (min-width: 901px) {
  .page--portal .sp-adjustment {
    margin-inline-start: calc(var(--portal-sidebar) / 2);
  }
}

/* A five-column table inside a rail-narrowed column has to be scrollable, not
   clipped. */
.page--portal .skill-sessions__wrap {
  overflow-x: auto;
}

/* Every section heading in the portal, not only the ones whose wrapper class
   was known when the ladder was written. Each screen names its own sections
   (.stat-section, .skills-section, .gd-section …), so listing them was always
   going to miss the next one; the rule is on the content column instead, and
   the two places that legitimately sit outside it - the rail's cards, and a
   chart card's own title block - restate themselves one step more specific. */
.page--portal .portal-content h2 {
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: 0;
}

.page--portal .portal-content .portal-rail :is(h2, h3),
.page--portal .portal-rail .rail-card :is(h2, h3) {
  font-size: var(--font-size-md);
  line-height: var(--line-height-md);
  font-weight: var(--weight-semibold);
}

.page--portal .portal-content h3 {
  font-size: var(--font-size-base);
  line-height: var(--line-height-base);
  font-weight: var(--weight-semibold);
}

/* The adjustment banner is a transient message about the session that is
   about to start, and it is position: fixed - so wherever the page head ends
   up, it lands on top of it. It moves to the foot of the viewport, which is
   where a message that interrupts nothing belongs, and clears the rail. */
.page--portal .sp-adjustment {
  top: auto;
  bottom: var(--space-24);
}

/* A rail is a grid item of the section above it, and a grid item's default
   min-width is auto - it refuses to shrink below the width of the four tiles
   inside it, so `overflow-x: auto` had nothing to scroll and the page grew a
   horizontal scrollbar on a phone instead. */
.page--portal .portal-section > *,
.page--portal .game-rail {
  min-width: 0;
}

/* The front door's bar carries a lockup and two controls on a 390px screen.
   It wraps rather than pushing the last control off the edge. */
.page--portal-welcome .welcome-bar {
  flex-wrap: wrap;
  row-gap: var(--space-8);
}

.page--portal-welcome .welcome-bar__brand { min-width: 0; }

@media (max-width: 480px) {
  .page--portal-welcome .welcome-bar__actions { margin-inline-start: 0; }
  .page--portal-welcome .welcome-bar__brand .brand-logo { width: 112px; }
}

/* The theme control in that bar is written as a bare .icon-button on this
   screen, which carries no geometry of its own. It takes the same square the
   shell's utilities take so the bar measures the same on both. */
.page--portal-welcome .welcome-bar .icon-button {
  width: var(--space-36);
  height: var(--space-36);
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-control);
  background: transparent;
  color: var(--ui-muted);
  cursor: pointer;
}

.page--portal-welcome .welcome-bar .icon-button:hover {
  background: var(--surface-raised);
  color: var(--ui-text);
}

.page--portal-welcome .welcome-bar .icon-button .sp-icon {
  --icon-size: var(--icon-size-sm);
  --icon-stroke: var(--icon-stroke-sm);
}

/* Three numbers across a 320px rail: a value like "1h 12m" is two words and
   was breaking over two lines while its neighbours stayed on one, which set
   the row's height from the longest label rather than from the numbers. */
.page--portal .rail-stat dd {
  font-size: var(--font-size-base);
  line-height: var(--line-height-base);
  white-space: nowrap;
}

.page--portal .rail-card .streak-row,
.page--portal .rail-card .layout-flex.items-center.justify-between.separator-top {
  flex-wrap: wrap;
  row-gap: var(--space-8);
}


/* ==========================================================================
   PORTAL · THE COLLAPSIBLE RAIL, THE FIRST-RUN HERO, AND THE SKILL GRID

   Three additions that share one idea: the portal's home screen is the first
   thing a player with no Skillprint yet sees, and it has to say what to do
   next before it says anything else.

   The rail collapses to its icons so the reading column keeps the width on a
   laptop. Collapsed is a width change and nothing else - the same three rows,
   the same active treatment, the labels moved into a tooltip - because a rail
   that re-orders itself when it narrows is a second navigation to learn.
   ========================================================================== */

.page--portal {
  --portal-sidebar-collapsed: var(--space-72);
}

.page--portal.nav-collapsed {
  --portal-sidebar: var(--portal-sidebar-collapsed);
}

/* The toggle sits with the brand, not in the scroll, so it holds still while
   the routes move. It is hidden on the phone, where the rail is a drawer and
   already has a close button doing this job. */
/* A small circle with a chevron in it, sitting on the rail's own hairline.
   It was a square tertiary button at the button component's full 36px, which
   read as a third route rather than as a control on the frame. Compact,
   quiet, and still an obvious target: 24px is above the 24px minimum for a
   pointer-only affordance and it is hidden on touch, where the drawer has its
   own close button. */
/* The lockup and the collapse control share this row, which is what lets the
   control sit beside the logo when the rail is open and beside the mark when
   it is collapsed. It cannot live inside .portal-brand: that is an <a>, and a
   button inside a link is neither valid nor operable. */
/* The row the lockup and the collapse control share. It is the control's
   positioning context, which is how the control stays level with the logo in
   both states without either of them knowing the other's size. */
.page--portal .portal-sidebar__head {
  position: relative;
}

/* A tab on the rail's own edge, straddling the border: half on the navigation
   and half on the page it opens onto, which is what the control does. Sitting
   inside the rail's padding it read as one more item in the list; on the seam
   it reads as a handle for the seam.

   Held level with the first line of the brand, not with the middle of the row:
   the row also carries the product line under the wordmark, so centring on it
   put the tab 11px below the logo it belongs beside. Measured, the wordmark's
   centre sits 33px down and the collapsed mark's 36px, so one offset serves
   both to within three pixels. */
.page--portal .portal-sidebar__toggle {
  position: absolute;
  inset-block-start: var(--space-36);
  inset-inline-end: 0;
  translate: 50% -50%;
  z-index: 3;
  /* It carries .icon-button as well as .button, and that class sizes itself
     from --icon-button-size rather than from the button height - so setting
     only --button-height left a 28x40 pill where a circle was wanted. */
  --icon-button-size: var(--space-28);
  --icon-button-icon: var(--icon-size-xs);
  --button-height: var(--space-28);
  --button-padding: 0;
  border-radius: var(--radius-full);
  border: 1px solid var(--side-nav-border);
  background: var(--side-nav-surface);
  color: var(--side-nav-muted);
  box-shadow: var(--shadow-sm);
}

.page--portal .portal-sidebar__toggle:hover {
  border-color: var(--side-nav-text);
  background: var(--side-nav-hover);
  color: var(--side-nav-text);
}

.page--portal .portal-sidebar__toggle .sp-icon {
  --icon-size: var(--icon-size-xs);
  --icon-stroke: var(--icon-stroke-xs);
}

.page--portal .portal-sidebar__toggle .sp-icon {
  transition: transform var(--duration-fast) var(--ease-standard);
}

.page--portal.nav-collapsed .portal-sidebar__toggle .sp-icon {
  transform: rotate(180deg);
}

/* --- what collapsing actually does --------------------------------------- */

.page--portal.nav-collapsed .portal-brand {
  justify-items: center;
}

/* The wordmark is replaced by the mark, not scaled down: a 132px lockup
   squeezed into 40px is unreadable, and the favicon is the same identity
   drawn for this size. */
/* The light-ground rule that swaps which wordmark shows is
   html:is(...) .page--portal .portal-brand .brand-logo--light, which scores a
   type selector higher than this did - so on the portal's own light default
   the wordmark stayed laid out while collapsed, held the brand at its full
   132px, and pushed the collapse control clean out of the 72px rail. Matching
   that rule's weight means carrying the same html type selector here, and
   winning on order rather than on weight. */
html .page--portal.nav-collapsed .portal-brand .brand-logo,
.page--portal.nav-collapsed .portal-brand__product {
  display: none;
}

.page--portal .portal-brand__mark { display: none; }

.page--portal.nav-collapsed .portal-brand__mark {
  display: block;
  width: var(--space-32);
  height: var(--space-32);
}

.page--portal.nav-collapsed .portal-sidebar__scroll {
  padding-inline: var(--space-12);
}

.page--portal.nav-collapsed .sp-side-nav__link {
  justify-content: center;
  gap: 0;
  padding-inline: 0;
}

/* The label goes, the row does not. Keeping the row at its full height and
   its active background means the current route is as obvious collapsed as
   expanded, which is the whole test for a rail like this. */
.page--portal.nav-collapsed .sp-side-nav__link .sp-side-nav__label {
  display: none;
}

.page--portal.nav-collapsed .portal-subnav {
  margin-inline-start: 0;
  padding-inline-start: 0;
  border-inline-start: 0;
  justify-items: center;
}

.page--portal.nav-collapsed .portal-subnav__link {
  justify-content: center;
  padding-inline: 0;
}

.page--portal.nav-collapsed .portal-subnav__link .portal-subnav__label {
  display: none;
}

/* The screen switcher is a labelled dropdown; there is no honest icon-only
   form of it, so it stands down rather than shrinking into a mystery button. */
.page--portal.nav-collapsed .portal-sidebar__foot .screen-switch {
  display: none;
}

/* With the switcher gone, the rule dividing it from Profile, Settings and
   Theme is a hairline with nothing under it. */
.page--portal.nav-collapsed .portal-sidebar__utilities {
  padding-block-end: 0;
  border-block-end: 0;
}

@media (max-width: 1023px) {
  /* The rail is a drawer here, and a drawer is already the collapsed state. */
  .page--portal .portal-sidebar__toggle { display: none; }
  .page--portal.nav-collapsed { --portal-sidebar: var(--space-256); }
}


/* --- the first-run hero --------------------------------------------------
   Next up used to be the last card in the right rail, below the playbook and
   two lists - the least prominent thing on the page, holding the only
   sentence that explains what the games are for. On a first run there is no
   Skillprint to read yet, so this is the page's subject, not its footnote. */

.page--portal .portal-nextup {
  --card-current-padding: var(--space-24);
  --card-current-radius: var(--radius-3xl);
  position: relative;
  display: grid;
  /* Two columns only while both of them fit, and the card decides that from its
     own width rather than from the viewport's.

     The track list this replaces was `minmax(0, 1fr) minmax(0, 384px)`, and the
     384 is an inflexible track that claims its maximum before 1fr is given
     anything: in the 597px card a 1280px laptop produces, the copy was left
     107px - four words a line, with the primary action overflowing its column.
     Putting a floor on the copy instead only moved the starvation across: at
     1024px the card is 405px and the progress panel was left 43px, drawing five
     7px slots outside its own edge.

     auto-fit is the answer rather than a third breakpoint because the card's
     width does not follow the viewport's - the rail, the drawer and the
     collapsed nav all move it - so the only honest question is whether
     2 x 256 + 40 fits in the card, which is the question this asks. Below that
     it is one column, which is what the 767px rule below already did. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, var(--space-256)), 1fr));
  /* 24, not 40. Whole page sections stand 32 apart, so at 40 the two columns
     inside this one card were further from each other than two sections of the
     page are - the spacing said "different parts of the page" about a copy
     block and the progress panel that belongs to it. 24 keeps them a group
     inside a component: wider than the 12 between the lines of the copy, and
     comfortably under the 32 that separates this card from what follows. */
  gap: var(--space-24);
  align-items: center;
  border-color: color-mix(in srgb, var(--mindset-violet-500) 38%, transparent);
  /* Flat for the reason .rail-card--record is: the portal draws no gradients,
     and this is the largest surface on Home, so a corner-lit wash here is the
     most visible exception to that rule in the product. One violet value
     across the card keeps the accent that marks this as the page's subject. */
  background: color-mix(in srgb, var(--mindset-violet-500) 8%, var(--surface-box));
}

.page--portal .portal-nextup__copy {
  display: grid;
  gap: var(--space-12);
  min-width: 0;
}

/* No size of its own. This asked for Display XS through
   `--text-display-xs-font-size`, a token that is declared nowhere in the sheet
   (the real name has no `text-` prefix), so both declarations have always been
   invalid and the heading has always been drawn by the ladder's own h2 rule at
   Text LG 18/28 instead.

   That is the size it should be, so the reference is removed rather than
   corrected: "page title Display XS 24/32 .portal-head h1" is the one step the
   ladder reserves for the page title, and this is an h2 inside the content
   column. Repairing the token would have put a second 24px heading on Home and
   re-made the exact fault the ladder was written to remove - "three levels of
   hierarchy all drawn at roughly one size". Weight and colour stay, because
   those two do apply. */
.page--portal .portal-nextup__title {
  margin: 0;
  font-weight: var(--weight-semibold);
  color: var(--ui-text);
}

.page--portal .portal-nextup__lede {
  margin: 0;
  max-width: 52ch;
  color: var(--ui-muted);
  font-size: var(--font-size-base);
  line-height: var(--line-height-base);
}

.page--portal .portal-nextup__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-12);
  margin-top: var(--space-4);
}

/* The progress side reads as one object with the meter: how many sessions
   close the reading, how many are done, and the same bar the tiles use. */
.page--portal .portal-nextup__progress {
  display: grid;
  gap: var(--space-12);
  align-content: start;
  padding: var(--space-20);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-2xl);
  background: color-mix(in srgb, var(--surface-canvas) 62%, transparent);
}

/* One line, one size. It used to be a 30px figure with "of 5 sessions" set
   small beside it, which made "3" the headline of a card whose headline is
   already the thing to do next. */
.page--portal .nextup-progress__count {
  margin: 0;
  color: var(--ui-text);
  font-size: var(--text-md-font-size);
  line-height: var(--text-md-line-height);
  font-weight: var(--weight-semibold);
}

/* Five slots, three of them filled. A bar reports the same fraction but says
   nothing about what is in it; the slots show which games were played and
   leave two holes the size of the games that would fill them. */
/* Count-agnostic: five slots for a first Skillprint on Home, three for a
   playbook's sequence, without either knowing the other's number. */
.page--portal .nextup-slots {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: var(--space-8);
  margin: 0;
  padding: 0;
  list-style: none;
}

.page--portal .nextup-slot {
  aspect-ratio: 1;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--surface-raised);
}

.page--portal .nextup-slot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* An empty slot is a place for a game, so it is drawn as an outline of one
   rather than as a filled tile that happens to be blank. */
.page--portal .nextup-slot--empty {
  border: 1px dashed var(--ui-border-strong, var(--surface-border));
  background: transparent;
}

.page--portal .nextup-progress__note {
  margin: 0;
  color: var(--ui-muted);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
}

@media (max-width: 767px) {
  .page--portal .portal-nextup {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-20);
  }
}


/* --- compounding padding on a phone ---------------------------------------
   Measured on Home at 375: the Get Started card spent 72px of the screen on
   chrome before a word of content - a 20px page gutter, a 32px card inside it,
   and a 20px panel inside that. Three levels, each defensible alone, stacking
   to 39% of the width. The nested panel was left 229px to work in.

   Fixed at the tokens rather than on Home, because the same three levels are
   how every portal page is built and patching one card leaves the pattern in
   place everywhere else:

   - the gutter belongs to the portal shell
   - the card step belongs to the card
   - the inner-panel step is shared by every card-inside-a-card listed below

   Compact and flush cards are excluded: they have already chosen a tighter
   value and this rule would push them back up. */
@media (max-width: 640px) {
  .page--portal {
    --portal-gutter: var(--space-16);
  }

  /* One step down, not to the 16px base. These cards carry a heading, a lede
     and full-width actions; at 16 the button edge sits on the card edge. */
  .page--portal :is(.portal-nextup, .sp-card):not(.card--compact, .card--flush) {
    --card-current-padding: var(--space-20);
  }

  /* A card inside a card does not repeat its parent's padding. One list, so a
     new inner panel joins by being named here rather than by growing its own
     mobile rule on its own page. */
  .page--portal :is(
    .portal-nextup__progress,
    .playbook-card__progress,
    .play-card__progress,
    .gd-adapt__panel,
    .pp-print__body,
    .pp-orbit-detail__body,
    .skill-game__body,
    .embed-card__body
  ) {
    padding: var(--space-14);
  }
}


/* --- play by skill -------------------------------------------------------
   The homepage's four-up version of the Skills page. Same glyphs, same three
   accents, same order - one row instead of a page, so the reader learns the
   Skills page's grammar before they get there. Each card carries its own
   [data-dimension], which is what re-values --skill-accent and --skill-ink. */

.page--portal .skill-launch {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-16);
}

/* At rest these take the ordinary card border. Four cards each outlined in
   their own dimension colour read as four states rather than four routes -
   the colour is the answer to "which dimension", and it is already carried by
   the glyph. It arrives on hover, where it confirms what you are about to
   open. */
.page--portal .skill-launch__card {
  --card-current-padding: var(--space-20);
  position: relative;
  display: grid;
  gap: var(--space-12);
  align-content: start;
  overflow: hidden;
  text-decoration: none;
  color: inherit;
}

/* The accent is the card's whole border rather than a bar along its top edge.
   A hairline all the way round names the dimension just as clearly as a 4px
   rule did, and it does not stack four saturated bars in a row above the copy
   - the objection that put the accent on one edge in the first place.

   .sp-card--interactive signals its states by swapping border-color, which
   these cards have now spent on the accent, so both states are restated here.
   Hover washes the ground instead; focus keeps Part A's ring by declaring only
   the colour and leaving box-shadow alone. */
.page--portal .skill-launch__card:is(:hover, :active, .is-hover, .is-pressed) {
  border-color: var(--skill-accent);
  background: color-mix(in srgb, var(--skill-accent) 8%, var(--surface-box));
}

.page--portal .skill-launch__card:focus-visible {
  border-color: var(--skill-accent);
}

.page--portal .skill-launch__card .sp-icon-frame {
  border-color: color-mix(in srgb, var(--skill-accent) 42%, transparent);
  background: color-mix(in srgb, var(--skill-accent) 12%, transparent);
}

.page--portal .skill-launch__card .sp-icon {
  color: var(--skill-ink);
}

.page--portal .skill-launch__name {
  font-size: var(--font-size-base);
  line-height: var(--line-height-base);
  font-weight: var(--weight-semibold);
  color: var(--ui-text);
}

.page--portal .skill-launch__meta {
  color: var(--ui-muted);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-sm);
}

/* All skills is not a dimension, so it takes the highlight rather than one of
   the three accents - the same rule the status pill follows. */
.page--portal .skill-launch__card--all {
  --skill-accent: var(--skill-highlight);
  --skill-ink: var(--skill-highlight);
}


/* --- the rail's Skillprint preview --------------------------------------- */

.page--portal .rail-print {
  display: grid;
  gap: var(--space-16);
}

.page--portal .rail-print__figure {
  position: relative;
  display: grid;
  place-items: center;
  overflow: hidden;
  border-radius: var(--radius-2xl);
  background: color-mix(in srgb, var(--surface-canvas) 62%, transparent);
}

/* The print is drawn at 1000x1000 and cropped rather than scaled down past
   legibility: at rail width the leaf labels are noise, so they are dropped
   and the dial is over-drawn and clipped, which is what the partner embed
   does with the same figure. */
.page--portal .rail-print__figure .skillprint__wheel {
  display: block;
  width: 132%;
  max-width: none;
  min-width: 0;
  height: auto;
  margin: -16%;
  aspect-ratio: 1;
}

.page--portal .rail-print__figure .leaf-label { display: none; }

/* A first run has three of five sessions in, so the print is a preview of
   something unfinished. Saying so with a wash and a chip is more honest than
   showing a partial dial as though it were the reading. */
.page--portal .rail-print__veil {
  position: absolute;
  inset: 0;
  display: grid;
  align-content: end;
  justify-items: center;
  padding: var(--space-12);
  background: linear-gradient(
    to bottom,
    transparent 38%,
    color-mix(in srgb, var(--surface-box) 88%, transparent)
  );
}


/* --- the tile's artwork is a link now ------------------------------------
   A tile carries two real controls in its foot, so the whole card can no
   longer be one <a> - interactive content cannot nest. The artwork becomes
   the link to the detail page and the foot holds Details and Play, which is
   the shape the library's .game-card already had. An <a> is inline, and
   aspect-ratio needs a block box. */
.page--portal a.play-card__art {
  display: block;
}

/* Details left, Play right. .play-card__foot already spaces its two children
   apart; this only stops the pair wrapping into a column at rail width. */
.page--portal .play-card__foot .button {
  flex: 0 1 auto;
  min-width: 0;
}

/* A meter that reports progress rather than a dimension is the brand's own
   green. It used to read --skill-accent, which falls back to --mood-500 - the
   Mood dimension's lime - so "Skillprint complete" and the streak beside it
   were wearing one dimension's colour to report all three, and the lime read
   as a fifth accent nobody had chosen. The structural exception meant to catch
   this only matched a doubly-nested .layout-grid and never fired on the card
   that needed it, which is why this is a plain default now. */
.page--portal .rail-meter {
  --meter-tone: var(--core-green-500);
}

/* A meter that does sit under a dimension's name keeps that dimension's
   colour. Those three declare [data-dimension] themselves, which outranks the
   default above on its own. */
.page--portal [data-dimension] .rail-meter {
  --meter-tone: var(--skill-accent, var(--mindset-violet-500));
}


/* --- the playbook card ---------------------------------------------------
   A playbook is a short sequence of games with one job, and this is a card you
   act on rather than one you look at. It is the compact card on the shelf: the
   title and its one line at the top left clear of the mark in the corner, the
   count under them beside a square of artwork from the sequence, and Play and
   How to play at the foot. It used to be the height of a library card with the
   middle left empty and no control at all - the one thing it never offered was
   a way to start the thing it was advertising.

   ONE GROUND, ONE INK
   Each card names its family once in the markup through the same .tone--* class
   every other card here carries, and takes two values off it: the ground and
   the ink that the card's own copy is set in. Purple carries white; pink and
   yellow carry deep navy. The controls are outside that scheme on purpose -
   they are the house buttons, untouched, for the reason argued where they are
   styled below.

   WHAT THE INK MEASURES
   Deep navy is 11.16:1 on Skills Orange 500 and 6.53:1 on Skills Pink 500, so
   both light grounds clear 4.5:1 on the ink rather than on the ramp. White is
   4.35:1 on Mindset Magenta 500, under the 4.5:1 that normal text is held to,
   and one step down clears it at Magenta 600 (5.28:1). Every filled control is
   exempt either way, because its label is on the ink itself at 20.14:1.

   The mark is the one thing that stays on the ramp rather than following the
   ink: each one is drawn from the light end of its own family, 200 through 400,
   so it sits on the ground as a tone rather than as a second piece of type. It
   is deliberately the quiet element - it measures between 1.15:1 and 2.81:1
   depending on the family, which is a watermark, not a label. The title beside
   it is what names the playbook. */

.page--portal .playbook-card {
  --card-current-padding: var(--space-16);
  --pb-hairline: color-mix(in srgb, var(--pb-ink) 26%, transparent);

  position: relative;
  gap: var(--space-16);
  background: var(--pb-500);
  border-color: var(--pb-hairline);
  color: var(--pb-ink);
  cursor: auto;
}

/* Two ways out of the same problem, one per family. White measured 4.35:1 on
   Magenta 500 and 3.08:1 on Pink 500, so the blurb, the count and even the
   card's own title sat under the 4.5:1 normal text is held to. Magenta goes one
   rung down its ramp and keeps white (Magenta 600, 5.28:1). Pink instead keeps
   the canonical 500 - the Skills Pink the brand actually names - and flips the
   ink to deep navy, which reads 6.53:1 there: better than the white-on-Pink-700
   it replaces (5.11:1), and the same treatment Orange already uses. It also
   costs nothing on the controls; the violet Play button separates from Pink 500
   at 2.07:1 against 1.22:1 on Pink 700. The custom property keeps the -500 in
   its name for the reason the pillars do: it is the semantic step, not a
   promise about the rung. */
.page--portal :is(.playbook-card, .pb-tone).tone--magenta {
  --pb-500: var(--mindset-magenta-600);
  --pb-ink: var(--geistface-grey-25);
}

.page--portal :is(.playbook-card, .pb-tone).tone--pink {
  --pb-500: var(--skills-pink-500);
  --pb-ink: var(--deep-navy-950);
}

.page--portal :is(.playbook-card, .pb-tone).tone--orange {
  --pb-500: var(--skills-orange-500);
  --pb-ink: var(--deep-navy-950);
}

/* The lift is the library card's own, inherited from .game-card--portal. Only
   the hairline is restated, because the card's edge is an ink now and the
   shared hover colour would put a grey edge on a saturated field. The focus
   ring is not restated: every focusable thing on this card is a link or a
   button that draws its own, and a card-level ring behind them read as two. */
.page--portal .playbook-card:hover,
.page--portal .playbook-card:focus-within {
  border-color: color-mix(in srgb, var(--pb-ink) 52%, transparent);
}

/* --- the head ---
   The mark is out of flow in the corner, so the copy beside it is kept off it
   by padding rather than by a float or a second column: one measure for the
   title and the line under it, and it holds however either of them wraps. */
.page--portal .playbook-card__head {
  padding-inline-end: var(--space-64);
}

.page--portal .playbook-card__mark {
  position: absolute;
  top: var(--space-16);
  right: var(--space-16);
  width: var(--space-48);
  height: var(--space-48);
}

/* The portal's own card title - the body size, semibold. The element is in the
   selector because `.portal-content h3` sets that size on every h3 here and a
   class alone does not outrank a class plus an element; the colour is restated
   for the same reason, since the house heading rule declares --ui-text on the
   h3 itself and the card's ink would not otherwise reach it. */
.page--portal .portal-content h3.playbook-card__title {
  margin: 0;
  color: inherit;
  font-size: var(--font-size-base);
  line-height: var(--line-height-base);
  font-weight: var(--weight-semibold);
}

.page--portal .playbook-card__link {
  color: inherit;
}

.page--portal .playbook-card__link:focus-visible {
  outline: var(--focus-ring-width) solid var(--pb-ink);
  outline-offset: var(--space-2);
  border-radius: var(--radius-xs);
}

.page--portal .playbook-card__blurb {
  margin: var(--space-4) 0 0;
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

/* --- what has been played ---
   A square of the artwork from the playbook's own sequence, the count in
   words, and the set drawn as segments under it. */
.page--portal .playbook-card__progress {
  gap: var(--space-12);
}

.page--portal .playbook-card__preview {
  flex: none;
  width: var(--space-40);
  height: var(--space-40);
  border-radius: var(--radius-md);
  box-shadow: inset 0 0 0 1px var(--pb-hairline);
}

.page--portal .playbook-card__preview img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.page--portal .playbook-card__count {
  margin: 0;
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  font-weight: var(--weight-semibold);
}

/* Played and unplayed are told apart by fill against outline rather than by two
   steps of one ramp, which measured 1.29:1 on magenta and 1.11:1 on orange -
   not a difference anyone can read. The count beside them says the same thing
   in words, which is what a screen reader gets; the segments are decorative. */
.page--portal .playbook-pips {
  gap: var(--space-4);
  margin-block-start: var(--space-6);
  list-style: none;
}

.page--portal .playbook-pip {
  flex: 1;
  height: var(--space-4);
  border: 1px solid color-mix(in srgb, var(--pb-ink) 55%, transparent);
  border-radius: var(--radius-full);
}

.page--portal .playbook-pip--played {
  border-color: var(--pb-ink);
  background: var(--pb-ink);
}

/* --- the controls ---
   The house primary and secondary at the small step, and nothing about them is
   re-valued. That is deliberate and it is the part of this card that was got
   wrong first: the button system carries one primary accent, Mindset Violet,
   and the only two components that touch button tokens at all - .popup--dark
   and the session rail - re-value the surface and text family and restate
   canonical values while doing it. Neither invents an accent, and .popup--dark
   says so in as many words. An inverse primary drawn from the card's own ink
   was a sixth hierarchy that the system does not have and index.html does not
   document, so it is gone.

   The cost is on the record: Mindset Violet 500 separates from Skills Orange
   500 at 3.53:1 but from Skills Pink 500 at 2.07:1 and from Mindset Magenta 500
   at 1.46:1, so on those two the Play button reads as a label rather than as a
   shape. Its own label is unaffected at 6.22:1. Closing that needs either a
   documented on-colour variant or deeper grounds; it is not something a page
   should decide on its own. */
/* The library card's own pair, in the library card's own order: the secondary
   first and the primary last, so Play lands on the outside edge where it does
   on every other card here. It is .card-actions .cluster for that reason rather
   than a row of this card's own - which also picks up the rule that fills the
   play triangle instead of stroking it, the one thing that still told the two
   Play buttons apart. There is nothing on the left of this row to space against,
   the way the game card has its duration, so the pair is pushed rather than
   spaced. The gap steps down from the cluster's 12, and the secondary carries a
   bare label where the game card's Details carries an icon, because the pair has
   to hold one row at the rail's 248px floor as well as at the 309px the shelf
   gets on a desktop - and "How to play" with a mark measures wider than the card
   at that floor. It cannot be solved with a breakpoint: the rail sits at its
   floor at 768px wide and again at 1000px, but not at 1280px, so the width that
   decides this is the card's and not the screen's. */
.page--portal .playbook-card__actions {
  justify-content: flex-end;
  gap: var(--space-8);
  margin-block-start: auto;
}

.page--portal .playbook-card .button {
  flex: 0 1 auto;
  min-width: 0;
}

/* THE TOUCH FLOOR, FOR THE WHOLE PORTAL

   The small step keeps a card compact on a pointer that can hit 38px. A finger
   cannot, and AGENTS.md names Mobile_Behaviours.html as the reference for the
   touch target floor - that page applies 44px to `:is(.button,
   .button-group__item)` on a coarse pointer.

   This rule used to say `.page--portal .playbook-card .button`, so the floor
   reached exactly one component and nothing else a finger actually uses. On a
   375px phone, with (pointer: coarse) matching, Home alone had 42 controls
   under it: every row of the one navigation at 40px, the drawer's opener at 40
   and its close at 36, and every .button--sm at 38 - the screen switcher and
   the state preview among them. The playbook card was correct and alone.

   The navigation rows are in the selector because the rail IS the portal's
   navigation, and a mis-tap there is the most expensive one on the screen. The
   two immersive screens are named because they carry no .page--portal and are
   the flow a phone actually plays through - Mobile_Performance is composed of
   them. Icon-only buttons take the floor on both axes, since a square control
   that is 44 tall and 36 wide is still a 36px target.

   Tags and chips are deliberately left out: they are secondary filters at
   32px, already clear of the 24px WCAG floor, and lifting a row of them to 44
   would rewrite the library's filter bar rather than correct it. */
@media (pointer: coarse) {
  :is(.page--portal, .page--game-session)
    :is(.button, .button-group__item, .icon-button) {
    min-height: var(--space-44);
  }

  /* The rail's rows are stated at the rail's own specificity rather than folded
     into the selector above. `.page--portal .portal-sidebar .sp-side-nav__link`
     already sets min-height: 40 at (0,3,0), and a (0,2,0) rule loses to it on
     specificity no matter how late it appears - which is exactly what happened
     on the first pass: every other control took the floor and the six
     navigation rows stayed at 40. Matching (0,3,0) and sitting later in the
     file is what makes this one win. */
  .page--portal .portal-sidebar :is(.sp-side-nav__link, .portal-subnav__link) {
    min-height: var(--space-44);
  }

  :is(.page--portal, .page--game-session)
    :is(.icon-button, .button--icon-only) {
    min-width: var(--space-44);
  }

  /* Form controls were never in the floor above, which reads .button,
     .button-group__item and .icon-button only - so on a phone the library's
     search field sat at 42px and Skill Detail's compare <select> at 36, the
     shortest control in the portal. They are not the tags and chips the
     paragraph above deliberately leaves out: a chip is a secondary filter in a
     row of its own kind, while these are the primary control of the thing
     they belong to, and a native <select> opens a system picker that a missed
     tap dismisses. Checkboxes and radios are excluded because they are drawn
     by .sp-toggle and the choice controls, which set their own geometry. */
  :is(.page--portal, .page--game-session)
    :is(
      input:not([type="checkbox"]):not([type="radio"]),
      select,
      textarea
    ) {
    min-height: var(--space-44);
  }
}

/* --- the status pill on the compact tile ---------------------------------
   The library card is a wide grid item and carries the pill at full size. The
   rail tile is 232px, so the chip steps down with it. */
.page--portal .play-card .media-badge {
  top: var(--space-10);
  right: var(--space-10);
  min-height: var(--space-20);
  padding: 0 var(--space-6);
  font-size: var(--text-xxs-font-size);
  line-height: var(--text-xxs-line-height);
}

/* A rail column wide enough for the longest pairing the catalogue holds. */
.page--portal .game-rail {
  grid-auto-columns: minmax(248px, 1fr);
}

/* --- play by skill: four across -----------------------------------------
   auto-fit against a 180px floor gave 3 + 1 next to the rail, which reads as
   a dropped card rather than a set of four. The set is fixed at four, so the
   track count is stated and steps down in pairs. */
.page--portal .skill-launch {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

@media (max-width: 1279px) {
  .page--portal .skill-launch { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 479px) {
  .page--portal .skill-launch { grid-template-columns: minmax(0, 1fr); }
}


/* --- collapsed rail: the toggle, and the current route -------------------
   Two things the first pass got wrong at 72px.

   The toggle was pinned to the brand's top-right corner, which at full width
   is empty space and at 72px is exactly where the mark sits - a 40px button
   laid over a 32px logo, with no way to tell which one you were clicking and
   no obvious way back out of the collapsed state. Collapsed, it stops being
   an overlay and takes its own row under the mark.

   And the current route's pill was a 16% violet on a near-black rail: 1.12:1,
   which is a background you cannot see. Expanded that is fine, because the
   label beside it is also violet and bold. Collapsed the pill is the only
   thing left carrying "you are here", so it has to hold on its own. */

/* Collapsed it moves out of the corner and sits beside the mark, which is
   the only other thing left in the brand. The two together are 32 + 4 + 24,
   which is exactly the 72px rail once the padding comes down to 6. */
/* Collapsed, the row centres the mark and the control together: 32 + 4 + 24
   is exactly the 72px rail once the padding comes down to 6. */
/* Collapsed, the mark centres in the 72px rail and the tab stays on the seam
   beside it - the same place it was, because the seam has not moved. */
.page--portal.nav-collapsed .portal-sidebar__head .portal-brand {
  padding-inline: 0;
}

.page--portal.nav-collapsed .portal-brand {
  padding-block-end: var(--space-8);
}

.page--portal.nav-collapsed .sp-side-nav__link {
  width: var(--space-44);
  min-width: var(--space-44);
  min-height: var(--space-44);
  margin-inline: auto;
}

.page--portal.nav-collapsed .sp-side-nav__link.is-active,
.page--portal.nav-collapsed .sp-side-nav__link[aria-current="page"] {
  background: color-mix(in srgb, var(--mindset-violet-500) 32%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--mindset-violet-400) 58%, transparent);
}

.page--portal.nav-collapsed .sp-side-nav__link.is-active .sp-icon,
.page--portal.nav-collapsed .sp-side-nav__link[aria-current="page"] .sp-icon {
  color: var(--nav-current-text, var(--mindset-violet-200));
}

/* The group is a grid and was sizing to its content once the labels went, so
   the rows - and the current row's pill with them - shrank to the width of a
   glyph and sat against the left edge of a 72px rail. Stretch the group, then
   the rows fill it and the icons centre inside a proper target. */
/* Don't depend on the group stretching. Collapsed, each row is a fixed
   square centred in the rail: a 44px target, the same size as the toggle
   above it, and the current row's pill is that square rather than something
   shrink-wrapped to a glyph and sitting off-centre. */
.page--portal.nav-collapsed .sp-side-nav__group {
  grid-template-columns: var(--space-44);
  justify-items: stretch;
}

/* The scroller reserves a 15px scrollbar gutter on its right. At 256px that
   is invisible; at 72px it is a fifth of the rail, and it was taking the
   content box down to 36px - which is what pushed every row 4px left of the
   rail's centre line and stopped the 44px pill from fitting at all. Three
   routes never overflow, so the scrollbar is hidden rather than reserved.
   overflow stays auto: a short screen with the Skills sub-rows open still
   has to scroll, it just does it without spending a fifth of the width. */
.page--portal.nav-collapsed .portal-sidebar__scroll {
  display: grid;
  justify-items: center;
  align-content: start;
  padding-inline: var(--space-10);
  /* .scrollbar-none in rule form: the hide is a function of the collapsed
     state, which the markup does not know about. */
  scrollbar-width: none;
}

.page--portal.nav-collapsed .portal-sidebar__scroll::-webkit-scrollbar {
  width: 0;
  height: 0;
}

/* ==========================================================================
   Portal · one game card, everywhere
   The card component is now rendered on every portal surface rather than only
   the library and Skill Progression, so its rules above are scoped to
   .page--portal. What follows is only what the widened component still needed:
   a rail that lays the cards out, the best-score figure in the head, and the
   two treatments Your Record grew.
   ========================================================================== */

/* A rail of cards. The library keeps its own grid; this is for the homepage,
   Game Detail and Skill Detail, which show a handful side by side and scroll
   on narrow screens rather than reflowing to one very tall column.

   ONE GAP FOR ONE CARD FAMILY
   The gap is 16 because that is what this card is already spaced at everywhere
   it is not on this hook: Portal_Games draws the identical
   .game-card--portal at 16 in the library rail, in the playbook rail and in
   the All games grid, and .portal-rail, .game-grid and .skill-launch are all
   16 as well. This rule alone said 24, so Home, Skills, Game Detail and Skill
   Detail spaced the same tile half again as far apart as the library did - and
   because a rail on Home carries .game-rail and [data-game-rail] at once, the
   24 quietly won there over .game-rail's own 16.

   It also costs the page its spacing ladder. The bar above a rail binds to its
   cards at 14 and whole sections stand 32 apart, so a 24 between two cards in
   one row left only 8 between "these are siblings" and "these are different
   parts of the page". At 16 the ladder reads 14/16 inside a section against 32
   between them, which is the distinction the portal is supposed to draw. */
.page--portal [data-game-rail],
.page--portal [data-gd-related] {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(min(19rem, 82vw), 1fr);
  gap: var(--space-16);
  /* The inline axis hands its padding back the way the block axis does below,
     so the 4px of shadow room does not push the row off the page grid: at rest
     the first card's edge lands on the section bar above it, not 4px inside
     it. Until the snap was corrected this was invisible, because every rail
     opened scrolled by exactly the 4px that was displacing it. */
  margin: 0 calc(var(--space-4) * -1);
  padding: 0 var(--space-4) var(--space-8);
  overflow-x: auto;
  overscroll-behavior-inline: contain;
  scroll-snap-type: inline mandatory;
  /* The rail keeps 4px of inline padding so a lifted card's shadow is not
     sheared sideways, and mandatory snap aligns a card's start edge to the
     snapport's - which, without this, is the padding edge, so every rail
     opened resting at scrollLeft: 4 with a 4px dead zone behind it. Matching
     the scroll padding to the box padding puts the snapport back on the
     card and the rail opens at 0. */
  scroll-padding-inline: var(--space-4);
  list-style: none;
}

/* On a phone the card is a share of the row, not a pixel floor. `min(19rem,
   82vw)` answers "how wide is a card" without knowing how wide the row is - and
   the row is not the viewport, it sits inside a gutter and often inside a card
   as well. At some widths that leaves a healthy slice of the next card; at
   others the first card very nearly fills the row, the next one falls off the
   edge, and the rail reads as a single card with no sign it scrolls at all.

   86% of the row pins it: one whole card, and a deliberate strip of the next -
   enough to say "there is more here" without a second card competing for the
   same attention. The trailing fade then sits over that strip rather than over
   a card that was already complete. */
@media (max-width: 640px) {
  .page--portal [data-game-rail],
  .page--portal [data-gd-related] {
    /* A flat track, not a minmax. With `minmax(0, 86%)` the floor is zero, and
       seven columns in an auto-flow row promptly collapse to their content -
       43px each. The row wants one width per card, stated once. */
    grid-auto-columns: 86%;
  }
}

.page--portal [data-game-rail] > *,
.page--portal [data-gd-related] > * {
  scroll-snap-align: start;
  min-width: 0;
}

/* ROOM FOR THE LIFT
   A rail scrolls sideways, and `overflow-x: auto` forces the block axis to
   compute to `auto` as well - `visible` is not a value the other axis can keep
   once one axis scrolls. So a rail clipped its own children top and bottom:
   hovering a card slid it 4px up and the rail sheared the top off it, square
   corners and all, and the shadow under it stopped dead at the rail's edge
   instead of falling away. Nothing about the card was wrong; it had nowhere to
   lift into.

   The padding is the room the lift and --shadow-md actually need - 4px of
   travel plus half the 32px blur above, and the 12px offset plus that same
   half-blur below, less the travel - and the negative margin hands every pixel
   of it straight back, so each rail occupies exactly the space it did before
   and no section moves. `clip` with an `overflow-clip-margin` would say this
   directly, but a scrolling axis forces `clip` to `hidden` too, which is the
   same shear again.

   WHERE THE SCROLLBAR ACTUALLY SITS
   A previous pass here recorded that `overflow-x: auto` lays the scrollbar
   inside the content box, above the padding. Measurement says the opposite: the
   bar sits at the foot of the padding box, *below* the block-end padding, so
   every pixel reserved under the cards pushes the bar further from them.
   Setting the end padding to 24 / 16 / 8 / 0 puts the top of the bar at exactly
   24 / 16 / 8 / 0 below the last card, with its 11px added underneath each
   time.

   That inverted the one relationship this band has to express. The 24px the
   shadow needs was opening a 24px gap between the cards and their scrollbar,
   and the -24px margin then clawed that space back off the *bottom*, leaving
   the bar 8px from the next section's heading. The bar read as belonging to
   whatever came after it rather than to the rail it scrolls.

   So the end reserves 16px rather than 24 and hands none of it back. 16px still
   covers --shadow-md's dense core - the shadow is `0 12px 32px`, so its centre
   lands 12px under the card and only the faint tail past that is clipped - and
   it puts the bar 16px under the cards with the section gap, 32px, entirely
   below it. Two to one, in the right direction. Sections below a rail do move
   down by the 24px that is no longer clawed back, which is the point.

   All three rail hooks are here and this sits after both of the rules that set
   the box: Home's shelves carry .game-rail and [data-game-rail] at once, so a
   fix written against either name alone is overridden on the page that shows it
   three times. Only the block axis is touched, which leaves each rail's own
   inline padding and the scroll-snap geometry that depends on it alone. */
.page--portal .game-rail,
.page--portal [data-game-rail],
.page--portal [data-gd-related] {
  padding-block: var(--space-8) var(--space-16);
  margin-block: calc(var(--space-8) * -1) 0;
}

/* Three across once there is room for three, so the homepage rails do not read
   as a scroller on a desktop where everything already fits. */
@media (min-width: 64rem) {
  .page--portal [data-game-rail]:not([data-game-rail-scroll]) {
    grid-auto-flow: row;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    overflow-x: visible;
  }

  /* More like this keeps scrolling sideways at every width, the way the rails
     on Home do. It used to flatten to four across here, which was fine when
     the section ran the full content width - since Your record took a column
     the four were squeezed into about 690px, and it stopped reading like a
     rail at exactly the point it needed to. */
}

/* A rail with no [data-game-rail-scroll] lays out as three columns above
   1024px, which is right in a full-width section and wrong in the opening
   row: one card in a 320px track was getting a third of it, 88px, with the
   title broken to one word a line. Here the rail is one column. */
.page--portal-home .portal-openers .portal-section [data-game-rail] {
  grid-auto-flow: row;
  grid-template-columns: minmax(0, 1fr);
  overflow-x: visible;
}

/* The reader's best flow score, beside the title. Mono because it is a figure,
   and quiet because the title is the thing being scanned. */
.page--portal .game-head__best {
  color: var(--ui-muted);
  font-size: var(--font-size-sm);
  line-height: var(--line-height-sm);
  flex: none;
}

/* Your record · the figures belong to the reader, so the section says so once
   and then shows the connection to the wider Skillprint rather than repeating
   it per tile. */
.page--portal .gd-record__figures {
  gap: var(--space-16);
}

.page--portal .gd-record__feed,
.page--portal .gd-record__empty {
  margin-block-start: var(--space-24);
  display: grid;
  gap: var(--space-16);
}

.page--portal .gd-record__into,
.page--portal .gd-record__empty p {
  max-width: var(--measure-md);
}

/* The way into the whole print. It was a tertiary link sized to its own label
   at the foot of a card full of figures, which read as a footnote. It is the
   card's one primary action now, and it takes the card's width the way every
   other primary action in a rail card does - a control the reader is meant to
   take should not have to be found. */
.page--portal .gd-record__link {
  justify-self: stretch;
  justify-content: center;
}

/* Played declares nothing on purpose: the status vocabulary above says grey is
   the default and that a spent state should be the quietest thing on the card.
   Giving it an accent here made the label unreadable against it. */

/* ==========================================================================
   Portal · the whole card is the click target
   A skill card opens its progression; a game card opens play. The card's own
   title carries the link and grows a ::after over the whole card, so the card
   becomes clickable without a second one: no anchor is nested inside another,
   the tab-stop count is unchanged, and cmd- or middle-click still open a tab
   because the target is a real href rather than a JS handler.

   The sheet has to hang off an UNPOSITIONED anchor, because inset:0 resolves
   against the nearest positioned ancestor and that must be the card. The
   progress button was the obvious host and is the wrong one - .sp-progress-
   button sets position:relative to clip its own fill, so its sheet would have
   covered the button and nothing else.
   ========================================================================== */

.page--portal .skill-card,
.page--portal .game-card--portal {
  position: relative;
}

.page--portal [data-card-link]::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
}

/* Everything else the card offers stays reachable, lifted over that sheet. The
   copy is deliberately not lifted, which is what keeps a drag on the
   description selecting text rather than starting a link drag. */
.page--portal .skill-card .skill-card__game-list,
.page--portal .skill-card .skill-card__action,
.page--portal .game-card--portal .card-actions,
.page--portal .game-card--portal .game-media {
  position: relative;
  z-index: 2;
}

/* The pointer agrees with the hover state the card already had, across the
   whole surface rather than only over its controls. */
.page--portal .skill-card:hover,
.page--portal .game-card--portal:hover {
  cursor: pointer;
}

/* ===========================================================================
   PLAY SCREEN
   One full-screen treatment the whole library inherits. The only things that
   change per game are the artwork, its accent, the name, the score and the two
   adjustment values; every control below is identical on all fifty games.

   The ground is the game's own card art scaled up and nothing else. The screen
   it replaced blurred that art 28-72px and laid a tinted radial veil over it,
   which is why every game arrived looking like the same pale wash. There is no
   blur, no veil, no saturate, no opacity here on purpose: cover plus a scale
   step is the whole treatment, so what fills the screen is the card's own
   colour, contrast and texture. Legibility is bought with the solid dark
   controls instead, which is the trade the brief asks for.
   ========================================================================== */

/* One reserve for the chrome, measured rather than guessed.

   Everything that draws over the stage - the centre control, the two veils and
   the review dialog - used to clear the fixed chrome with its own hand-written
   sum of spacing tokens, and every one of them went stale the moment the bar
   changed height: the dialog reserved 188px for a 74px bar and a 50px rail,
   and the two corrections written for narrow screens were `padding-bottom` and
   `padding-top` against a `padding-block-end`/`-start` declared later in the
   sheet, so at the same specificity they lost and never applied at all.

   The script measures the three fixed pieces and publishes them here, so the
   reserves below follow the real boxes at any width, any zoom and any change
   to what the bar holds, with no arithmetic to re-derive. The fallbacks are
   the resting sizes, which is what the first frame and a scriptless page get.
   env() carries the phone's home indicator into the same sum. */
.page--game-session {
  --play-bar-height: var(--space-56);
  --session-rail-height: var(--space-48);
  --game-switch-height: var(--space-56);

  --play-bar-inset: var(--space-16);
  --play-bar-safe: env(safe-area-inset-bottom, 0px);

  /* Clear of the bar: its offset off the foot of the screen, its own box, the
     safe area under it and one step of air. */
  --play-bar-clear:
    calc(var(--play-bar-inset) + var(--play-bar-safe)
      + var(--play-bar-height) + var(--space-12));

  /* Clear of the bar and of the step rail sitting on top of it, which is what
     an overlay reaching the foot of the screen has to miss. */
  --session-chrome-clear:
    calc(var(--play-bar-clear) + var(--session-rail-height) + var(--space-16));

  /* Clear of the game picker at the head of the screen. */
  --session-chrome-clear-start:
    calc(var(--space-16) + var(--game-switch-height) + var(--space-16));
}

.page--game-session .play-field {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  /* Cover plus the scale below always fills, so this only guards against a
     one-frame flash before the art decodes. The accent is the game's own. */
  background: var(--stage-accent);
}

.page--game-session .play-field__art {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* "Scale the texture up significantly": cover already fills the viewport, so
     this is the step past it that makes the pattern read as a full-screen
     environment rather than a card stretched to fit. */
  transform: scale(1.25);
  transform-origin: 50% 50%;
}

/* --- the one control in the centre ---------------------------------------- */

.page--game-session .play-stage {
  position: fixed;
  inset: 0;
  z-index: 10;
  display: grid;
  place-items: center;
  /* Clear of the bar, by the bar's own measured box. */
  padding-block-end: var(--play-bar-clear);
  pointer-events: none;
}

.page--game-session .play-stage__toggle {
  --play-size: var(--space-96);

  pointer-events: auto;
  display: grid;
  place-items: center;
  width: var(--play-size);
  height: var(--play-size);
  padding: 0;
  /* A drawn ring, not a hairline. This was 1px of white at 16%, which on a
     ground as loud as a full-screen game card was less a border than a hint of
     one - the control read as a dark disc that happened to be there. The solid
     4px reads as a ring at arm's length and gives the disc an edge on every
     one of the fifty grounds it has to sit on, light or dark.

     currentColor rather than the token: the triangle inside is drawn from
     currentColor too, so the ring and the mark are the same white by
     construction and cannot drift apart if the button's ink is ever re-valued.
     box-sizing is border-box globally, so this is cut out of --play-size
     rather than added to it and the control keeps its 96px target.

     It was two rules at identical specificity - `border: 0` here and the
     hairline restated in a second block immediately below - so the base said
     one thing and the sheet did another. One declaration now. */
  border: var(--space-4) solid currentColor;
  border-radius: var(--radius-full);
  background: var(--panel-navy-950);
  color: var(--geistface-grey-25);
  box-shadow: var(--shadow-lg);
  cursor: pointer;
  transition: var(--transition-interactive);
}

.page--game-session .play-stage__toggle:hover {
  transform: scale(1.04);
  box-shadow: var(--shadow-xl);
}

.page--game-session .play-stage__toggle:focus-visible {
  outline: var(--focus-ring-width) solid var(--geistface-grey-25);
  outline-offset: var(--space-4);
}

/* A drawn triangle rather than the sprite's outline play glyph: the brief asks
   for a solid white triangle, and the family's icon is a stroked outline. The
   pause state re-values the same box into two bars through a border, so one
   element carries both marks and the control never reflows between them. */
.page--game-session .play-stage__mark {
  width: 0;
  height: 0;
  margin-inline-start: var(--space-6);
  border-block: var(--space-16) solid transparent;
  border-inline-start: var(--space-28) solid currentColor;
  border-inline-end: 0;
}

.page--game-session[data-sequence="playing"]:not([data-paused="true"]) .play-stage__mark {
  width: var(--space-24);
  height: var(--space-32);
  margin-inline-start: 0;
  border: 0;
  border-inline: var(--space-8) solid currentColor;
  background: none;
}

/* --- the bar ------------------------------------------------------------- */

.page--game-session .play-bar {
  position: fixed;
  inset-block-end: calc(var(--play-bar-inset) + var(--play-bar-safe));
  /* Centred by auto margins on a fit-content box, not by a translate. Any
     transform makes an element the containing block for everything fixed
     inside it, and then clips those to its own overflow - which is what put
     the two selector menus straight back inside the box they have to escape.
     Auto margins centre the plate without establishing one. */
  inset-inline: 0;
  width: fit-content;
  margin-inline: auto;
  z-index: 320;
  display: flex;
  /* Centred, not stretched. A stretched row is only ever as short as its
     tallest child's outer box, which is how one control's own padding set the
     height of every other one and left the bar 16px thicker than its content. */
  align-items: center;
  gap: var(--space-4);
  /* Against the viewport, not 100vw: vw counts the page scrollbar, so on a
     scrolling page the bar was allowed to be wider than the room it had. */
  max-width: calc(100% - var(--space-32));
  padding: var(--space-6);
  border: 1px solid color-mix(in srgb, var(--geistface-grey-25) 16%, transparent);
  border-radius: var(--radius-full);
  /* Never tinted by the game. Opaque, not translucent: a glass bar would take
     a different colour on all fifty grounds, which is the one thing the brief
     rules out.

     The bottom of the ramp, not the 900 step above it. Legibility on this
     screen is bought entirely with these controls - the artwork behind them is
     drawn at full strength with no blur and no veil - so the darkest panel
     value is the one that holds the row's white ink against fifty different
     grounds rather than the one that only just does against the brightest.
     The round Play control, the selector and the help card take it too: those
     four are one family, and a bar a step darker than the selector directly
     above it reads as two systems on one screen. */
  background: var(--panel-navy-950);
  box-shadow: var(--shadow-lg);
  /* One line at every size. Past the last width that fits, the row scrolls
     sideways rather than wrapping or dropping a control, and the scroll stops
     at the bar rather than carrying on into the stage behind it. */
  overflow-x: auto;
  overscroll-behavior-inline: contain;
}

.page--game-session .play-bar__group,
.page--game-session .play-bar__control {
  /* Nothing in the row flexes: a control that shrinks stops being the size the
     next breakpoint measured, and the bar has a scroller for the overflow. */
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-8);
  /* One row height for the whole bar, so an icon-only action and a readout are
     the same target and the bar's height is a single number rather than
     whichever child happened to be tallest. 44 was sized for a two-line field;
     nothing in the row is two lines any more. */
  min-height: var(--space-32);
  padding: var(--space-4) var(--space-12);
  border: 0;
  border-radius: var(--radius-full);
  background: none;
  color: var(--geistface-grey-25);
  white-space: nowrap;
}

.page--game-session .play-bar__control {
  cursor: pointer;
  font-family: var(--skillprint-font-ui);
  transition: var(--transition-interactive);
}

.page--game-session .play-bar__control:hover {
  background: color-mix(in srgb, var(--geistface-grey-25) 14%, transparent);
}

.page--game-session .play-bar__control:focus-visible {
  outline: var(--focus-ring-width) solid var(--geistface-grey-25);
  outline-offset: calc(var(--space-2) * -1);
}

/* The two selectors are one control inside a positioning wrapper. The wrapper
   used to carry .play-bar__group as well, so every field was padded twice -
   12px of dead space wrapped around a control that already had its own - and
   .sp-dropdown's `width: 100%` made this pair the only flexible items in a row
   that must not flex at all, so they were also the two that got squeezed. */
.page--game-session .play-bar__select {
  flex: 0 0 auto;
  display: flex;
  width: auto;
}

/* A field reads as a field. Every other control in the bar is a bare pill, so
   the two things the player can actually change carry a hairline and a lifted
   ground, and both deepen on hover and again while the menu is open. */
.page--game-session .play-bar__control--field {
  width: auto;
  /* The hairline takes the pixel the padding gives up, so a bordered field and
     a bare pill are the same height and the row stays one number tall. */
  padding-block: calc(var(--space-4) - 1px);
  padding-inline: var(--space-10) var(--space-8);
  border: 1px solid color-mix(in srgb, var(--geistface-grey-25) 24%, transparent);
  background: color-mix(in srgb, var(--geistface-grey-25) 8%, transparent);
}

.page--game-session .play-bar__control--field:hover {
  border-color: color-mix(in srgb, var(--geistface-grey-25) 44%, transparent);
  background: color-mix(in srgb, var(--geistface-grey-25) 14%, transparent);
}

/* Part A rings an open trigger in violet, off the light page's own focus
   tokens - and --button-focus-inner resolves near-white on this document, so
   an open selector drew a white halo round a dark pill. This is the same
   failure .popup--dark and the step rail both document. The open state is the
   field one step brighter instead, which needs no token work at all. */
.page--game-session .play-bar__control--field[aria-expanded="true"] {
  border-color: color-mix(in srgb, var(--geistface-grey-25) 72%, transparent);
  background: color-mix(in srgb, var(--geistface-grey-25) 18%, transparent);
  box-shadow: none;
}

.page--game-session .play-bar__sep {
  flex: 0 0 auto;
  align-self: center;
  width: 1px;
  height: var(--space-24);
  background: color-mix(in srgb, var(--geistface-grey-25) 22%, transparent);
}

/* One size, one colour, on every icon in the bar. */
.page--game-session .play-bar__icon {
  flex: 0 0 auto;
  width: var(--icon-size-sm);
  height: var(--icon-size-sm);
  color: var(--geistface-grey-25);
  fill: none;
  stroke: currentColor;
  stroke-width: var(--icon-stroke, 2);
  stroke-linecap: round;
  stroke-linejoin: round;
}

.page--game-session .play-bar__chevron {
  flex: 0 0 auto;
  width: var(--icon-size-xs);
  height: var(--icon-size-xs);
  color: var(--geistface-grey-25);
  fill: none;
  stroke: currentColor;
  stroke-width: var(--icon-stroke, 2);
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* 24, not 28. With the readouts on one line the mark became the tallest thing
   in the row and was setting the plate's height on its own - the bar was 4px
   taller than any of its controls needed purely to clear the picture. At 24 it
   matches the row and is still the largest single object in the bar. */
.page--game-session .play-bar__art {
  flex: 0 0 auto;
  display: block;
  overflow: hidden;
  width: var(--space-24);
  height: var(--space-24);
  border-radius: var(--radius-sm);
}

.page--game-session .play-bar__art img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Label beside value, not above it. Stacked, every readout was two lines tall
   and the bar had to be 58px to hold them - on a screen whose entire job is the
   game behind it. Inline, the tallest thing in the row is one line of text and
   the plate comes down to what the controls actually need.

   Baseline, not centre: "Score" and "0" are read as one phrase, and centring
   two different type sizes sits the smaller one visibly high against the
   larger. */
.page--game-session .play-bar__stack {
  display: flex;
  align-items: baseline;
  gap: var(--space-6);
  line-height: var(--text-xs-line-height);
  white-space: nowrap;
}

/* The label/value pair, the action labels and the game name all read from the
   same two UI steps: xs for the quiet label, sm for the value. No caps, no
   bold, no per-game variation. */
.page--game-session .play-bar__label {
  color: color-mix(in srgb, var(--geistface-grey-25) 64%, transparent);
  font-family: var(--skillprint-font-ui);
  /* The scale's smallest step. Above the value it needed to hold its own as a
     line; beside it, it is a tag on a number and only has to be readable. */
  font-size: var(--text-xxs-font-size);
  font-weight: var(--weight-regular);
  letter-spacing: normal;
  text-transform: none;
}

.page--game-session .play-bar__value,
.page--game-session .play-bar__name,
.page--game-session .play-bar__action-label {
  color: var(--geistface-grey-25);
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-sm-font-size);
  font-weight: var(--weight-medium);
  letter-spacing: normal;
  text-transform: none;
}

/* The menu is placed against the viewport rather than against its trigger.

   The bar scrolls sideways, and a scroll container clips its own absolutely
   positioned descendants: the menu was drawn 68px above a 74px box that hid
   everything outside itself, so both selectors opened onto nothing at all.
   Fixed takes it out of that box; the script writes the edge it opens from,
   how tall it may be and where it sits, so it clears the bar, stays inside
   both screen edges and flips below the trigger when the room above runs out.
   The only geometry left here is what does not depend on measuring. */
.page--game-session .play-bar__menu {
  position: fixed;
  inset: auto;
  /* Over the bar's own 320: it is drawn from a control the bar contains. */
  z-index: 330;
  width: var(--space-224);
  max-width: calc(100% - var(--space-16));
  max-height: var(--space-320);
  overflow-y: auto;
  overscroll-behavior: contain;
  /* The bar holds its row on one line and every control inherits that. A list
     of options must not: nine inline-flex rows were laid end to end on a
     single line, so the menu stood 67px tall and showed only the first. */
  white-space: normal;
}

/* --- the two pills that stay ---------------------------------------------- */

/* The selector's label was a <strong>, which put a browser bold next to a row
   of medium-weight controls. Same font, same step, same weight as the bar. */
.page--game-session .game-switch {
  border-color: color-mix(in srgb, var(--geistface-grey-25) 16%, transparent);
  background: var(--panel-navy-950);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

.page--game-session .game-switch__name,
.page--game-session .game-switch__trigger {
  color: var(--geistface-grey-25);
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-sm-font-size);
  font-weight: var(--weight-medium);
  letter-spacing: normal;
  text-transform: none;
}

.page--game-session .game-switch__trigger .sp-dropdown__chevron {
  color: var(--geistface-grey-25);
}

/* The hover fill, on the ground this control actually sits on.

   .game-switch__trigger is a .button--tertiary, and the tertiary hover fill
   comes from the button tokens - which this page re-values to the light
   palette, because portal screens declare data-theme="light" on <html>. The
   rule above pins the label to near-white for the dark chrome and nothing
   pinned the fill, so hovering the selector laid #fcfcfd text on an almost
   white plate: 1.18:1, the control effectively disappearing under the pointer.

   The value is the one .play-bar__control already uses two rules up, because
   this is the same control on the same chrome and there is no reason for the
   two to answer a hover differently. Pressed and open take the same treatment
   so the selector cannot flash light at either. */
.page--game-session .game-switch__trigger:is(:hover, :active),
.page--game-session .game-switch__trigger[aria-expanded="true"] {
  background: color-mix(in srgb, var(--geistface-grey-25) 14%, transparent);
  color: var(--geistface-grey-25);
}

/* The step rail keeps its own job and its own plate; it only moves up so the
   bar can own the foot of the screen - by the bar's measured box rather than
   by a number that had to be re-derived every time the bar changed. */
.page--game-session .session-rail {
  inset-block-end: var(--play-bar-clear);
}

/* --- smaller screens ------------------------------------------------------ */

/* Spacing goes before readability: the padding and the gaps tighten, the label
   over value stacks stay, and the bar scrolls sideways rather than wrapping so
   every control stays reachable at one height. */
@media (max-width: 900px) {
  .page--game-session .play-bar {
    inset-inline: var(--space-8);
    width: auto;
    max-width: none;
    justify-content: flex-start;
  }

  .page--game-session .play-bar__group,
  .page--game-session .play-bar__control {
    padding-inline: var(--space-8);
  }

  /* A portrait viewport already crops a 380x210 card hard just to cover it, so
     the extra step desktop needs to make the texture read large is 25% of the
     card thrown away for nothing here. Cover alone is the scale-up on a phone. */
  .page--game-session .play-field__art {
    transform: none;
  }
}

@media (max-width: 560px) {
  .page--game-session .play-stage__toggle { --play-size: var(--space-80); }

  /* The action labels are the first thing to go: the icons carry Play, Exit
     and Help on their own, and dropping the words keeps every target at full
     size rather than shrinking the row. */
  .page--game-session .play-bar__action .play-bar__action-label { display: none; }

  .page--game-session .play-bar__action {
    padding-inline: var(--space-10);
  }

  /* The two selectors keep their labels but lose the qualifier - "Combo"
     rather than "Combo Duration" - so the row gives back 62px without either
     field turning into an unlabelled icon. */
  .page--game-session .play-bar__label-more { display: none; }

  /* Identity holds its ground while the rest of the row scrolls under it.
     Sticky rather than a second fixed element: it stays in the flex flow, so
     it still takes its slot at rest and only pins once the row has been pushed
     past it. The plate colour is repeated here because scrolled controls pass
     behind it and would otherwise show through. */
  .page--game-session .play-bar__identity {
    position: sticky;
    inset-inline-start: 0;
    z-index: 1;
    background: var(--panel-navy-950);
  }

  /* The prototype step rail is scaffolding for demonstrating the flow, not
     part of the product. On a phone it was taking a full control row off a
     screen whose whole point is the game, so it shrinks to the smallest thing
     that is still hittable and gets out of the way. */
  .page--game-session .session-rail__step {
    min-height: var(--space-24);
    padding: 0 var(--space-6);
    font-size: var(--text-xs-font-size);
    letter-spacing: normal;
  }

  .page--game-session .session-rail {
    gap: var(--space-2);
    padding: var(--space-2);
  }
}

/* --- section 4 · no game switching from inside a session -------------------
   The selector is a second way to answer a question the toolbar already
   answers - the icon and the name say which game this is - and on a phone it
   was a full-width control across the top of the stage. Removed from the
   handheld view entirely; the measuring module reads its box as zero and the
   stage takes the room back. Desktop keeps it: there the stage is not
   competing for the same pixels. */
@media (max-width: 900px) {
  .page--game-session .game-switch { display: none; }
}

/* --- section 3 · the two readouts -----------------------------------------
   Targeting and Combo are stated by the game. They were both nine- and
   four-option dropdowns, which invited an edit the session cannot accept and
   which had nothing to do with the game on the stage. They are plain groups
   now, sharing the row's own label-over-value stack, with no trigger, no
   chevron, no hover and nothing to press. */
/* --- section 2 · the row says when there is more ---------------------------
   The same mask the game rows use, over a shorter distance. A rail of cards
   fades across 64px because the thing being revealed is a 200px card; the bar
   is a row of 40px controls on a plate 44px tall, and at 64px the fade ate two
   whole controls and softened the plate's own edge into the artwork. 24px
   reveals the cut edge of the next control and leaves the plate reading as a
   plate.

   No media query and no mobile-only rule: the classes are set from the scroll
   position, so a desktop bar with room for every control is never masked, and
   the same bar on a phone is. The breakpoint is the content, not the screen. */
.page--game-session .play-bar {
  --scroll-fade: var(--space-24);
}

.page--game-session .play-bar__readout {
  cursor: default;
}

/* The standing best sits a step back from the running score: same row, same
   height, quieter ink, so the eye lands on the number that is moving. */
.page--game-session .play-bar__group--quiet .play-bar__value {
  color: color-mix(in srgb, var(--geistface-grey-25) 62%, transparent);
  font-variant-numeric: tabular-nums;
}

.page--game-session .play-bar__group--quiet {
  padding-inline-start: 0;
}

/* The running score is read while it changes, so it takes tabular figures -
   proportional digits reflow the box on every tick. */
.page--game-session #scoreValue {
  font-variant-numeric: tabular-nums;
}

  /* The artwork used to be dropped here - the ground behind the bar is the same
     picture, so a 28px copy of it read as said twice. It stays now: once the
     row scrolls, the icon and the name are the only fixed point telling you
     which game you are in, and the pair is pinned open below rather than
     scrolled away with everything else. */
  .page--game-session .play-bar__art { display: block; }
}

/* Help sits on the bar's own plate rather than in a dialog: it is one line of
   the catalogue's how-to and reading it should not stop the screen. */
.page--game-session .play-help {
  position: fixed;
  inset-block-end: var(--play-bar-clear);
  inset-inline-start: 50%;
  translate: -50% 0;
  z-index: 321;
  display: grid;
  gap: var(--space-4);
  width: min(var(--space-384), calc(100vw - var(--space-32)));
  padding: var(--space-16);
  border: 1px solid color-mix(in srgb, var(--geistface-grey-25) 16%, transparent);
  border-radius: var(--radius-2xl);
  background: var(--panel-navy-950);
  color: var(--geistface-grey-25);
  box-shadow: var(--shadow-lg);
}

.page--game-session .play-help[hidden] { display: none; }

.page--game-session .play-help__title {
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-sm-font-size);
  font-weight: var(--weight-medium);
}

.page--game-session .play-help__copy {
  margin: 0;
  color: color-mix(in srgb, var(--geistface-grey-25) 72%, transparent);
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

/* The review dialog shares the screen with three fixed pieces of chrome, and
   the backdrop's flat 24px reserved for none of them: the analysis opened with
   its head under the game selector and its foot under both the step rail and
   the bar, so the panel could not be read end to end however far it scrolled.

   The reserves are the chrome's measured boxes rather than round numbers, so
   moving or resizing a control moves what the dialog clears, at every width
   and every zoom level - where the two hand-written narrow-screen corrections
   this replaces were both losing the cascade and doing nothing at all.
   `.popup` measures max-height against this padded box already, which is why
   reserving here is the whole fix: the dialog resizes into what is left and
   scrolls inside itself, so the last metric and the footer's own actions are
   always reachable. Every other overlay on the screen reserves the same way. */
.page--game-session .popup-backdrop {
  padding-block-start: var(--session-chrome-clear-start);
  padding-block-end: var(--session-chrome-clear);
}

/* ===========================================================================
   09 - Unique composition · Badges · one component, four surfaces

   A badge is drawn in four places - the celebration between a finished game
   and its analysis, the analysis itself, Game Detail's rail and the profile -
   and it is one component in all four. `.sp-badge` owns the whole of it: the
   artwork frame, the caption order, the earned chip and the locked treatment.
   A surface picks a size modifier and, where the badge is a line in a
   dashboard rather than the subject of a screen, the row modifier. Nothing
   else changes, which is what stops four surfaces becoming four components.

   The supplied SVGs letter the badge's name, its EXP value and its skill
   label into the artwork, so the file is scaled proportionally on a 1:1
   canvas and never cropped, masked, recoloured or redrawn - the production
   rules the design system's section 37 states, applied in the product.

   Colour is the one thing the component does not decide. The celebration and
   the result dialog sit on the immersive screens' dark plate and the rail and
   profile sit on the portal's light ground, so each restates the chip's four
   `.ui-badge` tokens below rather than the component carrying a surface with
   it.
   ========================================================================== */

.sp-badge {
  /* The artwork step is the only thing a size modifier moves. The caption
     keeps the page's own type scale at every size, so the badge in a rail is
     this component at a smaller picture, not a shrunken celebration. */
  --badge-art: var(--space-96);

  min-width: 0;
  margin: 0;
  display: grid;
  justify-items: center;
  gap: var(--space-12);
  text-align: center;
  /* The stretched link's ::after resolves against this, so the hit area is the
     badge itself wherever the component is used. */
  position: relative;
}

.sp-badge--sm { --badge-art: var(--space-64); }
.sp-badge--md { --badge-art: var(--space-96); }
.sp-badge--lg { --badge-art: var(--space-144); }

/* The one place the artwork is the screen rather than an element on it. It is
   held against the viewport's shorter measure as well as its width, so a
   laptop in a short window still has room for the button and the countdown
   under it rather than pushing them off the plate. */
.sp-badge--xl { --badge-art: min(var(--space-256), 62vw, 34dvh); }

.sp-badge__art {
  width: var(--badge-art);
  height: auto;
  /* Stated as well as the intrinsic 610x610 on the element, so the caption
     under the badge does not jump when the file decodes. */
  aspect-ratio: 1;
  object-fit: contain;
}

/* Beside, not above, wherever the badge is a line in a dashboard and the
   reading has to stay horizontal. Same caption, same order, same component. */
.sp-badge--row {
  grid-template-columns: var(--badge-art) minmax(0, 1fr);
  align-items: center;
  justify-items: start;
  gap: var(--space-14);
  text-align: start;
  /* The row answers its own width. A container cannot style itself, so this
     sits on the figure and the rules below reach the caption inside it - the
     same row is 600px on a full-width card and 280px in a rail, which no
     viewport breakpoint can tell apart. */
  container-type: inline-size;
  container-name: badge-row;
}

/* Beside the badge, the top-right corner is the title's own line - so the pill
   goes back into the flow and takes a column of its own. Absolute positioning
   here put the score on top of the words, which is what a corner cannot do
   when something else already occupies it. */
.sp-badge--row .sp-badge__copy {
  width: 100%;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  column-gap: var(--space-12);
}

.sp-badge--row .sp-badge__name {
  grid-column: 1;
  grid-row: 1;
}

.sp-badge--row .sp-badge__points {
  position: static;
  grid-column: 2;
  grid-row: 1;
  justify-self: end;
  align-self: start;
}

.sp-badge--row :is(.sp-badge__animal, .sp-badge__foot, .sp-badge__reason) {
  grid-column: 1 / -1;
}

/* Not wide enough for a title and a pill on one line. The pill drops under
   the title rather than squeezing the words to two per line. */
@container badge-row (max-width: 420px) {
  /* Same specificity as the rules above: a container query adds none of its
     own, so `.sp-badge__copy` alone would lose to `.sp-badge--row
     .sp-badge__copy` and the pill would never leave the title's line. */
  .sp-badge--row .sp-badge__copy { grid-template-columns: minmax(0, 1fr); }

  .sp-badge--row .sp-badge__points {
    grid-column: 1;
    grid-row: auto;
    justify-self: start;
    margin-top: var(--space-6);
  }
}

.sp-badge__copy {
  /* Near zero: the talent and the animal are a pair and the space between them
     should read as leading, not as a gap. Everything below sets its own
     margin-top, so the caption has three rhythms instead of one. */
  gap: var(--space-2);
  justify-items: inherit;
}

.sp-badge__name {
  /* The name takes the ink of whatever surface the badge is on. Stated rather
     than left to the cascade because the celebration renders it as an <h2> to
     label its dialog, and a heading on these pages is given the light ground's
     near-black - which on the dark plate the celebration draws on left the
     badge's own name the one unreadable thing on the screen. */
  color: inherit;
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-md-font-size);
  line-height: var(--text-md-line-height);
  font-weight: var(--weight-semibold);
}

/* Only the celebration takes a display step. A badge in a grid of badges is a
   card title and reads as one - given the display size it wrapped to two lines
   and became the loudest thing on the profile, which is a section of the
   record rather than the subject of it. */
.sp-badge--xl .sp-badge__name {
  font-size: var(--display-xs-font-size);
  line-height: var(--display-xs-line-height);
}

/* Three weights, not four lines of one. The talent keeps the component's own
   md step; the animal sits directly under it at 12px so the two read as a
   title and its subtitle rather than as two facts; the foot drops to 10px and
   carries the provenance. Only the points pill takes colour. */
.sp-badge__animal {
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  opacity: 0.72;
}

/* The reward, and the one thing here allowed to be a chip. It reuses
   `.ui-badge` so the per-surface token work the earned chip already carried -
   the portal's light ground and the session screens' dark plate - applies to
   it unchanged. */
.sp-badge__points {
  /* The corner, not the stack. What a badge is worth is a constant of the
     shape, so it sits out of the reading order in the top right and lets the
     caption below be three lines about this badge rather than four. */
  position: absolute;
  top: 0;
  right: 0;
  z-index: 1;
  width: max-content;
  /* The space between the number and the word is a non-breaking space in the
     text now, not a gap - a gap is invisible to textContent and to the
     accessible name, so the pill read "4points" to anything not looking at
     the pixels. */
  gap: 0;
  align-items: baseline;
  font-size: var(--text-xxs-font-size);
  letter-spacing: 0.02em;
}

.sp-badge__points b {
  font-size: var(--text-sm-font-size);
  font-weight: var(--weight-semibold);
  line-height: 1;
}

/* Which game, and when. One line, wrapping rather than clipping, at the
   smallest step the scale has - this is provenance, not a headline. */
.sp-badge__foot {
  margin-top: var(--space-8);
  display: flex;
  /* One line wherever the column affords it, and a wrap rather than a clipped
     date where it does not. */
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
  min-width: 0;
  max-width: 100%;
  /* A step below the animal, not level with it. At 12px the provenance carried
     the same weight as the badge's own name, which is the flat stack the
     caption exists to avoid. The Game Detail rail overrides it back up, since
     that surface runs the whole scale one step larger. */
  font-size: var(--text-xxs-font-size);
  line-height: var(--text-xxs-line-height);
  opacity: 0.72;
}

/* The same square the Recent Sessions rail gives a game: the artwork is
   380x210, so it is cropped to the frame rather than letterboxed inside it,
   over the violet tint that stays as the ground when a file fails to load. */
.sp-badge__glyph {
  width: var(--space-18);
  height: var(--space-18);
  flex: 0 0 auto;
  /* The rail's 8px on a 32px thumb is the same proportion as 4px here. At the
     rail's radius an 18px tile reads as a circle, which is the avatar's shape,
     not the game tile's. */
  border-radius: var(--radius-xs);
  object-fit: cover;
  background: color-mix(in srgb, var(--mindset-violet-500) 8%, transparent);
}

.sp-badge__game-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sp-badge__when {
  display: inline-flex;
  align-items: center;
  gap: var(--space-4);
  white-space: nowrap;
  flex: 0 0 auto;
}

.sp-badge__dot {
  flex: 0 0 auto;
  opacity: 0.6;
}

/* A stretched link. The anchor carries the talent so it has real text and a
   real destination for a screen reader and the status bar; its ::after covers
   the figure so the whole badge is the target. The figure is the positioning
   context rather than whatever card happens to hold it, so the hit area is the
   same on all four surfaces. */
.sp-badge__link {
  color: inherit;
  text-decoration: none;
}

.sp-badge__link::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

/* No underline. The card itself answers the hover - it lifts its ground and
   its border the way a session row does - so underlining the title as well
   would be the same signal twice. */
.sp-badge__link:hover { text-decoration: none; }

/* The ring belongs on the badge, not on the few words of the anchor, because
   the badge is what the click targets. */
.page--portal-profile .pp-badge-card .sp-badge:has(.sp-badge__link:focus-visible) {
  outline: none;
}

.sp-badge:has(.sp-badge__link:focus-visible) {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
  border-radius: var(--radius-card);
}

.sp-badge__link:focus-visible { outline: none; }

@supports not selector(:has(*)) {
  .sp-badge__link:focus-visible {
    outline: var(--focus-ring-width) solid var(--focus-ring-color);
    outline-offset: var(--focus-ring-offset);
  }
}


/* The award screen animates a transform on .sp-badge__copy, and a transform
   makes an element the containing block for anything absolutely positioned
   inside it - so on that one surface the pill anchors to the caption rather
   than the figure. It lands level with the title at the right, which is where
   the xl badge wants it anyway: the artwork is the screen here, and a pill in
   its corner would read as part of the picture. Declared rather than left to
   the animation, so changing the animation cannot silently move the score. */
.sp-badge--xl .sp-badge__copy { position: relative; }

/* The title is centred in the caption while the pill is pinned to its right
   edge, so the clearance between them was whatever the reason paragraph's 46ch
   happened to leave - 5px at 390px, and a collision below 380px. Reserving the
   pill's own width on both sides keeps the title centred and keeps it clear
   whatever the caption measures. */
.sp-badge--xl .sp-badge__name { max-width: calc(100% - 2 * var(--space-72)); }

.sp-badge__reason {
  max-width: 46ch;
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
  opacity: 0.8;
}

/* Locked. Held back rather than hidden: the artwork is still the artwork, so
   the reader can see the family a badge belongs to without being told it is
   theirs. Nothing is added - no unlock threshold, no progress, no hint at what
   it would take - because the catalogue holds none of that and inventing it
   here is how a prototype starts promising something the product has not
   measured. */
.sp-badge[data-earned="false"] .sp-badge__art {
  opacity: 0.42;
  filter: grayscale(1);
}

.sp-badge[data-earned="false"] .sp-badge__name { opacity: 0.74; }

/* --- the chip, on the portal's light ground ------------------------------- */

.page--portal .sp-badge__points {
  --badge-accent: var(--green);
}

.page--portal .sp-badge[data-earned="false"] .sp-badge__points {
  --badge-accent: var(--panel-navy-400);
}

/* --- the chip, on the immersive screens' dark plate ------------------------
   The `.ui-badge` defaults resolve against `--surface-box` and
   `--surface-text`, and both are declared near-white on `<html>` for these
   pages. That is the substitution `.popup--dark` documents: re-valuing a
   surface token here could never reach a badge token declared up there, so the
   four leaves are restated on the element that owns the dark ground. */
.page--game-session .sp-badge__points {
  --badge-accent: var(--green);
  --badge-bg: color-mix(in srgb, var(--green) 16%, transparent);
  --badge-border: color-mix(in srgb, var(--green) 42%, transparent);
  --badge-text: var(--geistface-grey-25);
}


/* ---------------------------------------------------------------------------
   Portal Game Session · the badge celebration

   A step of the sequence like Load and Calculate, so it hides and shows on
   `data-sequence` and the step rail can review it. It sits between Calculate
   and Review because that is where the reward belongs: the session is scored
   and the analysis is one beat away.

   The plate is the review dialog's, deliberately - the celebration and the
   analysis behind it are two halves of the same moment, so arriving at the
   second should feel like the first opening out rather than a second design.
   -------------------------------------------------------------------------- */

.page--game-session[data-sequence="badge"] [data-step~="badge"] { display: grid; }

/* Reserved the way the review dialog reserves, off the same two measured
   properties. The selector at the top and the step rail and bar at the foot
   are fixed chrome this screen shares, so the celebration centres in what is
   left rather than sitting under either of them - and because the reserve is
   the chrome's measured box rather than a sum of tokens, it holds at every
   width without a second set of numbers at the breakpoint. */
.page--game-session .session-veil--award {
  padding-block-start: var(--session-chrome-clear-start);
  padding-block-end: var(--session-chrome-clear);
  padding-inline: var(--space-24);
  /* `safe` so a panel taller than the room it has scrolls from its top rather
     than centring its overflow out of reach above the viewport. */
  align-items: safe center;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.page--game-session .award-panel {
  width: min(var(--space-560), 100%);
  padding: var(--space-32);
  justify-items: center;
  gap: var(--space-20);
  border: 1px solid var(--result-line);
  border-radius: var(--radius-3xl);
  background: var(--result-fill);
  -webkit-backdrop-filter: blur(var(--backdrop-blur-md));
  backdrop-filter: blur(var(--backdrop-blur-md));
  box-shadow: var(--shadow-xl);
  color: var(--geistface-grey-25);
  text-align: center;
}

/* The panel is focused by the sequence, not reached by the reader, so the
   programmatic landing draws nothing. A ring a keyboard genuinely puts here
   still does, which is why the two states are separated rather than the
   outline being turned off outright. */
.page--game-session .award-panel:focus { outline: none; }

.page--game-session .award-panel:focus-visible {
  outline: var(--focus-ring-width) solid var(--geistface-grey-25);
  outline-offset: var(--space-4);
}

/* The dark-ground violet, not the page's.

   Portal screens declare `data-theme="light"` on <html>, and the light ground
   re-values --violet from mindset-violet-400 to mindset-violet-500 - a shade
   picked to be read on near-white. The award panel is a dark island on that
   light-declared page, so the eyebrow was drawing #573eeb on a navy panel at
   1.69:1, and "Badge earned" was effectively invisible.

   This is the case .popup--dark already documents: a dark surface restates the
   tokens its own ground implies. It is restated here on the one element rather
   than on the panel, because --violet is also the primary button's fill, where
   the darker shade is what earns 6.2:1 against its white label - lightening the
   token for the whole panel would fix the eyebrow by breaking the button.

   mindset-violet-300 is the shade .sp-tour__eyebrow already uses on a dark
   ground, and it measures 6.9:1 here. The icon takes it too: the trophy is
   currentColor and was the same colour as the words beside it. */
.page--game-session .award-panel__eyebrow {
  color: var(--mindset-violet-300);
  opacity: 1;
}

.page--game-session .award-panel__badge {
  display: grid;
  justify-items: center;
  /* The pulse below is drawn from this box's centre. */
  position: relative;
}

.page--game-session .award-panel__actions {
  justify-items: center;
  width: 100%;
}

/* --- the countdown --------------------------------------------------------
   Small, quiet and beside the button rather than over the badge: it has to
   make the automatic move expected, not compete with the thing being
   celebrated. The ring is the shared `.sp-progress-ring` counting a total of
   ten down to nought; the number inside it is the same figure in words the
   reader can check at a glance. */

.page--game-session .award-count {
  color: color-mix(in srgb, var(--geistface-grey-25) 70%, transparent);
}

.page--game-session .award-count__ring {
  --progress-ring-size: var(--space-36);
  --progress-ring-stroke: 5;
  --progress-colour: var(--violet);
}

.page--game-session .award-count__ring .sp-progress-ring__track {
  stroke: color-mix(in srgb, var(--geistface-grey-25) 18%, transparent);
}

/* A second per step, linear, so the arc sweeps rather than jumping four times
   its own width once a second. The component's own 150ms ease is right for a
   value that changes when data does; this one changes on a clock. */
.page--game-session .award-count__ring .sp-progress-ring__value {
  transition: stroke-dasharray 1s linear;
}

.page--game-session .award-count__seconds {
  color: var(--geistface-grey-25);
  font-family: var(--font-mono);
  font-size: var(--text-xs-font-size);
  font-variant-numeric: tabular-nums;
  font-weight: var(--weight-medium);
}

/* Paused reads as paused: the arc holds where it stopped and goes to the
   plate's own quiet ink, so nothing on the screen suggests a clock still
   running. */
.page--game-session .award-count[data-state="paused"] .award-count__ring {
  --progress-colour: color-mix(in srgb, var(--geistface-grey-25) 42%, transparent);
}

/* Under reduced motion there is no clock at all, so there is no dial. The
   sentence beside it is the whole indicator, and it says the button is the
   way on. */
.page--game-session .award-count[data-state="held"] .award-count__ring { display: none; }

/* --- the celebration, in motion -------------------------------------------
   Opt in rather than switch off, which is the sheet's own way of putting a
   decoration behind a reader's preference: nothing below exists at all under
   prefers-reduced-motion, and the screen still says everything it has to say.

   Restrained is the brief. The panel arrives on the dialog's own entrance so
   the two halves of this moment match; the badge lands a beat later; one ring
   opens out from behind it and is gone. There is no confetti, no loop and
   nothing that keeps moving while the badge is being read. */
@media (prefers-reduced-motion: no-preference) {
  .page--game-session[data-sequence="badge"] .award-panel {
    animation: award-panel-enter var(--duration-slow) var(--ease-standard) both;
  }

  .page--game-session[data-sequence="badge"] .award-panel__badge .sp-badge__art {
    animation: award-badge-land var(--duration-slower) var(--ease-standard) 120ms both;
  }

  .page--game-session[data-sequence="badge"] .award-panel__badge::after {
    content: "";
    position: absolute;
    top: calc(var(--badge-art, var(--space-96)) / 2);
    width: var(--badge-art, var(--space-96));
    aspect-ratio: 1;
    translate: 0 -50%;
    border: 2px solid var(--violet);
    border-radius: var(--radius-full);
    pointer-events: none;
    animation: award-ring var(--duration-slowest) var(--ease-standard) 260ms both;
  }

  .page--game-session[data-sequence="badge"] .award-panel__badge .sp-badge__copy,
  .page--game-session[data-sequence="badge"] .award-panel__actions {
    animation: award-copy-rise var(--duration-slow) var(--ease-standard) 320ms both;
  }
}

@keyframes award-panel-enter {
  from { opacity: 0; transform: translateY(var(--space-12)) scale(0.98); }
  to { opacity: 1; transform: none; }
}

@keyframes award-badge-land {
  from { opacity: 0; transform: translateY(var(--space-16)) scale(0.88); }
  to { opacity: 1; transform: none; }
}

@keyframes award-ring {
  from { opacity: 0.55; transform: scale(0.55); }
  to { opacity: 0; transform: scale(1.28); }
}

@keyframes award-copy-rise {
  from { opacity: 0; transform: translateY(var(--space-8)); }
  to { opacity: 1; transform: none; }
}


/* ---------------------------------------------------------------------------
   The analysis · the badge inside the result dashboard

   A strip across the foot of the overview rather than a third column beside
   the score: the score and the session facts are what the reader came for and
   they keep the width they had, and the badge reads as one more fact of the
   session rather than as something floating on top of it. Same hairline, same
   fill and same radius as the two cards above it, so it belongs to the
   dashboard rather than sitting on it.
   -------------------------------------------------------------------------- */

.page--game-session .game-result__badge {
  grid-column: 1 / -1;
  border: 1px solid var(--result-line);
  background: var(--result-fill);
}

/* No `display` above, deliberately. The row modifier on the figure inside
   already lays the badge out, and any display declared here would outrank
   [hidden] and leave an empty bordered strip in the dashboard of every game
   the catalogue awards no badge. Same reason the three rules below exist:
   each of those elements is a grid, so each has to say what hidden means. */
.page--game-session .game-result__badge[hidden] { display: none; }

.page--game-session .game-result__badge .sp-badge {
  --badge-art: var(--space-64);
  gap: var(--space-14);
}


/* ---------------------------------------------------------------------------
   Portal Game Detail · the badge in the rail

   The second card in the reader's own column, under Your record. It is about
   them rather than about the game, which is the column's whole contract, and
   it takes `.rail-card`'s padding, radius and gap rather than restating any of
   it. It is drawn only for a game the catalogue gives a badge; a game with
   none has no card at all rather than an empty one.
   -------------------------------------------------------------------------- */

.page--portal .rail-card--badge[hidden] { display: none; }

/* Smaller than the component's own small step. The rail is a column of short
   cards beside a long read, and this one has to sit under Your record without
   taking the rail past the viewport - a sticky column cannot be scrolled to
   its own bottom. */
/* Big enough to read. At 56px the lettering the artwork carries was a texture,
   which on the one page where the badge is the subject rather than a line item
   is the wrong trade - so the art takes the rail's full measure and the copy
   sits under it, each line with room to be read. */
/* Big enough to read. At 56px the lettering the artwork carries was texture,
   which on the one page where the badge is the subject rather than a line item
   is the wrong trade. 112px is what the 282px rail affords with the copy still
   beside it rather than under it. */
.page--portal .rail-card--badge .sp-badge {
  --badge-art: var(--space-112);
  gap: var(--space-16);
}

.page--portal .rail-card--badge .sp-badge__name {
  font-size: var(--text-lg-font-size);
  line-height: var(--text-lg-line-height);
}

.page--portal .rail-card--badge .sp-badge__animal {
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}

.page--portal .rail-card--badge .sp-badge__foot {
  font-size: var(--text-xs-font-size);
}

.page--portal .rail-card--badge .sp-badge__name {
  color: var(--ui-text);
}

.page--portal .rail-card--badge :is(.sp-badge__animal, .sp-badge__foot) {
  color: var(--ui-muted);
  opacity: 1;
}

/* The rail is 282px on a desktop but 218px in the band where the page has
   narrowed the column and not yet dropped it below the main content. Beside
   the artwork that left the copy 90px - two words a line - so the slot asks
   its own width and stacks rather than squeezes. */
.page--portal .rail-card--badge [data-gd-badge] {
  container-type: inline-size;
  container-name: badge-slot;
}

@container badge-slot (max-width: 260px) {
  .sp-badge--row {
    grid-template-columns: minmax(0, 1fr);
    justify-items: center;
    text-align: center;
    gap: var(--space-12);
  }

  .sp-badge--row .sp-badge__foot { justify-content: center; }
}

/* Arriving from a badge. A badge anywhere in the portal links to the game that
   awarded it and lands on this card, so the card says "here" for a moment
   rather than leaving the reader to find it in a sticky column. The ring is
   the focus colour because that is what the system already uses for "this is
   the thing you asked for". */
.page--portal .rail-card--badge {
  scroll-margin-top: var(--space-24);
  transition: box-shadow 240ms ease, border-color 240ms ease;
}

.page--portal .rail-card--badge.is-highlighted {
  border-color: var(--focus-ring-color);
  box-shadow: 0 0 0 var(--focus-ring-width) var(--focus-ring-color);
}

@media (prefers-reduced-motion: reduce) {
  .page--portal .rail-card--badge { transition: none; }
}


/* ---------------------------------------------------------------------------
   Portal Profile · the Badges section

   Beside the play it came out of: the section sits between the game insights
   and the full session ledger, so the record reads reward, then history. The
   grid is the goals grid's shape rather than a third card rhythm on one page.
   -------------------------------------------------------------------------- */

/* Wider cells than the goals grid it used to borrow. A badge card carries a
   title, a subtitle and a provenance line, and at the goals width the last of
   those wrapped mid-phrase - so the grid takes two across and gives the copy
   the measure it needs. Four badges then sit two by two rather than three and
   a widow. */
.page--portal-profile .pp-badge-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(var(--space-256), 1fr));
  gap: var(--spacing-2xl);
}

.page--portal-profile .pp-badge-grid[hidden],
.page--portal-profile .pp-badge-empty[hidden] { display: none; }

.page--portal-profile .pp-badge-card {
  padding: var(--spacing-2xl);
  align-content: start;
  border-radius: var(--radius-card);
  /* The card asks its own width so the corner treatment can give way when the
     corner is not big enough to hold anything. Grid arithmetic alone left only
     2.7px of clearance at 1200px, which one token change would swallow. */
  container-type: inline-size;
  container-name: badge-card;
  /* The card, not the figure, is the positioning context: the stretched link
     then covers the whole card and the points pill lands in the card's own
     corner rather than the artwork's. */
  position: relative;
  transition: var(--transition-interactive);
}

/* The hover a session row gets - the ground lifts and the border with it -
   because a badge card is the same kind of thing: a row of record that opens
   the game behind it. */
.page--portal-profile .pp-badge-card:hover {
  background: var(--surface-raised);
  border-color: var(--surface-border-hover);
}

.page--portal-profile .pp-badge-card:has(.sp-badge__link:focus-visible) {
  outline: var(--focus-ring-width) solid var(--focus-ring-color);
  outline-offset: var(--focus-ring-offset);
}

.page--portal-profile .pp-badge-card .sp-badge {
  --badge-art: var(--space-112);
  position: static;
}

.page--portal-profile .pp-badge-card .sp-badge__points {
  top: var(--spacing-lg);
  right: var(--spacing-lg);
}

/* Too narrow for a corner. The pill leaves the corner and joins the caption
   rather than being printed over the artwork - the badge is the brand asset
   on this surface and nothing gets to sit on top of it. */
@container badge-card (max-width: 300px) {
  .page--portal-profile .pp-badge-card .sp-badge__points {
    position: static;
    justify-self: center;
    margin-top: var(--space-8);
  }
}

.page--portal-profile .pp-badge-card .sp-badge__name { color: var(--ui-text); }

.page--portal-profile .pp-badge-card :is(.sp-badge__animal, .sp-badge__foot) {
  color: var(--ui-muted);
  opacity: 1;
}

/* The empty state names what a badge is and where they come from, rather than
   drawing greyed-out shapes of badges nobody has. A locked silhouette here
   would be inventing a reward the reader has not been promised. */
.page--portal-profile .pp-badge-empty {
  padding: var(--spacing-3xl);
  display: grid;
  justify-items: center;
  gap: var(--spacing-lg);
  border-radius: var(--radius-card);
  text-align: center;
}

.page--portal-profile .pp-badge-empty__icon {
  color: var(--ui-muted);
}


/* --- responsive -----------------------------------------------------------
   Three moves, and they are the ones the surrounding screens already make at
   the same widths. */

@media (max-width: 900px) {
  /* Only the side padding changes here now. The chrome this screen reserves
     for does change at 900 - the selector goes full width at the top and the
     step rail centres at the foot - but both are measured, so the block
     reserves above follow on their own and a second copy of them would only
     be a number to keep in step. */
  .page--game-session .session-veil--award {
    padding-inline: var(--space-16);
  }

  .page--game-session .award-panel {
    padding: var(--space-24);
    gap: var(--space-16);
  }
}

@media (max-width: 720px) {
  /* The overview is one column here, so the strip is simply the third row of
     it and the explicit span would be a no-op that outlives the grid. */
  .page--game-session .game-result__badge {
    grid-column: auto;
  }
}

@media (max-width: 560px) {
  .page--game-session .sp-badge--xl { --badge-art: min(var(--space-192), 58vw, 30dvh); }

  .page--game-session .award-panel {
    padding: var(--space-20);
    border-radius: var(--radius-2xl);
  }

  /* Full width, the way the footer's controls go full width in the dialog at
     this size: a lone pill centred on a narrow plate reads as unfinished. */
  .page--game-session .award-panel__actions [data-award-go] { width: 100%; }
}

@media (max-width: 640px) {
  /* Not 144px. The card holds a 112px badge centred with a points pill pinned
     to its top-right corner, and the two collide once the card is narrower
     than about 257px - so below this breakpoint the grid goes to one card a
     row rather than two cramped ones. */
  .page--portal-profile .pp-badge-grid {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--spacing-xl);
  }
}

/* ----------------------------------------------------------------------------
   42.1 · The two boundaries that are not standard

   Sections carry 96px top and bottom (above), which is now the same as
   the two page edges below - one value governs every boundary. The two edges
   read wider than that on purpose: the hero hands over to the first section,
   and the last section hands over to the footer. Both are stated once, here,
   rather than as a one-off margin on whichever block happens to be first or
   last on a given page.
   -------------------------------------------------------------------------- */

.page--site .hero + .section--homepage,
.page--site .hero + .cta-section { padding-block-start: var(--space-96); }

.page--site main > section:last-of-type { padding-block-end: var(--space-96); }

@media (max-width: 1023px) {
  .page--site .hero + .section--homepage,
  .page--site .hero + .cta-section { padding-block-start: var(--space-96); }

  .page--site main > section:last-of-type { padding-block-end: var(--space-96); }
}

@media (max-width: 767px) {
  .page--site .hero + .section--homepage,
  .page--site .hero + .cta-section { padding-block-start: var(--space-72); }

  .page--site main > section:last-of-type { padding-block-end: var(--space-72); }
}

/* ============================================================================
   43 · Marketing site · campaign landing pages

   The three campaign templates carry `.page--campaign` beside `.page--site`,
   so what a landing page needs and a site page does not is stated once here
   rather than three times in three files.

   Only one kind of thing qualifies today, and it is the same defect twice: a
   grid whose column count does not divide its item count across the tablet
   band, which leaves the last card hanging on its own row directly above the
   call to action. The shared rules are right for the pages they were written
   for - `repeat(3)` suits the three-figure strips, and `auto-fit` at 280px
   suits a set whose length varies - so these are scoped to the campaign pages
   rather than changed underneath `about.html` and `index.html`.

   The counts are part of the contract: `.signal-matrix` carries three tiles on
   a campaign page and `.surface-grid` carries four. A different count wants a
   different rule here, not a fifth tile pushed into an existing one.
   ========================================================================== */

@media (max-width: 1023px) {
  /* Four figures, two rows of two. The shared rule is `repeat(3)`, which puts
     the fourth on a row by itself for the whole 640-1023 band. */
  .page--campaign .stats-strip--bare { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* Three tiles: `auto-fit` at `minmax(280px, 1fr)` needs about 880px of
     content width to hold all three, so below that it drops one to its own
     row. Two-up would only move the orphan, so the set stacks. */
  .page--campaign .signal-matrix { grid-template-columns: minmax(0, 1fr); }

  /* Four tiles divide, so this one pairs rather than stacking. */
  .page--campaign .surface-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 639px) {
  .page--campaign .surface-grid { grid-template-columns: minmax(0, 1fr); }
}


/* ==========================================================================
   07 - Shared component · the portal's blank state

   The panel a portal section shows when it has nothing to list yet. Five
   screens had already written one of these - .empty-state four times, under
   four different page scopes, plus .pp-session-empty and .gd-record__empty -
   so a sixth page-local copy was the wrong answer. This is the portal's one
   blank: dashed rather than solid, because a dashed edge reads as "a thing
   that will be filled" where a solid one reads as a card that failed to load.

   It says what will appear and never apologises for the absence. The action
   that fills it belongs to the section above, not in here: two primary calls
   a hand-span apart is what makes a first-run screen feel like a form.
   ========================================================================== */

.page--portal .portal-blank {
  display: grid;
  justify-items: start;
  gap: var(--space-8);
  padding: var(--space-28) var(--space-24);
  border: 1px dashed var(--ui-border-strong, var(--surface-border));
  border-radius: var(--radius-compact);
  background: var(--surface-panel);
}

.page--portal .portal-blank .sp-icon-frame { margin-block-end: var(--space-4); }

.page--portal .portal-blank__title {
  margin: 0;
  color: var(--surface-text);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
  font-weight: var(--weight-semibold);
}

.page--portal .portal-blank__note {
  margin: 0;
  max-width: var(--space-560);
  color: var(--surface-text-muted);
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
}


/* ==========================================================================
   09 - Unique composition · Games Portal · Home, the three states

   Home is one page with three readings of the same player: before any game
   has been played, part-way through the five-session run, and after it. The
   states are not three pages - the shell, the sections and the rail cards are
   one set of structures whose copy, counts and rails are written from a single
   table in the script. Only three things are authored twice in the markup, and
   each for the same reason: the module that draws them runs once, at parse.

     - the two .rail-print__figure blocks (the blank dial and the inked print),
       because [data-skillprint] is built once and is not re-buildable
     - the per-state [data-game-rail] lists, because fillRails() marks a rail
       done and never revisits it
     - nothing else

   Everything the reader sees switch is one [hidden] toggle or one string.
   ========================================================================== */

/* --- the review-only state switcher --------------------------------------
   It sits in the rail's foot beside the screen switcher because that is where
   this portal keeps its review affordances - secondary to the routes above,
   and out of the content column entirely. One wrapper element, so shipping the
   page means deleting one node rather than unpicking a state machine. */

/* [hidden] is a user-agent rule, so any author declaration of display beats it
   however specific that author rule is. Six of the switched blocks are drawn by
   a .page--portal rule that sets display - the two print figures, the four
   per-state rails - so hiding one left it on the page at full height. The house
   answer is a page-scoped restatement; the library and the embed each carry the
   same line. Attribute-qualified rather than blanket, so it can only ever reach
   the blocks a screen switches - and scoped to .page--portal rather than to one
   screen, because Home and Profile both switch blocks now and Profile's print
   panels and chart frames are drawn by rules that set display too. */
.page--portal [data-state-when][hidden] { display: none; }


.page--portal .portal-preview {
  display: grid;
  gap: var(--space-6);
  padding-block-end: var(--space-8);
  border-block-end: 1px solid var(--side-nav-border);
}

.page--portal .portal-preview__label {
  color: var(--side-nav-muted);
  font-family: var(--skillprint-font-mono);
  font-size: var(--text-xxs-font-size);
  line-height: var(--text-xxs-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* The collapsed rail is 4 icons wide and has no room for a labelled control.
   The preview is a review affordance, so it goes rather than being squeezed -
   the URL parameter still reaches every state. */
.page--portal.nav-collapsed .portal-preview { display: none; }

/* Which option is the current state. Part A draws both the tint and the tick
   from aria-selected, and aria-selected is only valid on an option inside a
   listbox - this is a menu of three mutually exclusive choices, so its options
   are menuitemradio and the state they legitimately carry is aria-checked.
   Declaring the wrong role to reach the styling would be the tail wagging the
   dog, so the state is mapped onto the drawing instead. */
.page--portal .portal-preview [role="menuitemradio"][aria-checked="true"] {
  background: color-mix(in srgb, var(--mindset-violet-500) 16%, var(--surface-box));
}

.page--portal .portal-preview [role="menuitemradio"][aria-checked="true"]
  .sp-dropdown__selected-mark {
  opacity: 1;
}


/* --- the first-run tutorial ----------------------------------------------
   A deliberate departure, and it is worth saying why. The coachmark section
   in Part A states that a tour "deliberately not modal… a coachmark that hides
   the interface behind a backdrop cannot teach the interface". That holds for
   a coachmark explaining one control to a player who is already using the
   product. It does not hold for the first thirty seconds of an account that
   has no data in it: there is nothing behind the scrim to keep working on,
   and an un-dimmed tour on a page of empty states gives the reader no way to
   tell which of six quiet panels the step is about.

   So this reuses the bubble - .sp-tour, its type, its arrow, its footer, its
   light-mode pair and its reduced-motion rule all come from Part A untouched -
   and changes exactly two things: the bubble is measured against the viewport
   rather than an anchor it is nested inside, and a scrim is drawn around the
   step's target. Nothing in the Part A block is redefined; the overrides are
   carried by .portal-tour, which is this page's own class on the same element.

   Why measured-and-pinned rather than nested: three of the five targets are
   inside a box the bubble could not escape. .portal-rail is position: sticky
   and .game-rail is overflow-x: auto - both are stacking or clipping contexts,
   and an absolutely positioned bubble inside either is cut off at its edge. */

/* The page is held still while a step is open.

   Every length the tutorial writes is measured against the viewport, so a page
   that can slide underneath it is a page that can carry the highlighted element
   off the screen - and place() then puts the bubble wherever the target went,
   which after one page-down is entirely above the fold. What the reader was
   left with was a flat scrim: no ring, no bubble, and no way out but Escape.

   Nothing is taken from them by this. The tutorial scrolls itself to each step,
   and Skip, Finish and Escape all give the page straight back. The gutter is
   the width the scrollbar was occupying, handed back as padding so locking
   cannot shift the layout sideways on a platform that reserves space for one.
   overflow on <body> propagates to the viewport, which is what stops a wheel;
   programmatic scrolling still works, which is what lets a step move. */
.page--portal-home.home-tour-open {
  overflow: hidden;
  padding-inline-end: var(--home-tour-gutter, 0px);
}

.page--portal-home .portal-spotlight {
  /* Heavier than --side-nav-scrim, which is tuned for a drawer that only
     covers part of the screen. This one has to make five pale panels read as
     "not now" at a glance. */
  --spot-scrim: color-mix(in srgb, var(--deep-navy-950) 74%, transparent);
  --spot-ring: color-mix(in srgb, var(--mindset-violet-400) 52%, transparent);

  position: fixed;
  inset: 0;
  /* Over the rail (90) and the drawer scrim (80), under .popup-backdrop (300)
     so a game modal opened from anywhere still lands on top of everything. */
  z-index: 200;
  /* The scrim is what blocks the page for a pointer. Keyboard and assistive
     technology are blocked by `inert` on .portal-app instead, which is the
     only one of the two that also takes the rest of the page out of the tab
     order - a scrim a reader can Tab straight underneath is not a scrim.

     It blocks only while it is open, and `visibility` is not enough to say so:
     a visible -> hidden transition holds `visible` for the whole 200ms, so a
     scrim that leaned on it went on swallowing clicks for a fifth of a second
     after Finish - long enough that the first press of "Play your first game"
     did nothing. pointer-events is not transitioned, so it switches at once. */
  pointer-events: none;
  /* Clips the hole's shadow to the viewport, so a 200vw spread cannot reach
     the document's scroll width. */
  overflow: hidden;
  opacity: 0;
  visibility: hidden;
  transition:
    opacity var(--duration-base) var(--ease-standard),
    visibility var(--duration-base) var(--ease-standard);
}

.page--portal-home .portal-spotlight[data-open="true"] {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

/* The hole is the element; the dim is its shadow. Painting the scrim outward
   from a measured rectangle is what lets the target stay visible without being
   lifted - raising a z-index would do nothing for a target inside the sticky
   rail, because a stacking context cannot be escaped from within.

   Shadow order is load-bearing: the first shadow paints on top, so the ring is
   listed before the scrim and reads as a ring rather than being flooded by it. */
.page--portal-home .portal-spotlight::before {
  content: "";
  position: absolute;
  left: var(--spot-x, 0px);
  top: var(--spot-y, 0px);
  width: var(--spot-w, 0px);
  height: var(--spot-h, 0px);
  border-radius: var(--spot-radius, var(--radius-card));
  box-shadow:
    0 0 0 var(--space-4) var(--spot-ring),
    0 0 0 max(200vw, 200vh) var(--spot-scrim);
  /* The geometry deliberately does not transition. The hole is re-measured on
     every scroll and every resize, so an animated left/top would trail the
     page by a fifth of a second the whole way down it - and on the first step
     it would slide in from the corner the properties default to, because there
     is no previous position to travel from. The scrim's own fade is the motion
     this needs; where the hole is is a fact, not a movement. */
}

html:is([data-theme="light"], [data-surface="light"]) .page--portal-home .portal-spotlight {
  --spot-scrim: color-mix(in srgb, var(--deep-navy-900) 56%, transparent);
  --spot-ring: color-mix(in srgb, var(--mindset-violet-500) 46%, transparent);
}

/* The bubble, pinned to the viewport. Part A anchors it to a .sp-tour-anchor
   with left: 50% and a translate; here the script has already measured where
   it goes, so the inset properties are absolute and the transform carries
   nothing but the entrance offset. */
.page--portal-home .portal-tour {
  position: fixed;
  /* Above the scrim it is drawn on. */
  z-index: 210;
  /* Same reasoning as the scrim: .sp-tour transitions visibility, so the closed
     bubble stays hittable for the length of its fade without this. */
  pointer-events: none;
  max-width: min(var(--space-320), calc(100vw - var(--space-32)));
  transform: translateY(var(--space-4));
}

.page--portal-home .portal-tour[data-placement] {
  right: auto;
  bottom: auto;
  left: var(--tour-left, 0px);
  top: var(--tour-top, 0px);
}

.page--portal-home .portal-tour[data-open="true"] {
  transform: translateY(0);
  pointer-events: auto;
}

/* The arrow points at the target from whichever side the bubble ended up on.
   Part A draws top and bottom; the two horizontal placements are the same
   rotated square with the other pair of borders removed. */
.page--portal-home .portal-tour[data-placement="bottom"] .sp-tour__arrow,
.page--portal-home .portal-tour[data-placement="top"] .sp-tour__arrow {
  left: var(--tour-arrow-x, 50%);
}

.page--portal-home .portal-tour[data-placement="right"] .sp-tour__arrow,
.page--portal-home .portal-tour[data-placement="left"] .sp-tour__arrow {
  left: auto;
  top: var(--tour-arrow-y, 50%);
  transform: translateY(-50%) rotate(45deg);
}

.page--portal-home .portal-tour[data-placement="right"] .sp-tour__arrow {
  inset-inline-start: calc(var(--tour-arrow) / -2);
  border-inline-end: 0;
  border-block-start: 0;
}

.page--portal-home .portal-tour[data-placement="left"] .sp-tour__arrow {
  inset-inline-end: calc(var(--tour-arrow) / -2);
  border-inline-start: 0;
  border-block-end: 0;
}

/* Docked: the fallback for a viewport with no room beside the target on any
   side. The bubble takes the foot of the screen, clear of the home indicator,
   and the script scrolls the target into the space above it. A phone gets this
   for every step, which is the right answer - a 320px bubble chasing a target
   around a 375px screen is worse than a sheet that never moves. */
.page--portal-home .portal-tour[data-placement="dock"] {
  left: var(--space-16);
  right: var(--space-16);
  top: auto;
  bottom: max(var(--space-16), env(safe-area-inset-bottom));
  /* Both insets AND a width is over-constrained, and a box that is over-
     constrained drops `right` - which is how the sheet's own width: max-content
     escaped the screen and laid the step's sentence out on one 900px line.
     Width comes from the two insets here, so max-content has nothing to say. */
  width: auto;
  max-width: none;
}

.page--portal-home .portal-tour[data-placement="dock"] .sp-tour__arrow { display: none; }

/* The room the sheet gives back to the page, so the last card on it can still
   be scrolled clear. The length is written by the script from the sheet's own
   measured height - the sheet is a component whose height is its content, so
   there is no constant here to keep in step with it. */
.page--portal-home.home-tour-open .portal-content {
  padding-block-end: var(--home-tour-dock, 0px);
}

/* The step counter is .sp-stepper, unmodified, in the footer slot Part A
   already leaves free - the tutorial popup in the design system reaches for
   the same component for the same reason: a tutorial is a sequence with a
   known length, which is the thing the stepper exists to say. The words are in
   .sp-tour__eyebrow inside the live region, so the marks are decoration and
   are hidden from assistive technology rather than read out twice. */
.page--portal-home .portal-tour__stepper {
  --stepper-gap: var(--space-6);

  flex: 0 0 auto;
}

.page--portal-home .portal-tour__body { display: block; }

/* Part A stacks .sp-tour__actions into a column below 520px, and it is right
   to: a 320px bubble hanging off a control has no room for three controls on
   one line. A docked sheet is not that bubble - it already spans the screen -
   and three stacked full-width buttons make the tutorial taller than the thing
   it is explaining. So the dock keeps its row back, and only the dock's. */
@media (max-width: 32.5rem) {
  .page--portal-home .portal-tour[data-placement="dock"] .sp-tour__footer {
    flex-direction: row;
    align-items: center;
    gap: var(--space-12);
  }

  .page--portal-home .portal-tour[data-placement="dock"] .sp-tour__actions {
    flex-direction: row;
    width: auto;
  }

  .page--portal-home .portal-tour[data-placement="dock"] .sp-tour__actions
    .button:not(.button--icon-only) {
    width: auto;
  }
}

/* Below this it genuinely does not fit: five marks and three controls need
   about 270px and a 320px phone leaves 256. The controls take the row they are
   pressed on and the marks sit under them as the report they are. */
@media (max-width: 23rem) {
  .page--portal-home .portal-tour[data-placement="dock"] .sp-tour__footer {
    flex-direction: column-reverse;
    align-items: stretch;
    gap: var(--space-12);
  }

  .page--portal-home .portal-tour[data-placement="dock"] .sp-tour__actions {
    justify-content: space-between;
  }

  .page--portal-home .portal-tour[data-placement="dock"] .portal-tour__stepper {
    justify-content: center;
  }
}

/* A coarse pointer gets the documented 44px floor on the three controls, added
   as a centred hit area rather than by resizing a small button - the idiom the
   mobile specimen sets out and the playbook card already uses. */
@media (pointer: coarse) {
  .page--portal-home .portal-tour .sp-tour__actions .button {
    position: relative;
  }

  .page--portal-home .portal-tour .sp-tour__actions .button::after {
    content: "";
    position: absolute;
    left: 50%;
    top: 50%;
    width: max(100%, var(--space-44));
    height: max(100%, var(--space-44));
    transform: translate(-50%, -50%);
  }
}

/* Opacity alone, and a spot that jumps rather than travels. The entrance
   offset and the geometry transition are the parts that have to go; the
   global blanket only shortens a duration, which snaps mid-flight instead. */
@media (prefers-reduced-motion: reduce) {
  .page--portal-home .portal-tour,
  .page--portal-home .portal-tour[data-open="true"] {
    transform: none;
  }

  .page--portal-home .portal-spotlight { transition: none; }
}


/* ==========================================================================
   09 - Unique composition · Portal Welcome & Sign In · the game field

   The front door's ground is the library itself: rows of the same card
   artwork the portal already ships, drifting slowly in alternating
   directions behind the auth card. It is the one screen where a player has
   not seen a single game yet, so the backdrop is the argument for signing in.

   Three things keep it a backdrop rather than a distraction. It is slow -
   the fastest row takes a minute and a half to cross - so nothing sweeps
   past while somebody is reading a label. It is scrimmed hard enough that the
   artwork reads as texture and colour rather than as fifty things competing
   for the eye. And it never moves under the card: the vignette is darkest in
   the middle, where the form is.

   The rows are built by the script from the game catalogue, so this section
   carries no artwork of its own and adding a game to the library adds it to
   the field. Each track holds its set twice and travels exactly half its own
   width, which is what makes the loop seamless: at -50% the second copy is
   standing where the first began.
   ========================================================================== */

.page--portal-welcome .welcome-field {
  /* The card's own proportion, not a square. The artwork is 380x210, so
     cropping it to a square cut the middle out of every tile and the field
     stopped reading as game cards at all - it read as coloured blocks. At the
     art's own ratio nothing is cropped and the row looks like a shelf. */
  --field-card: var(--space-192);
  --field-row: calc(var(--field-card) * 21 / 38);
  --field-gap: var(--space-16);
  --field-tilt: -3deg;
  /* The tilt costs coverage at the corners; the scale buys it back. */
  --field-scale: 1.18;

  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  /* Decorative: the catalogue is not content the reader is missing here, and
     the rows would otherwise read as forty unlabelled images. */
  contain: layout paint;
}

.page--portal-welcome .welcome-field__stage {
  position: absolute;
  inset: 0;
  display: grid;
  /* Rows are the card's own height and the script decides how many there
     are, so the field covers whatever height it is given without the card
     size having to follow the viewport. Dividing the height into equal rows
     instead made a tall screen draw four 250px cards and stopped the field
     saying "a lot of games", which is its whole job. */
  align-content: center;
  grid-auto-rows: var(--field-row);
  gap: var(--field-gap);
  transform: rotate(var(--field-tilt)) scale(var(--field-scale));
  transform-origin: center;
}

.page--portal-welcome .welcome-field__row {
  overflow: hidden;
  height: var(--field-row);
}

.page--portal-welcome .welcome-field__track {
  display: flex;
  width: max-content;
  gap: var(--field-gap);
  /* The row's own numbers, written by the script: no two rows share a
     duration, and the negative delay starts each one part-way through its
     own cycle so the columns never line up. */
  animation: welcome-drift var(--row-duration, 110s) linear infinite;
  animation-delay: var(--row-delay, 0s);
  will-change: transform;
}

.page--portal-welcome .welcome-field__row--reverse .welcome-field__track {
  animation-direction: reverse;
}

.page--portal-welcome .welcome-field__card {
  width: var(--field-card);
  height: var(--field-row);
  flex: 0 0 auto;
  overflow: hidden;
  border-radius: var(--radius-2xl);
  background: var(--surface-raised);
}

.page--portal-welcome .welcome-field__card img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Half its own width, because the track carries its set twice. Translating a
   percentage of the element rather than a length is what lets one keyframe
   serve every row whatever its card count. */
@keyframes welcome-drift {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}

/* --- the scrim ------------------------------------------------------------
   Two layers doing two jobs. The vignette is darkest at the centre, directly
   under the card, and lifts towards the edges where the artwork is allowed to
   show. The wash over it is flat and is what decides how loud the field is at
   all. Both are stated per ground: this screen carries the theme toggle in its
   own bar, so it has to answer in either. */
.page--portal-welcome .welcome-field::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    /* The chrome bands. The bar and the foot are the only text on this screen
       not standing on an opaque surface of its own, and the vignette is at its
       weakest exactly where they sit - so a third layer puts the darkness back
       at the top and bottom edges. It stops well short of the card, which the
       radial below is already handling. */
    linear-gradient(
      to bottom,
      var(--field-chrome) 0,
      transparent var(--space-144),
      transparent calc(100% - var(--space-112)),
      var(--field-chrome) 100%
    ),
    radial-gradient(
      ellipse 55% 52% at 50% 50%,
      var(--field-core) 0%,
      var(--field-mid) 58%,
      var(--field-edge) 100%
    ),
    var(--field-wash);
}

.page--portal-welcome {
  /* The two layers compound, so these are lower than they look: the wash
     multiplies the vignette, and 88 over 22 left the middle of the screen at
     91% navy with the rows behind it invisible. 75 over 12 reads as 78% at the
     core and 32% at the corners, which is artwork you can name under a scrim
     the card still wins against. */
  --field-core: color-mix(in srgb, var(--deep-navy-950) 82%, transparent);
  --field-mid: color-mix(in srgb, var(--deep-navy-950) 66%, transparent);
  --field-edge: color-mix(in srgb, var(--deep-navy-950) 46%, transparent);
  --field-wash: color-mix(in srgb, var(--deep-navy-950) 30%, transparent);
  --field-chrome: color-mix(in srgb, var(--deep-navy-950) 78%, transparent);
}

html:is([data-theme="light"], [data-surface="light"]) .page--portal-welcome {
  --field-core: color-mix(in srgb, var(--geistface-grey-25) 86%, transparent);
  --field-mid: color-mix(in srgb, var(--geistface-grey-25) 70%, transparent);
  --field-edge: color-mix(in srgb, var(--geistface-grey-25) 52%, transparent);
  --field-wash: color-mix(in srgb, var(--geistface-grey-25) 34%, transparent);
  --field-chrome: color-mix(in srgb, var(--geistface-grey-25) 84%, transparent);
}

/* The chrome and the card stand on their own layer. Without this the fixed
   field paints over the bar, which is the one piece of this screen that is
   not sitting on an opaque surface of its own. */
.page--portal-welcome .welcome-bar,
.page--portal-welcome .welcome-main,
.page--portal-welcome .portal-foot {
  position: relative;
  z-index: 1;
}

/* <main> carries an opaque --page ground on this screen (the rule near the top
   of this section), which is correct when the ground is all there is and is
   what hid the field: everything between the bar and the foot was painted over
   with flat navy, so the only rows that ever showed were the two bleeding past
   the chrome. The field is the ground now, and it is behind the card either
   way, so main hands the colour back. */
.page--portal-welcome .welcome-main {
  background: transparent;
}

/* --- responsive -----------------------------------------------------------
   Fewer rows and larger artwork as the screen narrows. A phone showing five
   rows of 160px cards reads as noise; three rows of 132px read as a library,
   and the card in front of them still has the screen. */
@media (max-width: 64rem) {
  .page--portal-welcome .welcome-field { --field-card: var(--space-176); }
}

@media (max-width: 40rem) {
  .page--portal-welcome .welcome-field {
    /* Larger on a phone, not smaller: fewer rows of bigger artwork reads as
       a library, where the same screen tiled with small cards reads as noise. */
    --field-card: var(--space-160);
    --field-gap: var(--space-12);
    --field-tilt: -4deg;
    --field-scale: 1.24;
  }

  /* The form is most of a phone screen, so the scrim under it goes further. */
  .page--portal-welcome {
    --field-core: color-mix(in srgb, var(--deep-navy-950) 84%, transparent);
  }
}

/* --- reduced motion -------------------------------------------------------
   The same composition, standing still. The rows keep their offsets - the
   script writes each one as a static translate as well as a delay - so what a
   reader sees is the layered field the animation would have been passing
   through, not five tracks all parked at nought. */
@media (prefers-reduced-motion: reduce) {
  .page--portal-welcome .welcome-field__track {
    animation: none;
    transform: translate3d(var(--row-rest, 0%), 0, 0);
    will-change: auto;
  }
}


/* ==========================================================================
   09 - Unique composition · Games Portal · the card's actions and its skills

   Three changes to one card, all of them about hierarchy. The foot used to
   hold a duration, a Details button and a Play button in one row, so the card
   ended on two competing controls and a fact. Now: the way to the game's own
   page is a mark beside the title, the duration sits with the description it
   qualifies, and Play is the whole width of the card - one action, unmissable,
   in the place the eye ends up.

   Nothing here is a new component. The mark is .sp-icon at the frame size the
   icon ladder already pairs with a small box, the pill is the Skills page's
   own .skill-game treatment restated for a name rather than a link, and the
   preview is the shared .game-peek with two slots dropped.
   ========================================================================== */

/* --- the details mark, beside the title ---------------------------------- */

/* A link on the library's cards and a button on Skill Progression's, because
   that page opens the same details in a modal. One rule covers both: the
   button carries its own reset, since .icon-button is a 40px control and this
   sits inline with a heading. */
.page--portal .game-info {
  width: var(--space-24);
  height: var(--space-24);
  flex: 0 0 var(--space-24);
  padding: 0;
  display: inline-grid;
  place-items: center;
  appearance: none;
  border: 0;
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--surface-text-muted);
  cursor: pointer;
  transition: var(--transition-interactive);
}

.page--portal .game-info .sp-icon {
  --icon-size: var(--icon-size-xs);
  --icon-stroke: var(--icon-stroke-16);
}

.page--portal .game-info:hover {
  background: var(--surface-raised);
  color: var(--surface-text);
}

.page--portal .game-info:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-default);
  outline-offset: var(--space-2);
}

/* Above the card's own sheet.

   [data-card-link]::after lays a transparent sheet over the whole card at
   z-index 1 so anywhere on it opens the game. The info mark sits inside that
   area and had no layer of its own, so every click aimed at it went through to
   the sheet and started the game instead - the one thing this control must
   never do. It is the same reason the foot's Play button is raised, stated for
   the mark beside the title.

   position is what makes z-index apply at all; the pair has to be written
   together or the property is inert. */
.page--portal .game-info,
.page--portal .play-btn,
.page--portal .trait-skill {
  position: relative;
  z-index: 2;
}

/* The best-score figure keeps the trailing edge now that the head is a plain
   left-aligned row rather than a justified one. */
.page--portal .game-head :is(.game-head__best, .stat-game__score) {
  margin-inline-start: auto;
}

/* The title no longer shares a justified row with the mark, so it takes the
   space it needs and the mark sits against it. */
.page--portal .game-head h3 {
  min-width: 0;
}

/* --- the skills, as pills ------------------------------------------------ */

/* The Skills page's pill, restated. These are names rather than links - the
   card's own facts - so the resting state is the quieter panel surface, and
   the pillar tint only arrives on hover the way it does there. */
.page--portal .trait-skill {
  min-height: var(--space-24);
  padding: var(--space-2) var(--space-10);
  display: inline-flex;
  align-items: center;
  gap: var(--space-4);
  appearance: none;
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-full);
  background: var(--surface-raised);
  color: var(--surface-text);
  font-family: var(--skillprint-font-ui);
  font-size: var(--font-size-xs);
  line-height: var(--line-height-sm);
  font-weight: var(--weight-medium);
  text-align: left;
  transition: var(--transition-interactive);
}

button.trait-skill {
  cursor: pointer;
}

/* The mark is quiet until it is asked about. At rest it takes the same muted
   ink the rest of the card's supporting text uses, so a row of five pills
   reads as five names rather than as five colours competing under a heading -
   which was the original objection to putting glyphs here at all. The colour
   is what the pointer buys, and it is the answer to "which dimension is this",
   one skill at a time. */
.page--portal .trait-skill__icon {
  --icon-stroke: var(--icon-stroke-xs);
  flex: 0 0 auto;
  color: var(--surface-text-muted);
}

.page--portal .trait-skill__name {
  min-width: 0;
}

/* Hover, press and the open peek all show the skill's own dimension. The
   colour is not chosen here - data-dimension on the pill resolves
   --skill-accent and --skill-ink-hover through the same mapping the meters,
   the skill cards and the progression chips read, so a skill cannot end up
   one colour on a card and another on its own page.

   --skill-ink-hover rather than the 500: the three 500s are drawn for a dark
   ground and measure about 1.2:1 on the portal's near-white one, which is the
   reason that token exists. The fill stays a wash of the accent so the label
   keeps the surface's own contrast rather than trying to sit on a saturated
   ground. */
button.trait-skill:hover,
button.trait-skill.is-peeking,
button.trait-skill:focus-visible {
  border-color: color-mix(in srgb, var(--skill-accent, var(--surface-text)) 46%, var(--surface-border));
  background: color-mix(in srgb, var(--skill-accent, var(--surface-text)) 12%, var(--surface-box));
  color: var(--surface-text);
}

/* --skill-ink, not the hover step. The sheet states where each step of the
   ramp is allowed to go a few hundred lines up: --skill-accent is the mid step
   and is only ever a large area, --skill-ink is the step far enough down to be
   read as a shape and "takes everything small or line-like: glyphs, chevrons,
   accent borders, link text". A 12px glyph is exactly that. Both grounds are
   already answered - the dark declaration puts --skill-ink at the 500s, the
   light one at the 900s - so this needs no theme of its own. */
button.trait-skill:hover .trait-skill__icon,
button.trait-skill.is-peeking .trait-skill__icon,
button.trait-skill:focus-visible .trait-skill__icon {
  color: var(--skill-ink, currentColor);
}

button.trait-skill:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-default);
  outline-offset: var(--space-2);
}

/* The row gap has to clear the pill's own border now that these are boxes
   rather than text with a glyph. */
.page--portal .trait-skills {
  column-gap: var(--space-6);
  row-gap: var(--space-6);
}

/* --- Play, full width ----------------------------------------------------- */

/* .full-width and .push-block-end carry the geometry; this only stops the
   button's default centring from leaving the label off-centre once the icon
   is in the same box. */
.page--portal .game-card--portal .play-btn {
  justify-content: center;
}

/* --- the preview, in its skill form -------------------------------------- */

/* Two slots go dark: a skill has no artwork and no running time. The head
   stays, because it is what holds the title. */
.page--portal .game-peek[data-kind="skill"] .game-peek__art {
  display: none;
}

.page--portal .game-peek[data-kind="skill"] .game-peek__foot {
  justify-content: flex-end;
}

/* The card is normally decoration: the trigger names the thing and the card
   only pictures it, so it fades in and out with `visibility` on the
   transition. Opened deliberately - a tap, Enter, Space - it becomes the only
   place the description and the way onward exist, and focus has to land in it.
   Focus cannot land on anything visibility:hidden, and a transitioned
   `visibility` still computes to `hidden` on the frame the change is made, so
   the property is taken off the transition here rather than waited on. Doing
   it in the sheet keeps the timing where the rest of the card's timing lives,
   and works in a backgrounded tab, where waiting a frame never returns. */
.page--portal .game-peek.is-interactive {
  visibility: visible;
  transition-property: opacity, transform;
}


/* ==========================================================================
   07 - Shared component · the compact skill filter

   One row: the label, an All Skills reset, and one dropdown per dimension.
   It replaces a panel that printed the whole 28-skill taxonomy as chips in
   three labelled rows above the thing it filtered - on the library that was
   about 9,700 characters of markup and roughly a third of the first screen,
   spent restating a vocabulary the reader already has.

   Nothing here is a new component. The reset is .ui-tag with its own pressed
   state, each dimension is the shared .sp-dropdown with a .button trigger, and
   the rows inside are .sp-dropdown__item. Only the row that holds them is new.
   ========================================================================== */

.page--portal .skill-filter {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-8);
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
}

/* The label names the row and then gets out of the way; it is the same eyebrow
   the section bars use, so it does not compete with the controls beside it. */
.page--portal .skill-filter__label {
  margin-inline-end: var(--space-4);
}

/* Each dimension is as wide as its content rather than the .sp-dropdown
   default of 100%, which is what puts four controls on one line. */
.page--portal .skill-filter__dim {
  position: relative;
  width: auto;
  flex: 0 0 auto;
}

.page--portal .skill-filter__dim .sp-dropdown__trigger {
  width: auto;
  gap: var(--space-6);
  white-space: nowrap;
}

/* A dimension holding a skill reads as chosen the way the reset pill does when
   it is pressed - the border and ink carry it, not a weight change. */
.page--portal .skill-filter__dim.is-set .sp-dropdown__trigger {
  border-color: var(--mindset-violet-500);
  color: var(--surface-text);
}

/* The menu is measured from its own trigger, so it opens under the dimension
   it belongs to rather than at the row's leading edge. */
.page--portal .skill-filter__menu {
  left: 0;
  width: max(100%, var(--space-224));
  /* Never wider than the window it opens in. The floor above is a readable
     line length, not a promise the screen can afford it. */
  max-width: calc(100vw - var(--space-32));
  max-height: min(60vh, var(--space-320));
}

.page--portal .skill-filter__menu .sp-dropdown__item {
  gap: var(--space-8);
}

.page--portal .skill-filter__item-label {
  min-width: 0;
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The dot that says a skill has been played. It is the same .progression-key
   the legend draws, so the row and the key beside it cannot disagree. */
.page--portal .skill-filter__menu .progression-key {
  flex: 0 0 var(--space-6);
}

/* The legend belongs to the dot, so it sits at the trailing end of the row and
   drops to its own line before the controls do. */
.page--portal .skill-filter__legend {
  margin-inline-start: auto;
}

/* --- narrow screens ------------------------------------------------------
   The row keeps its structure and wraps. It does not become a scroller: four
   controls is a short row, and a horizontal scroller holding the only way to
   filter is a worse trade than a second line. The legend goes first, since it
   explains a mark rather than offering an action. */
@media (max-width: 719px) {
  .page--portal .skill-filter__legend {
    order: 1;
    flex-basis: 100%;
    margin-inline-start: 0;
  }

  /* One dimension per line. Three triggers sharing a 390px row leaves each
     about 110px, and a menu with a 224px floor then opens wider than the
     control it belongs to and off the side of the screen. A full-width trigger
     is also the easier target, and the row keeps its parts and their order -
     label, reset, then the three dimensions - which is what has to survive. */
  .page--portal .skill-filter__dim {
    flex: 1 1 100%;
  }

  .page--portal .skill-filter__dim .sp-dropdown__trigger {
    width: 100%;
  }

  .page--portal .skill-filter__menu {
    width: 100%;
  }
}


/* ==========================================================================
   09 - Unique composition · Games Portal · the card's vertical rhythm

   One rhythm for the card body, stated once, inherited by every surface that
   draws a card - the library's static tiles, Home's three shelves, Game
   Detail's More like this, Skill Detail, Skills and the design system's own
   embed. It replaces a set of per-element margins that had accumulated one at
   a time: the description carried 6px above and 14px below, the duration and
   the skills group carried none at all, and the result was a card whose
   sections were spaced by whatever had been added last. "Skills developed"
   sat flush against the duration because nothing had ever put a number
   between them.

   The body is a flex column, so a gap is the honest way to say it. The
   elements hand their own margins back.
   ========================================================================== */

.page--design-system .portal-library-embed .game-body,
.page--portal .game-body {
  gap: var(--space-10);
}

.page--design-system .portal-library-embed .game-description,
.page--portal .game-description {
  margin: 0;
  /* Three lines, floor and ceiling. The floor was already here; without the
     ceiling a four-line blurb pushed the duration, the skills and the label
     down by a line, so two cards standing side by side in the same rail put
     their "Skills developed" at different heights. The whole string stays in
     the DOM - this clips the box, it does not shorten the text - so a screen
     reader and the game's own page still carry all of it. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
}

/* Reading facts, one after the other: what it is, then how long it takes. */
.page--design-system .portal-library-embed .duration,
.page--portal .duration {
  margin: 0;
}

/* The skills are a different kind of statement from the two lines above them -
   what the game measures rather than what it is - so the section takes a step
   more air than the body's own rhythm gives it. */
.page--design-system .portal-library-embed .trait-group,
.page--portal .trait-group {
  margin-block-start: var(--space-6);
  gap: var(--space-6);
}

/* Room for two full rows of pills, kept whether or not this card needs them.
   A rail whose cards each reserve the same height stops re-flowing as the eye
   moves along it, and the reservation is a floor rather than a cap: a card
   with enough skills to run to a third row grows into it and is never clipped.
   Two rows is the honest number - it is what five pills take at the narrowest
   card width the grid allows. */
.page--design-system .portal-library-embed .trait-skills,
.page--portal .trait-skills {
  min-height: calc(var(--space-24) * 2 + var(--space-6));
  align-content: flex-start;
}

/* The action stays on the floor of the card however tall the block above it
   turns out to be, which is what keeps a row of Play buttons on one line. */
.page--design-system .portal-library-embed .play-btn,
.page--portal .play-btn {
  margin-block-start: auto;
}

/* The other horizontal scroller in the portal. A scrolling table's bar lands
   hard against whatever follows it — on Skill Progression that is the stat
   table's footer and its separator line, measured at 0px between the two, so
   the bar read as the footer's top border rather than as the table's control.
   Same rule as the rails above: the bar stays with the thing it scrolls, and
   the gap goes underneath it. Scoped to the portal because .table-scroll is
   also a documentation component, where it sits in a different rhythm. */
.page--portal .table-scroll {
  margin-block-end: var(--space-16);
}


/* ==========================================================================
   07 - Shared repairs · the button group's icon never took its own size

   .button-group__icon asks for --icon-size-sm through `width` and `height`.
   .sp-icon sets the same two properties from `var(--icon-size, --icon-size-md)`
   and is declared about 2,800 lines later at the same specificity, so it wins
   on source order and every icon in a button group has been drawing at the
   24px --md anchor rather than the 20px the component asked for. The theme
   switcher on Settings is where it shows: three 24px glyphs in a 38px control
   beside 20px glyphs in every button next to it.

   The fix is the mechanism .sp-icon actually reads. Setting --icon-size is how
   every other size modifier in the sheet does it, it carries the matching
   stroke weight with it, and it leaves the Part A rule untouched.
   ========================================================================== */

.button-group__icon {
  --icon-size: var(--icon-size-sm);
  --icon-stroke: var(--icon-stroke-20);
}


/* ==========================================================================
   09 - Unique composition · Portal Settings · issued credentials

   A User ID and an API Key are not settings. They are issued, they are read,
   and they are copied - so the pair is presented as a value with a way to take
   it, not as a form with no way to submit. The fields kept a text input's full
   affordance, a caret and a placeholder reading "Enter User ID", while the
   section carried no Save, no validation and no auto-save: everything about it
   invited an edit that could not be made and would not have been kept.
   ========================================================================== */

:is(.page--portal-profile, .page--portal-settings) .pp-credential {
  display: grid;
  gap: var(--space-6);
}

/* The value and its copy control are one object. */
:is(.page--portal-profile, .page--portal-settings) .pp-credential__row {
  display: flex;
  /* `center` centred the controls against the whole .field - label *and* input,
     72px of it - which put every button 13px above the centre of the input it
     acts on. The input is the last thing in the field, so aligning the ends is
     what puts a button beside its own value. */
  align-items: end;
  gap: var(--space-8);
}

/* ...and the three controls in these rows are three different heights: a text
   button is 42, an icon-only button 40, the input 46. Aligned at the end that
   reads as a stepped edge, so the buttons take the field's own height and the
   row closes on one line. */
:is(.page--portal-profile, .page--portal-settings) .pp-credential__row .button {
  --button-height: var(--space-44);
}

:is(.page--portal-profile, .page--portal-settings) .pp-credential__row .field {
  flex: 1 1 auto;
  min-width: 0;
}

/* Mono, because every one of these is a token read character by character. The
   read-only treatment itself is Part A's - .field input[readonly] already
   states the quieter ground and the default cursor. */
:is(.page--portal-profile, .page--portal-settings) .pp-credential input {
  font-family: var(--skillprint-font-mono);
  font-size: var(--font-size-sm);
  letter-spacing: 0.01em;
}

:is(.page--portal-profile, .page--portal-settings) .pp-credential__note {
  margin: 0;
  color: var(--ui-muted);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
}

/* The skill mark on the preview card. The pill it opened from carries the
   dimension's colour on hover; the card is that same answer held still, so the
   mark takes --skill-ink for the reason the pill's glyph does - the step of the
   ramp the sheet reserves for anything small or line-like. --skill-ink is
   already answered on both grounds, so this needs no theme of its own.

   The game peek keeps its artwork square and hides this instead: a game is
   identified by its picture, a skill by its mark. */
.page--portal .game-peek__mark {
  flex: 0 0 auto;
  display: inline-grid;
  place-items: center;
  color: var(--skill-ink, var(--surface-text));
}

.page--portal .game-peek__mark[hidden] {
  display: none;
}


/* ==========================================================================
   Mobile density pass

   Measured across the portal at 375, not eyeballed. Two faults kept turning
   up, and they are opposite ones:

   - Shared empty states carried desktop insets onto a phone. .portal-blank sat
     at 28/24 and .empty-state at 32 on every side, on screens 343px wide - a
     third of the width spent framing a sentence and a button.
   - The chart frame had almost no horizontal inset at all: 8px, which put the
     outermost spoke labels of the orbit wheel a few pixels off the card edge
     and read as clipped rather than as centred.

   Deliberately last in the file. Everything here overrides a rule written
   earlier at the same specificity, and a media query buys none - which is the
   trap that already cost this stylesheet a silent no-op earlier today. Kept
   as one block so the mobile inset story is legible in one place instead of
   scattered through nine page sections.

   Not a blanket squeeze: cards that are already compact are untouched, and
   nothing here goes below 12px, which is the floor at which a border stops
   reading as a frame and starts reading as a mistake. */
@media (max-width: 640px) {
  /* The empty state is a sentence and a control. It does not need a desktop
     card's frame to say so. */
  .page--portal .portal-blank {
    padding: var(--space-20) var(--space-16);
  }

  .page--portal .empty-state {
    padding: var(--space-20);
  }

  /* The orbit readout is the one child of .chart-card that never got an inset.
     The card itself is deliberately 0 - its children each carry their own - so
     a child at 0 puts its text flush against the card's own border while the
     head above it sits 20 in. It takes its siblings' step. */
  .page--portal-profile .pp-orbit-detail {
    padding-inline: var(--space-16);
    padding-block-end: var(--space-16);
  }

  /* The other direction: give the chart's outermost labels somewhere to sit.
     12, not more - every pixel here comes off the drawing itself, which is the
     thing the card exists to show. */
  .page--portal .chart-frame {
    padding-inline: var(--space-12);
  }

  /* One card step across the portal. This one was still at the desktop 24 while
     its neighbours had come down to 20 - and it sets `padding` outright rather
     than through --card-current-padding, so the token had nothing to bite on.
     The min-height comes down with it: 224px was sized around the larger inset
     and left the card half empty once the inset shrank. */
  :is(.page--portal-profile, .page--portal-settings) .pp-goal {
    min-height: var(--space-192);
    padding: var(--space-20);
  }
}

/* ----------------------------------------------------------------------------
   44 · Marketing site · product page heroes

   One hero shared by the four product pages, in four colourways. The page, the
   menu and every section below stay Deep Navy; the hero alone carries the
   product colour, so the colour reads as that product's own rather than as a
   repaint of the whole site.

   Each page class supplies its family through --hero-*, and everything here is
   written once against those. --hero-texture carries the whole background-image
   value rather than just a file name, because Players draws its rings in CSS
   instead of from a tile - see 44.2.
   -------------------------------------------------------------------------- */

/* The ambient wash (37.3) is off on these pages: the hero owns the colour and
   a drifting spotlight underneath it muddies the flat ground. */

.page--hero-overlay { background-image: none; }

.page--site :is(.hero--product, .hero--full)::before,
.page--site :is(.hero--product, .hero--full)::after { display: none; content: none; }

/* Full screen. .page--homepage .nav-inner is already 76px, and the menu is
   fixed (44.3), so the hero takes the whole viewport and pads itself clear of
   the bar rather than sitting under it. */

.page--site .hero--product {
  position: relative;
  min-height: 100svh;
  padding-block-start: 77px;
  display: grid;
  align-items: center;
  text-align: start;
  background-color: var(--hero-950);

  /* Two layers: the texture, and above it a fade from the hero's colour into
     the page colour across the last third, so the panel resolves into the
     section below instead of ending on an edge. --surface-canvas is the page
     background in both themes, so the fade lands on the right colour either
     way. Per-layer repeat and size, or the fade would tile with the texture. */
  background-image:
    linear-gradient(to bottom, transparent 78%, var(--surface-canvas) 100%),
    var(--hero-texture);
  background-repeat: no-repeat, repeat;
  background-size: auto, var(--hero-texture-size, 120px 120px);

  /* The stock greys and the Deep Navy panel stack both go muddy on a coloured
     ground. These are the derived tokens on purpose: --ui-surface is declared
     on .page as var(--surface-card, var(--surface-box, ...)) and resolves
     there, so setting --surface-box here would change nothing. */
  --ui-muted             : color-mix(in srgb, var(--hero-100) 74%, transparent);
  --positive-text        : var(--hero-400);
  --ui-border            : color-mix(in srgb, var(--hero-300) 22%, transparent);
  --surface-panel        : color-mix(in srgb, var(--hero-900) 18%, var(--hero-950));
  --ui-surface           : color-mix(in srgb, var(--hero-900) 32%, var(--hero-950));
  --ui-surface-raised    : color-mix(in srgb, var(--hero-900) 46%, var(--hero-950));

  /* .button--secondary reads --button-surface, NOT --button-secondary, and
     that one is a literal --panel-navy-900 in :root. */
  --button-surface       : color-mix(in srgb, var(--hero-900) 32%, var(--hero-950));
  --button-surface-hover : color-mix(in srgb, var(--hero-900) 46%, var(--hero-950));
}

/* Copy left, art right. Undoes 37.4's centring in all three places it applies. */

.page--site .hero--product .hero-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
  gap: var(--space-64);
  width: 100%;
}

.page--site .hero--product .hero-copy {
  max-width: none;
  margin-inline: 0;
  text-align: start;
}

.page--site .hero--product .hero-actions { justify-content: flex-start; }

/* .hero-note is a flex row, so a note containing a link - Flow's and Signal's
   both do - turns the sentence into separate flex items laid out side by side
   instead of wrapping as text. Put it back to inline flow. */

.page--site .hero--product .hero-note {
  display: block;
  margin-inline: 0;
}

.page--site .hero--product .hero-note > i {
  display: inline-block;
  vertical-align: middle;
  margin-inline-end: var(--space-8);
}

/* Recoleta on the h1. Only Regular ships, so presence comes from size and
   tracking, never from weight. */

.page--site .hero--product h1 {
  font-family: var(--skillprint-font-display);
  font-weight: 400;
  font-size: clamp(40px, 5.2vw, 68px);
  line-height: 1.06;
  letter-spacing: -0.015em;
  color: var(--hero-500);
}

.page--site .hero--product h1 .text-positive { color: var(--hero-300); }

.page--site .hero--product .hero-art { display: grid; place-items: center; min-width: 0; width: 100%; }

.page--site .hero--product .hero-art .media-frame { width: 100%; }

.page--site .hero--product .hero-art img { width: 100%; height: auto; display: block; }

/* ----------------------------------------------------------------------------
   44.1 · The four colourways

   Only the family changes. Three pages take a 120px tile on a 24px rhythm;
   Players takes the rings in 44.2.
   -------------------------------------------------------------------------- */

.page--amplify .hero--product {
  --hero-100: var(--personality-mint-100);
  --hero-300: var(--personality-mint-300);
  --hero-400: var(--personality-mint-400);
  --hero-500: var(--personality-mint-500);
  --hero-900: var(--personality-mint-900);
  --hero-950: var(--personality-mint-950);
}

.page--enterprise .hero--product {
  --hero-100: var(--personality-blue-100);
  --hero-300: var(--personality-blue-300);
  --hero-400: var(--personality-blue-400);
  --hero-500: var(--personality-blue-500);
  --hero-900: var(--personality-blue-900);
  --hero-950: var(--personality-blue-950);
  --hero-texture: url("../assets/images/website/texture-blue-squares.svg");
}

.page--flow .hero--product {
  --hero-100: var(--core-lime-100);
  --hero-300: var(--core-lime-300);
  --hero-400: var(--core-lime-400);
  --hero-500: var(--core-lime-500);
  --hero-900: var(--core-lime-900);
  --hero-950: var(--core-lime-950);
  --hero-texture: url("../assets/images/website/texture-lime-waves.svg");
}

.page--signal .hero--product {
  --hero-100: var(--skills-orange-100);
  --hero-300: var(--skills-orange-300);
  --hero-400: var(--skills-orange-400);
  --hero-500: var(--skills-orange-500);
  --hero-900: var(--skills-orange-900);
  --hero-950: var(--skills-orange-950);
  --hero-texture: url("../assets/images/website/texture-orange-dots.svg");
}

/* ----------------------------------------------------------------------------
   44.2 · Players' rings

   One set of concentric rings with a single centre, expanding outward across
   the hero at any width - not a repeating cluster. Hard stops keep them crisp
   rings rather than a wash, on the same 24px rhythm as the three tiles, and
   because it is CSS both colours stay tokens instead of hex baked into an SVG.
   background-size must be auto here or the gradient would itself be tiled.
   -------------------------------------------------------------------------- */

.page--amplify .hero--product {
  --hero-texture: repeating-radial-gradient(circle at 50% 50%,
    var(--hero-950) 0 22.4px,
    color-mix(in srgb, var(--hero-900) 20%, var(--hero-950)) 22.4px 24px);
  --hero-texture-size: auto;
}

/* ----------------------------------------------------------------------------
   44.3 · The menu over the hero, and the way out of it

   The menu starts transparent on the hero and takes its dark state from the
   .scrolled class skillprint.js already toggles, so this adds no script. It is
   fixed from the first paint, so the state change moves nothing.
   -------------------------------------------------------------------------- */

.page--hero-overlay .sp-nav {
  position: fixed;
  inset-inline: 0;
  inset-block-start: 0;
  z-index: 100;
  background: transparent;
  border-bottom-color: transparent;
  -webkit-backdrop-filter: none;
          backdrop-filter: none;
}

@media (prefers-reduced-motion: no-preference) {
  .page--hero-overlay .sp-nav {
    transition: background-color var(--duration-base) var(--ease-standard),
                border-bottom-color var(--duration-base) var(--ease-standard),
                -webkit-backdrop-filter var(--duration-base) var(--ease-standard),
                        backdrop-filter var(--duration-base) var(--ease-standard);
  }
}

/* The way down: label over a bobbing arrow, centred on the fold. */

.page--site .hero-more {
  position: absolute;
  inset-block-end: var(--space-16);
  inset-inline: 0;
  width: fit-content;
  margin-inline: auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-6);
  padding: var(--space-6) var(--space-4);
  font-size: var(--text-xs-font-size);
  font-weight: 500;
  letter-spacing: 0.01em;
  color: var(--ui-muted);
  transition: color var(--duration-fast) var(--ease-standard);
}

.page--site .hero-more:hover { color: var(--hero-300, var(--sp-accent)); }

.page--site .hero-more span {
  font-family: var(--skillprint-font-mono);
  font-size: 14px;
  line-height: 1;
}

@media (prefers-reduced-motion: no-preference) {
  .page--site .hero-more span { animation: sp-bob 2s ease-in-out infinite; }
}

@keyframes sp-bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(5px); }
}

/* ----------------------------------------------------------------------------
   44.4 · Narrow
   -------------------------------------------------------------------------- */

@media (max-width: 1023px) {
  .page--site .hero--product {
    min-height: 0;
    padding-block: calc(77px + var(--space-40)) var(--space-64);
  }

  .page--site .hero--product .hero-split {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-40);
  }

  .page--site .hero--product .hero-art { order: 2; }

  /* Static below the fold-height hero, or it would sit on top of the copy. */
  .page--site .hero-more {
    position: static;
    margin-block-start: var(--space-40);
  }
}

/* ----------------------------------------------------------------------------
   44.5 · The homepage hero, full height

   The same treatment as the product heroes - one screen, the menu over it and
   a way down - but no texture and no colour panel. The homepage keeps the
   ambient wash and the brand ground it already has; only the height, the menu
   and the control at the bottom change.

   Room has to be found rather than assumed, because this hero carries its
   stats strip inside it. 100svh less the 77px menu leaves about 750px for a
   624px grid plus an 83px strip, which fits only once the stock 144px/80px
   block padding and the 56px gap above the strip are pulled in.
   -------------------------------------------------------------------------- */

/* The section after a full-height hero drops its rule. With the hero fading
   into the page there is nothing left to separate, and that 1px line is what
   made the two read as two pages stacked rather than one page continuing. */

.page--site :is(.hero--product, .hero--full) + .section.separator-top {
  border-top-color: transparent;
}

.page--site .hero--homepage.hero--full {
  min-height: 100svh;
  padding-block: 77px var(--space-48);
  align-content: center;
}

.page--site .hero--homepage.hero--full .stats-strip {
  margin-block-start: var(--space-20);
}

/* This hero ends in a stats strip, so the bottom padding has to reserve the
   band the control sits in - otherwise the absolutely placed control lands on
   the numbers. 48px leaves the strip ending ~20px clear of it. */

/* 1120px, not the 1023px the product heroes use: that is where the homepage
   grid drops to one column, and from there the hero is far taller than a
   screen. Holding it at 100svh past that point left a 1024-1120 band where the
   hero ran to ~1300px and the absolutely placed control landed on the stats. */

@media (max-width: 1120px) {
  .page--site .hero--homepage.hero--full {
    min-height: 0;
    padding-block: calc(77px + var(--space-40)) var(--space-64);
  }

  .page--site .hero--full .hero-more {
    position: static;
    justify-self: center;
    margin-block-start: var(--space-24);
  }
}

/* Recoleta on the homepage headline too, matching the product heroes. Font
   only - the headline keeps its own colours, including the accent span. */

.page--site .hero--homepage.hero--full h1 {
  font-family: var(--skillprint-font-display);
  font-weight: 400;
  font-size: clamp(40px, 5.2vw, 68px);
  line-height: 1.06;
  letter-spacing: -0.015em;
}

/* ----------------------------------------------------------------------------
   44.6 · The menu says where you are

   One treatment across all five: the current item is simply whiter than the
   rest. The homepage carried the base rule's violet pill, and the product
   pages showed almost nothing, so neither matched the other. Text weight and
   colour do the work - no fill, no border - which is how the product pages
   already read, and it keeps the menu quiet over a hero it now sits on top of.

   The routes selector has to outspecify the base pill at 1660, hence :is()
   round the two route containers.
   -------------------------------------------------------------------------- */

.page--site :is(.sp-nav__routes, .sp-nav__drawer-routes) .sp-nav__route[aria-current="page"],
.page--site [data-nav-menu-current="true"] .sp-nav__route--menu {
  border-color: transparent;
  background: none;
  color: var(--nav-current-text);
  font-weight: var(--weight-semibold);
}

/* ----------------------------------------------------------------------------
   44.7 · The homepage lead matches the product pages

   .hero-copy--homepage sets 20px/31px where .hero-copy is 18px/28px, so the
   homepage subtitle ran a size larger than the four product heroes.
   -------------------------------------------------------------------------- */

.page--site .hero--homepage.hero--full .hero-copy--homepage > p {
  font-size: 18px;
  line-height: 28px;
}

/* ----------------------------------------------------------------------------
   44.8 · The homepage figures

   Figure and label on one line, label to the right of the number, and no rules
   between the cells. Reading across is what the row is for; stacking each
   label under its number made five two-line blocks where one line does it.
   -------------------------------------------------------------------------- */

.page--site .hero--full .stats-strip--bare {
  padding-block-start: var(--space-20);
  gap: var(--space-24);
}

.page--site .hero--full .stats-strip--bare .stat {
  display: flex;
  align-items: baseline;
  gap: var(--space-8);
  padding-inline: 0;
  border: 0;
}

/* The figure never shrinks; the label takes what is left and wraps into it. */

.page--site .hero--full .stats-strip--bare .stat strong {
  flex: none;
  font-size: 20px;
  line-height: 26px;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.page--site .hero--full .stats-strip--bare .stat span {
  margin-top: 0;
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
}

/* ----------------------------------------------------------------------------
   44.9 · The homepage headline accent

   The same two-step the product heroes use - the headline in the family's
   500 and the accent phrase one step lighter at 400 - so the homepage is
   built from the brand green exactly as the four products are from theirs.
   -------------------------------------------------------------------------- */

.page--site .hero--homepage.hero--full h1 { color: var(--panel-navy-200); }

.page--site .hero--homepage.hero--full h1 .text-positive { color: var(--panel-navy-25); }

/* ----------------------------------------------------------------------------
   44.10 · The heroes run a little wider

   Two things bound the hero's width and which one bites depends on the screen:
   below 1440 it is the 32px gutter, above it the 1440px cap. Widening only one
   would leave half the displays unchanged, so both move - and only for the
   heroes, since the sections below keep the site's established measure.

   Set on .sp-container itself: the width and padding declarations that read
   these two tokens live on that element, so the values have to be declared
   there to be picked up.
   -------------------------------------------------------------------------- */

.page--site :is(.hero--product, .hero--full) .sp-container {
  --grid-container-max: 1560px;
  --grid-padding: var(--space-24);
}

/* ----------------------------------------------------------------------------
   44.11 · Even halves

   All five heroes split their row down the middle. The homepage grid also
   carried a 560px floor on its right column, which would have held the split
   off centre on narrower desktops, so that goes with it.
   -------------------------------------------------------------------------- */

/* Only above the width where the homepage grid collapses. Unscoped, this
   outspecifies that collapse (1120px, not the 1023px the rest of the hero
   uses) and holds the hero in two columns all the way down to a phone. */

@media (min-width: 1121px) {
  .page--site .hero--full .hero-grid--homepage {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }
}

/* ----------------------------------------------------------------------------
   45 · Polish pass

   45.1 · Section heads run on halves, so the heading starts on the same left
   edge as the content below it rather than in its own narrow column.
   -------------------------------------------------------------------------- */

.section-head--homepage { grid-template-columns: repeat(2, minmax(0, 1fr)); }

/* Humans and AI reads across two of the three cards below it. */

.page--homepage #human-ai .section-head--homepage h2 { max-width: none; }

.page--homepage #human-ai .section-head--homepage { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); }

/* 45.2 · Cards: the icon stands away from the title, the title sits close to
   the line under it. One rule per family so all comparable cards match. */

.page--homepage .dimension-card h3 { margin-block-end: var(--space-6); }

.page--site :is(.signal-tile, .surface-tile) { gap: var(--space-6); }

.page--site :is(.signal-tile, .surface-tile) > :is(.surface-tile__icon, .signal-tile__label) {
  margin-block-end: var(--space-12);
}

/* 45.3 · Six tiles read three and three, not four and a gap. */

@media (min-width: 1024px) {
  .page--site :is(.signal-matrix, .surface-grid) { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* 45.4 · The strengths tile in the hero mock was a 1.19:1 box against its own
   card - effectively invisible. Same accent-tinted recipe the page's other
   icon tiles use, so the smiley reads as a tile rather than a floating glyph. */

.page--homepage .signal-icon {
  border: 1px solid color-mix(in srgb, var(--accent) 36%, var(--border));
  background: color-mix(in srgb, var(--accent) 12%, var(--panel-900));
  border-radius: var(--radius-md);
}

/* 45.5 · How it works joins the product-hero colourway set, on the brand green
   since the page belongs to no single product. */

:is(.page--how-it-works, .page--about) .hero--product {
  --hero-100: var(--core-green-100);
  --hero-300: var(--core-green-300);
  --hero-400: var(--core-green-400);
  --hero-500: var(--core-green-500);
  --hero-900: var(--core-green-900);
  --hero-texture: none;
  --hero-texture-size: auto;

  /* No colour panel here: this hero takes the page ground, the way the
     homepage's does. The --hero-* steps above still feed the tokens the hero's
     contents read (borders, muted text, button surfaces). */
  --hero-950: var(--surface-canvas);
  background-color: var(--surface-canvas);
}

/* Panel Navy for the title, per the brief's "Pano Navy 50 / 25". */

:is(.page--how-it-works, .page--about) .hero--product h1 { color: var(--panel-navy-200); }

:is(.page--how-it-works, .page--about) .hero--product h1 .text-positive { color: var(--panel-navy-25); }

/* 45.6 · Explore-the-product cards: one icon, at the bottom, in the card's own
   accent. The top tile repeated the same glyph the button already carries. */

.page--site .route-card .button .sp-icon { color: var(--accent, var(--sp-accent)); }

/* 45.7 · The homepage hero mock, a size down

   It had no ceiling of its own and simply filled its half of the grid. Capped
   and pulled to the right edge, so the copy keeps its measure and the two
   halves read as balanced rather than as one column pushing the other.

   640 and not lower: the mock carries a two-column body, and below about
   620px its own labels start wrapping onto two and three lines, which reads
   as broken rather than as smaller.
   -------------------------------------------------------------------------- */

.page--homepage .hero-dashboard {
  max-width: var(--space-640);
  justify-self: end;
  width: 100%;
}

/* 45.8 · The homepage headline in light mode

   44.9 pins the headline to Core Green 500 and its accent to 400. Those are
   the right steps on the dark ground they were chosen for, but the homepage
   hero takes the PAGE background, which flips - and 500 on #f8f9fc is about
   1.7:1. The dark end of the same family carries the same colour idea at a
   contrast that survives the flip. (The product heroes need no equivalent:
   their panel stays dark in both themes.) */

html:is([data-theme="light"], [data-surface="light"]) .page--site .hero--homepage.hero--full h1 {
  color: var(--panel-navy-500);
}

html:is([data-theme="light"], [data-surface="light"]) .page--site .hero--homepage.hero--full h1 .text-positive {
  color: var(--panel-navy-900);
}

/* 45.9 · The last two icon boxes onto the 32px standard

   .surface-tile__icon and .route-card__icon were left on their own sizes by
   the first sweep. Everything comparable is 32 now, so these join it.
   -------------------------------------------------------------------------- */

.page--site :is(.surface-tile__icon, .route-card__icon) {
  width: var(--space-32);
  height: var(--space-32);
  border-radius: var(--radius-md);
}

/* ----------------------------------------------------------------------------
   45.10 · Loose ends the verification pass found
   -------------------------------------------------------------------------- */

/* The numbered badge shrank to 32px but the column holding it did not, so every
   process card carried a 22px gap where the old 54px track used to be. */

.page--homepage .process-card { grid-template-columns: var(--space-32) 1fr auto; }

/* --radius-card is 16px. On a 32px box that is a half-round pill, not a tile;
   every other box at this size uses --radius-md. */

.page--site .work-icon { border-radius: var(--radius-md); }

/* Card titles that are <strong> rather than <h3>. Block-level strong is a
   title in this codebase (feature rows, footer column heads, signal cards);
   inline strong stays emphasis and keeps its weight. */

.page :where(strong.layout-block) { font-weight: var(--weight-regular); }

/* An FAQ question is the heading of its row. */

.page--site .faq-list .accordion__summary { font-weight: var(--weight-regular); }

/* ----------------------------------------------------------------------------
   46 · Card rhythm

   46.1 · One vertical rhythm inside every card and section head: the eyebrow
   stands away from the title, the title sits tight to the line under it. The
   eyebrow is a label for the block, so it needs air; the subtitle belongs to
   the title, so it does not.
   -------------------------------------------------------------------------- */

.page--site :is(.eyebrow, .route-card__audience, .product-row__audience, .signal-tile__label) {
  margin-block-end: var(--space-16);
}

/* The subtitle usually carries .margin-none, which outranks a margin-top set
   here - so the gap is closed from the title's own bottom margin instead. */

.page--site :is(.section-head--homepage, .product-row__copy, .route-card, .link-card,
                .dimension-card, .signal-tile, .surface-tile, .work-step, .behaviour-card)
            :is(h2, h3, .route-card__title, strong) {
  margin-block-end: var(--space-6);
}

.page--site :is(h2, h3, .route-card__title) + p { margin-block-start: 0; }

/* The stack gap goes to zero so the two margins above are the only thing
   setting this rhythm. With both a gap AND a margin the spacing was the sum of
   the two, which is why the same "6px" rule produced 6px in one card and 22px
   in another. */

.page--site :is(.route-card, .signal-tile, .surface-tile, .product-row__copy, .link-card) {
  gap: 0;
}

/* 46.2 · Every card in a ROW OF CARDS ends on its call to action, whatever the
   copy above it does. The cards already stretch to the tallest in the row; this
   pushes the last thing in each to the floor so the buttons line up.

   .product-row__copy is deliberately not in this list: it sits beside a
   screenshot in a tall two-column row, so bottom-aligning its button stranded
   it ~240px under the paragraph. It still takes the rhythm rules above. */

.page--site :is(.route-card, .link-card) {
  display: flex;
  flex-direction: column;
  height: 100%;
}

.page--site :is(.route-card, .link-card) > .button {
  margin-block-start: auto;
  align-self: start;
}

/* On the tallest card in a row there is no slack for the auto margin to eat,
   so the button would sit straight on the copy. A floor keeps it clear. */

.page--site :is(.route-card, .link-card) > .button:not(:first-child) {
  margin-block-start: auto;
}

/* This floor used to say `p:last-of-type` and so has never once applied: the
   blurb in both card types is a <span>, not a <p>. With nothing holding it, the
   button sat hard against the copy on any card whose blurb filled the card -
   three of the four route cards measured a 0px gap, and only the one with a
   two-line blurb showed any daylight, from slack rather than from a rule. */

.page--site :is(.route-card, .link-card)
            > :is(p, .route-card__blurb, .link-card__blurb):has(+ .button) {
  margin-block-end: var(--space-28);
}

/* :has(+ .button), because it is a gap ABOVE A BUTTON and not trailing space.
   The developers link cards are the same component with no button - the whole
   card is the link - and an unconditional margin left 53px of nothing under
   their last line. */

/* The buttons are bottom-aligned, so a floor alone would leave the short-blurb
   card with the floor PLUS its slack - 28px on three cards and 52px on the
   fourth. Giving the blurb a three-line floor spends that slack inside the copy
   instead, where it is invisible, so all four gaps come out at 28px and the
   buttons still line up. min-height only, so longer copy and narrower widths
   still grow the way they always did. */

.page--site .route-card__blurb { min-height: 3lh; }

/* ----------------------------------------------------------------------------
   46.3 · How it works runs its card titles at the 24px rung
   -------------------------------------------------------------------------- */

.page--how-it-works :is(.dimension-card h3, .route-card__title, .signal-tile strong, .behaviour-card h3, .work-step h3) {
  font-weight: var(--weight-regular);
  font-size: var(--display-xs-font-size);
  line-height: var(--display-xs-line-height);
}

/* ----------------------------------------------------------------------------
   47 · Card padding and list ends

   47.1 · One internal padding for every card. They were running 16, 24 and 28
   depending on family, which is what made otherwise identical rows of cards
   look like they had different margins.
   -------------------------------------------------------------------------- */

.page--site :is(.compare-card, .route-card, .dimension-card, .signal-tile,
                .surface-tile, .link-card, .process-card, .behaviour-card, .work-step) {
  --card-current-padding: var(--space-24);
  padding: var(--space-24);
}

/* 47.2 · A divider separates rows; after the last row it is a line under
   nothing, and it drags the card's own bottom padding away from the content.
   Strip it wherever these lists are used. */

.page--site :is(.feature-list, .compare-card .feature-list) > :last-child {
  border-block-end: 0;
  padding-block-end: 0;
}

.page--site :is(.signal-list, .dimension-tags, .ontology-list) > :last-child {
  border-block-end: 0;
}


/* 47.4 · Side-by-side cards stretch to the tallest of the row, so the one with
   less copy was left with 65px of slack under its last row against the other's
   25px - which reads as two different paddings. Let the list take the spare
   height and share it between the rows instead of pooling it at the bottom. */

.page--site .compare-card { display: flex; flex-direction: column; }

.page--site .compare-card .feature-list {
  flex: 1;
  align-content: space-between;
}

/* ----------------------------------------------------------------------------
   57 · More room between a panel's title and its rule

   The head sat 25px above its divider and the first row's text sat 48px below
   it - 24px of stack margin the list does not need here, plus the row's own 18px
   of padding. The rule read as belonging to the list rather than to the head it
   follows. 29px above and 24px below is close to even.

   How It Works had a worse version of the same thing: its two compare heads
   never got the padding utility the other sixteen carry in their markup, so the
   title sat 1px off its own divider and 25px from the card edge where every
   other head sits at 49px. Making this a component rule rather than a utility
   fixes that page and lifts the other sixteen in one declaration - the utility
   is generic and used well beyond these heads, so it must not be touched.
   -------------------------------------------------------------------------- */

.page--homepage .compare-head { padding: var(--space-28); }

.page--site .compare-card .feature-list { margin-block-start: var(--space-6); }

/* 47.5 · The hero figures were the last thing on the homepage still off 400
   (mono 500). Nothing else on the page carries a second weight, so they join it. */

.page--site .hero--full .stats-strip--bare .stat strong { font-weight: var(--weight-regular); }

/* ----------------------------------------------------------------------------
   48 · Stacked blocks inside a section

   Every section runs 56px between its head and the grid under it, because
   .section-head--homepage carries that as a bottom margin. Any OTHER pair of
   stacked blocks got whatever was left over - #ontology's wheel sat 0px off
   the three cards beneath it, because the margin that used to hold them apart
   was removed as a one-off.

   State it once for every stacked pair instead. Adjacent vertical margins
   collapse, so this does not double up where the head already supplies 56.
   Grid containers are excluded: their children are columns, not a stack, and
   a top margin there pushes a whole column down.
   -------------------------------------------------------------------------- */

.page--site .section--homepage > .sp-container:not(.layout-grid) > * + * {
  margin-block-start: var(--space-56);
}

/* ----------------------------------------------------------------------------
   49 · The way down sits under the content, not on the fold

   Pinned to the hero's bottom edge it held the same y on every page (828), so
   its distance from the thing it follows swung from 24px on the homepage to
   173px on Players - on the short pages it read as unrelated to anything.

   In flow it is always the same 56px under the content, and the content plus
   control centre as one group, so the leftover height splits evenly above and
   below instead of pooling under the copy.
   -------------------------------------------------------------------------- */

.page--site :is(.hero--product, .hero--full) { align-content: center; }

.page--site :is(.hero--product, .hero--full) .hero-more {
  position: static;
  justify-self: center;
  margin-block-start: var(--space-56);
  inset: auto;
}

/* The bottom padding existed to reserve a band for the absolute control.
   It is in the flow now, so that reservation comes back as usable height. */

.page--site .hero--product { padding-block-end: var(--space-24); }

.page--site .hero--homepage.hero--full { padding-block: 77px var(--space-24); }

/* ----------------------------------------------------------------------------
   50 · Three corrections

   50.1 · No rules between sections. The 120px band already separates them; a
   hairline on top of that reads as a table, not a page. The footer keeps its
   own rule, because it sits on a different surface and the line is the join.
   -------------------------------------------------------------------------- */

.page--site :is(.section--homepage, .cta-section).separator-top { border-block-start: 0; }

/* ----------------------------------------------------------------------------
   50.2 · Menu hover is a colour, not a box

   The hover state drew a filled, bordered chip behind the label and also
   stepped the weight, so the row jumped as the pointer crossed it. Hovering
   just brings the label up to full strength against the 72% it rests at.
   -------------------------------------------------------------------------- */

.page--site :is(.sp-nav__routes, .sp-nav__drawer-routes) .sp-nav__route:hover,
.page--site :is(.sp-nav__routes, .sp-nav__drawer-routes) .sp-nav__route.is-hover {
  background: none;
  border-color: transparent;
  color: var(--nav-current-text);
  /* no font-weight here: the routes rest at 600, so setting 400 on hover just
     swapped one jump for another. Colour alone carries the state. */
}

/* The disclosure button is a route too, and carries the same treatment. */

.page--site .sp-nav__route--menu:hover {
  background: none;
  border-color: transparent;
  color: var(--nav-current-text);
}

/* ----------------------------------------------------------------------------
   50.3 · The hero mock, shorter

   Its height came from the game view's 460px floor, which made the whole panel
   taller than the copy beside it. Lower floor, shorter panel, same width.
   -------------------------------------------------------------------------- */

.page--homepage .game-view { min-height: var(--space-320); }

/* The floor alone does nothing: the game view stretches to whatever the signal
   panel beside it needs, and that panel is what set the 612px height. The
   height comes out of the panel's own padding and gaps. */

.page--homepage .hero-dashboard { padding: var(--space-16); }

.page--homepage .dashboard-top { height: var(--space-40); }

.page--homepage .signal-summary { padding: var(--space-12); }

.page--homepage .signal-list { gap: var(--space-8); }

.page--homepage .signal-panel { gap: var(--space-8); }

.page--homepage .signal-card { --card-current-padding: var(--space-8); padding: var(--space-8); }

@media (max-width: 1023px) {
  .page--homepage .game-view { min-height: var(--space-256); }
}

/* ----------------------------------------------------------------------------
   51 · The way down sits at the foot of the screen

   Section 49 put the control in flow at a constant 56px under the copy, which
   handed its distance from the bottom of the screen to whatever that page's
   copy happened to measure: 37px on the homepage, 161px on Players, 159px on
   How It Works. Same control, five different heights.

   Pinned to the foot it lands 24px off the bottom edge on every page, which is
   both the same everywhere and as low as it can sit without touching the edge.

   The heroes centre their content, so the bottom padding has to reserve the
   band the control occupies - a 24px inset under a 56px control - or the copy
   comes down on top of it on the taller pages. That band is 80px exactly, and
   it has to be 80px exactly rather than a rounder 96: the homepage hero needs
   660px for its own content, and 77 + 660 + 96 is 833, which overflows every
   viewport shorter than that and carries the control past the fold with it.
   -------------------------------------------------------------------------- */

.page--site :is(.hero--product, .hero--full) .hero-more {
  position: absolute;
  inset: auto 0 var(--space-24) 0;
  width: -moz-fit-content;
  width: fit-content;
  margin-block-start: 0;
  margin-inline: auto;
  justify-self: auto;
}

.page--site .hero--product { padding-block-end: var(--space-80); }

.page--site .hero--homepage.hero--full { padding-block: 77px var(--space-80); }

/* The homepage is the only hero carrying a stats strip, and the only one that
   runs out of screen. On a viewport short enough to be tight the strip gives
   back the 8px that keeps the control clear of the fold; above that height the
   rhythm is untouched. */

@media (max-height: 860px) {
  .page--site .hero--homepage.hero--full .stats-strip {
    margin-block-start: var(--space-12);
  }
}

/* Narrow undoes the pinning, and has to say so after section 51 rather than in
   44.4 and 44.5 where it used to: media queries carry no extra weight, so the
   rules above would otherwise win at every width. Below these breakpoints the
   hero is no longer one screen tall, and a control pinned to the bottom of a
   1,600px section is not a way down - it is a thing to scroll past twice. */

@media (max-width: 1023px) {
  .page--site .hero--product .hero-more {
    position: static;
    inset: auto;
    margin-block-start: var(--space-40);
  }

  .page--site .hero--product { padding-block-end: var(--space-64); }
}

@media (max-width: 1120px) {
  .page--site .hero--homepage.hero--full .hero-more {
    position: static;
    inset: auto;
    margin-block-start: var(--space-40);
  }

  .page--site .hero--homepage.hero--full {
    padding-block: calc(77px + var(--space-40)) var(--space-64);
  }
}

/* No height fallback here, deliberately. An earlier pass unpinned the control on
   screens too short for the hero, on the theory that a hero overrunning the fold
   drags the control under with it. Measured, that made it worse: at 1280x800 the
   control went from 4px above the fold when pinned to 36px below it when in the
   flow, because joining the flow makes the centred group taller and pushes the
   hero down further still. The pin is always the higher of the two, and the 80px
   reservation already guarantees it never lands on the copy - clearance measured
   at 0px or more across 324 width/height combinations. So the pin has no height
   exceptions: on a screen too short for the hero's own content the control sits
   just below the fold, which is the closest it can be, and the same rule holds on
   every page. Width is different and keeps its fallbacks above: below 1024/1120
   the hero stops being one screen at all and becomes a tall stacked layout, where
   pinning to the bottom edge would put the control 800px down the page. */


/* ----------------------------------------------------------------------------
   52 · One gap between cards

   Card grids had drifted to seven values: 16px on thirty grids, then 12, 20, 24
   and 32 on the rest, with how-it-works alone carrying five of them - a 16px
   dimension grid sitting directly above a 32px one, three cards each, same
   width, so the doubling was visible without scrolling.

   16px is the value to standardise on: it is already the majority, and it is
   what the system itself calls for - --grid-gap, --spacing-xl and --space-16
   are all 16px, and `.grid` has resolved to it all along.

   Deliberately left alone:
     .feature-list      0px - each row draws its own separator, so a gap here
                        would show as a double rule
     .signal-list,      8px - these are the insides of the miniature product
     .ontology-metrics  mockups, not page furniture. Their spacing is to scale
                        with the UI they depict, not with the cards around it

   .ontology-side was in that exempt list on the first pass and should not have
   been: its children are real .sp-card.ontology-card, sitting beside the wheel
   on the same page as the 16px grids. It takes 16px too, and like .process-list
   it had no rule of its own - the 12px came from the gap-lg utility.
     .product-grid      96px - stacked full-width rows, section rhythm not cards
     .stats-strip--bare not cards; unified above only because it disagreed with
                        itself, 32px normally and 24px inside a full hero
   -------------------------------------------------------------------------- */

/* .process-list is the biggest offender - 16 grids across 14 pages - and it has
   no rule of its own anywhere in this sheet. Its 12px comes from the gap-lg
   utility in the markup, so the fix is a component rule that outweighs it
   (0,2,0 against 0,1,0) rather than 16 edits across the HTML. This is how
   .work-rail and .behaviour-grid are already handled further up. */

.page--site .process-list,
.page--homepage .process-list,
.page--site .ontology-side,
.page--homepage .ontology-side {
  gap: var(--space-16);
}


/* ----------------------------------------------------------------------------
   53 · The paragraph starts at the title, not at the eyebrow

   The head is a two-column grid whose left cell holds an eyebrow and an h2 in a
   wrapper div. The right paragraph aligned to the top of that cell, which is the
   top of the EYEBROW - so it sat 38px above the title it belongs to, on all 39
   heads across 12 pages.

   The wrapper goes display:contents so the eyebrow and the h2 become grid items
   in their own right, then the eyebrow takes row 1 and the title and paragraph
   share row 2. The paragraph now starts at the title at every width, and it does
   so structurally: a fixed 38px offset would have been wrong on the eight heads
   whose eyebrow wraps to two lines on a phone (52px) and badly wrong on ai-labs'
   'The missing layer in AI training data', which wraps to three (70px).

   Row gap goes to 0 because the eyebrow already carries a 16px bottom margin;
   leaving the 72px row gap in place would push the title a further 72px down.
   -------------------------------------------------------------------------- */

.section-head--homepage:has(> div > .eyebrow) { row-gap: 0; }

.section-head--homepage > div:has(> .eyebrow) { display: contents; }

.section-head--homepage > div > .eyebrow { grid-column: 1; grid-row: 1; }

.section-head--homepage > div > h2 { grid-column: 1; grid-row: 2; }

.section-head--homepage:has(> div > .eyebrow) > p { grid-column: 2; grid-row: 2; }

/* Below 1120px the head is meant to be one column - there is a rule for it at
   the 44.x breakpoint, but it has been dead: an equal-specificity rule further
   down the sheet reset grid-template-columns to two equal tracks and, being
   later, won. The result shipped as two 171px columns on a 390px phone, with the
   h2 wrapping inside one of them. Restated here, after that rule, along with the
   placements above - in one column the eyebrow, title and paragraph simply
   stack, so the explicit rows and columns have to be handed back. */

@media (max-width: 1120px) {
  .section-head--homepage { grid-template-columns: minmax(0, 1fr); }

  /* The Humans and AI head carries an id-scoped 2fr/1fr split above; the id
     outranks the class rule, so it has to be stacked here by name too. */
  .page--homepage #human-ai .section-head--homepage { grid-template-columns: minmax(0, 1fr); }

  .section-head--homepage:has(> div > .eyebrow) { row-gap: var(--space-16); }

  .section-head--homepage > div:has(> .eyebrow) { display: block; }

  .section-head--homepage > div > .eyebrow,
  .section-head--homepage > div > h2,
  .section-head--homepage:has(> div > .eyebrow) > p {
    grid-column: auto;
    grid-row: auto;
  }
}


/* ----------------------------------------------------------------------------
   54 · How It Works' headline in light mode

   44.9 and 45.8 moved the homepage headline onto the Panel Navy ramp so it reads
   the same as How It Works. That exposed a hole on How It Works itself.

   45.8's comment says the product heroes need no light-mode headline rule
   because their panel stays dark in both themes. True of four of them - but How
   It Works is the one product hero that takes --surface-canvas for its ground,
   so its panel DOES flip, and its headline was pinned to Panel Navy 200 in both
   themes: #bcc0d5 on #f7f8fa, about 1.7:1, and its accent phrase at #f7f7fb on
   the same ground, near enough invisible.

   Same two steps the homepage now uses, so the pair stay identical in either
   theme: the title mid-ramp, the accent at the dark end, which keeps the accent
   the more prominent of the two exactly as it is in dark mode.
   -------------------------------------------------------------------------- */

html:is([data-theme="light"], [data-surface="light"]) :is(.page--how-it-works, .page--about) .hero--product h1 {
  color: var(--panel-navy-500);
}

html:is([data-theme="light"], [data-surface="light"]) :is(.page--how-it-works, .page--about) .hero--product h1 .text-positive {
  color: var(--panel-navy-900);
}


/* ----------------------------------------------------------------------------
   55 · One gap between a card's copy and its pills

   The 28px above a pill row was reachable from three directions at once, so the
   same pattern shipped at four different sizes:

     card, plain                 28px   the rule as written
     compare row                 45px   28px inherited into a row that already
                                        pads itself 17px, against 17px below it
     path card                   44px   28px on top of the card's own 16px gap
     developers privacy row      52px   28px on top of a space-top-3xl utility

   The base drops to 20px, which is the gap the complaint asked for and which
   also matches the 24px of card padding underneath the pills rather than
   overshooting it. The other three stop adding a second source on top.

   The one thing deliberately left uneven: on a row of cards with unequal copy,
   .push-block-end pins the pills to the card's bottom edge, so the leftover
   height pools above them and the gap reads larger on the shorter cards. That is
   the cost of the pills lining up across the row, which is worth more than an
   identical gap in each card. Dropping `push-block-end` from the markup is the
   switch if the opposite is ever wanted.
   -------------------------------------------------------------------------- */

/* Inside a compare row the pills are the only content and the row already pads
   itself. The 28px was separating them from nothing, and made the row 45px above
   and 17px below. */

.page--homepage .compare-row .dimension-tags { padding-top: 0; }

/* The path card is a flex column with its own 16px gap; the padding only has to
   make up the difference to 20px. */

.page--site .path-card .dimension-tags { padding-top: var(--space-4); }


/* ----------------------------------------------------------------------------
   56 · Icons

   Six unrelated faults, one symptom. Each is a place where something other than
   the icon contract got to decide an icon's size, weight, shape or colour.

   Fixed above, at the rules that caused them:
     .link-external > svg      hard-set 14px on nine BUTTON icons that should be
                               20px - the rule is for inline text links, so it
                               now says :not(.button), the same exemption the
                               gap on line 3893 already had
     .sp-nav__menu             a 10px radius beside the theme toggle's 8px, on
                               two 40px controls sitting next to each other
     .signal-icon              missing from the light-mode accent-ink list, so
                               its glyph kept the dark-theme mint on a near-white
                               tile: 1.19:1
     route-card CTA            padding-block-start:0 collapsed the button to 40px
                               against 42px elsewhere and pushed its leading icon
                               off centre; margin-block-start:auto above it is
                               what holds it clear of the copy, so the padding
                               kill was only ever doing harm
     .link-card__title         8px to the trailing glyph where the rest of the
                               site uses 6px
   -------------------------------------------------------------------------- */

/* The theme toggle never got the stroke that goes with its 20px box, so the
   sprite fell back to 2 user units and drew heavier than every other 20px icon.
   This is exactly what the note above .button svg warns about. */

.icon-button svg,
.icon-button .sp-icon {
  --icon-stroke: var(--icon-stroke-20);
}

/* The external-link glyph in a link-card title sits inside a <strong>, so the
   child combinator on .link-external could never reach it and it fell through to
   the 24px default - as tall as the whole line box, at a 2px stroke. */

.page--site .link-card__title .sp-icon {
  --icon-size: 14px;
  --icon-stroke: var(--icon-stroke-14);
  opacity: 0.72;
}

/* .link-external brings align-items:center, which on a column-flex card centres
   the title instead of the icon - the reason one card in the developers row read
   as centred and the other two did not. The card owns its own cross-axis. */

.page--site :is(.route-card, .link-card) { align-items: stretch; }

/* Two recipes for one 32px tile: .dimension-icon mixed its border and fill
   against --border and --panel-900, .work-icon and .surface-tile__icon against
   transparent. Mixing against a colour rather than transparent shifts the hue as
   well as the strength, so the same accent came out as two different tiles. */

.page--homepage .dimension-icon {
  border-color: color-mix(in srgb, var(--accent, var(--sp-accent)) 26%, transparent);
  background: color-mix(in srgb, var(--accent, var(--sp-accent)) 12%, transparent);
}

/* A 0.22 x 32px radius is 7.04px, against 8px on every other 32px tile. */

.page--homepage .marker-badge { border-radius: var(--radius-md); }

.page--homepage .marker-badge .sp-icon {
  --icon-size: var(--space-16);
  --icon-stroke: var(--icon-stroke-16);
}

/* Left alone: .signal-icon keeps its 38px box and 18px glyph. It is inside the
   miniature product mock in the hero, and is sized to the UI it depicts rather
   than to the page furniture around it - the same reason .signal-card keeps 8px
   of padding where site cards carry 24px. */


/* ----------------------------------------------------------------------------
   58 · The heroes in light mode

   Two opposite faults with one appearance - text you cannot read - because the
   ink and the ground stopped agreeing about which theme they were in.

   The four product heroes keep a DARK panel in light mode: --hero-950 is a fixed
   brand ramp step, deliberately. But their ink is page ink, and that flips. So
   the badge, the secondary button's label and the note all turned dark on a dark
   panel: 1.1:1 to 2.8:1 measured. Those three read page tokens, so the panel has
   to restate them the way it already restates --ui-muted and --ui-border a few
   hundred lines up - same reasoning, the tokens the panel was missing.

   How It Works is the opposite: it is the one product hero that takes
   --surface-canvas, so its panel DOES flip - and its copy reads --ui-muted,
   which the shared block ties to --hero-100. Light green on near-white, 1.06:1.
   It needs the page's muted ink, not the panel's, which is why it is excluded
   from the block above rather than following it.

   The way down is a third case again. It is pinned to the bottom of the hero,
   which is where the panel has already faded into --surface-canvas - so whatever
   theme it is in, it sits on the PAGE colour, not the panel's. It measured
   1.07:1 to 1.36:1 on all five. --muted is the page's own token and is not
   restated by the hero, so it is the one that follows the ground underneath it.
   -------------------------------------------------------------------------- */

html:is([data-theme="light"], [data-surface="light"]) .page--site:not(.page--how-it-works):not(.page--about) .hero--product {
  --sp-accent-ink: var(--sp-accent);
  --sp-accent-ink-lg: var(--sp-accent);
  --button-text: var(--hero-100);
}

/* ----------------------------------------------------------------------------
   59 · The panel holds its colour until the fade

   The hero's bottom fade started at 64%, which left the hero note - about 72%
   down - sitting in the middle of the blend. A mid-tone ground beats both inks:
   dark ink measured 1.4-2.4:1 there in light mode, and on AI Labs the light ink
   managed only 2.9:1 in DARK mode, against the amber halfway through its own
   texture. No choice of colour fixes that; the ground has to stop being halfway
   between two.

   Starting at 78% puts the note back on solid panel in both themes and still
   leaves the fade the last fifth of the hero, about 200px, to resolve into the
   page. The way down, at 94%, stays well inside the faded end and keeps taking
   the page's ink.

   With the note back on solid panel it wants the panel's ink again, like
   everything else in this hero.
   -------------------------------------------------------------------------- */

html:is([data-theme="light"], [data-surface="light"]) .page--site:not(.page--how-it-works):not(.page--about) .hero--product {
  --quiet: color-mix(in srgb, var(--hero-100) 84%, transparent);
}

html:is([data-theme="light"], [data-surface="light"]) :is(.page--how-it-works, .page--about) .hero--product {
  --ui-muted: var(--muted);
}

html:is([data-theme="light"], [data-surface="light"]) .page--site :is(.hero--product, .hero--full) .hero-more {
  color: var(--muted);
}


/* ----------------------------------------------------------------------------
   60 · The menu and the footer take the header's measure

   44.10 widened the heroes to 1560px with a 24px gutter and left everything
   else on the site's older 1440/32. At 1440 that is only an 8px step, but the
   wider the display the worse it reads: at 1920 the hero's first pixel of copy
   is at 204 and the logo above it at 272, a 68px stagger between the top of the
   page and the thing directly under it.

   The bar and the footer now read the same two tokens the heroes do. They are
   declared on the container elements themselves because that is where the width
   and padding declarations reading them live - set anywhere higher and they
   resolve against the wrong element.

   Note the sections between are still on 1440/32, so they now sit inset from
   the bar, hero and footer rather than aligned to them. That is a deliberate
   stopping point, not an oversight: moving the sections changes the measure of
   every page on the site and is a bigger call than this one.
   -------------------------------------------------------------------------- */

.page--site .sp-nav__inner,
.page--site .footer > .sp-container {
  --grid-container-max: 1560px;
  --grid-padding: var(--space-24);
}

/* ----------------------------------------------------------------------------
   61 · The AI Labs call to action, brought back toward the others

   Every call-to-action panel on the site measures 550-555px. AI Labs' is 739,
   because it is the only one carrying a three-item list between its copy and
   its buttons - 236px of list plus a 30px stack margin on top.

   The list gets the compact treatment inside a panel: rows at 8px rather than
   the 18px they take in a card, and a 20px approach instead of 30px. It cannot
   reach 553 without losing a row or its descriptions, which is content rather
   than spacing, so this closes most of the gap rather than all of it.
   -------------------------------------------------------------------------- */

.page--site .cta-panel .feature-list { margin-block-start: var(--space-20); }

.page--site .cta-panel .feature-item { padding-block: var(--space-8); }

.page--site .cta-panel .hero-actions { margin-block-start: var(--space-24); }


/* ----------------------------------------------------------------------------
   62 · About takes the How It Works treatment

   Same hero shape as How It Works - one screen, a split of copy and a figure,
   the menu over it and a way down - and the same brand-green colourway, since
   like that page it belongs to no single product. The colourway, the Panel Navy
   title and both light-mode blocks are shared by adding .page--about to the
   selectors that already carried them rather than restating them.

   The stats strip that used to sit inside this hero moved out to a band of its
   own below it. Inside a one-screen hero it would have been the homepage's
   problem all over again: that hero carries a strip too, and it is the one hero
   on the site whose content does not fit a short screen. The strip loses its
   own top margin here because the band around it already supplies the room.
   -------------------------------------------------------------------------- */

.page--site .stats-strip--band { margin-top: 0; }


/* ----------------------------------------------------------------------------
   63 · Team photographs

   The component already knew how to hold a picture - .sp-avatar > img has been
   sized and clipped since the library was written - so the cards only needed
   the image. The monogram styling has to come off with it though: the border,
   the raised fill and the letter-spacing were drawn for two letters on a tinted
   disc, and behind a photograph they read as a ring and a seam.

   Six of the eight people in the deck are on this page, so six are here. The
   deck's other two are a content decision, not an oversight to fix in CSS.
   -------------------------------------------------------------------------- */

.page--site .sp-avatar--photo {
  border: 0;
  background: none;
  letter-spacing: normal;
  overflow: hidden;
}

/* ----------------------------------------------------------------------------
   64 · The bar and the footer only

   This section briefly put every .sp-container on the heroes' 1560/24. It is
   gone deliberately: the sections between the hero and the footer keep the
   site's established 1440/32 measure, and only the bar, the heroes and the
   footer run wide. Section 60 is what does that, and it is enough.

   Which means the page has two measures on purpose. The bands that frame the
   page share the heroes' edge; the reading column inside them stays where it
   has always been.
   -------------------------------------------------------------------------- */


/* ----------------------------------------------------------------------------
   65 · One gap above a button, one padding inside a card

   Measured across the site, the space between a piece of copy and the button
   under it came in six sizes:

     0px    About, under the research list - the button touching the copy
     8px    homepage section buttons
     24px   inside a call-to-action panel
     28px   route cards and some section buttons
     32px   every hero
     56px   sections where the button follows a whole block

   28px is the value, because it is the one already agreed for the route cards
   and it sits comfortably between the 24 the panels used and the 32 the heroes
   did. It is stated for the button rows and for a lone button following copy,
   at every width - a phone had its own 24px, which is exactly the kind of
   nearly-the-same the brief is trying to remove.
   -------------------------------------------------------------------------- */

.page--site .hero-actions,
.page--site .cta-panel .hero-actions,
.page--homepage .cta-copy .hero-actions {
  margin-block-start: var(--space-28);
}

.page--site :is(p, h2, h3, ul, ol, .feature-list, .link-list, .process-list, .dimension-tags) + .button {
  margin-block-start: var(--space-28);
}

/* Three button rows sat at 56px because section 48's stacked-block rhythm
   reaches them and outweighs the rule above - it carries a :not(), which counts.
   Restated at that weight: block rhythm is for blocks, and a button belongs to
   the copy it follows. */

.page--site .section--homepage > .sp-container:not(.layout-grid) > .hero-actions {
  margin-block-start: var(--space-28);
}

/* Card padding was 24px on about 130 cards and something else on thirteen.
   The three that disagreed now agree. */

.page .card--spacious { --card-current-padding: var(--space-24); }

.page--site .path-card { --card-current-padding: var(--space-24); }

:is(.page--ontology-wheel, .ontology-root) { --ontology-card-padding: var(--space-24); }

/* Deliberately still their own size, and not cards:
     .cta-panel   64px - a full-width panel, not a card in a grid
     .signal-card  8px - inside the miniature product mock in the hero
     .card--flush   0px - the variant whose whole job is to have no padding,
                    so its head can draw the inset and its rule reach the edge */


/* ----------------------------------------------------------------------------
   66 · A textured section

   The same treatment the product heroes carry, made available to a section. The
   three existing textures are each keyed to a product's panel colour, so this
   needs its own: the research section sits on the page ground, not on a panel,
   and any of the three would have painted a coloured box behind it.

   The texture is a 120px tile on a 24px rhythm like the others, and it fades out
   at the top and bottom rather than ending on an edge - the same reason the
   heroes fade into the page below them.
   -------------------------------------------------------------------------- */

.page--site .section--textured {
  position: relative;
  isolation: isolate;
}

.page--site .section--textured::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image: url("../assets/images/website/texture-navy-squares.svg");
  background-repeat: repeat;
  background-size: 120px 120px;
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 12%, #000 88%, transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0, #000 12%, #000 88%, transparent 100%);
}

/* In light mode the near-black tile would be a dark slab on a light page. The
   texture is the page's own ground either way, so it comes off rather than
   being redrawn in a second set of colours. */

html:is([data-theme="light"], [data-surface="light"]) .page--site .section--textured::before {
  display: none;
}

/* ----------------------------------------------------------------------------
   67 · The product slider

   The homepage listed its four products as four full rows stacked 96px apart,
   so seeing all four meant scrolling past three. As a slider each product keeps
   the row it already had - copy one side, screenshot the other - and the dots
   underneath carry the product NAMES rather than being anonymous circles, so
   the four are readable at a glance and any one is a click away. That naming is
   the point: a plain dot row would hide the very thing the section exists to
   show.

   Scroll snap does the work, so it stays a scrollable list with no JavaScript.
   The script only lights the right dot and lets the arrow keys drive it.
   -------------------------------------------------------------------------- */

.page--site .sp-slider__viewport {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 100%;
  gap: var(--space-24);
  overflow-x: auto;
  overscroll-behavior-inline: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}

.page--site .sp-slider__viewport::-webkit-scrollbar { display: none; }

.page--site .sp-slider__viewport > * { scroll-snap-align: start; }

.page--site .sp-slider__viewport:focus-visible {
  outline: 2px solid var(--tone, var(--sp-accent));
  outline-offset: 8px;
}

/* 67.1 · Each slide is a panel in its product's texture

   The same pair the route cards and product heroes use: the product's 950 as
   the ground, its 900 motif tiled over it. Two things keep it quiet. The motif
   lives behind the copy and dissolves to plain 950 across the screenshot's
   half, so the shot sits on a calm ground instead of fighting a pattern. And a
   soft pool of the product's tone sits in the top-left corner, where the
   eyebrow and heading are, so the panel has a light source rather than being a
   flat tile.

   Same derived tokens as the heroes (section 58): the greys and the Deep Navy
   button stack go muddy on a coloured ground, so they are restated here. */

.page--site .sp-slider__viewport > .product-row {
  position: relative;
  isolation: isolate;
  padding: var(--space-40);
  border: 1px solid color-mix(in srgb, var(--hero-300, var(--tone)) 20%, transparent);
  border-radius: var(--radius-panel);
  background-color: var(--hero-950, var(--ui-surface));
  background-image:
    radial-gradient(120% 90% at 0% 0%,
      color-mix(in srgb, var(--tone, var(--sp-accent)) 14%, transparent) 0%,
      transparent 55%),
    linear-gradient(to right, transparent 34%, var(--hero-950, var(--ui-surface)) 62%),
    var(--card-texture, none);
  background-repeat: no-repeat, no-repeat, repeat;
  background-size: auto, auto, var(--card-texture-size, 120px 120px);
  overflow: hidden;

  --ui-muted             : color-mix(in srgb, var(--hero-100) 74%, transparent);
  --positive-text        : var(--hero-400);
  --ui-border            : color-mix(in srgb, var(--hero-300) 22%, transparent);
  --border-subtle        : color-mix(in srgb, var(--hero-300) 22%, transparent);
  --button-surface       : color-mix(in srgb, var(--hero-900) 32%, var(--hero-950));
  --button-surface-hover : color-mix(in srgb, var(--hero-900) 46%, var(--hero-950));
}

.page--site .sp-slider__viewport > .product-row.tone--mint {
  --hero-100: var(--personality-mint-100);
  --hero-300: var(--personality-mint-300);
  --hero-400: var(--personality-mint-400);
  --hero-900: var(--personality-mint-900);
  --hero-950: var(--personality-mint-950);
  --card-texture: repeating-radial-gradient(circle at 18% 40%,
    var(--hero-950) 0 22.4px,
    color-mix(in srgb, var(--hero-900) 20%, var(--hero-950)) 22.4px 24px);
  --card-texture-size: auto;
}

.page--site .sp-slider__viewport > .product-row.tone--blue {
  --hero-100: var(--personality-blue-100);
  --hero-300: var(--personality-blue-300);
  --hero-400: var(--personality-blue-400);
  --hero-900: var(--personality-blue-900);
  --hero-950: var(--personality-blue-950);
  --card-texture: url("../assets/images/website/texture-blue-squares.svg");
}

.page--site .sp-slider__viewport > .product-row.tone--lime {
  --hero-100: var(--core-lime-100);
  --hero-300: var(--core-lime-300);
  --hero-400: var(--core-lime-400);
  --hero-900: var(--core-lime-900);
  --hero-950: var(--core-lime-950);
  --card-texture: url("../assets/images/website/texture-lime-waves.svg");
}

.page--site .sp-slider__viewport > .product-row.tone--orange {
  --hero-100: var(--skills-orange-100);
  --hero-300: var(--skills-orange-300);
  --hero-400: var(--skills-orange-400);
  --hero-900: var(--skills-orange-900);
  --hero-950: var(--skills-orange-950);
  --card-texture: url("../assets/images/website/texture-orange-dots.svg");
}

/* The shot's shadow is the panel's own ground, deepened, so it grounds the
   screenshot on the coloured panel instead of leaving a navy halo on it. */

.page--site .sp-slider__viewport > .product-row .product-row__shot {
  box-shadow: 0 24px 64px color-mix(in srgb, var(--hero-950) 70%, black);
}

/* Stacked, the copy sits above the shot, so the motif lives behind the copy at
   the top and gives way to plain ground down the panel rather than across it. */

@media (max-width: 1023px) {
  .page--site .sp-slider__viewport > .product-row {
    padding: var(--space-24);
    background-image:
      radial-gradient(120% 70% at 0% 0%,
        color-mix(in srgb, var(--tone, var(--sp-accent)) 14%, transparent) 0%,
        transparent 55%),
      linear-gradient(to bottom, transparent 30%, var(--hero-950, var(--ui-surface)) 58%),
      var(--card-texture, none);
  }
}

/* In light mode the panel is still a dark coloured tile, exactly as the route
   cards are, so it keeps the dark-surface inks rather than the page's. */

html:is([data-theme="light"], [data-surface="light"]) .page--site .sp-slider__viewport > .product-row {
  --surface-text: var(--geistface-grey-25);
  --sp-accent-ink: var(--sp-accent);
  --sp-accent-ink-lg: var(--sp-accent);
  --accent-ink: var(--accent, var(--sp-accent));
  --button-text: var(--hero-100);
  color: var(--surface-text);
}

html:is([data-theme="light"], [data-surface="light"]) .page--site .sp-slider__viewport > .product-row :is(h2, h3) {
  color: var(--geistface-grey-25);
}

html:is([data-theme="light"], [data-surface="light"]) .page--site .sp-slider__viewport > .product-row .product-row__audience {
  color: var(--tone, var(--sp-accent));
}

/* The dots: each one is a small pill wearing its product's texture - the same
   950 ground and 900 motif the route cards and product heroes use (section 68),
   tiled at half size so the motif still shows inside a pill. The icon and the
   ring in the product's tone mark the slide that is showing; the others sit
   back at reduced strength. */

.page--site .sp-slider__dots {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-8);
  margin-block-start: var(--space-28);
}

.page--site .sp-slider__dot {
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
  padding: var(--space-8) var(--space-14);
  border: 1px solid color-mix(in srgb, var(--hero-300, var(--tone)) 14%, transparent);
  border-radius: var(--radius-full);
  background-color: var(--hero-950, var(--ui-surface));
  background-image: var(--card-texture, none);
  background-repeat: repeat;
  background-size: var(--card-texture-size, 60px 60px);
  color: color-mix(in srgb, var(--hero-100, var(--surface-text)) 70%, transparent);
  font-family: var(--skillprint-font-ui);
  font-size: var(--text-sm-font-size);
  font-weight: var(--weight-regular);
  cursor: pointer;
  opacity: 0.72;
  transition: color var(--duration-fast) var(--ease-standard),
              border-color var(--duration-fast) var(--ease-standard),
              opacity var(--duration-fast) var(--ease-standard);
}

.page--site .sp-slider__dot.tone--mint {
  --hero-100: var(--personality-mint-100);
  --hero-300: var(--personality-mint-300);
  --hero-900: var(--personality-mint-900);
  --hero-950: var(--personality-mint-950);
  --card-texture: repeating-radial-gradient(circle at 50% 50%,
    var(--hero-950) 0 11.2px,
    color-mix(in srgb, var(--hero-900) 20%, var(--hero-950)) 11.2px 12px);
  --card-texture-size: auto;
}

.page--site .sp-slider__dot.tone--blue {
  --hero-100: var(--personality-blue-100);
  --hero-300: var(--personality-blue-300);
  --hero-900: var(--personality-blue-900);
  --hero-950: var(--personality-blue-950);
  --card-texture: url("../assets/images/website/texture-blue-squares.svg");
}

.page--site .sp-slider__dot.tone--lime {
  --hero-100: var(--core-lime-100);
  --hero-300: var(--core-lime-300);
  --hero-900: var(--core-lime-900);
  --hero-950: var(--core-lime-950);
  --card-texture: url("../assets/images/website/texture-lime-waves.svg");
}

.page--site .sp-slider__dot.tone--orange {
  --hero-100: var(--skills-orange-100);
  --hero-300: var(--skills-orange-300);
  --hero-900: var(--skills-orange-900);
  --hero-950: var(--skills-orange-950);
  --card-texture: url("../assets/images/website/texture-orange-dots.svg");
}

.page--site .sp-slider__dot-icon {
  --icon-size: var(--icon-size-xs);
  --icon-stroke: var(--icon-stroke-20);
  color: var(--tone, var(--sp-accent));
  opacity: 0.7;
  transition: opacity var(--duration-fast) var(--ease-standard);
}

.page--site .sp-slider__dot:hover {
  opacity: 1;
  color: var(--surface-text);
}

.page--site .sp-slider__dot:hover .sp-slider__dot-icon { opacity: 0.9; }

.page--site .sp-slider__dot[aria-current="true"] {
  opacity: 1;
  border-color: color-mix(in srgb, var(--tone, var(--sp-accent)) 55%, transparent);
  color: var(--surface-text);
  font-weight: var(--weight-medium);
}

.page--site .sp-slider__dot[aria-current="true"] .sp-slider__dot-icon { opacity: 1; }


/* ----------------------------------------------------------------------------
   68 · The route cards wear their product's texture

   The texture belongs to the card, not to the section behind it. Each of the
   four cards routes to a product page, and each product page's hero is built
   from one family: a 950 ground with a 900 motif over it. Giving the card the
   same pair makes it a small piece of the page it leads to - you recognise
   Enterprise's blue squares before you have read the word Enterprise.

   That is also why the ground has to come with the texture. These tiles are not
   transparent overlays; every one of them paints its own 950 as the ground and
   the motif on top. Dropping the image alone onto a navy card would have shown
   a blue rectangle on three of them. And once the card is a coloured panel, the
   ink on it needs the same treatment the heroes give theirs, or the muted text
   and the button go muddy - the same derived tokens, restated on the card.

   Players is the odd one: its hero draws rings from a gradient rather than a
   tiled file, so it needs the gradient and background-size: auto with it.
   -------------------------------------------------------------------------- */

.page--site .route-card {
  background-color: var(--hero-950, var(--ui-surface));
  background-image: var(--card-texture, none);
  background-repeat: repeat;
  background-size: var(--card-texture-size, 120px 120px);
  border-color: color-mix(in srgb, var(--hero-300, var(--tone)) 22%, transparent);

  --ui-muted             : color-mix(in srgb, var(--hero-100) 74%, transparent);
  --positive-text        : var(--hero-400);
  --ui-border            : color-mix(in srgb, var(--hero-300) 22%, transparent);
  --button-surface       : color-mix(in srgb, var(--hero-900) 32%, var(--hero-950));
  --button-surface-hover : color-mix(in srgb, var(--hero-900) 46%, var(--hero-950));
}

.page--site .route-card.tone--mint {
  --hero-100: var(--personality-mint-100);
  --hero-300: var(--personality-mint-300);
  --hero-400: var(--personality-mint-400);
  --hero-900: var(--personality-mint-900);
  --hero-950: var(--personality-mint-950);
  --card-texture: repeating-radial-gradient(circle at 50% 50%,
    var(--hero-950) 0 22.4px,
    color-mix(in srgb, var(--hero-900) 20%, var(--hero-950)) 22.4px 24px);
  --card-texture-size: auto;
}

.page--site .route-card.tone--blue {
  --hero-100: var(--personality-blue-100);
  --hero-300: var(--personality-blue-300);
  --hero-400: var(--personality-blue-400);
  --hero-900: var(--personality-blue-900);
  --hero-950: var(--personality-blue-950);
  --card-texture: url("../assets/images/website/texture-blue-squares.svg");
}

.page--site .route-card.tone--lime {
  --hero-100: var(--core-lime-100);
  --hero-300: var(--core-lime-300);
  --hero-400: var(--core-lime-400);
  --hero-900: var(--core-lime-900);
  --hero-950: var(--core-lime-950);
  --card-texture: url("../assets/images/website/texture-lime-waves.svg");
}

.page--site .route-card.tone--orange {
  --hero-100: var(--skills-orange-100);
  --hero-300: var(--skills-orange-300);
  --hero-400: var(--skills-orange-400);
  --hero-900: var(--skills-orange-900);
  --hero-950: var(--skills-orange-950);
  --card-texture: url("../assets/images/website/texture-orange-dots.svg");
}

/* ----------------------------------------------------------------------------
   69 · Six streams, three and three

   The six-item list ran one full-width row per item, so a reader scrolled a
   screen and a half to see six short definitions. Three across and two down
   fits them on one screen.

   Scoped to the six-item variant. The other thirteen .process-list blocks hold
   three, four or five items, where three columns would leave a ragged last row.

   The card's own three columns - badge, copy, label - do not survive being
   quartered in width, so at 3-up the label drops under the copy.
   -------------------------------------------------------------------------- */

.page--site .process-list--3up { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.page--homepage .process-list--3up .process-card { grid-template-columns: var(--space-32) 1fr; }

.page--site .process-list--3up .process-card > code {
  grid-column: 2;
  justify-self: start;
}

@media (max-width: 1023px) {
  .page--site .process-list--3up { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 680px) {
  .page--site .process-list--3up { grid-template-columns: minmax(0, 1fr); }
}
/* ============================================================================
   70 · QA pass, 2 September 2026

   Defects found by driving every page at 1920, 1440, 1280, 1024, 768, 430,
   390, 375 and 320. Each rule names the failure it closes; nothing here is a
   new style, only the existing one holding at a width it did not.
   ========================================================================== */

/* 70.1 · The drawer on a short phone. It is position: absolute inside a fixed
   bar, so on a 320×568 screen its Contact button sat at 602px and page scroll
   could not reach it. It now scrolls inside itself. */

.page--site .sp-nav__drawer {
  max-height: calc(100dvh - 88px);
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* 70.2 · Open the drawer on a phone, widen the window past the route row, and
   the drawer stayed open with its opener gone. The script closes it on the
   breakpoint change; this makes sure it is never drawn there regardless. */

@media (min-width: 1024px) {
  .page--site .sp-nav__drawer { display: none; }
}

/* 70.3 · The bar at 320px. Brand and actions each took half the row, and the
   three controls need 180px, so the colour toggle slid under the wordmark.
   The controls keep their size; the wordmark takes what is left. Below 360px
   that is not enough for a wordmark, so on pages with a drawer - which repeats
   the Contact button - the bar's own Contact steps aside. */

@media (max-width: 767px) {
  .page--site .sp-nav__inner { gap: var(--space-16); }
  .page--site .sp-nav__actions { flex: 0 0 auto; }
}

@media (max-width: 359px) {
  .page--site .sp-nav__inner:has(.sp-nav__menu) .sp-nav__actions > .button { display: none; }
}

/* 70.4 · The hero figures. Two columns of "figure + label" at 375px wrapped
   "people have played" onto three lines beside its number, and the fifth
   figure sat alone. One column keeps each figure and its label on a line. */

@media (max-width: 480px) {
  .page--site .hero--full .stats-strip--bare {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-12);
  }
}

/* 70.5 · Full-width list rows. A process card or work step spans the whole
   container, so its 14px copy ran to 170 characters a line at 1440. The copy
   is measured; the row is not. */

.page--site :is(.process-card, .work-step, .ontology-card) p { max-width: 72ch; }

/* 70.6 · Legal prose quotes URLs verbatim. One of them is wider than a 320px
   screen and forced the page sideways. */

.page--site .prose a { overflow-wrap: anywhere; }

/* 70.7 · The ontology panel's hint line and its two mono headings were 9px.
   The system's smallest label is the eyebrow size; they take it. */

.page--site .ontology-visual .font-2xs { font-size: var(--text-xs-font-size); }

/* 70.8 · The pill beside a compare-card title broke "Fine tuning" onto two
   lines at 375px. A label is one line; the title takes the wrap. */

.page--site .compare-head .eyebrow--pill {
  flex: none;
  white-space: nowrap;
}

/* 70.9 · The two profile chips float over the portal shot. At 560px and under
   there is no room to float: they overlapped each other and hid the image
   they annotate. They step out of the picture into a row beneath it. */

@media (max-width: 560px) {
  .page--homepage .profile-visual {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-12);
    place-items: start stretch;
    min-height: auto;
  }

  .page--homepage .profile-visual > .media-frame { grid-column: 1 / -1; }

  .page--homepage .profile-chip {
    position: static;
    min-width: 0;
  }

  /* Side by side the figure floated right of a title that had no room; in
     the row it sits above its title instead. */
  .page--homepage .profile-chip b {
    float: none;
    display: block;
  }
}

/* 70.10 · The benchmark hero is centred, and its note kept the left-aligned
   dot of the product heroes while its sentence sat in the middle. */

.page--benchmark .hero--audience .hero-note { justify-content: center; }

/* 70.11 · The endpoints table squeezed its three columns into 343px and cut
   the credential column to a letter. Inside its scroll region it keeps a
   readable width and scrolls instead. Inline code such as Api-Key was also
   breaking at its hyphen; a token is one line. */

.page--site .table-scroll .sp-table { min-width: 560px; }

.page--site .inline-code { white-space: nowrap; }

/* 70.12 · A note under a centred figure inherited the centring, so its
   sentence floated away from its own dot once it wrapped. */

.page--site .hero-note > span { text-align: start; }

/* ============================================================================
   71 · Asset resources, product textures and screenshot previews (3 Sep 2026)

   Design-system page only. Three additions: the Figma + Google Drive
   resources bar that closes every documented section, the 06.3 product
   texture cards (rendered from the marketing site's own tiles and gradient,
   so they cannot drift from the site), and the screenshot-backed interface
   cards in section 45. The texture tokens are the browser's own sRGB results
   for the site's color-mix() and are the values baked into the tile SVGs.
   ========================================================================== */

:root {
  --texture-mint-ground: var(--personality-mint-950);
  --texture-mint-motif: #1e4a56;
  --texture-mint-surface: #1f4e5a;
  --texture-mint-raised: #21535e;
  --texture-blue-ground: var(--personality-blue-950);
  --texture-blue-motif: #11395e;
  --texture-blue-surface: #113c62;
  --texture-blue-raised: #124067;
  --texture-lime-ground: var(--core-lime-950);
  --texture-lime-motif: #374523;
  --texture-lime-surface: #3b4a22;
  --texture-lime-raised: #3f4f22;
  --texture-orange-ground: var(--skills-orange-950);
  --texture-orange-motif: #473624;
  --texture-orange-surface: #4b3a23;
  --texture-orange-raised: #513d23;
  --texture-navy-ground: var(--deep-navy-900);
  --texture-navy-motif: #0e122d;
}

/* 71.1 · Resources bar — compact link tiles. One tile per destination:
   a tinted glyph (green = Google Drive, violet = Figma), the name in normal
   case, and one muted line saying where it lives. The whole tile is the link. */

.page--design-system .section-resources {
  margin-top: var(--space-48);
  padding-top: var(--space-24);
  border-top: 1px solid var(--ui-border);
}

.page--design-system .section-resources__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-8) var(--space-32);
  margin-bottom: var(--space-12);
}

.page--design-system .section-resources__head > p {
  max-width: 560px;
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
}

.page--design-system .section-resources__list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: var(--space-8);
}

.page--design-system .resource-tile {
  display: grid;
  grid-template-columns: 30px minmax(0, 1fr) 14px;
  align-items: center;
  gap: var(--space-12);
  min-width: 0;
  padding: var(--space-10) var(--space-14);
  border: 1px solid var(--ui-border);
  border-radius: var(--radius-xl);
  background: var(--surface-box);
  color: var(--surface-text);
  text-decoration: none;
  transition: border-color var(--duration-fast), background var(--duration-fast);
}

.page--design-system .resource-tile:hover,
.page--design-system .resource-tile:focus-visible {
  background: var(--surface-raised);
  border-color: color-mix(in srgb, var(--surface-brand) 45%, var(--ui-border));
}

.page--design-system .resource-tile--figma { --surface-brand: var(--mindset-violet-400); }
.page--design-system .resource-tile--drive { --surface-brand: var(--core-green-500); }

.page--design-system .resource-tile__icon {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--surface-brand) 14%, transparent);
  color: var(--surface-brand);
}

.page--design-system .resource-tile__icon svg { width: 15px; height: 15px; }
.page--design-system .resource-tile__body { min-width: 0; }

.page--design-system .resource-tile__name,
.page--design-system .resource-tile__path {
  display: block;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.page--design-system .resource-tile__name {
  font-size: var(--text-sm-font-size);
  line-height: var(--text-sm-line-height);
  font-weight: var(--weight-medium);
}

.page--design-system .resource-tile__path {
  font-size: var(--text-xs-font-size);
  line-height: var(--text-xs-line-height);
  color: var(--ui-muted);
}

.page--design-system .resource-tile__ext { width: 14px; height: 14px; color: var(--ui-muted); }
.page--design-system .resource-tile:hover .resource-tile__ext { color: var(--surface-text); }

/* 71.2 · Product texture cards. The stages carry the site's exact
   declarations: ground 950, motif 900 at 20% over 950, 120px tile on a 24px
   rhythm, and the rings as a gradient with background-size auto. */

.page--design-system .product-texture-card {
  display: grid;
  grid-template-rows: auto 1fr;
  background: var(--surface-box);
}

.page--design-system .product-texture-stage {
  position: relative;
  display: grid;
  align-content: end;
  gap: var(--space-4);
  min-height: 250px;
  padding: var(--space-24);
  color: var(--pt-ink);
  background-color: var(--pt-ground);
  background-image: var(--pt-texture, none);
  background-repeat: repeat;
  background-size: var(--pt-texture-size, 120px 120px);
}

.page--design-system .product-texture-stage--strip { min-height: 120px; }
.page--design-system .product-texture-stage__kicker { color: var(--pt-kicker); }
.page--design-system .product-texture-stage__title {
  font-size: var(--text-display-sm-font-size, 30px);
  line-height: var(--text-display-sm-line-height, 38px);
  font-weight: var(--weight-medium);
  color: var(--pt-title);
}

.page--design-system .product-texture-stage--amplify {
  --pt-ground: var(--personality-mint-950);
  --pt-ink: var(--personality-mint-100);
  --pt-kicker: var(--personality-mint-300);
  --pt-title: var(--personality-mint-500);
  --pt-texture: repeating-radial-gradient(circle at 50% 50%,
    var(--personality-mint-950) 0 22.4px,
    color-mix(in srgb, var(--personality-mint-900) 20%, var(--personality-mint-950)) 22.4px 24px);
  --pt-texture-size: auto;
}

.page--design-system .product-texture-stage--enterprise {
  --pt-ground: var(--personality-blue-950);
  --pt-ink: var(--personality-blue-100);
  --pt-kicker: var(--personality-blue-300);
  --pt-title: var(--personality-blue-500);
  --pt-texture: url("../assets/images/website/texture-blue-squares.svg");
}

.page--design-system .product-texture-stage--flow {
  --pt-ground: var(--core-lime-950);
  --pt-ink: var(--core-lime-100);
  --pt-kicker: var(--core-lime-300);
  --pt-title: var(--core-lime-500);
  --pt-texture: url("../assets/images/website/texture-lime-waves.svg");
}

.page--design-system .product-texture-stage--signal {
  --pt-ground: var(--skills-orange-950);
  --pt-ink: var(--skills-orange-100);
  --pt-kicker: var(--skills-orange-300);
  --pt-title: var(--skills-orange-500);
  --pt-texture: url("../assets/images/website/texture-orange-dots.svg");
}

.page--design-system .product-texture-stage--navy {
  --pt-ground: var(--deep-navy-900);
  --pt-ink: var(--geistface-grey-200);
  --pt-texture: url("../assets/images/website/texture-navy-squares.svg");
}

.page--design-system .product-texture-card__body { padding: var(--space-24); }
.page--design-system .product-texture-fact dt { margin-bottom: var(--space-4); }
.page--design-system .product-texture-fact dd code { font-size: 0.92em; }
.page--design-system .product-texture-swatches { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.page--design-system .product-texture-note { grid-template-columns: 220px minmax(0, 1fr); align-items: center; }
.page--design-system .product-texture-table td, .page--design-system .product-texture-table th { vertical-align: top; }

/* The swatch tiles reuse .shade from section 05 and only supply their colour. */
.page .tex-swatch--mint-ground   { --swatch-color: var(--texture-mint-ground); }
.page .tex-swatch--mint-motif    { --swatch-color: var(--texture-mint-motif); }
.page .tex-swatch--mint-surface  { --swatch-color: var(--texture-mint-surface); }
.page .tex-swatch--mint-raised   { --swatch-color: var(--texture-mint-raised); }
.page .tex-swatch--blue-ground   { --swatch-color: var(--texture-blue-ground); }
.page .tex-swatch--blue-motif    { --swatch-color: var(--texture-blue-motif); }
.page .tex-swatch--blue-surface  { --swatch-color: var(--texture-blue-surface); }
.page .tex-swatch--blue-raised   { --swatch-color: var(--texture-blue-raised); }
.page .tex-swatch--lime-ground   { --swatch-color: var(--texture-lime-ground); }
.page .tex-swatch--lime-motif    { --swatch-color: var(--texture-lime-motif); }
.page .tex-swatch--lime-surface  { --swatch-color: var(--texture-lime-surface); }
.page .tex-swatch--lime-raised   { --swatch-color: var(--texture-lime-raised); }
.page .tex-swatch--orange-ground { --swatch-color: var(--texture-orange-ground); }
.page .tex-swatch--orange-motif  { --swatch-color: var(--texture-orange-motif); }
.page .tex-swatch--orange-surface{ --swatch-color: var(--texture-orange-surface); }
.page .tex-swatch--orange-raised { --swatch-color: var(--texture-orange-raised); }
.page .tex-swatch--navy-ground   { --swatch-color: var(--texture-navy-ground); }
.page .tex-swatch--navy-motif    { --swatch-color: var(--texture-navy-motif); }

/* 71.3 · Texture steps under a brand scale (section 05) */

.page--design-system .scale-texture {
  margin-top: var(--space-20);
  padding-top: var(--space-16);
  border-top: 1px solid var(--border-subtle, var(--border));
}

.page--design-system .scale-texture__head { margin-bottom: var(--space-12); }
.page--design-system .scale-texture__head p { max-width: 720px; }
.page--design-system .scale-texture__row { grid-template-columns: repeat(4, minmax(0, 1fr)); max-width: 640px; }
.page--design-system .scale-texture[data-family="deep-navy"] .scale-texture__row { grid-template-columns: repeat(2, minmax(0, 1fr)); max-width: 320px; }

/* Section labels are nowrap by default; inside the texture strips, the navy
   note and the resources head they have to wrap on a phone. */
.page--design-system .scale-texture__head .section-index,
.page--design-system .product-texture-note .section-index,
.page--design-system .section-resources__head .section-index { white-space: normal; }
.page--design-system .scale-texture__head > div,
.page--design-system .product-texture-note > div { min-width: 0; }

/* 71.4 · Screenshot-backed interface cards (section 45) */

.page--design-system .interface-window__body--shot { min-height: 0; }
.page--design-system .interface-window__shot { width: 100%; height: auto; aspect-ratio: 16 / 10; object-fit: cover; }

@media (max-width: 1023px) {
  .page--design-system .product-texture-grid { grid-template-columns: 1fr; }
  .page--design-system .product-texture-note { grid-template-columns: 1fr; }
  .page--design-system .interface-window__body--shot { min-height: 0; }
}

@media (max-width: 767px) {
  .page--design-system .section-resources__list { grid-template-columns: 1fr; }
  .page--design-system .resource-tile__name { white-space: normal; }
  .page--design-system .product-texture-swatches, .page--design-system .scale-texture__row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .page--design-system .product-texture-stage { min-height: 200px; }
}


/* ==========================================================================
   09 - Unique composition · Adaptive Assist Panel
   The Hextris demo scoring dashboard, api.staging.skillprint.co/scoring/admin/
   hextris-demo/, rebuilt on the system. The shell is the portal's furniture -
   a sticky .sp-nav bar, a page head with an eyebrow, a main column and a rail -
   and every panel is .sp-card, .ui-badge, .field, .sp-alert, .sp-progress,
   .sp-progress-ring, .sp-table, .button-group and .code-block. Only the
   dashboard's own geometry is written here; every value is a token.
   ========================================================================== */

.page--adaptive-assist {
  --aa-max: var(--space-1600);
  --aa-gutter: var(--space-32);
  --aa-rail: var(--space-384);
  --aa-gap: var(--space-16);
  --aa-negative-text: var(--skills-pink-500);
  --grid-container-max: var(--aa-max);
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--ui-page);
  color: var(--ui-text);
}

html:is([data-theme="light"], [data-surface="light"]) > .page--adaptive-assist {
  --aa-negative-text: var(--skills-pink-700);
  color-scheme: light;
}

/* The [hidden] attribute is the page's one show/hide mechanism, and several of
   the boxes it lands on carry display: grid or flex at higher specificity than
   the UA rule. Same remedy the portal's QA pass settled on: no rule below ever
   reveals a hidden element, so the override is safe. */
.page--adaptive-assist [hidden] { display: none !important; }

/* --------------------------------------------------------------------------
   Top bar: the shared navigation with a product label in place of routes
   -------------------------------------------------------------------------- */

.page--adaptive-assist .aa-topbar .sp-nav__inner {
  min-height: var(--space-64);
  gap: var(--space-16);
}

.page--adaptive-assist .aa-topbar .sp-nav__brand { gap: var(--space-12); }

.page--adaptive-assist .aa-topbar .brand-logo {
  width: var(--space-144);
  height: auto;
}

.page--adaptive-assist .aa-topbar__divider {
  width: 1px;
  height: var(--space-24);
  flex: 0 0 1px;
  background: var(--nav-border);
}

.page--adaptive-assist .aa-topbar__product {
  color: var(--nav-muted);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
}

.page--adaptive-assist .aa-topbar .sp-nav__actions { gap: var(--space-10); }

.page--adaptive-assist .aa-live[data-badge-tone="success"] .ui-badge__dot {
  animation: aa-pulse 1.6s var(--ease-standard) infinite;
}

@keyframes aa-pulse {
  50% { opacity: 0.35; }
}

/* --------------------------------------------------------------------------
   Page column and head
   -------------------------------------------------------------------------- */

.page--adaptive-assist .aa-main {
  flex: 1 1 auto;
  width: 100%;
}

.page--adaptive-assist .aa-container {
  width: min(100% - var(--aa-gutter) * 2, var(--aa-max));
  margin-inline: auto;
  padding-block: var(--space-32) var(--space-48);
  display: grid;
  gap: var(--space-24);
}

.page--adaptive-assist .aa-head {
  display: grid;
  gap: var(--space-6);
}

.page--adaptive-assist .aa-eyebrow {
  color: var(--ui-muted);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.page--adaptive-assist .aa-head__row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-16) var(--space-24);
}

.page--adaptive-assist .aa-head h1 {
  margin: 0;
  color: var(--ui-text);
  font-size: var(--app-display-xs-font-size);
  line-height: var(--app-display-xs-line-height);
  font-weight: var(--app-heading-weight);
  letter-spacing: var(--app-title-tracking);
}

.page--adaptive-assist .aa-lede {
  margin: var(--space-6) 0 0;
  max-width: var(--measure-md);
  color: var(--ui-muted);
  font-size: var(--app-text-md-font-size);
  line-height: var(--app-text-md-line-height);
}

.page--adaptive-assist .aa-head__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-8);
}

/* --------------------------------------------------------------------------
   Cards: one head contract for every panel on the page
   -------------------------------------------------------------------------- */

.page--adaptive-assist .sp-card {
  --card-current-padding: 0;
  --card-current-radius: var(--radius-2xl);
  display: grid;
  align-content: start;
  min-width: 0;
}

.page--adaptive-assist .aa-card__head {
  min-height: var(--space-56);
  padding: var(--space-12) var(--space-16);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-8) var(--space-12);
  border-bottom: 1px solid var(--surface-border);
}

.page--adaptive-assist .aa-card__title:is(h2, h3) {
  margin: 0;
  min-width: 0;
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
  color: var(--ui-text);
  font-size: var(--app-text-md-font-size);
  line-height: var(--app-text-md-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--app-heading-tracking);
}

.page--adaptive-assist .aa-card__title .sp-icon {
  --icon-size: var(--icon-size-sm);
  --icon-stroke: var(--icon-stroke-sm);
  color: var(--violet);
}

.page--adaptive-assist .aa-card__tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-8);
}

.page--adaptive-assist .aa-card__body {
  padding: var(--space-16);
  display: grid;
  gap: var(--space-14);
}

.page--adaptive-assist .aa-card__lede {
  margin: 0;
  padding: var(--space-12) var(--space-16) 0;
  color: var(--ui-muted);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
}

.page--adaptive-assist .aa-start__lede {
  margin: 0;
  color: var(--ui-text);
  font-size: var(--app-text-md-font-size);
  line-height: var(--app-text-md-line-height);
}

/* The key folds behind one line, since the demo usually carries its own. */
.page--adaptive-assist .aa-key-box {
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-lg);
  background: var(--surface-raised);
}

.page--adaptive-assist .aa-key-box__summary {
  min-height: var(--space-44);
  padding: var(--space-6) var(--space-12);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-6) var(--space-12);
  color: var(--ui-text);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
  font-weight: var(--weight-semibold);
  list-style: none;
  cursor: pointer;
}

.page--adaptive-assist .aa-key-box__summary::-webkit-details-marker { display: none; }

.page--adaptive-assist .aa-key-box__summary:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-default);
  outline-offset: calc(var(--space-2) * -1);
  border-radius: var(--radius-lg);
}

.page--adaptive-assist .aa-key-box__title {
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
}

.page--adaptive-assist .aa-key-box__title .sp-icon {
  --icon-size: var(--icon-size-xs);
  --icon-stroke: var(--icon-stroke-xs);
  color: var(--violet);
}

.page--adaptive-assist .aa-key-box__state {
  flex: 1 1 auto;
  color: var(--ui-muted);
  font-weight: var(--weight-regular);
}

.page--adaptive-assist .aa-key-box[data-missing] .aa-key-box__state { color: var(--aa-negative-text); }

.page--adaptive-assist .aa-key-box[open] .aa-tech__chevron { transform: rotate(180deg); }

.page--adaptive-assist .aa-key-box .aa-key {
  padding: var(--space-4) var(--space-12) var(--space-12);
}

.page--adaptive-assist .aa-card__note {
  margin: 0;
  padding: var(--space-10) var(--space-16) var(--space-14);
  color: var(--ui-muted);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
}

.page--adaptive-assist .aa-card__note code,
.page--adaptive-assist .aa-field__hint code {
  font-size: var(--app-text-xxs-font-size);
}

/* Small mono readouts: ids, keys, parameter values. */
.page--adaptive-assist .aa-mono {
  color: var(--ui-text);
  font-family: var(--font-mono);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
  font-variant-numeric: tabular-nums;
}

/* Key / value rows. Facts read down the left, values in mono on the right. */
.page--adaptive-assist .aa-kv {
  margin: 0;
  display: grid;
}

.page--adaptive-assist .aa-kv > div {
  min-height: var(--space-36);
  padding: var(--space-6) 0;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-16);
  border-top: 1px solid var(--surface-border);
}

.page--adaptive-assist .aa-kv > div:first-child { border-top: 0; }

.page--adaptive-assist .aa-kv dt,
.page--adaptive-assist .aa-kv .aa-kv__key {
  flex: 0 0 auto;
  color: var(--ui-muted);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
  font-weight: var(--weight-medium);
}

.page--adaptive-assist .aa-kv dd,
.page--adaptive-assist .aa-kv .aa-kv__value {
  margin: 0;
  min-width: 0;
  color: var(--ui-text);
  font-family: var(--font-mono);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
  font-variant-numeric: tabular-nums;
  text-align: end;
  overflow-wrap: anywhere;
}

.page--adaptive-assist .aa-placeholder {
  margin: 0;
  grid-column: 1 / -1;
  padding: var(--space-20) var(--space-12);
  color: var(--ui-muted);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
  text-align: center;
}

/* --------------------------------------------------------------------------
   Idle: the start card and the loop it starts
   -------------------------------------------------------------------------- */

.page--adaptive-assist .aa-start {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
  gap: var(--aa-gap);
  align-items: start;
}

.page--adaptive-assist .aa-key__control {
  display: flex;
  align-items: stretch;
  gap: var(--space-8);
}

.page--adaptive-assist .aa-key__control input {
  flex: 1 1 auto;
  min-width: 0;
  font-family: var(--font-mono);
  font-size: var(--app-text-sm-font-size);
  letter-spacing: 0.02em;
}

.page--adaptive-assist .aa-key__reveal {
  --button-height: var(--space-44);
  flex: 0 0 auto;
}

.page--adaptive-assist .aa-field__hint {
  margin: 0;
  color: var(--ui-muted);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
}

.page--adaptive-assist .aa-loop {
  margin: 0;
  padding: var(--space-4) var(--space-16) var(--space-16);
  list-style: none;
  display: grid;
  gap: var(--space-10);
  counter-reset: aa-step;
}

.page--adaptive-assist .aa-loop li {
  display: grid;
  grid-template-columns: var(--space-28) minmax(0, 1fr);
  gap: var(--space-10);
  align-items: start;
  counter-increment: aa-step;
  color: var(--ui-muted);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
}

.page--adaptive-assist .aa-loop li::before {
  content: counter(aa-step, decimal-leading-zero);
  width: var(--space-28);
  height: var(--space-28);
  display: grid;
  place-items: center;
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-sm);
  background: var(--surface-raised);
  color: var(--ui-text);
  font-family: var(--font-mono);
  font-size: var(--app-text-xxs-font-size);
  font-weight: var(--weight-semibold);
}

.page--adaptive-assist .aa-loop strong {
  display: block;
  color: var(--ui-text);
  font-weight: var(--weight-semibold);
}

.page--adaptive-assist .aa-start__facts {
  padding: 0 var(--space-16) var(--space-8);
}

/* --------------------------------------------------------------------------
   Live: the control strip. Sticky under the top bar so the counters, the
   status line and Exit stay in reach however far the page is scrolled.
   -------------------------------------------------------------------------- */

.page--adaptive-assist .aa-session {
  display: grid;
  gap: var(--aa-gap);
}

.page--adaptive-assist .aa-strip {
  position: sticky;
  top: calc(var(--space-64) + var(--space-8));
  z-index: 40;
  padding: var(--space-10) var(--space-12) var(--space-10) var(--space-16);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-10) var(--space-16);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-2xl);
  background: color-mix(in srgb, var(--surface-box) 92%, transparent);
  -webkit-backdrop-filter: blur(var(--backdrop-blur-lg));
  backdrop-filter: blur(var(--backdrop-blur-lg));
  box-shadow: var(--shadow-sm);
}

.page--adaptive-assist .aa-strip__stats {
  margin: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
}

.page--adaptive-assist .aa-strip__stat {
  min-height: var(--space-36);
  padding: var(--space-4) var(--space-12);
  display: flex;
  align-items: baseline;
  gap: var(--space-8);
  border-radius: var(--radius-lg);
  background: var(--surface-raised);
}

.page--adaptive-assist .aa-strip__stat dt {
  display: inline-flex;
  align-items: center;
  gap: var(--space-4);
  color: var(--ui-muted);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
  font-weight: var(--weight-medium);
}

.page--adaptive-assist .aa-strip__stat dt .sp-icon {
  --icon-size: var(--icon-size-xs);
  --icon-stroke: var(--icon-stroke-xs);
}

.page--adaptive-assist .aa-strip__stat dd {
  margin: 0;
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-6);
  color: var(--ui-text);
  font-size: var(--app-text-md-font-size);
  line-height: var(--app-text-md-line-height);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

.page--adaptive-assist .aa-strip__stat dd small {
  color: var(--ui-muted);
  font-size: var(--app-text-xs-font-size);
  font-weight: var(--weight-regular);
}

.page--adaptive-assist .aa-strip__status {
  --status-accent: var(--personality-blue-500);
  flex: 1 1 var(--space-224);
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--space-8);
  color: var(--ui-muted);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
}

.page--adaptive-assist .aa-strip__status .sp-icon {
  --icon-size: var(--icon-size-xs);
  --icon-stroke: var(--icon-stroke-xs);
  flex: 0 0 auto;
  color: var(--status-accent);
}

.page--adaptive-assist .aa-strip__status span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.page--adaptive-assist .aa-strip__status[data-alert-tone="success"] { --status-accent: var(--core-green-500); }
.page--adaptive-assist .aa-strip__status[data-alert-tone="danger"] { --status-accent: var(--skills-pink-500); color: var(--ui-text); }

html:is([data-theme="light"], [data-surface="light"]) > .page--adaptive-assist .aa-strip__status { --status-accent: var(--personality-blue-600); }
html:is([data-theme="light"], [data-surface="light"]) > .page--adaptive-assist .aa-strip__status[data-alert-tone="success"] { --status-accent: var(--core-green-700); }
html:is([data-theme="light"], [data-surface="light"]) > .page--adaptive-assist .aa-strip__status[data-alert-tone="danger"] { --status-accent: var(--skills-pink-600); }

.page--adaptive-assist .aa-strip__exit {
  margin-inline-start: auto;
}

.page--adaptive-assist .aa-copy .sp-icon {
  --icon-size: var(--icon-size-xs);
  --icon-stroke: var(--icon-stroke-xs);
}

.page--adaptive-assist .sp-alert--compact .sp-alert__text {
  color: var(--surface-text);
}

/* --------------------------------------------------------------------------
   Live: the stage and the rail
   -------------------------------------------------------------------------- */

/* The game and the live scores share one row and one height: the panel
   stretches to the frame, and the skill list inside it scrolls. */
.page--adaptive-assist .aa-layout {
  display: grid;
  grid-template-columns: minmax(0, 3fr) minmax(var(--aa-rail), 2fr);
  gap: var(--aa-gap);
  align-items: stretch;
}

.page--adaptive-assist .aa-details {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: var(--aa-gap);
  align-items: start;
}

.page--adaptive-assist .aa-details > .aa-speed,
.page--adaptive-assist .aa-details > .aa-analysis,
.page--adaptive-assist .aa-details > .aa-record { grid-column: span 2; }

.page--adaptive-assist .aa-card__meta {
  color: var(--ui-muted);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
}

.page--adaptive-assist .aa-stage {
  grid-template-rows: auto minmax(0, 1fr) auto;
}

/* Beside the live scores the frame takes the row's height, so the game is
   as tall as the panel of scores next to it; alone in its column it keeps a
   4 : 3 shape instead. */
.page--adaptive-assist .aa-stage__frame {
  position: relative;
  width: 100%;
  align-self: stretch;
  min-height: var(--space-384);
  overflow: hidden;
  border-block: 1px solid var(--surface-border);
  background: var(--deep-navy-950);
}

.page--adaptive-assist .aa-stage__frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  border: 0;
  background: var(--deep-navy-950);
}

.page--adaptive-assist .aa-stage__veil {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  gap: var(--space-8);
  background: color-mix(in srgb, var(--deep-navy-950) 84%, transparent);
  color: var(--geistface-grey-200);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
  text-align: center;
}

.page--adaptive-assist .aa-stage__ready {
  display: grid;
  justify-items: center;
  gap: var(--space-10);
}

.page--adaptive-assist .aa-stage__ready-hint {
  color: color-mix(in srgb, var(--geistface-grey-200) 78%, transparent);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
}

.page--adaptive-assist .aa-stage__foot {
  padding: var(--space-12) var(--space-16);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-8) var(--space-16);
}

.page--adaptive-assist .aa-stage__hint {
  color: var(--ui-muted);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
}

.page--adaptive-assist .aa-adjust .ui-label {
  color: var(--ui-muted);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
  font-weight: var(--weight-medium);
}

.page--adaptive-assist .aa-adjust {
  min-height: var(--space-32);
  padding: var(--space-4) var(--space-12);
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-full);
  background: var(--surface-raised);
  transition: var(--transition-interactive);
}

.page--adaptive-assist .aa-adjust.is-fresh {
  border-color: color-mix(in srgb, var(--violet) 46%, transparent);
  background: color-mix(in srgb, var(--violet) 14%, var(--surface-raised));
}

.page--adaptive-assist .aa-adjust__text {
  color: var(--ui-text);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
  font-weight: var(--weight-medium);
  font-variant-numeric: tabular-nums;
}

.page--adaptive-assist .aa-adjust__time {
  color: var(--ui-muted);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
}

/* Live scores: the flow ring as the hero, then every skill as a row. */
.page--adaptive-assist .aa-panel {
  grid-template-rows: auto auto minmax(0, 1fr) auto;
  min-height: 0;
}

.page--adaptive-assist .aa-flow-hero {
  padding: var(--space-16);
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-16);
  align-items: center;
  border-bottom: 1px solid var(--surface-border);
}

.page--adaptive-assist .aa-flow__ring {
  --progress-ring-size: var(--space-112);
  --progress-ring-stroke: 5;
  --progress-colour: var(--aa-flow-colour, var(--violet));
}

.page--adaptive-assist .aa-flow__total {
  color: var(--ui-muted);
  font-family: var(--font-mono);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
}

.page--adaptive-assist .aa-flow-hero__name {
  color: var(--ui-text);
  font-size: var(--app-text-md-font-size);
  line-height: var(--app-text-md-line-height);
  font-weight: var(--weight-semibold);
}

.page--adaptive-assist .aa-flow-hero__copy {
  min-width: 0;
  display: grid;
  gap: var(--space-4);
}

.page--adaptive-assist .aa-flow-hero__label {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-8);
}

.page--adaptive-assist .aa-flow-hero__mood {
  color: var(--ui-muted);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
}

.page--adaptive-assist .aa-flow-hero__mood strong {
  color: var(--ui-text);
  font-weight: var(--weight-semibold);
}

.page--adaptive-assist .aa-flow-hero__meta {
  color: var(--ui-muted);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
  font-variant-numeric: tabular-nums;
}

.page--adaptive-assist .aa-skill-list__head {
  padding: var(--space-8) var(--space-16) 0;
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr) var(--space-80);
  gap: var(--space-12);
  color: var(--ui-muted);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
  font-weight: var(--weight-medium);
}

.page--adaptive-assist .aa-skill-list__head span:last-child { text-align: end; }

.page--adaptive-assist .aa-skill-list {
  min-height: var(--space-160);
  padding: var(--space-6) var(--space-16);
  display: grid;
  align-content: start;
  overflow: auto;
  overscroll-behavior: contain;
}

.page--adaptive-assist .aa-skill-row {
  min-height: var(--space-40);
  padding: var(--space-6) 0;
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr) var(--space-80);
  align-items: center;
  gap: var(--space-12);
  border-top: 1px solid var(--surface-border);
  transition: background var(--duration-slow) var(--ease-standard);
}

.page--adaptive-assist .aa-skill-row:first-child { border-top: 0; }

.page--adaptive-assist .aa-skill-row.is-updated {
  background: color-mix(in srgb, var(--violet) 12%, transparent);
}

.page--adaptive-assist .aa-skill-row__name {
  min-width: 0;
  overflow: hidden;
  color: var(--ui-text);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
  font-weight: var(--weight-medium);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.page--adaptive-assist .aa-skill-row__score {
  display: flex;
  align-items: center;
  gap: var(--space-8);
}

.page--adaptive-assist .aa-skill-row__score .sp-progress {
  flex: 1 1 auto;
  --progress-colour: var(--violet);
}

.page--adaptive-assist .aa-skill-row__score .sp-progress__track { height: var(--space-6); }

.page--adaptive-assist .aa-skill-row__value {
  min-width: var(--space-40);
  color: var(--ui-text);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
  text-align: end;
}

.page--adaptive-assist .aa-skill-row .aa-trend {
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
  font-weight: var(--weight-medium);
}

.page--adaptive-assist .aa-skill-row .aa-trend { justify-self: end; }

.page--adaptive-assist .aa-flow__value {
  color: var(--ui-text);
  font-family: var(--font-mono);
  font-size: var(--app-text-xl-font-size);
  line-height: var(--app-text-xl-line-height);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.page--adaptive-assist [data-flow-band="very-low"] { --aa-flow-colour: var(--pink); }
.page--adaptive-assist [data-flow-band="low"] { --aa-flow-colour: var(--orange); }
.page--adaptive-assist [data-flow-band="medium"] { --aa-flow-colour: var(--lime); }
.page--adaptive-assist [data-flow-band="high"] { --aa-flow-colour: var(--green); }
.page--adaptive-assist [data-flow-band="very-high"] { --aa-flow-colour: var(--mint); }

/* Skill scores: the shared table, numerals in mono, the score doubled as a bar. */
.page--adaptive-assist .aa-table {
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
}

.page--adaptive-assist .aa-table :is(th, td) {
  padding: var(--space-10) var(--space-12);
  vertical-align: middle;
  white-space: nowrap;
}

.page--adaptive-assist .aa-table :is(th, td):first-child { padding-inline-start: var(--space-16); }
.page--adaptive-assist .aa-table :is(th, td):last-child { padding-inline-end: var(--space-16); }

.page--adaptive-assist .aa-table td {
  font-family: var(--font-mono);
  font-size: var(--app-text-xs-font-size);
  font-variant-numeric: tabular-nums;
  transition: background var(--duration-slow) var(--ease-standard);
}

.page--adaptive-assist .aa-table td.aa-table__skill {
  color: var(--ui-text);
  font-family: var(--font-ui);
  font-size: var(--app-text-sm-font-size);
  font-weight: var(--weight-medium);
  white-space: normal;
}

.page--adaptive-assist .aa-table td.is-updated {
  background: color-mix(in srgb, var(--violet) 14%, transparent);
}

.page--adaptive-assist .aa-table__score {
  display: flex;
  align-items: center;
  gap: var(--space-8);
}

.page--adaptive-assist .aa-table__score .sp-progress {
  width: var(--space-56);
  --progress-colour: var(--violet);
}

.page--adaptive-assist .aa-table__score .sp-progress__track { height: var(--space-4); }

.page--adaptive-assist .aa-trend {
  display: inline-flex;
  align-items: center;
  gap: var(--space-4);
}

.page--adaptive-assist .aa-trend .sp-icon {
  --icon-size: var(--icon-size-2xs);
  --icon-stroke: var(--icon-stroke-12);
}

.page--adaptive-assist .aa-trend[data-trend="up"] { color: var(--positive-text); }
.page--adaptive-assist .aa-trend[data-trend="down"] { color: var(--aa-negative-text); }
.page--adaptive-assist .aa-trend[data-trend="flat"] { color: var(--ui-muted); }

.page--adaptive-assist .aa-table__placeholder td {
  padding-block: var(--space-24);
  color: var(--ui-muted);
  font-family: var(--font-ui);
  font-size: var(--app-text-sm-font-size);
  text-align: center;
  white-space: normal;
}

/* Creation speed modifier: two readouts, then the drawing. */
.page--adaptive-assist .aa-speed__body {
  padding: var(--space-16);
  display: grid;
  gap: var(--space-12);
}

.page--adaptive-assist .aa-speed__stats {
  margin: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-8);
}

.page--adaptive-assist .aa-speed__stats > div {
  padding: var(--space-8) var(--space-12);
  display: grid;
  gap: var(--space-2);
  border-radius: var(--radius-lg);
  background: var(--surface-raised);
}

.page--adaptive-assist .aa-speed__stats dt {
  color: var(--ui-muted);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
  font-weight: var(--weight-medium);
}

.page--adaptive-assist .aa-speed__stats dd {
  margin: 0;
  color: var(--ui-text);
  font-family: var(--font-mono);
  font-size: var(--app-text-md-font-size);
  line-height: var(--app-text-md-line-height);
  font-weight: var(--weight-semibold);
  font-variant-numeric: tabular-nums;
}

.page--adaptive-assist .aa-chart {
  position: relative;
  min-height: var(--space-160);
}

.page--adaptive-assist .aa-chart__svg {
  width: 100%;
  height: auto;
  display: block;
  overflow: visible;
}

/* SVG type is in viewBox units, sized to render at the rail's width. */
.page--adaptive-assist .aa-chart__svg .grid { stroke: var(--surface-border); stroke-width: 1; }
.page--adaptive-assist .aa-chart__svg .axis-label { fill: var(--ui-muted); font-family: var(--font-mono); font-size: 10px; }
.page--adaptive-assist .aa-chart__svg .area { fill: color-mix(in srgb, var(--violet) 14%, transparent); }
.page--adaptive-assist .aa-chart__svg .line { fill: none; stroke: var(--violet); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.page--adaptive-assist .aa-chart__svg .point { fill: var(--violet); stroke: var(--surface-box); stroke-width: 2; }
.page--adaptive-assist .aa-chart__svg .point.is-latest { fill: var(--mint); }

.page--adaptive-assist .aa-chart__empty {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: var(--space-16);
  display: grid;
  place-items: center;
  color: var(--ui-muted);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
  text-align: center;
}

/* --------------------------------------------------------------------------
   Live: analysis results, the SDK debug panel and the session record
   -------------------------------------------------------------------------- */

.page--adaptive-assist .aa-tabs {
  min-height: var(--space-36);
  box-shadow: none;
}

.page--adaptive-assist .aa-tabs .button-group__item {
  min-height: var(--space-32);
  min-width: 0;
  padding: var(--space-6) var(--space-14);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
}

.page--adaptive-assist .aa-analysis__panel {
  padding: var(--space-16);
}

.page--adaptive-assist .aa-skill-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-8);
}

.page--adaptive-assist .aa-skill-item {
  min-width: 0;
  padding: var(--space-10) var(--space-12);
  display: grid;
  gap: var(--space-2);
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-lg);
  background: var(--surface-raised);
}

.page--adaptive-assist .aa-skill-item__name {
  color: var(--ui-text);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
  font-weight: var(--weight-semibold);
  overflow-wrap: anywhere;
}

.page--adaptive-assist .aa-skill-item__score {
  color: var(--ui-muted);
  font-family: var(--font-mono);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
  font-variant-numeric: tabular-nums;
}

.page--adaptive-assist .aa-skill-item__score strong {
  color: var(--accent-text);
  font-weight: var(--weight-semibold);
}

.page--adaptive-assist .aa-debug__facts,
.page--adaptive-assist .aa-record__facts {
  padding: var(--space-4) var(--space-16);
}

.page--adaptive-assist .aa-record__id {
  padding: var(--space-12) var(--space-16) var(--space-4);
  display: grid;
  gap: var(--space-2);
}

.page--adaptive-assist .aa-record__value {
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--space-8);
}

.page--adaptive-assist .aa-record__value .aa-mono {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The log and the SDK panel are one click away rather than on the page:
   the same disclosure the partner analysis uses for its raw JSON. */
.page--adaptive-assist .aa-tech {
  border: 1px solid var(--surface-border);
  border-radius: var(--radius-2xl);
  background: var(--surface-box);
}

.page--adaptive-assist .aa-tech[open] { border-color: var(--surface-border-hover); }

.page--adaptive-assist .aa-tech__summary {
  min-height: var(--space-56);
  padding: var(--space-8) var(--space-16);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-8) var(--space-16);
  color: var(--ui-text);
  font-size: var(--app-text-md-font-size);
  line-height: var(--app-text-md-line-height);
  font-weight: var(--weight-semibold);
  list-style: none;
  cursor: pointer;
}

.page--adaptive-assist .aa-tech__summary::-webkit-details-marker { display: none; }

.page--adaptive-assist .aa-tech__summary:focus-visible {
  outline: var(--focus-ring-width) solid var(--focus-default);
  outline-offset: calc(var(--space-2) * -1);
  border-radius: var(--radius-2xl);
}

.page--adaptive-assist .aa-tech__title {
  display: inline-flex;
  align-items: center;
  gap: var(--space-8);
}

.page--adaptive-assist .aa-tech__title .sp-icon {
  --icon-size: var(--icon-size-sm);
  --icon-stroke: var(--icon-stroke-sm);
  color: var(--violet);
}

.page--adaptive-assist .aa-tech__meta {
  flex: 1 1 auto;
  color: var(--ui-muted);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
  font-weight: var(--weight-regular);
}

.page--adaptive-assist .aa-tech__action {
  display: inline-flex;
  align-items: center;
  gap: var(--space-4);
  color: var(--ui-muted);
  font-size: var(--app-text-sm-font-size);
  font-weight: var(--weight-medium);
}

.page--adaptive-assist .aa-tech__chevron {
  --icon-size: var(--icon-size-xs);
  --icon-stroke: var(--icon-stroke-xs);
  transition: transform var(--duration-fast) var(--ease-standard);
}

.page--adaptive-assist .aa-tech[open] .aa-tech__chevron { transform: rotate(180deg); }

.page--adaptive-assist .aa-tech__body > .aa-skills { grid-column: 1 / -1; }

.page--adaptive-assist .aa-tech__body {
  padding: 0 var(--space-16) var(--space-16);
  display: grid;
  grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
  gap: var(--aa-gap);
  align-items: start;
  border-top: 1px solid var(--surface-border);
  padding-top: var(--space-16);
}

.page--adaptive-assist .aa-debug__controls {
  padding: var(--space-12) var(--space-16);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-8);
  border-top: 1px solid var(--surface-border);
}

.page--adaptive-assist .aa-debug__controls .button .sp-icon {
  --icon-size: var(--icon-size-xs);
  --icon-stroke: var(--icon-stroke-xs);
}

.page--adaptive-assist .aa-debug__toggle {
  margin: 0;
  padding: 0 var(--space-16) var(--space-12);
  display: flex;
  align-items: center;
  gap: var(--space-10);
  color: var(--ui-muted);
  font-size: var(--app-text-sm-font-size);
  line-height: var(--app-text-sm-line-height);
}

/* --------------------------------------------------------------------------
   The analysis log: the shared code block, wrapped, scrolling, newest last
   -------------------------------------------------------------------------- */

.page--adaptive-assist .aa-log {
  --aa-log-height: var(--space-320);
}

.page--adaptive-assist .aa-log .code-head {
  gap: var(--space-12);
  flex-wrap: wrap;
}

.page--adaptive-assist .aa-log .code-head strong {
  color: var(--code-text);
  font-family: var(--font-ui);
  font-size: var(--app-text-sm-font-size);
}

.page--adaptive-assist .aa-log .code-head span {
  font-variant-numeric: tabular-nums;
}

.page--adaptive-assist .aa-log__body {
  max-height: var(--aa-log-height);
  padding: var(--space-14) var(--space-18);
  overflow: auto;
  overscroll-behavior: contain;
  color: var(--code-text);
  font-family: var(--font-mono);
  font-size: var(--text-xs-font-size);
  line-height: var(--text-sm-line-height);
}

.page--adaptive-assist .aa-log__body:focus-visible {
  outline: 0;
}

.page--adaptive-assist .aa-log__entry {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-12);
  padding: var(--space-2) 0;
}

.page--adaptive-assist .aa-log__time {
  color: var(--code-muted);
  font-variant-numeric: tabular-nums;
}

.page--adaptive-assist .aa-log__text {
  min-width: 0;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}

.page--adaptive-assist .aa-log__entry[data-tone="error"] .aa-log__text { color: var(--syntax-deleted); }
.page--adaptive-assist .aa-log__entry[data-tone="success"] .aa-log__text { color: var(--syntax-inserted); }
.page--adaptive-assist .aa-log__entry[data-tone="update"] .aa-log__text { color: var(--syntax-keyword); }

/* --------------------------------------------------------------------------
   Foot
   -------------------------------------------------------------------------- */

.page--adaptive-assist .aa-foot {
  width: min(100% - var(--aa-gutter) * 2, var(--aa-max));
  margin-inline: auto;
  padding: var(--space-20) 0 var(--space-28);
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-8) var(--space-16);
  border-top: 1px solid var(--ui-border);
  color: var(--ui-muted);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
}

/* --------------------------------------------------------------------------
   Responsive: the grid tiers, then the phone ladder
   -------------------------------------------------------------------------- */

@media (max-width: 1279px) {
  .page--adaptive-assist { --aa-rail: var(--space-320); }
  .page--adaptive-assist .aa-details > .aa-speed,
  .page--adaptive-assist .aa-details > .aa-analysis { grid-column: span 3; }
  .page--adaptive-assist .aa-details > .aa-record { grid-column: span 6; }
}

@media (max-width: 1023px) {
  .page--adaptive-assist { --aa-gutter: var(--space-24); }
  .page--adaptive-assist .aa-container { padding-block: var(--space-24) var(--space-40); }
  .page--adaptive-assist .aa-start { grid-template-columns: minmax(0, 1fr); }
  .page--adaptive-assist .aa-layout { grid-template-columns: minmax(0, 1fr); }
  .page--adaptive-assist .aa-panel { grid-template-rows: auto auto auto auto; }
  .page--adaptive-assist .aa-skill-list { overflow: visible; }
  .page--adaptive-assist .aa-tech__body { grid-template-columns: minmax(0, 1fr); }
  .page--adaptive-assist .aa-stage { grid-template-rows: auto auto auto; }
  .page--adaptive-assist .aa-stage__frame { align-self: start; aspect-ratio: 4 / 3; }
}

@media (max-width: 767px) {
  .page--adaptive-assist { --aa-gutter: var(--space-16); --aa-gap: var(--space-12); }
  .page--adaptive-assist .aa-container { gap: var(--space-16); }
  .page--adaptive-assist .aa-topbar .brand-logo { width: var(--space-120); }
  .page--adaptive-assist .aa-topbar__product { display: none; }
  .page--adaptive-assist .aa-topbar__divider { display: none; }
  .page--adaptive-assist .aa-strip { position: static; padding: var(--space-10) var(--space-12); }
  .page--adaptive-assist .aa-strip__stats { width: 100%; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .page--adaptive-assist .aa-strip__stat { min-height: var(--space-48); padding: var(--space-6) var(--space-10); display: grid; align-content: center; gap: 0; }
  .page--adaptive-assist .aa-strip__status { flex-basis: 100%; }
  .page--adaptive-assist .aa-strip__status span { white-space: normal; }
  .page--adaptive-assist .aa-strip__exit { width: 100%; margin-inline-start: 0; }
  .page--adaptive-assist .aa-card__head { min-height: var(--space-48); padding: var(--space-10) var(--space-14); }
  .page--adaptive-assist .aa-card__body,
  .page--adaptive-assist .aa-analysis__panel,
  .page--adaptive-assist .aa-flow__body,
  .page--adaptive-assist .aa-speed__body { padding: var(--space-14); }
  .page--adaptive-assist .aa-card__note,
  .page--adaptive-assist .aa-debug__facts,
  .page--adaptive-assist .aa-record__facts,
  .page--adaptive-assist .aa-start__facts,
  .page--adaptive-assist .aa-debug__toggle { padding-inline: var(--space-14); }
  .page--adaptive-assist .aa-loop { padding-inline: var(--space-14); }
  .page--adaptive-assist .aa-debug__controls,
  .page--adaptive-assist .aa-stage__foot { padding: var(--space-12) var(--space-14); }
  .page--adaptive-assist .aa-flow-hero { padding: var(--space-14); }
  .page--adaptive-assist .aa-skill-list { padding-inline: var(--space-14); }
  .page--adaptive-assist .aa-skill-row,
  .page--adaptive-assist .aa-skill-list__head { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) var(--space-64); gap: var(--space-8); }
  .page--adaptive-assist .aa-skill-list__head { padding-inline: var(--space-14); }
  .page--adaptive-assist .aa-card__lede { padding-inline: var(--space-14); }
  .page--adaptive-assist .aa-tech__body { padding: var(--space-14); }
  .page--adaptive-assist .aa-record__id { padding-inline: var(--space-14); }
  .page--adaptive-assist .aa-skill-grid { grid-template-columns: minmax(0, 1fr); }
  .page--adaptive-assist .aa-details > :is(.aa-speed, .aa-analysis, .aa-record) { grid-column: span 6; }
  .page--adaptive-assist .aa-stage__frame { min-height: 0; aspect-ratio: 1 / 1; }
  /* The five-column table becomes a stack of cards, each cell captioned by
     its heading, the way the Labs leaderboard folds on a phone. */
  .page--adaptive-assist .aa-table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  .page--adaptive-assist .aa-table,
  .page--adaptive-assist .aa-table tbody,
  .page--adaptive-assist .aa-table tr,
  .page--adaptive-assist .aa-table td { display: block; }
  .page--adaptive-assist .aa-table tbody { display: grid; gap: var(--space-8); padding: var(--space-12) var(--space-14); }
  .page--adaptive-assist .aa-table tr { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-6) var(--space-12); padding: var(--space-10) var(--space-12); border: 1px solid var(--surface-border); border-radius: var(--radius-lg); background: var(--surface-raised); }
  .page--adaptive-assist .aa-table :is(th, td),
  .page--adaptive-assist .aa-table :is(th, td):first-child,
  .page--adaptive-assist .aa-table :is(th, td):last-child { padding: 0; border: 0; white-space: normal; }
  .page--adaptive-assist .aa-table td.aa-table__skill { grid-column: 1 / -1; }
  .page--adaptive-assist .aa-table td[data-label]::before { content: attr(data-label); display: block; color: var(--ui-muted); font-family: var(--font-ui); font-size: var(--app-text-xxs-font-size); line-height: var(--app-text-xs-line-height); font-weight: var(--weight-medium); }
  .page--adaptive-assist .aa-table__placeholder td { grid-column: 1 / -1; padding-block: var(--space-12); }
  .page--adaptive-assist .aa-log { --aa-log-height: var(--space-256); }
  .page--adaptive-assist .aa-log__body { padding: var(--space-12) var(--space-14); }
  .page--adaptive-assist .aa-log__entry { grid-template-columns: minmax(0, 1fr); gap: 0; padding-block: var(--space-4); }
}

@media (max-width: 480px) {
  .page--adaptive-assist { --aa-gutter: var(--space-12); }
  .page--adaptive-assist .aa-topbar .sp-nav__inner { padding-inline: var(--space-12); gap: var(--space-8); }
  .page--adaptive-assist .aa-env { display: none; }
  .page--adaptive-assist .aa-flow-hero { grid-template-columns: minmax(0, 1fr); justify-items: center; text-align: center; }
  .page--adaptive-assist .aa-flow-hero__label { justify-content: center; }
  .page--adaptive-assist .aa-strip__stat dd { flex-wrap: wrap; }
  .page--adaptive-assist .aa-speed__stats { grid-template-columns: minmax(0, 1fr); }
  .page--adaptive-assist .aa-debug__controls .button { flex: 1 1 calc(50% - var(--space-4)); }
}

@media (prefers-reduced-motion: reduce) {
  .page--adaptive-assist .aa-live .ui-badge__dot { animation: none; }
}

/* ============================================================================
   72 · Page label and product attribution (7 September 2026)

   Every surface names itself the same way and says which product it belongs
   to in the same place.

   The page label is the Games Portal's own label under its logo - Geist,
   12/18, medium, 0.06em tracking - and that one spec runs everywhere. Below
   the logo in a sidebar it is Title Case on --side-nav-muted (Games Portal,
   Work Portal, Documentation, Partner Portal). Beside the logo in a top bar
   it follows a 1px divider, in the same Title Case on --nav-muted
   (Documentation, Labs, Live Session Console). On phones a labelled top-bar brand folds the label
   under the logo, as the Live Session Console already did, so the name
   survives beside the bar's actions.

   The product attribution is the site's product-menu row at sidebar scale:
   the product's icon in a 28px tile tinted with its accent, the audience in
   the label size, the platform in mono caps on the accent. The four pairs
   are the site's own - Players and Enterprise on Amplify (Personality Mint
   and Personality Blue), Game Studios on Flow (Core Lime), AI Labs on Signal
   (Skills Orange) - the 500 step on the dark surface and the 900 ink on the
   light one, exactly as .sp-nav__product does on the site. A sidebar carries
   it at its foot, below everything else; a top-bar page carries it in the
   footer beside the identity.
   ========================================================================== */

.sp-page-label {
  color: var(--side-nav-muted);
  font-family: var(--skillprint-font-ui);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
  font-weight: var(--weight-medium);
  letter-spacing: 0.06em;
}

.sp-page-label--nav {
  min-width: 0;
  overflow: hidden;
  color: var(--nav-muted, var(--ui-muted));
  white-space: nowrap;
  text-overflow: ellipsis;
}

.sp-nav__divider {
  flex: 0 0 1px;
  width: 1px;
  height: var(--space-20);
  background: var(--nav-border, var(--ui-border));
}

.sp-nav__brand--labelled {
  display: inline-flex;
  align-items: center;
  gap: var(--space-12);
  min-width: 0;
}

@media (max-width: 480px) {
  .sp-nav__brand--labelled {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-4);
    padding-block: var(--space-8);
  }

  .sp-nav__brand--labelled .sp-nav__divider { display: none; }
}

.sp-product-tag {
  --product-accent: var(--mindset-violet-400);
  --product-ink: var(--product-accent);
  display: flex;
  align-items: center;
  gap: var(--space-10);
  min-width: 0;
  color: inherit;
  text-decoration: none;
}

.sp-product-tag__icon {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: var(--space-28);
  height: var(--space-28);
  border: 1px solid color-mix(in srgb, var(--product-accent) 26%, transparent);
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--product-accent) 12%, transparent);
  color: var(--product-ink);
}

.sp-product-tag__icon .sp-icon {
  --icon-size: var(--icon-size-xs);
  --icon-stroke: var(--icon-stroke-16);
}

.sp-product-tag__copy {
  display: grid;
  min-width: 0;
}

.sp-product-tag__audience {
  overflow: hidden;
  color: var(--product-tag-text, inherit);
  font-family: var(--skillprint-font-ui);
  font-size: var(--app-text-xs-font-size);
  line-height: var(--app-text-xs-line-height);
  font-weight: var(--weight-semibold);
  white-space: nowrap;
  text-overflow: ellipsis;
}

.sp-product-tag__name {
  color: var(--product-ink);
  font-family: var(--skillprint-font-mono);
  font-size: var(--app-text-xxs-font-size);
  line-height: var(--app-text-xxs-line-height);
  font-weight: var(--weight-medium);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
}

.sp-product-tag[data-product="amplify"]    { --product-accent: var(--personality-mint-500); }
.sp-product-tag[data-product="enterprise"] { --product-accent: var(--personality-blue-500); }
.sp-product-tag[data-product="flow"]       { --product-accent: var(--core-lime-500); }
.sp-product-tag[data-product="signal"]     { --product-accent: var(--skills-orange-500); }

html:is([data-surface="light"], [data-theme="light"]) .sp-product-tag[data-product="amplify"]    { --product-ink: var(--personality-mint-900, #28686e); }
html:is([data-surface="light"], [data-theme="light"]) .sp-product-tag[data-product="enterprise"] { --product-ink: var(--personality-blue-900, #144f79); }
html:is([data-surface="light"], [data-theme="light"]) .sp-product-tag[data-product="flow"]       { --product-ink: var(--core-lime-900, #4f6221); }
html:is([data-surface="light"], [data-theme="light"]) .sp-product-tag[data-product="signal"]     { --product-ink: var(--skills-orange-900, #664c22); }

/* Where it sits: the last thing in a sidebar, on the nav links' inset; in a
   footer bar, beside the identity behind a rule, with the links pushed to
   the far edge. Below 650px the bar stacks and the rule goes. */
.sp-side-nav .sp-product-tag {
  padding: var(--space-8) var(--space-10) var(--space-4);
}

.sp-footer .sp-product-tag { --product-tag-text: var(--footer-strong); }

.sp-footer--bar .sp-footer__nav { margin-inline-start: auto; }

.sp-footer--bar .sp-product-tag {
  padding-inline-start: var(--space-24);
  border-inline-start: 1px solid var(--footer-border);
}

@media (max-width: 650px) {
  .sp-footer--bar .sp-product-tag {
    padding-inline-start: 0;
    border-inline-start: 0;
  }
}

/* The collapsed rail keeps the tile and drops the words, like every link. */
.page--portal.nav-collapsed .sp-product-tag {
  width: var(--space-44);
  justify-content: center;
  padding-inline: 0;
}

.page--portal.nav-collapsed .sp-product-tag__copy { display: none; }
