/* ═══════════════════════════════════════════════════════════════════════
   Popcode UI — shared tokens
   ───────────────────────────────────────────────────────────────────────
   WHY THIS FILE EXISTS, given the repo's inline-duplication idiom:

   The composite.js lesson (2026-07-17) was about shared *JavaScript that a
   code path calls*. When it failed to load, `window.compositeBadgedImage is
   not a function` threw and the order page broke. CSS has the opposite
   failure mode: if this file 404s a page renders with unstyled buttons and
   keeps working. Nothing throws, nothing blocks checkout.

   The precedent is already here: /assets/fonts.css is a shared stylesheet
   linked by 21 pages carrying the base64 CooperBT, and nav.js injects a
   shared <style> block on every page. Shared CSS is already how this site
   works. Duplicating tokens per page is what produced the 21 button heights
   and 9 radii the audit measured, and would produce them again.

   ROLLOUT: this file is linked per page, as that page is converted. 24 pages
   already carry class="btn" in markup while only 7 define a .btn rule, so
   linking it everywhere at once would restyle 17 unconverted pages. Add the
   <link> in the same commit that converts the page.

   Link it FIRST in <head>, before the page's own <style>, so a page can
   always override. Never put layout in here — tokens only.

   RULES THAT HOLD EVERYWHERE:
     · height is fixed per size, so a border can never change it
     · every variant carries a 1px border, transparent where unwanted
     · radius is pill for text, 50% for icon-only. Nothing else.
     · family, size and weight are always set, never left to the browser
     · buttons do not cast shadows. `.btn-float` is the one exception and
       it is for controls that sit over an image.

   Colours read the page's own custom properties where they exist
   (var(--ink, …)), so a page keeps its palette. The only things declared on
   :root are the card tokens below, all prefixed --pc- — a namespace no page
   used before they were added, so they cannot collide with --ink/--bg/--grad.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── Card tokens ──────────────────────────────────────────────────────
   Measured across 21 pages: 160 surfaces wearing 14 radii and 14 shadows.
   Most of that was one thing — an app card — written fourteen times.

   These are TOKENS, not classes. Pages keep their own .card / .section /
   .content / .cat-card names and reference the variable, so nothing depends
   on a shared class name and a page can still opt out by writing a literal.

   TWO RADII. A card, and a surface nested inside one (a thumbnail, a stage,
   a preview well). Inner is visibly smaller so the nesting reads.

   TWO ELEVATIONS. Resting is every ordinary card. Raised is for something
   floating over the page — a dialog, a popover — where the shadow is doing
   the work of saying "this is above everything else".

   WHAT THESE DO NOT COVER, deliberately: illustration. The phones, product
   mockups and hero cards cast deep dramatic shadows (0 40px 80px on the
   homepage phone) because they are pictures of objects, not surfaces of the
   UI. Same reasoning as .btn-float. Leave them alone; a token would flatten
   them into cards and lose the point. */
:root {
  --pc-radius-card: 20px;
  --pc-radius-inner: 12px;
  --pc-shadow-card: 0 2px 12px rgba(0, 0, 0, .06);
  /* A clickable card's hover lift. Four of them had four values; this is the
     one the two storefront grids already used, so the shop is unchanged and
     the odd ones come to it. Not for inner thumbnails -- .pg-thumb and
     .pk-thumb lift on their own, smaller scale. */
  --pc-shadow-card-hover: 0 12px 32px rgba(0, 0, 0, .12);
  --pc-shadow-raised: 0 16px 40px rgba(20, 16, 40, .18);
  /* A bottom sheet cannot use --pc-shadow-raised: it is anchored to the
     bottom edge and casts UPWARD. Same weight, negative y. */
  --pc-shadow-sheet: 0 -10px 44px rgba(0, 0, 0, .22);
  /* A small popover -- a share menu, a kebab menu, a tip. Lighter than a
     modal because it is attached to the thing that opened it rather than
     covering the page, and it takes --pc-radius-inner, not the card radius:
     20px on something the size of three menu rows reads as a bubble. */
  --pc-shadow-popover: 0 8px 32px rgba(0, 0, 0, .18);
}

/* ── Page title ───────────────────────────────────────────────────────
   The h1 at the top of an app page: "My Popcodes", "Your cart", "Edit
   Popcode". Measured across 21 pages at two widths it rendered at 12 sizes,
   and the pages expressed the same intent with OPPOSITE breakpoints -- one
   group set 36px and stepped UP to 42 at min-width 960, the other set 44 and
   stepped DOWN at max-width 680. Between 681 and 959 that left an 8px gap
   between pages doing the same job.

   One token, one breakpoint. A page sets `font-size: var(--pc-h1)` and drops
   its own media query. 34 rather than 36 on phones because the app pages with
   the tightest headers already used 34 and shrinking can only reduce overflow
   risk; manage keeps a further step below 389px, which is load-bearing for
   its heading-plus-button row.

   NOT for marketing heroes. index (100), shop (90) and pricing (36) are
   deliberate, set per page, and shop's was chosen by hand. */
