/* Presets Hub, layer 1 and the world contract.
   R2 section 1 (values), S sections 1, 2, 3, 5 and 6.
   One file, no build step, no bundler. Components read names, never values. */

/* The platform face. S section 5.1 wants it self hosted; the woff2 is not in
   the tree, so this is R2's Google Fonts entry with the same swap policy.
   Replace this @import with a local @font-face when the file lands, and keep
   the fallback face below either way. */
@import url("https://fonts.googleapis.com/css2?family=Mona+Sans:wdth,wght@75..125,200..900&display=swap");

/* The size-adjust fallback is what makes font-display: swap safe next to a
   56 px price: the swap changes glyphs and nothing else. S section 5.1: these
   three overrides are a starting point and must be measured against the real
   file before ship. */
@font-face{
  font-family:"Mona Sans Fallback";
  src:local("Helvetica Neue"),local("Arial"),local("Roboto");
  size-adjust:97%; ascent-override:92%; descent-override:24%; line-gap-override:0%;
}

:root{
  /* colour: never themed, never overridden by a world */
  --px-ink:#0D1014; --px-ink-2:#151A21; --px-ink-3:#1E252F;
  --px-chalk:#F2F4F7; --px-dim:#98A2B3; --px-rule:#262E39; --px-seam:#F2F4F7;

  /* the one signal. Money only: CTA fill, save chip, timer, cart count. */
  --px-signal:#FF2D6F; --px-signal-deep:#D6004E;
  --px-signal-ink:#0D1014; --px-signal-on:#FFFFFF;

  /* state, non-negotiable meanings */
  --px-owned:#7C8899; --px-ok:#34D399; --px-focus:#7DD3FC;

  /* geometry. Radius is information: 6 on anything you can buy, 2 on the save
     chip, 999 on filter pills, 14 on modal sheets. Nothing else gets one. */
  --px-r-card:6px; --px-r-chip:2px; --px-r-pill:999px; --px-r-sheet:14px;
  --px-hair:1px; --px-icon-stroke:1.5px;

  /* space, one 4 px scale */
  --px-1:4px;  --px-2:8px;  --px-3:12px; --px-4:16px; --px-5:20px; --px-6:24px;
  --px-8:32px; --px-10:40px; --px-12:56px; --px-14:72px; --px-16:96px;
  --px-20:128px; --px-24:160px;

  /* type: one family, the width axis is the platform's signature */
  --px-font:"Mona Sans","Mona Sans Fallback",system-ui,sans-serif;

  /* type scale, mobile column (R2 section 1.3). Desktop below. */
  --px-t-price-hero:56px; --px-lh-price-hero:.92;
  --px-t-display:clamp(34px,8.8vw,40px); --px-lh-display:1.02;
  --px-t-h2:24px; --px-t-h3:18px; --px-t-price:20px; --px-t-was:14px;
  --px-t-body:16px; --px-t-ui:15px; --px-t-micro:12px;

  /* z-order, the whole scale (O section 1.2), nothing outside it */
  --px-z-plate:0; --px-z-rail:5; --px-z-topbar:20; --px-z-buybar:30;
  --px-z-scrim:40; --px-z-sheet:50; --px-z-toast:60;

  /* motion, one rhythm (R2 section 8) */
  --px-t-exit:160ms; --px-t-enter:220ms; --px-t-wipe:480ms;
  --px-e-exit:cubic-bezier(.4,0,1,1);
  --px-e-enter:cubic-bezier(0,0,.2,1);
  --px-e-wipe:cubic-bezier(.2,.7,.2,1);

  /* the seam mark (R2 section 1.9): two vertical rules 6 px apart with a cross
     bar. A CSS mask, not a text arrow: \2194 renders as an emoji on some
     Android builds, which is what pack_page.py and playable.py ship today. */
  /* The drag handle glyph: a left and right arrow, the one affordance that
     reads as draggable at 28px on any photograph. */
  --px-icon-drag:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%230D1014' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M10 8l-4 4 4 4M14 8l4 4-4 4'/%3E%3C/svg%3E");
  --px-icon-seam:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 5v14M15 5v14M9 12h6'/%3E%3C/svg%3E");

  /* World token defaults, so an unthemed section is legal and no component
     needs a fallback argument or a second code path (S section 1). */
  --w-ground:var(--px-ink); --w-text:var(--px-chalk); --w-dim:var(--px-dim);
  --w-dim-2:color-mix(in srgb, var(--w-dim) 65%, var(--w-text));
  --w-accent:var(--px-chalk); --w-accent-text:var(--px-ink);
  --w-rule:var(--px-rule); --w-display:var(--px-font);
  --w-case:none;
  --px-signal-here:var(--px-signal); --px-signal-here-ink:var(--px-signal-ink);
}

