/* Shared tokens. Load before dashboard.css and product.css. */
:root {
  /* Shared primitives. Alpha is local paint; opacity dims the whole subtree.
     Use the nearest step, not a new token for each component. 0/1 are structural. */
  --black: #000000;
  --white: #ffffff;
  --opacity-faint: 0.04;
  --opacity-wash: 0.08;
  --opacity-tint: 0.14;
  --opacity-light: 0.2;
  --opacity-soft: 0.35;
  --opacity-disabled: 0.5;
  --opacity-muted: 0.65;
  --opacity-strong: 0.8;
  --opacity-solid: 0.92;

  /* Black ink at the shared opacity steps, the mirror of the white --wash ladder.
     Shadows, scrim gradients and drop-shadows all draw from it. */
  --shade-1: color-mix(in srgb, var(--black) calc(var(--opacity-tint) * 100%), transparent);
  --shade-2: color-mix(in srgb, var(--black) calc(var(--opacity-light) * 100%), transparent);
  --shade-3: color-mix(in srgb, var(--black) calc(var(--opacity-soft) * 100%), transparent);
  --shade-4: color-mix(in srgb, var(--black) calc(var(--opacity-disabled) * 100%), transparent);
  --shade-5: color-mix(in srgb, var(--black) calc(var(--opacity-muted) * 100%), transparent);
  --shade-6: color-mix(in srgb, var(--black) calc(var(--opacity-strong) * 100%), transparent);
  --shade-7: color-mix(in srgb, var(--black) calc(var(--opacity-solid) * 100%), transparent);

  /* The page's own ground at the same steps, for the scrims that hand off to
     it. These are inks, not a fourth rung: a head drawn in black over artwork
     is a different material from the page below it, and the seam shows where
     one ends. Drawn in --background it is the same material thinning out, and
     the hero's foot already fades to exactly this. */
  --ground-3: color-mix(in srgb, var(--background) calc(var(--opacity-soft) * 100%), transparent);
  --ground-4: color-mix(in srgb, var(--background) calc(var(--opacity-disabled) * 100%), transparent);
  --ground-6: color-mix(in srgb, var(--background) calc(var(--opacity-strong) * 100%), transparent);

  /* Outlines and elevation. Zero is reserved for resets/geometry.
     Only shadows repeated across components earn a token; one-off depth stays
     with the rule that needs it. */
  --border-width: 1px;
  --border-width-strong: 2px;
  --radius-round: 50%;
  --shadow-popover: 0 18px 42px var(--shade-4);
  --shadow-text: 0 1px 3px var(--shade-5);
  --shadow-signal: 0 0 9px color-mix(in srgb, var(--brand) calc(var(--opacity-disabled) * 100%), transparent);

  /* Typography supplements: shared weights, leading and tracking. */
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  --weight-semibold: 600;
  --weight-bold: 700;
  --text-log: 400 0.72rem/1.5 var(--font-mono);
  --text-log-strong: 600 0.72rem/1.5 var(--font-mono);
  --text-icon: 400 1.5rem/1 var(--font-main);
  --leading-tight: 1.25;
  --leading-body: 1.4;
  --leading-reading: 1.6;
  --tracking-tight: -0.015em;
  --tracking-normal: 0.01em;

  /* Data colours are categorical, not competing UI accents. */
  --chart-blue: #6ea8ff;
  --chart-pink: #ff7aa2;
  --chart-cell-bg: hsl(var(--hue, 210) 42% 32%);
  --chart-cell-border: hsl(var(--hue, 210) 45% 48%);
  /* The remainder bucket of a categorical chart — "everything else" is the
     absence of a category, so it never wears one of the categorical hues. */
  --chart-neutral: #6b7178;
  /* A bar's resting fill: a quantity, told apart from the secondary text that
     labels it. */
  --chart-bar: #7f868e;

  /* Activity calendar. One ordered scale: the brand hue gaining weight over the
     page's own surface — the colour an active day already wears in the streak
     strip. A day that was observed with no session is the bare surface; a day
     outside the observed window keeps the page ground and says so with a
     hairline, because "not watched" is not "played nothing". */
  --heat-0: var(--surface);
  --heat-1: color-mix(in srgb, var(--brand) 26%, var(--surface));
  --heat-2: color-mix(in srgb, var(--brand) 50%, var(--surface));
  --heat-3: color-mix(in srgb, var(--brand) 74%, var(--surface));
  --heat-4: var(--brand);
  --heat-untracked: transparent;

  /* Layout. One gutter for every page: the header, the footer and each shell
     inset by it, so a page's left edge is the wordmark's left edge. */
  --page-content-max: 1380px;
  /* Text-led pages — the service sections — hold a narrower column than the
     data screens: a 1380px line of prose has nothing to fill it with. */
  --page-reading-max: 62rem;
  --page-gutter: 1rem;
  --measure: 70ch;

  /* System font */
  --font-main: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text",
    sans-serif;

  /* Three opaque neutral grounds, and every fill in the UI is one of them.
     The page is the darkest; --surface is anything the page draws on itself --
     a card, a panel, a row, a resting control -- and --raised is what sits
     above that: a popover, a menu, a dialog, the well under an avatar, and the
     hover or selected state of something already on --surface. The middle rung
     had been folded into the page for a while, which left a card the same
     colour as the ground under it and nothing but its border to say where it
     ended. Hover moves a rung rather than stacking a white wash.
     Borders and artwork scrims are functional strokes, not grounds. */
  --background: #16181a;
  --surface: #202225;
  --raised: #2b2e31;
  --line: #ffffff24;

  /* Text, status and brand colours */
  --text: #f2f3f4;
  --muted: #9a9ea3;
  --warning: #ffb86f;
  --danger: #ff7b72;
  --brand: #c7f547;

  /* Platform identity */
  --platform-psn-bg: #0070cc29;
  --platform-psn-border: #0070cc6b;
  --platform-psn-text: #4da3e6;
  --platform-steam-bg: #66c0f429;
  --platform-steam-border: #66c0f457;
  --platform-steam-text: #66c0f4;
  --platform-nintendo-bg: #e6001229;
  --platform-nintendo-border: #e6001257;
  --platform-nintendo-text: #ff5b66;
  --platform-xbox-bg: #107c102e;
  --platform-xbox-border: #52c4525c;
  --platform-xbox-text: #79c83d;
  --platform-ps3-bg: #b89bff29;
  --platform-ps3-border: #b89bff57;
  --platform-ps3-text: #b89bff;
  --platform-psvita-bg: #4fd1c529;
  --platform-psvita-border: #4fd1c557;
  --platform-psvita-text: #4fd1c5;
  --platform-switch-bg: #ff8f5e29;
  --platform-switch-text: #ff8f5e;
  --platform-discord-bg: #5865f229;
  --platform-discord-border: #5865f257;
  --platform-discord-text: #8b93f8;
  /* Telegram's own blue, for the button that opens the bot. The mark itself is
     the official logo asset in /services and carries its own colour. */
  --telegram: #2aabee;
  --telegram-hover: #229ed9;
  /* Google's sign-in button, dark theme, straight from its branding
     guidelines: surface, hairline, label, and the white its state layer is
     drawn with. They are Google's values, not this palette's — nothing else may
     reference them, and a palette change must leave them where they are. */
  --google-surface: #131314;
  --google-line: #8e918f;
  --google-ink: #e3e3e3;
  --google-state: #ffffff;

  /* Trophies, rarity, difficulty and log states */
  --trophy-bronze: #c87b46;
  --trophy-silver: #d7deea;
  --trophy-gold: #f0c24b;
  --trophy-platinum: #9fd4ff;
  --trophy-bg: color-mix(in srgb, var(--trophy-platinum) calc(var(--opacity-tint) * 100%), transparent);
  --rarity-ultra: #ffd166;
  --rarity-very-rare: #f0abfc;
  --rarity-rare: #7cc7ff;
  --rarity-uncommon: #aeb9d4;
  /* The two quiet tiers are also the two most frequent, and they were the
     closest pair in the whole palette: ΔE 5.2, against 22-47 between every
     other neighbour on this scale. A trophy nobody struggled for and one a few
     people did looked the same. Common drops instead of uncommon rising, so
     the scale stays "the rarer, the brighter" and keeps clear of the silver. */
  --rarity-common: #7f8da8;
  --difficulty-easy: #7fd1a3;
  --difficulty-medium: #7ecbff;
  --difficulty-hard: #ff9f7e;

  /* Gradients. Every ramp in the UI is named here, so a component never invents
     its own light. Four roles: scrims that keep text legible over artwork,
     fills that carry a value, and tints that mark a state.
     A ramp that fakes a light source on a flat surface is not a role — the
     interface has no highlights, so a new --wash overlay does not belong here. */
  --scrim-hero-side: linear-gradient(90deg, var(--shade-6), var(--shade-1) 70%);
  --scrim-hero-side-strong: linear-gradient(90deg, var(--shade-7) 0%, var(--shade-3) 52%, var(--shade-2) 76%, var(--shade-4) 100%);
  --scrim-hero-foot: linear-gradient(0deg, var(--black) 0%, var(--shade-6) 18%, transparent 64%);
  --scrim-hero-head: linear-gradient(180deg, var(--ground-3), transparent 35%);
  --scrim-caption: linear-gradient(180deg, transparent, var(--shade-6));
  --scrim-nav: linear-gradient(180deg, var(--ground-6) 0%, var(--ground-4) 54%, transparent 100%);
  /* Artwork that has not loaded, and the diagonal reveal of a live card's cover. */
  --hero-placeholder: linear-gradient(135deg, var(--line), var(--black));
  --mask-hero-diagonal: linear-gradient(100deg, transparent 0%, transparent 18%, var(--shade-4) 52%, var(--black) 100%);
  /* Both edges of a horizontal scroller dissolved into the page: an edge that
     fades says the content continues past it, where a hard cut says the data
     ends there. */
  --mask-scroll-x: linear-gradient(
    to right,
    transparent 0,
    var(--black) var(--space-7),
    var(--black) calc(100% - var(--space-7)),
    transparent 100%
  );

  /* A fill whose length is the datum: warm-to-brand reads as "more". */
  --fill-value-up: linear-gradient(180deg, var(--brand), var(--warning));

  /* Tints: a state written across a row or pill, not a light source. A ramp that
     reads a component's own custom property (the podium's --grade) cannot live
     here: var() inside a token resolves against :root, so it stays local. */
  --tint-platinum: linear-gradient(90deg, color-mix(in srgb, var(--trophy-platinum) calc(var(--opacity-tint) * 100%), transparent), color-mix(in srgb, var(--trophy-platinum) calc(var(--opacity-wash) * 100%), transparent) 58%, color-mix(in srgb, var(--white) calc(var(--opacity-faint) * 100%), transparent));

  /* Community banner artwork. */
  --scrim-banner-foot: linear-gradient(0deg, var(--shade-7), transparent 80%);
  --banner-gwm-stripes: repeating-linear-gradient(125deg, transparent 0 75px, var(--raised) 76px 78px, transparent 79px 140px);

  /* Effects and focus */
  /* The ring is painted over the artwork, so a translucent value is not one
     colour: 24% white lands differently on a pale cover than on a dark one, and
     the frame meant to make every game icon look alike came out a different
     tone per game. Opaque is what makes the set read as one style -- the same
     colour around all of them, holding a light logo off the row behind it.
     A game's artwork is the one thing on the page the palette does not control,
     so the ring is where the site puts its own colour back on top of it -- at
     half strength, which still reads as the brand next to a cover but stays
     under the full lime the focus ring and the live dot are spending. */
  --ring-icon: color-mix(
    in srgb,
    var(--brand) calc(var(--opacity-disabled) * 100%),
    color-mix(in srgb, var(--white) calc(var(--opacity-light) * 100%), var(--background))
  );
  --shadow: 0 24px 80px var(--shade-3);
  --focus-ring: var(--border-width-strong) solid var(--brand);
  --focus-ring-offset: 2px;

  /* Motion: feedback and entrance */
  --dur-1: 150ms;
  --dur-2: 260ms;
  --ease-out: cubic-bezier(0.25, 1, 0.5, 1);

  /* Spacing. One ladder, about x1.4 a rung: padding and gap take the nearest
     step instead of a hand-picked value. Rules name the rung directly -- the
     ladder used to carry a second set of role names on top of it (--pad-panel
     and --space-panel were both --space-6), and one object drawn through two
     names for one value is how the card recipes drifted apart. The two
     shorthands that survive pair two rungs for a single padding declaration.
     Geometry measured against a specific object -- an icon's width, a hairline
     seam, a clamp against the viewport -- is not spacing: it stays a literal in
     the rule that needs it. */
  --space-1: 0.15rem;
  --space-2: 0.25rem;
  --space-3: 0.4rem;
  --space-4: 0.55rem;
  --space-5: 0.75rem;
  --space-6: 1rem;
  --space-7: 1.35rem;
  --space-8: 2rem;
  --space-9: 3rem;

  --pad-card-compact: var(--space-4) var(--space-5);
  --pad-chip: var(--space-2) var(--space-4);

  /* Typography: hero and display are fluid; other roles use fixed sizes */
  --text-hero: 600 clamp(2.25rem, 1.3rem + 3vw, 4rem)/1.04 var(--font-main);
  --text-display: 200 clamp(1.9rem, 1.1rem + 3.3vw, 2.75rem)/1.02 var(--font-main);
  --text-page: 700 1.5rem/1.15 var(--font-main);
  --text-title: 700 1.15rem/1.1 var(--font-main);
  --text-item: 700 0.9rem/1.25 var(--font-main);
  --text-body: 400 0.82rem/1.35 var(--font-main);
  --text-meta: 600 0.72rem/1.25 var(--font-main);
  --text-micro: 500 0.625rem/1.25 var(--font-main);

  /* Reading, editable values and uppercase labels */
  --text-reading: 400 1rem/1.6 var(--font-main);
  --text-input: 400 1rem/1.4 var(--font-main);
  --tracking-label: 0.06em;

  /* Corner scale */
  --radius-xs: 4px;
  --radius-control: 8px;
  --radius-surface: 16px;
  --radius-pill: 999px;
}
@media (max-width: 720px) {
  :root {
    --text-body: 400 0.875rem/1.4 var(--font-main);
    --text-meta: 600 0.75rem/1.3 var(--font-main);
    --text-micro: 500 0.6875rem/1.3 var(--font-main);
  }
}