:root { --pc-h1: 44px; }
@media (max-width: 680px) { :root { --pc-h1: 34px; } }

/* ── Buttons ─────────────────────────────────────────────────────────── */

.btn {
  --btn-h: 44px;          /* md — the default */
  --btn-pad: 22px;
  --btn-fs: 15px;

  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;

  /* Fixed height + border-box is what makes the border free. A bordered and
     an unbordered button at the same size are the same height, to the pixel.
     This is the fix for the hero pair that prompted the audit. */
  height: var(--btn-h);
  padding: 0 var(--btn-pad);
  border: 1px solid transparent;
  border-radius: 999px;

  font-family: var(--sans, 'Inter', -apple-system, BlinkMacSystemFont, system-ui, sans-serif);
  font-size: var(--btn-fs);
  font-weight: 600;
  line-height: 1;
  letter-spacing: inherit;
  text-decoration: none;
  white-space: nowrap;

  background: transparent;
  color: var(--ink, #1a1a1a);
  cursor: pointer;
  appearance: none;
  transition: transform .12s ease, background-color .15s ease,
              border-color .15s ease, color .15s ease;
}

.btn:hover { transform: translateY(-1px); }
.btn:active { transform: none; }
.btn:focus-visible { outline: 2px solid #7657FC; outline-offset: 2px; }
.btn:disabled,
.btn[aria-disabled="true"] { opacity: .45; cursor: default; transform: none; }

/* Sizes. Three, and only three. */
.btn-sm { --btn-h: 36px; --btn-pad: 16px; --btn-fs: 14px; }
.btn-lg { --btn-h: 52px; --btn-pad: 28px; --btn-fs: 16px; }

/* Intents. */
.btn-primary   { background: var(--ink, #1a1a1a); color: #fff; }
.btn-primary:hover { background: #000; }

.btn-secondary { border-color: var(--ink, #1a1a1a); color: var(--ink, #1a1a1a); }
.btn-secondary:hover { background: var(--ink, #1a1a1a); color: #fff; }

.btn-quiet     { background: #fff; border-color: var(--line, #e3e3e3);
                 color: var(--ink, #1a1a1a); }
.btn-quiet:hover { border-color: #c9c9c9; }

.btn-danger    { background: #c92a2a; color: #fff; }
.btn-danger:hover { background: #ad2121; }

/* On the brand gradient an outlined button reads as white-on-white, so the
   secondary fills instead. Used by the hero. */
.on-grad .btn-secondary { background: #fff; border-color: #fff; color: var(--ink, #1a1a1a); }
.on-grad .btn-secondary:hover { background: #fff; color: var(--ink, #1a1a1a); }

/* Icon-only: a circle, square at the size's height. */
.btn-icon { --btn-pad: 0; width: var(--btn-h); border-radius: 50%; }
.btn-icon svg { width: 20px; height: 20px; display: block; }
.btn-sm.btn-icon svg { width: 16px; height: 16px; }

/* The one sanctioned shadow: a control floating over an image, where
   elevation is doing real work separating it from the photo behind. */
.btn-float { box-shadow: 0 6px 20px rgba(20, 16, 40, .12); }

/* Text-only affordance. Not on the height scale — it is a link that happens
   to sit among buttons, so it gets the family and weight and nothing else. */
.btn-link {
  height: auto; padding: 8px 10px; border-color: transparent;
  background: none; color: var(--muted, #56514a);
  font-size: 14px; text-decoration: none;
}
.btn-link:hover { color: var(--ink, #1a1a1a); transform: none; }

/* Escape hatch for a button whose label genuinely has to wrap. Keeps the
   family, weight and radius; gives up the fixed height. Use sparingly —
   every use is a button that will not line up with its neighbours. */
.btn-auto { height: auto; min-height: var(--btn-h); padding-block: 12px;
            white-space: normal; line-height: 1.25; }

/* Full-width, for sheets and mobile bars. */
.btn-block { display: flex; width: 100%; }

/* Centred under a column of copy. Block-level so margin-inline works, still
   flex so the label stays centred.

   NEVER override .btn's display with `block` or `inline-block` to get this.
   The height here is fixed, and only flex centres the label inside it — with
   `display: block` the label top-aligns and the button reads bottom-heavy.
   If a page needs .btn to be block-level, it wants `flex`, not `block`. */
.btn-centered { display: flex; width: fit-content; margin-inline: auto; }