@media (min-width:1024px){
  :root{
    --px-t-price-hero:88px; --px-lh-price-hero:.90;
    --px-t-display:64px; --px-lh-display:1.0;
    --px-t-h2:34px; --px-t-h3:19px; --px-t-price:22px; --px-t-was:15px;
    --px-t-body:17px; --px-t-ui:15px; --px-t-micro:12.5px;
  }
}

/* ------------------------------------------------------------------
   Layer 2: the world contract (S section 2).

   A world block is generated in Python from the brand's page: block in
   platform/config/brands.yaml and shipped in /static/hub/worlds.css. This
   file declares the contract: the exact ten names a world sets, and where
   each one comes from. Nothing here sets a brand colour.

     --w-ground        page.ground        verbatim
     --w-text          page.text          verbatim
     --w-dim           page.dim           verbatim, never under 15 px in a plate
     --w-accent        page.accent        decorative marks only
     --w-accent-text   page.accent_text   verbatim
     --w-rule          page.rule          verbatim
     --w-display       page.display       the full stack, ending in a generic family
     --w-case          page.heading_case  none or uppercase
     --px-signal-here      computed in Python: luminance(ground) < .18
     --px-signal-here-ink  ? var(--px-signal) / var(--px-signal-ink)
                           : var(--px-signal-deep) / var(--px-signal-on)

   Derived in CSS, so they live in the emitted block, not in Python:
     --w-surface   = color-mix(in srgb, var(--w-ground) 92%, var(--w-text))
     --w-surface-2 = color-mix(in srgb, var(--w-ground) 84%, var(--w-text))
     --w-dim-2     = color-mix(in srgb, var(--w-dim) 65%, var(--w-text))
     --w-rule-soft = color-mix(in srgb, var(--w-rule) 55%, var(--w-ground))
     --w-scrim     = color-mix(in srgb, var(--w-ground) 72%, transparent)
     --w-tint-2 / -6 / -12 = color-mix(in srgb, var(--w-accent) N%, var(--w-ground))
     --w-atmos     = url(...) the pair's 24 px LQIP, blurred at authoring time

   A --w-* token never reaches a price, a struck price, a save chip, a timer,
   a button label, a badge, the cart count or the subtotal. If you can spend
   money on it, it is --px-*. Enforced by the lint in S section 9.1 test 4.
   ------------------------------------------------------------------ */

[data-world]{ position:relative; background:var(--w-ground); color:var(--w-text); }
/* N4 (a11y audit, finding 6). --w-dim on --w-surface-2 fails 4.5:1 in 45 of the
   60 worlds (worst rainpane 3.49:1, measured live at 4.24:1 on reeltint's home
   plate and 3.72:1 on roamhue's brand panel). --w-surface-2 is the ground
   pulled 16 per cent toward the text, which eats most of dim's headroom.
   --w-dim-2 is dim pulled 35 per cent toward the text: worst case across all
   60 worlds on ground, surface and surface-2 is then 4.71:1. Declared HERE and
   not on :root, because a custom property substitutes on the element it is
   declared on: from :root it would resolve against the instrument's dim, not
   the world's. Secondary copy on a world PANEL takes this; secondary copy on
   the world's own ground keeps --w-dim, which already passes (min 4.76:1). */
[data-world]{ --w-dim-2:color-mix(in srgb, var(--w-dim) 65%, var(--w-text)); }
[data-world]::before{
  content:""; position:absolute; inset:0; z-index:var(--px-z-plate); pointer-events:none;
  background:var(--w-atmos) center/cover no-repeat;
  opacity:.18; filter:saturate(1.15);
}
[data-world] > *{ position:relative; z-index:1; }
[data-world]{ transition:none; }            /* a plate never fades in */

/* Reduced motion, one place, one set of rules (S section 6). What it must not
   remove: the timer digits, the strikethrough, the price value, the cart
   count. A countdown is information, not decoration, so none of those is
   built as a CSS animation in the first place. */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.01ms !important; animation-iteration-count:1 !important;
    transition-duration:.01ms !important; scroll-behavior:auto !important;
  }
  [data-world]::before{ opacity:.10; }
  ::view-transition-group(*),::view-transition-old(*),::view-transition-new(*){
    animation:none !important;
  }
}

/* Low power (S section 6). The class is set once at boot from
   matchMedia('(update:slow)'), navigator.deviceMemory < 4 or saveData. It
   does exactly this and no more; everything commercial is untouched. */
.px-cheap [data-world]::before{ display:none; }
