/* ==========================================================================
   SniperCaddie — "Scorecard"

   Design intent, so future edits don't drift back to a generic dashboard:

   · It should read like a printed scorecard, not a SaaS admin panel. That means
     hairline rules doing the structural work, an editorial serif for numerals,
     uppercase micro-labels, and asymmetry — one dominant figure with a ruled
     strip beside it, never a row of identical equal-weight tiles.
   · Colour is an accent, not a surface treatment. Panels are near-flat; the
     baby-blue→lilac→pink ramp appears on small marks, one rule, and the ball.
     Resist gradient-filling large areas.
   · Motion is golf: a struck ball arcs across the page, a putt rolls into each
     card, the miss state holes out into a cup. Ambient drifting blobs are
     banned — every animation should be something that happens on a course.
   ========================================================================== */

/* `redesign` is declared last and therefore outranks everything above it,
   regardless of selector specificity. That is deliberate: the 2026 rebuild
   restyles the shell, the score card, the sidebar and the product cards, while
   the club swing, the glitch burst, the hole-out easter egg and the animated
   mark keep working from the original rules untouched. */
@layer tokens, base, motion, layout, components, redesign;

@layer tokens {
  :root {
    --sky-50:#f1f7fd; --sky-100:#dcedfb; --sky-200:#bfe0f7; --sky-300:#9ccdf1;
    --sky-400:#77b5e8; --sky-500:#5498d8; --sky-600:#3f79b4; --sky-700:#305c8a; --sky-900:#0f2438;

    --lilac-100:#ebe4f6; --lilac-200:#d9cdee; --lilac-300:#c3b0e2; --lilac-400:#ab92d4;
    --lilac-500:#9377c2; --lilac-600:#785da6; --lilac-700:#5d4783; --lilac-900:#241b33;

    --pink-300:#f0b6d3; --pink-400:#d98fb8; --pink-600:#a8437c;

    --ramp: linear-gradient(100deg, var(--sky-400), var(--sky-300) 34%, var(--lilac-400) 74%, var(--pink-400));

    --serif: ui-serif, "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
    --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, system-ui, sans-serif;
    --mono: ui-monospace, "SF Mono", Menlo, monospace;

    /* One dial for the whole glitch. It is a wink, not a takeover: at .5 the
       canvas wash dims the entire page and the effect stops being charming
       within about two viewings. Keep it low. */
    --glitch-opacity: .1;

    --ease: cubic-bezier(.22,.68,.28,1);
    --ease-out: cubic-bezier(.16,1,.3,1);

    /* dark */
    --ink: #080c13;
    --panel: #0d131c;
    --panel-2: #121a25;
    --rule: rgba(190, 214, 238, .14);
    --rule-soft: rgba(190, 214, 238, .07);
    --text: #e9eff6;
    --text-2: #9dafc2;
    --text-3: #63758a;
    --ball: #f4f8fc;
  }

  @media (prefers-color-scheme: light) {
    :root {
      --ink: #f4f6f9;
      --panel: #ffffff;
      --panel-2: #f7fafc;
      --rule: rgba(28, 58, 90, .16);
      --rule-soft: rgba(28, 58, 90, .08);
      --text: #101a25;
      --text-2: #4b5c6d;
      --text-3: #7d8d9d;
      --ball: #ffffff;
    }
  }
}

@layer base {
  * { box-sizing: border-box; }

  body {
    margin: 0;
    padding: 0 clamp(16px, 5vw, 56px) 0;
    min-height: 100vh;
    background: var(--ink);
    color: var(--text);
    font: 15px/1.55 var(--sans);
    -webkit-font-smoothing: antialiased;
    overflow-x: hidden;
  }

  /* Mown fairway stripes — the only background texture. Barely visible by
     design; if you can clearly see it, it's too strong. */
  .contours {
    position: fixed; inset: 0; z-index: -2; pointer-events: none;
    background:
      radial-gradient(70vw 50vh at 82% -10%, rgba(171,146,212,.13), transparent 60%),
      radial-gradient(60vw 45vh at 4% 8%, rgba(84,152,216,.11), transparent 62%),
      repeating-linear-gradient(102deg, transparent 0 54px, var(--rule-soft) 54px 55px);
  }
  .grain {
    position: fixed; inset: 0; z-index: -1; pointer-events: none; opacity: .035;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
  }

  h1,h2,h3 { margin: 0; font-weight: 600; }
  a { color: inherit; text-decoration: none; }
  ::selection { background: var(--lilac-300); color: var(--lilac-900); }
  ::-webkit-scrollbar { width: 9px; height: 9px; }
  ::-webkit-scrollbar-thumb { background: var(--rule); border-radius: 5px; }

  /* Shared micro-label. Every uppercase caption in the UI uses this. */
  .rule-label, .l, .diag h3, .tagline {
    font-size: 10px; text-transform: uppercase; letter-spacing: .16em;
    font-weight: 600; color: var(--text-3);
  }
}

/* ============================================================ golf motion */
@layer motion {
  /* --- the struck ball ---------------------------------------------------- */
  /* X and Y are animated on two nested elements: linear travel outside, eased
     rise-and-fall inside. That gives a real ballistic arc, and unlike an
     offset-path it stays correct at any viewport width. */
  @keyframes fly-x { from { transform: translateX(0); } to { transform: translateX(calc(100vw - clamp(16px,5vw,56px) * 2 - 34px)); } }
  @keyframes fly-y {
    0%   { transform: translateY(0) rotate(0deg); }
    45%  { transform: translateY(-58px) rotate(320deg); }
    72%  { transform: translateY(-30px) rotate(520deg); }
    88%  { transform: translateY(-6px)  rotate(650deg); }
    94%  { transform: translateY(-11px) rotate(690deg); }
    100% { transform: translateY(0) rotate(720deg); }
  }
  /* Rotation is about the grip, and the ball flies left→right, so the head has
     to travel left→right through impact.
     Watch the signs: with the pivot ABOVE the head, CSS rotate(+deg) swings the
     head LEFT and rotate(-deg) swings it RIGHT. So the backswing is positive
     (club goes back over the left shoulder) and the follow-through negative.
     Getting this backwards makes the club appear to hit the ball away from the
     direction it then flies. */
  @keyframes swing {
    0%   { transform: rotate(6deg); }     /* address — blade just behind the ball */
    10%  { transform: rotate(12deg); }    /* waggle */
    34%  { transform: rotate(78deg); }    /* top of the backswing */
    52%  { transform: rotate(-4deg); }    /* IMPACT — ball launches here */
    70%  { transform: rotate(-56deg); }   /* follow-through, up and right */
    86%  { transform: rotate(-38deg); }
    100% { transform: rotate(6deg); }     /* back to address */
  }
  @keyframes trail  { 0% { opacity: 0; clip-path: inset(0 100% 0 0); } 12% { opacity: .55; } 90% { opacity: .35; } 100% { opacity: 0; clip-path: inset(0 0 0 0); } }
  @keyframes flutter{ 0%,100% { transform: skewY(0) scaleX(1); } 50% { transform: skewY(-7deg) scaleX(.9); } }
  @keyframes putt   { from { transform: translateX(-16px) rotate(0); opacity: 0; } to { transform: translateX(0) rotate(300deg); opacity: 1; } }
  @keyframes holeout{
    0%   { transform: translateX(-70px) rotate(0); }
    72%  { transform: translateX(0) rotate(560deg); }
    84%  { transform: translateX(2px) translateY(1px) rotate(600deg); }
    100% { transform: translateX(0) translateY(9px) scale(.55) rotate(640deg); opacity: .25; }
  }
  @keyframes stamp  { from { transform: scale(1.5) rotate(-6deg); opacity: 0; } to { transform: none; opacity: 1; } }
  @keyframes deal-in{ from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
  @keyframes glide  { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-2.5px); } }
  @keyframes tick   { to { background-position: 200% 0; } }
  @keyframes flap-l { to { transform: rotate(-19deg) translateY(-1px); } }
  @keyframes flap-r { to { transform: rotate(19deg) translateY(-1px); } }
  @keyframes ace-stamp { from { opacity: 0; transform: scale(1.35) rotate(-4deg); } to { opacity: 1; transform: none; } }

  /* Glitch burst. steps(1,end) on every one of these: the corruption should
     JUMP between states, never tween — interpolation reads as a smooth wobble
     and kills the effect. clip-path picks out a different band each step so a
     different slice of the screen tears each time. */
  @keyframes glitch-burst {
    0%,100% { opacity: 0; transform: none; }
    6%   { opacity: 1; transform: translateX(-9px); clip-path: inset(18% 0 66% 0); }
    14%  { opacity: 1; transform: translateX(7px);  clip-path: inset(52% 0 33% 0); }
    22%  { opacity: .8; transform: none;            clip-path: inset(0 0 0 0); }
    34%  { opacity: 1; transform: translateX(-14px);clip-path: inset(72% 0 12% 0); }
    44%  { opacity: 1; transform: translateX(4px);  clip-path: inset(6% 0 84% 0); }
    58%  { opacity: .7; transform: translateX(-5px);clip-path: inset(40% 0 46% 0); }
    70%  { opacity: 1; transform: translateX(11px); clip-path: inset(26% 0 60% 0); }
    82%  { opacity: .45; transform: none;           clip-path: inset(0 0 0 0); }
    92%  { opacity: .8; transform: translateX(-3px);clip-path: inset(60% 0 26% 0); }
  }
  @keyframes tear-a {
    0%,100% { transform: none; }
    12% { transform: translate(-13px, 2px); }
    36% { transform: translate(9px, -3px); }
    62% { transform: translate(-6px, 1px); }
    88% { transform: translate(4px, 0); }
  }
  @keyframes tear-b {
    0%,100% { transform: none; }
    18% { transform: translate(11px, -2px); }
    42% { transform: translate(-8px, 3px); }
    68% { transform: translate(6px, -1px); }
    94% { transform: translate(-4px, 0); }
  }
  @keyframes word-glitch {
    0%,100% { transform: none; opacity: 1; }
    8%   { transform: translate(-3px, 1px) skewX(-6deg); }
    16%  { transform: translate(2px, -1px) skewX(4deg); opacity: .82; }
    30%  { transform: none; }
    46%  { transform: translate(4px, 1px) skewX(7deg); opacity: .9; }
    58%  { transform: translate(-2px, 0) skewX(-3deg); }
    74%  { transform: none; opacity: 1; }
    86%  { transform: translate(1px, -1px) skewX(2deg); }
  }

  @media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
      animation-duration: .001ms !important; animation-iteration-count: 1 !important;
      transition-duration: .001ms !important;
    }
  }
}

@layer layout {
  .masthead {
    display: flex; align-items: flex-end; justify-content: space-between;
    gap: 24px; flex-wrap: wrap; padding: 30px 0 16px;
  }
  .masthead-right { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; }

  .scoreboard {
    display: grid; grid-template-columns: minmax(230px, .82fr) 2.4fr;
    gap: 0; border: 1px solid var(--rule); border-radius: 3px;
    background: var(--panel); margin-bottom: 26px; overflow: hidden;
  }
  @media (max-width: 760px) { .scoreboard { grid-template-columns: 1fr; } }

  .toolbar {
    display: flex; align-items: center; justify-content: space-between;
    gap: 18px; flex-wrap: wrap; margin-bottom: 22px;
    border-bottom: 1px solid var(--rule); padding-bottom: 0;
  }
  .filters { display: flex; gap: 8px; flex-wrap: wrap; padding-bottom: 10px; }

  /* Sidebar + results. The sidebar collapses to zero width rather than
     display:none so it can animate, and the grid re-flows into the space. */
  .layout { display: grid; grid-template-columns: 250px minmax(0, 1fr); gap: 26px; align-items: start; }
  .layout.collapsed { grid-template-columns: 0 minmax(0, 1fr); gap: 0; }
  @media (max-width: 860px) { .layout, .layout.collapsed { grid-template-columns: minmax(0, 1fr); } }

  .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(264px, 1fr)); gap: 18px; }
}

@layer components {
  /* --- masthead ----------------------------------------------------------- */
  .lockup { display: flex; align-items: center; gap: 13px; }
  .mark { width: 46px; height: 46px; flex: none; display: block; animation: glide 5.5s var(--ease) infinite; }
  .mark svg { width: 100%; height: 100%; display: block; }
  .mark .w1 { fill: var(--sky-400); }
  .mark .w2 { fill: var(--lilac-400); }
  .mark .tail { fill: var(--sky-500); }
  .mark .bod { fill: var(--ball); }
  /* The dimple field carries its own per-circle opacity (it tapers toward the
     rim), so only set the colour here — an opacity on the group would flatten
     the sphere back into a disc of dots. */
  .mark .dimp { fill: #7ea6c9; }
  .mark .shine { fill: #fff; opacity: .85; }
  .mark .shade { fill: #9fc0da; opacity: .42; }
  .lockup:hover .mark { animation-duration: 1.6s; }

  /* Hover: the bird beats its wings and the ball spins on its axis. Origins are
     in viewBox units, which needs transform-box: view-box — without it they'd
     resolve against each path's own bounding box and the wings would pivot from
     their own middles instead of from the shoulder. */
  .mark svg * { transform-box: view-box; }
  .mark .w1 { transform-origin: 25px 31px; }
  .mark .w2 { transform-origin: 39px 31px; }
  .mark .dimp, .mark .shine { transform-origin: 32px 32px; }
  .lockup:hover .w1   { animation: flap-l .42s var(--ease) infinite alternate; }
  .lockup:hover .w2   { animation: flap-r .42s var(--ease) infinite alternate; }
  .lockup:hover .dimp { animation: spin 3.4s linear infinite; }
  .mark { cursor: pointer; }

  /* --- click easter egg: the mark tees one up and holes it out ------------- */
  .ace-ball, .ace-cup, .ace-stamp { position: fixed; z-index: 60; pointer-events: none; }
  .ace-ball i {
    display: block; width: 15px; height: 15px; border-radius: 50%;
    background: radial-gradient(circle at 34% 30%, #fff, var(--sky-100) 58%, var(--sky-300));
    box-shadow: 0 3px 10px rgba(0,0,0,.4);
  }
  .ace-cup { width: 78px; height: 46px; }
  .ace-cup .hole {
    position: absolute; left: 22px; bottom: 4px; width: 32px; height: 11px;
    border-radius: 50%; background: #05080d; box-shadow: inset 0 2px 5px #000;
  }
  .ace-cup .flagpin { position: absolute; left: 37px; bottom: 8px; width: 2px; height: 38px; background: var(--text-2); }
  .ace-cup .flagpin::after {
    content: ''; position: absolute; left: 2px; top: 0; width: 18px; height: 11px;
    background: var(--ramp); clip-path: polygon(0 0, 100% 50%, 0 100%);
    animation: flutter .55s var(--ease) infinite;
  }
  .ace-stamp {
    left: 50%; top: 44%; translate: -50% -50%;
    font-family: var(--serif); font-size: clamp(38px, 8vw, 76px); font-weight: 600;
    letter-spacing: -.03em; white-space: nowrap;
    background: var(--ramp); -webkit-background-clip: text; background-clip: text; color: transparent;
    animation: ace-stamp .3s var(--ease-out) both;
  }
  .ace-stamp small {
    display: block; text-align: center; margin-top: 6px;
    font: 600 11px var(--sans); letter-spacing: .28em; text-transform: uppercase;
    color: var(--text-3); -webkit-text-fill-color: var(--text-3);
  }

  .words { display: flex; flex-direction: column; gap: 1px; }
  .wordmark {
    font-family: var(--serif); font-size: 31px; font-weight: 600;
    letter-spacing: -.018em; line-height: 1;
    background: var(--ramp); background-size: 200% 100%;
    -webkit-background-clip: text; background-clip: text; color: transparent;
    animation: tick 16s linear infinite;
  }
  .tagline { color: var(--text-3); }

  .status { margin: 0; font-size: 12px; color: var(--text-2); font-family: var(--mono); }
  .actions { display: flex; gap: 8px; }

  .btn {
    position: relative; overflow: hidden; background: transparent; color: var(--text);
    border: 1px solid var(--rule); padding: 9px 17px; border-radius: 2px;
    font: 600 12px/1 var(--sans); letter-spacing: .06em; text-transform: uppercase;
    cursor: pointer; transition: background .2s var(--ease), border-color .2s, color .2s;
  }
  .btn:hover { border-color: var(--sky-400); color: var(--sky-300); }
  .btn:disabled { opacity: .5; cursor: default; }
  .btn.primary { border-color: transparent; background: var(--ramp); background-size: 180% 100%; color: var(--sky-900); animation: tick 11s linear infinite; }
  .btn.primary:hover { filter: brightness(1.08); color: var(--sky-900); }
  @media (prefers-color-scheme: light) { .btn:hover { color: var(--sky-700); } }

  /* --- the flight lane ---------------------------------------------------- */
  .lane { position: relative; height: 48px; margin-bottom: 10px; }
  .lane::after {                                  /* dotted trajectory */
    content: ''; position: absolute; left: 44px; right: 22px; top: 30px; height: 1px;
    background-image: linear-gradient(90deg, var(--sky-400) 42%, transparent 42%);
    background-size: 9px 1px; opacity: 0;
  }
  .lane.swing::after { animation: trail 2.3s .5s var(--ease) forwards; }

  /* An iron, not a hammer: tapered shaft into a hosel, then a blade whose sole
     is longer than its topline, with two grooves. The pivot is at the GRIP —
     rotating about the head is what made the old one swing like a mallet and
     miss the ball entirely. */
  .club {
    position: absolute; left: 12px; top: 2px; width: 40px; height: 40px;
    transform-origin: 22.5% 5%;          /* the hands, at the top of the grip */
    transform: rotate(6deg);
  }
  .club svg { width: 100%; height: 100%; display: block; overflow: visible; }
  .club .grip  { fill: #2b3542; }
  .club .shaft { fill: url(#none); fill: #b9c6d3; }
  .club .head  { fill: #dbe4ec; stroke: #93a3b3; stroke-width: .4; }
  .club .groove{ stroke: #9aa9b8; stroke-width: .55; fill: none; opacity: .8; }
  @media (prefers-color-scheme: light) {
    .club .shaft { fill: #8fa0b0; }
    .club .head { fill: #c8d4e0; stroke: #7e8fa0; }
  }
  .lane.swing .club { animation: swing 1.05s var(--ease); }

  /* The ball sits exactly where the blade passes at rotation 0 — impact. */
  .flyer { position: absolute; left: 32px; top: 26px; width: 12px; height: 12px; opacity: 0; }
  .flyer i {
    display: block; width: 100%; height: 100%; border-radius: 50%;
    background: radial-gradient(circle at 34% 30%, #fff, var(--sky-100) 60%, var(--sky-300));
    box-shadow: 0 1px 5px rgba(0,0,0,.32);
  }
  .lane.swing .flyer   { animation: fly-x 1.6s .546s linear forwards; opacity: 1; }
  .lane.swing .flyer i { animation: fly-y 1.6s .546s var(--ease) forwards; }

  .pin { position: absolute; right: 8px; bottom: 8px; width: 2px; height: 30px; background: var(--text-3); }
  .pin b {
    position: absolute; left: 2px; top: 1px; width: 15px; height: 10px;
    background: var(--ramp); transform-origin: 0 50%; clip-path: polygon(0 0, 100% 50%, 0 100%);
  }
  .lane.swing .pin b { animation: flutter .7s var(--ease) 2.15s 3; }

  /* --- banner ------------------------------------------------------------- */
  .banner {
    border: 1px solid var(--rule); border-left: 2px solid var(--lilac-400);
    background: var(--panel); border-radius: 2px; padding: 13px 17px;
    margin-bottom: 22px; font-size: 13px; color: var(--text-2);
  }
  .banner strong { color: var(--text); }
  .banner code { font: 11.5px var(--mono); color: var(--lilac-300); }
  @media (prefers-color-scheme: light) { .banner code { color: var(--lilac-700); } }
  .hidden { display: none !important; }

  /* --- scoreboard --------------------------------------------------------- */
  .hero-stat {
    position: relative; padding: 22px 24px 20px;
    border-right: 1px solid var(--rule); display: flex; flex-direction: column;
    background: linear-gradient(160deg, rgba(171,146,212,.09), transparent 68%);
  }
  @media (max-width: 760px) { .hero-stat { border-right: 0; border-bottom: 1px solid var(--rule); } }
  .rule-label em { font-style: normal; color: var(--lilac-400); letter-spacing: .1em; }
  .hero-n {
    font-family: var(--serif); font-size: clamp(58px, 8vw, 82px); line-height: .9;
    font-weight: 600; letter-spacing: -.05em; margin: 12px 0 8px;
    font-variant-numeric: tabular-nums;
    background: var(--ramp); -webkit-background-clip: text; background-clip: text; color: transparent;
  }
  .hero-note { font-size: 11.5px; color: var(--text-3); line-height: 1.5; max-width: 30ch; }

  /* The strip is a scorecard row: ruled cells, label over figure. */
  .card-strip { display: grid; grid-template-columns: repeat(auto-fit, minmax(118px, 1fr)); }
  .cell {
    padding: 20px 18px; border-right: 1px solid var(--rule-soft);
    display: flex; flex-direction: column; gap: 7px;
    transition: background .25s var(--ease);
  }
  .cell:last-child { border-right: 0; }
  .cell:hover { background: var(--panel-2); }
  .cell .n {
    font-family: var(--serif); font-size: 30px; line-height: 1; font-weight: 600;
    letter-spacing: -.03em; font-variant-numeric: tabular-nums; color: var(--text);
  }
  .cell[data-tier="glitch"] .n  { color: var(--lilac-300); }
  .cell[data-tier="extreme"] .n { color: var(--sky-300); }
  .cell[data-tier="discount"] .n{ color: var(--sky-400); }
  @media (prefers-color-scheme: light) {
    .cell[data-tier="glitch"] .n { color: var(--lilac-600); }
    .cell[data-tier="extreme"] .n { color: var(--sky-600); }
    .cell[data-tier="discount"] .n { color: var(--sky-700); }
  }

  /* --- toolbar ------------------------------------------------------------ */
  .tabs { display: flex; gap: 2px; }
  .tab {
    position: relative; background: none; border: 0; cursor: pointer;
    color: var(--text-3); padding: 11px 15px 13px;
    font: 600 12px/1 var(--sans); letter-spacing: .09em; text-transform: uppercase;
    transition: color .22s var(--ease);
  }
  .tab:hover { color: var(--text-2); }
  .tab.active { color: var(--text); }
  .tab::after {
    content: ''; position: absolute; left: 12px; right: 12px; bottom: -1px; height: 2px;
    background: var(--ramp); transform: scaleX(0); transform-origin: 50%;
    transition: transform .34s var(--ease-out);
  }
  .tab.active::after { transform: scaleX(1); }
  /* A pennant marks the active tab — the same flag as the pin. */
  .tab.active::before {
    content: ''; position: absolute; left: 3px; top: 8px; width: 8px; height: 6px;
    background: var(--ramp); clip-path: polygon(0 0, 100% 50%, 0 100%);
    animation: flutter 2.6s var(--ease) infinite;
  }

  input, select {
    background: var(--panel); border: 1px solid var(--rule); color: var(--text);
    padding: 8px 12px; border-radius: 2px; font: 13px var(--sans);
    transition: border-color .2s, box-shadow .2s;
  }
  input::placeholder { color: var(--text-3); }
  input:focus, select:focus {
    outline: none; border-color: var(--sky-400);
    box-shadow: 0 0 0 3px color-mix(in oklab, var(--sky-400) 18%, transparent);
  }
  #search { min-width: 230px; }
  #maxPrice, #minPrice { width: 88px; }
  #sort { min-width: 148px; }
  .saved { font: 700 11px var(--mono); color: var(--text-2); }

  /* --- deal cards --------------------------------------------------------- */
  .card {
    position: relative; display: flex; flex-direction: column; overflow: hidden;
    background: var(--panel); border: 1px solid var(--rule); border-radius: 3px;
    animation: deal-in .44s var(--ease-out) both; animation-delay: var(--d, 0ms);
    transition: transform .3s var(--ease-out), border-color .3s, box-shadow .3s;
  }
  .card:hover {
    transform: translateY(-3px); border-color: var(--rule);
    box-shadow: 0 14px 30px -18px rgba(0,0,0,.75);
  }
  /* Tier is a rule across the top, not a coloured card. */
  .card::before { content: ''; position: absolute; inset: 0 0 auto; height: 2px; z-index: 2; }
  .card.tier-ace::before      { background: linear-gradient(90deg, var(--pink-400), var(--lilac-400), var(--sky-300)); }
  .card.tier-glitch::before   { background: linear-gradient(90deg, var(--lilac-400), var(--pink-300)); }
  .card.tier-extreme::before  { background: linear-gradient(90deg, var(--sky-300), var(--lilac-300)); }
  .card.tier-discount::before { background: var(--sky-400); }
  .card.tier-par::before      { background: var(--text-3); }
  .card.tier-bogey::before    { background: #b4703f; }

  /* A putt rolls in along the base rule on hover. */
  .card .putt {
    position: absolute; left: 14px; bottom: 9px; width: 9px; height: 9px; border-radius: 50%;
    background: radial-gradient(circle at 34% 30%, #fff, var(--sky-200));
    opacity: 0; pointer-events: none;
  }
  .card:hover .putt { animation: putt .5s var(--ease-out) forwards; }

  .thumb {
    /* Tall enough for the product to read. Golf clubs are long and narrow, and
       at 148px an iron set was cropped to a blur of shafts. Scales with the
       card so the grid stays even at any column width. */
    position: relative; aspect-ratio: 4 / 3.4; min-height: 210px;
    display: grid; place-items: center; overflow: hidden;
    background:
      radial-gradient(90% 70% at 50% 8%, rgba(156,205,241,.10), transparent 70%),
      var(--panel-2);
    border-bottom: 1px solid var(--rule-soft);
  }
  .thumb img { width: 100%; height: 100%; object-fit: contain; padding: 10px; transition: transform .5s var(--ease-out); }
  .card:hover .thumb img { transform: scale(1.05); }
  .thumb svg.ph { width: 88px; height: 88px; color: var(--sky-400); opacity: .85; transition: transform .5s var(--ease-out); }
  .card:hover .thumb svg.ph { transform: translateY(-3px) scale(1.05); }

  .body { padding: 13px 15px 16px; display: flex; flex-direction: column; gap: 9px; flex: 1; }
  .badges { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
  .badge {
    font: 700 9.5px/1 var(--sans); letter-spacing: .12em; text-transform: uppercase;
    padding: 5px 8px; border-radius: 2px; animation: stamp .34s var(--ease-out) both;
  }
  .badge.ace      { background: linear-gradient(100deg, var(--pink-400), var(--lilac-400)); color: #2a1226; }
  .badge.glitch   { background: var(--lilac-400); color: var(--lilac-900); }
  .badge.extreme  { background: var(--sky-300); color: var(--sky-900); }
  .badge.discount { background: transparent; color: var(--sky-300); border: 1px solid var(--sky-400); }
  .badge.par      { background: transparent; color: var(--text-3); border: 1px solid var(--rule); }
  .badge.bogey    { background: transparent; color: #c98a55; border: 1px solid #8a5a30; }
  .badge.src, .badge.used { background: transparent; color: var(--text-3); border: 1px solid var(--rule); font-weight: 600; }

  /* Sample marker. Deliberately the loudest badge on the card: fixture rows
     carry realistic source badges so the layout can be previewed, and nothing
     may let that be mistaken for a real listing. */
  .badge.sample {
    background: repeating-linear-gradient(135deg, #b4703f 0 5px, #96592f 5px 10px);
    color: #1a0f06; letter-spacing: .14em;
  }

  /* Source badge: which site the listing actually lives on. A coloured dot
     separates marketplace from retailer at a glance — an eBay price is one
     punter's asking price, a shop price is the shop's, and that changes how
     much a "deal" is worth. */
  .badge.src { display: inline-flex; align-items: center; gap: 5px; }
  .badge.src .dot { width: 5px; height: 5px; border-radius: 50%; flex: none; background: currentColor; }
  .badge.src[data-kind="ebay"]   { color: var(--sky-300); border-color: color-mix(in oklab, var(--sky-400) 40%, transparent); }
  .badge.src[data-kind="retail"] { color: var(--lilac-300); border-color: color-mix(in oklab, var(--lilac-400) 38%, transparent); }
  .badge.src[data-kind="demo"]   { color: var(--text-3); }
  @media (prefers-color-scheme: light) {
    .badge.src[data-kind="ebay"]   { color: var(--sky-700); }
    .badge.src[data-kind="retail"] { color: var(--lilac-700); }
  }

  /* Listing reference + the where-it-goes affordance. Muted until hover: it's
     for confirming you're clicking through to the right place, not something
     to read on every card. */
  .meta-row.ident { font-size: 10px; opacity: .55; transition: opacity .22s var(--ease); }
  .card:hover .meta-row.ident { opacity: 1; }
  .meta-row.ident .id {
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 52%;
  }
  .meta-row.ident .goto { color: var(--sky-300); white-space: nowrap; }
  @media (prefers-color-scheme: light) { .meta-row.ident .goto { color: var(--sky-700); } }
  @media (prefers-color-scheme: light) {
    .badge.discount { color: var(--sky-700); }
  }

  .title { font-size: 13.5px; font-weight: 550; line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
  .prices { display: flex; align-items: baseline; gap: 9px; flex-wrap: wrap; }
  .now { font-family: var(--serif); font-size: 25px; font-weight: 600; letter-spacing: -.03em; font-variant-numeric: tabular-nums; }
  .was { color: var(--text-3); text-decoration: line-through; font-size: 12.5px; }
  .off { font: 700 11px var(--mono); color: var(--sky-300); }
  .tier-ace .off, .tier-glitch .off { color: var(--pink-300); }
  .tier-bogey .off { color: #c98a55; }
  @media (prefers-color-scheme: light) {
    .off { color: var(--sky-700); }
    .tier-ace .off, .tier-glitch .off { color: var(--pink-600); }
  }

  .pcts { display: flex; gap: 7px; flex-wrap: wrap; align-items: center; }
  .pct {
    display: inline-flex; align-items: baseline; gap: 5px;
    border: 1px solid var(--rule); border-radius: 2px; padding: 3px 7px;
  }
  .pct b { font: 700 11.5px var(--mono); color: var(--sky-300); font-style: normal; }
  .pct i { font: 500 9.5px var(--sans); font-style: normal; letter-spacing: .07em; text-transform: uppercase; color: var(--text-3); }
  .pct.site b { color: var(--lilac-300); }
  .pct.pending { font: 500 9.5px var(--sans); letter-spacing: .07em; text-transform: uppercase; color: var(--text-3); border-style: dashed; }
  @media (prefers-color-scheme: light) {
    .pct b { color: var(--sky-700); }
    .pct.site b { color: var(--lilac-700); }
  }

  .caution {
    grid-column: 1 / -1; margin: 0 0 6px; padding: 11px 14px;
    border: 1px solid var(--rule); border-left: 2px solid #b4703f; border-radius: 2px;
    font-size: 11.5px; line-height: 1.6; color: var(--text-2); background: var(--panel);
  }
  .caution strong { color: var(--text); }

  .meta-row { display: flex; justify-content: space-between; gap: 10px; font: 11px var(--mono); color: var(--text-3); }
  .why { font-size: 11.5px; color: var(--text-2); line-height: 1.5; border-top: 1px solid var(--rule-soft); padding-top: 9px; margin-top: auto; }

  .result-count { margin: 0 0 12px; font: 11px var(--mono); color: var(--text-3); }
  .more-wrap { display: flex; justify-content: center; padding: 28px 0 4px; }

  .skel { height: 372px; border: 1px solid var(--rule); border-radius: 3px; background: var(--panel); position: relative; overflow: hidden; }
  .skel::after {
    content: ''; position: absolute; inset: 0;
    background: linear-gradient(100deg, transparent 22%, var(--panel-2) 50%, transparent 78%);
    background-size: 220% 100%; animation: tick 1.5s linear infinite;
  }

  /* --- empty state: the ball holes out ------------------------------------ */
  .empty { grid-column: 1/-1; text-align: center; padding: 56px 24px 64px; color: var(--text-2); }
  .empty .cup { position: relative; width: 120px; height: 34px; margin: 0 auto 22px; }
  .empty .cup .hole {
    position: absolute; right: 8px; bottom: 6px; width: 26px; height: 9px; border-radius: 50%;
    background: #05080d; box-shadow: inset 0 2px 4px rgba(0,0,0,.9);
  }
  @media (prefers-color-scheme: light) { .empty .cup .hole { background: #263341; } }
  .empty .cup .flagpin { position: absolute; right: 19px; bottom: 10px; width: 2px; height: 30px; background: var(--text-3); }
  .empty .cup .flagpin::after {
    content: ''; position: absolute; left: 2px; top: 0; width: 14px; height: 9px;
    background: var(--ramp); clip-path: polygon(0 0, 100% 50%, 0 100%);
    animation: flutter 2.4s var(--ease) infinite;
  }
  .empty .cup .rollball {
    position: absolute; right: 12px; bottom: 6px; width: 10px; height: 10px; border-radius: 50%;
    background: radial-gradient(circle at 34% 30%, #fff, var(--sky-200));
    animation: holeout 2.6s var(--ease) infinite;
  }
  .empty p { margin: 0; font-size: 14px; }
  .empty .hint { display: block; margin-top: 9px; font-size: 12.5px; color: var(--text-3); max-width: 48ch; margin-inline: auto; line-height: 1.65; }

  /* --- diagnostics + footer ----------------------------------------------- */
  .diag { margin-top: 44px; border-top: 1px solid var(--rule); padding-top: 15px; }
  .diag summary { cursor: pointer; color: var(--text-3); font: 600 10px var(--sans); letter-spacing: .16em; text-transform: uppercase; list-style: none; }
  .diag summary::-webkit-details-marker { display: none; }
  .diag summary::before { content: '▸ '; display: inline-block; transition: transform .26s var(--ease); }
  .diag[open] summary::before { transform: rotate(90deg); }
  .diag-body { display: grid; grid-template-columns: 1fr 1fr; gap: 28px; margin-top: 18px; }
  @media (max-width: 820px) { .diag-body { grid-template-columns: 1fr; } }
  .diag h3 { margin: 16px 0 8px; }
  .diag table { width: 100%; border-collapse: collapse; font: 11.5px var(--mono); }
  .diag td { padding: 6px 10px 6px 0; border-bottom: 1px solid var(--rule-soft); color: var(--text-3); }
  .diag pre { background: var(--panel); border: 1px solid var(--rule); border-radius: 2px; padding: 13px; font: 11px/1.7 var(--mono); color: var(--text-3); max-height: 320px; overflow: auto; margin: 0; }

  .foot {
    margin-top: 52px; border-top: 1px solid var(--rule); padding: 20px 0 34px;
    display: flex; gap: 18px; align-items: baseline; justify-content: space-between; flex-wrap: wrap;
  }
  .foot p { margin: 0; font-size: 11.5px; color: var(--text-3); max-width: 62ch; line-height: 1.6; }
  .foot strong { color: var(--text-2); }
  .foot nav { display: flex; gap: 16px; }
  .foot nav a, .linkish {
    font: 600 10px var(--sans); letter-spacing: .12em; text-transform: uppercase;
    color: var(--text-3); background: none; border: 0; padding: 0; cursor: pointer;
    border-bottom: 1px solid transparent; transition: color .2s, border-color .2s;
  }
  .foot nav a:hover, .linkish:hover { color: var(--sky-300); border-bottom-color: var(--sky-400); }

  /* --- the glitch -----------------------------------------------------------
     On-brand rather than decorative: the top tier of this app is a claim that a
     price has glitched, so every ten seconds the page briefly corrupts. Two
     layers, both inert until JS adds .on, so nothing paints between bursts:

       .glitch-rain   — canvas of falling prices/digits, Matrix-style
       .glitch-slices — horizontal scanline bands that tear sideways, with a
                        cyan/magenta chromatic split

     Kept under ~700ms and low-contrast. This must never look like a fault, and
     it must never flash: no full-screen luminance jumps, nothing above 3Hz. */
  .glitch-rain, .glitch-slices {
    position: fixed; inset: 0; z-index: 30; pointer-events: none;
    opacity: 0; visibility: hidden;
  }
  .glitch-rain.on { opacity: var(--glitch-opacity); visibility: visible; transition: opacity .12s linear; }
  .glitch-rain.fading { opacity: 0; transition: opacity .45s ease-out; }

  .glitch-slices.on {
    visibility: visible; opacity: 1;
    animation: glitch-burst .62s steps(1, end);
    background: repeating-linear-gradient(
      180deg,
      transparent 0 6px,
      rgba(156, 205, 241, .022) 6px 7px,
      transparent 7px 13px
    );
    mix-blend-mode: screen;
  }
  /* Two offset copies of the scanline field give the RGB fringe without having
     to duplicate and recolour the actual page content. */
  .glitch-slices.on::before, .glitch-slices.on::after {
    content: ''; position: absolute; inset: 0;
    background: inherit; mix-blend-mode: screen;
  }
  .glitch-slices.on::before { background-color: rgba(84, 152, 216, .018); animation: tear-a .62s steps(1, end); }
  .glitch-slices.on::after  { background-color: rgba(217, 143, 184, .018); animation: tear-b .62s steps(1, end); }

  /* The wordmark takes a chromatic split during the burst — the brand itself
     glitching is the joke, so it gets the most visible treatment. */
  body.glitching .wordmark {
    animation: word-glitch .62s steps(1, end);
    text-shadow: -1.5px 0 rgba(217, 143, 184, .45), 1.5px 0 rgba(119, 181, 232, .45);
  }

  /* --- consent ------------------------------------------------------------ */
  .consent {
    position: fixed; left: clamp(12px,4vw,28px); bottom: clamp(12px,4vw,28px); z-index: 40;
    width: min(430px, calc(100vw - 24px)); background: var(--panel);
    border: 1px solid var(--rule); border-top: 2px solid var(--sky-400); border-radius: 3px;
    padding: 18px 20px; box-shadow: 0 22px 50px -22px rgba(0,0,0,.85);
    animation: deal-in .4s var(--ease-out) both;
  }
  .consent h2 { font-family: var(--serif); font-size: 18px; margin-bottom: 7px; }
  .consent p { margin: 0 0 12px; font-size: 12.5px; color: var(--text-2); line-height: 1.6; }
  .consent p a { color: var(--sky-300); border-bottom: 1px solid var(--rule); }
  .consent .row { display: flex; gap: 8px; flex-wrap: wrap; }
  .consent label { display: flex; gap: 8px; align-items: flex-start; font-size: 12.5px; color: var(--text-2); margin-bottom: 10px; }
  .consent input[type=checkbox] { margin-top: 2px; accent-color: var(--sky-500); }
  .consent .fixed-note { font-size: 11px; color: var(--text-3); }
}

@layer components {
  /* ---------------------------------------------------------- filter rail */
  .filter-toggle {
    display: inline-flex; align-items: center; gap: 9px; margin-bottom: 16px;
  }
  .filter-toggle .bars { display: grid; gap: 2.5px; width: 12px; }
  .filter-toggle .bars i {
    display: block; height: 1.5px; background: currentColor; border-radius: 1px;
    transition: width .3s var(--ease), transform .3s var(--ease);
  }
  .filter-toggle .bars i:nth-child(1) { width: 12px; }
  .filter-toggle .bars i:nth-child(2) { width: 8px; }
  .filter-toggle .bars i:nth-child(3) { width: 10px; }
  .filter-toggle[aria-expanded="true"] .bars i { width: 12px; }
  .filter-toggle .active-count {
    min-width: 17px; height: 17px; padding: 0 5px; border-radius: 9px;
    background: var(--ramp); color: var(--sky-900);
    font: 700 10px/17px var(--mono); text-align: center;
  }

  .sidebar {
    position: sticky; top: 18px; overflow: hidden;
    border: 1px solid var(--rule); border-radius: 3px; background: var(--panel);
    max-height: calc(100vh - 40px); display: flex; flex-direction: column;
    transition: opacity .3s var(--ease), transform .34s var(--ease-out);
  }
  .layout.collapsed .sidebar {
    opacity: 0; transform: translateX(-14px); pointer-events: none;
    border-color: transparent;
  }
  @media (max-width: 860px) {
    .sidebar { position: static; max-height: none; margin-bottom: 20px; }
    .layout.collapsed .sidebar { display: none; }
  }

  .side-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: 13px 15px; border-bottom: 1px solid var(--rule); flex: none;
  }
  .side-body { overflow-y: auto; padding: 4px 0 10px; }

  .facet { border-bottom: 1px solid var(--rule-soft); }
  .facet > summary {
    display: flex; align-items: center; justify-content: space-between;
    padding: 12px 15px; cursor: pointer; list-style: none;
    font: 600 10px var(--sans); letter-spacing: .14em; text-transform: uppercase;
    color: var(--text-2); transition: color .2s;
  }
  .facet > summary::-webkit-details-marker { display: none; }
  .facet > summary:hover { color: var(--text); }
  .facet > summary .chev { transition: transform .28s var(--ease); font-size: 9px; color: var(--text-3); }
  .facet[open] > summary .chev { transform: rotate(90deg); }
  /* Each panel animates open; the delay ladder makes the rail unfold rather
     than snap, which is the only motion here that is purely decorative. */
  .facet-body { padding: 2px 15px 14px; animation: facet-open .3s var(--ease-out) both; }

  .opt {
    display: flex; align-items: center; gap: 9px; padding: 5px 0;
    font-size: 12.5px; color: var(--text-2); cursor: pointer;
    transition: color .18s;
  }
  .opt:hover { color: var(--text); }
  /* Branded check: instead of a tick, the mark draws a ball flight — a low arc
     that rises and finishes on the ball, in the brand gradient. Same shape
     language as the masthead's struck ball and the empty-state hole-out. */
  .opt input {
    position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none;
  }
  .opt .tick {
    flex: none; width: 17px; height: 17px; border-radius: 3px;
    border: 1px solid var(--hairline, var(--rule)); background: var(--panel-2);
    display: grid; place-items: center;
    transition: border-color .2s var(--ease), background .2s var(--ease), transform .2s var(--ease);
  }
  .opt:hover .tick { border-color: var(--sky-400); }
  .opt .tick svg { width: 15px; height: 15px; overflow: visible; }
  .opt .tick .arc {
    fill: none; stroke: url(#scTickGrad); stroke-width: 2.1;
    stroke-linecap: round; stroke-linejoin: round;
    /* Hidden by dashoffset so it can be drawn on rather than faded in. */
    stroke-dasharray: 20; stroke-dashoffset: 20;
  }
  .opt .tick .ball { fill: var(--ball); opacity: 0; transform-origin: 14.2px 4.4px; }

  .opt input:checked + .tick {
    background: color-mix(in oklab, var(--sky-400) 16%, transparent);
    border-color: var(--sky-400);
    animation: tick-pop .34s var(--ease-out);
  }
  .opt input:checked + .tick .arc { animation: tick-draw .42s var(--ease-out) forwards; }
  .opt input:checked + .tick .ball { animation: tick-land .26s var(--ease-out) .3s forwards; }
  /* Partial: some categories in this department are selected, not all. Shown as
     a dash rather than a drawn arc, so it never looks like a finished check. */
  .opt .tick.partial { background: color-mix(in oklab, var(--sky-400) 12%, transparent); border-color: var(--sky-400); }
  .opt .tick.partial svg { display: none; }
  .opt .tick.partial::after {
    content: ''; width: 8px; height: 2px; border-radius: 1px; background: var(--sky-300);
  }

  .opt input:focus-visible + .tick {
    outline: none; box-shadow: 0 0 0 3px color-mix(in oklab, var(--sky-400) 26%, transparent);
  }
  .opt .label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .opt .n { font: 10.5px var(--mono); color: var(--text-3); }
  .opt input:checked + .label { color: var(--sky-300); font-weight: 600; }
  @media (prefers-color-scheme: light) { .opt input:checked + .label { color: var(--sky-700); } }

  .facet-body .range { display: flex; gap: 8px; align-items: center; }
  .facet-body .range input { width: 100%; min-width: 0; }
  .facet-body .hint { font: 10px var(--mono); color: var(--text-3); margin: 8px 0 0; }
  .facet-more {
    background: none; border: 0; padding: 6px 0 0; cursor: pointer;
    font: 600 10px var(--sans); letter-spacing: .1em; text-transform: uppercase; color: var(--sky-400);
  }

  .chips { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 14px; }
  .chip {
    display: inline-flex; align-items: center; gap: 6px; padding: 4px 9px;
    border: 1px solid var(--rule); border-radius: 20px; background: var(--panel);
    font: 500 11px var(--sans); color: var(--text-2);
    animation: deal-in .26s var(--ease-out) both;
  }
  .chip button { background: none; border: 0; padding: 0; cursor: pointer; color: var(--text-3); font-size: 13px; line-height: 1; }
  .chip button:hover { color: var(--pink-400); }
}

@layer motion {
  @keyframes tick-draw { to { stroke-dashoffset: 0; } }
  @keyframes tick-land { from { opacity: 0; transform: scale(.2); } to { opacity: 1; transform: scale(1); } }
  @keyframes tick-pop  { 0% { transform: scale(1); } 45% { transform: scale(1.16); } 100% { transform: scale(1); } }
  @keyframes facet-open { from { opacity: 0; transform: translateY(-5px); } to { opacity: 1; transform: none; } }
}

/* ---------------------------------------------------------------- join rail */
/* Lives in `components` so it sits with the other cards in the cascade; layers
   outrank specificity, so this never has to fight the button rules. */
@layer components {
  .join {
    display: flex; align-items: center; gap: 18px;
    margin: 22px 0 6px; padding: 17px 20px;
    border: 1px solid var(--rule); border-radius: 4px;
    background:
      radial-gradient(120% 180% at 0% 50%, rgba(156, 205, 241, .13), transparent 62%),
      var(--panel);
    position: relative; overflow: hidden;
  }
  /* The gradient hairline ties it to the brand ramp without colouring the
     whole panel, which would read as an ad. */
  .join::before {
    content: ''; position: absolute; inset: 0 0 auto; height: 2px; background: var(--ramp);
  }

  .join-glyph {
    flex: none; display: grid; place-items: center;
    width: 44px; height: 44px; border-radius: 50%;
    border: 1px solid var(--rule);
    background: var(--panel-2);
  }
  .join-glyph svg { fill: var(--sky-300); }
  .join-glyph svg .fold { fill: var(--sky-100); opacity: .55; }
  @media (prefers-color-scheme: light) {
    .join-glyph svg { fill: var(--sky-600, #3f8fcf); }
    .join-glyph svg .fold { fill: #1c3a5a; opacity: .3; }
  }

  .join-copy { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
  .join-copy strong { font-family: var(--serif); font-size: 17px; letter-spacing: .1px; }
  .join-copy span { color: var(--text-2); font-size: 13px; line-height: 1.55; }
  .join-copy em { font-style: normal; color: var(--sky-300); }
  @media (prefers-color-scheme: light) { .join-copy em { color: var(--sky-700, #1f6ba8); } }

  .join-btn { flex: none; display: inline-flex; align-items: center; gap: 8px; }
  .join-arrow { transition: transform .22s cubic-bezier(.2, .8, .2, 1); }
  .join-btn:hover .join-arrow { transform: translateX(4px); }

  /* The paper plane lifts off on hover — one small reward, not a loop, so it
     never competes with the board for attention. */
  @media (prefers-reduced-motion: no-preference) {
    .join:hover .join-glyph svg { animation: plane-lift .6s cubic-bezier(.2, .8, .2, 1); }
  }

  @media (max-width: 720px) {
    .join { flex-wrap: wrap; gap: 13px; }
    .join-btn { width: 100%; justify-content: center; }
  }
}

@layer motion {
  @keyframes plane-lift {
    0%   { transform: none; }
    45%  { transform: translate(4px, -4px) rotate(-8deg); }
    100% { transform: none; }
  }
}

/* ═══════════════════════════════════════════════════════════════════════════
   REDESIGN — 2026
   Glass surfaces, generous radii, pill controls and a proper score card, in the
   existing sky → lilac → pink palette rather than the mockup's purple/blue.
   Mobile is the priority: the filter sidebar becomes a drawer and the products
   come first, because on a phone the old layout pushed every card below a
   full-height column of checkboxes.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Circular Std, supplied with the design. Weight 900 is what gives the score
   card its presence, so it is worth the four files. */
@font-face { font-family: 'Circular Std'; src: url('/fonts/CircularStd-Book.woff2') format('woff2');   font-weight: 400; font-display: swap; }
@font-face { font-family: 'Circular Std'; src: url('/fonts/CircularStd-Medium.woff2') format('woff2'); font-weight: 500; font-display: swap; }
@font-face { font-family: 'Circular Std'; src: url('/fonts/CircularStd-Bold.woff2') format('woff2');   font-weight: 700; font-display: swap; }
@font-face { font-family: 'Circular Std'; src: url('/fonts/CircularStd-Black.woff2') format('woff2');  font-weight: 900; font-display: swap; }

@layer redesign {
  :root {
    --sans: 'Circular Std', -apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, system-ui, sans-serif;

    /* Glass surfaces. Two levels, so a card inside a card still reads. */
    --glass:    rgba(255, 255, 255, .038);
    --glass-2:  rgba(255, 255, 255, .06);
    --glass-br: rgba(255, 255, 255, .075);

    --r-lg: 20px; --r-md: 16px; --r-sm: 11px; --pill: 9999px;
    --shadow-card: 0 18px 44px rgba(0, 0, 0, .5);
    --shadow-pill: 0 8px 24px rgba(84, 152, 216, .32);

    /* Savings read in brand sky rather than the mockup's green — the palette has
       no green in it, and inventing one for money would fight the wordmark. */
    --good-bg: rgba(119, 181, 232, .15);
    --good-br: rgba(119, 181, 232, .34);
    --good-fg: var(--sky-200);

    --warn: var(--lilac-300);
    --warn-bg: rgba(171, 146, 212, .1);
    --warn-br: rgba(171, 146, 212, .28);
  }

  @media (prefers-color-scheme: light) {
    :root {
      --glass:    rgba(16, 40, 66, .028);
      --glass-2:  rgba(16, 40, 66, .05);
      --glass-br: rgba(16, 40, 66, .08);
      --shadow-card: 0 14px 34px rgba(28, 58, 90, .12);
      --good-bg: rgba(63, 121, 180, .12);
      --good-br: rgba(63, 121, 180, .3);
      --good-fg: var(--sky-700);
      --warn: var(--lilac-700);
      --warn-bg: rgba(120, 93, 166, .08);
      --warn-br: rgba(120, 93, 166, .22);
    }
  }

  body {
    font-family: var(--sans);
    -webkit-font-smoothing: antialiased;
    /* Two soft washes bleeding in from the top corners, sky on one side and
       lilac/pink on the other, so the page has depth without a visible seam. */
    background:
      radial-gradient(1200px 620px at 12% -8%, rgba(171, 146, 212, .17), transparent 60%),
      radial-gradient(1000px 560px at 92% 2%, rgba(119, 181, 232, .16), transparent 62%),
      var(--ink);
    /* NOT background-attachment: fixed. On a board 11,000px tall it forces a
       full-viewport repaint on every scroll frame, which is exactly the jank
       this rebuild is meant to remove on phones. The washes are anchored to the
       top of the document instead, which is where they read anyway. */
    background-repeat: no-repeat;
    padding-bottom: 96px;
  }
  @media (prefers-color-scheme: light) {
    body {
      background:
        radial-gradient(1200px 620px at 12% -8%, rgba(171, 146, 212, .2), transparent 60%),
        radial-gradient(1000px 560px at 92% 2%, rgba(119, 181, 232, .22), transparent 62%),
        var(--ink);
    }
  }

  ::-webkit-scrollbar { width: 8px; height: 8px; }
  ::-webkit-scrollbar-thumb { background: rgba(171, 146, 212, .3); border-radius: var(--pill); }
  ::-webkit-scrollbar-track { background: transparent; }

  /* ---------------------------------------------------------------- masthead */
  /* Sticky, because the tier tabs and Refresh are the two things you reach for
     repeatedly and hunting for them up-page is friction. */
  .masthead {
    position: sticky; top: 0; z-index: 30;
    display: flex; align-items: center; justify-content: space-between;
    gap: 24px; flex-wrap: nowrap;
    margin: 0 calc(clamp(16px, 5vw, 56px) * -1); padding: 14px clamp(16px, 5vw, 56px);
    background: color-mix(in srgb, var(--ink) 82%, transparent);
    backdrop-filter: blur(18px) saturate(140%);
    border-bottom: 1px solid var(--glass-br);
  }
  .lockup { gap: 12px; min-width: 0; }
  .mark { width: 40px; height: 40px; }
  .wordmark {
    font-family: var(--sans); font-size: 25px; font-weight: 700; letter-spacing: -.02em;
  }
  .tagline {
    font-size: 9.5px; font-weight: 500; letter-spacing: .26em; text-transform: uppercase;
    color: var(--text-3);
  }

  .masthead-right { gap: 18px; flex-wrap: nowrap; }
  .status {
    display: flex; align-items: center; gap: 11px;
    font-family: var(--sans); font-size: 12.5px; color: var(--text-2); white-space: nowrap;
  }
  .status b { color: var(--text); font-weight: 700; }
  .status .sep { color: var(--rule); }
  /* Live pulse: proof the cron is running, which is the whole basis of the
     numbers on the page. */
  .live { display: inline-flex; align-items: center; gap: 7px; color: var(--text-2); }
  .live i {
    width: 7px; height: 7px; border-radius: var(--pill); background: var(--sky-300);
    box-shadow: 0 0 10px var(--sky-400);
  }
  @media (prefers-reduced-motion: no-preference) {
    .live i { animation: live-pulse 2.4s var(--ease) infinite; }
  }
  .live.busy i { background: var(--pink-400); box-shadow: 0 0 10px var(--pink-400); }

  .btn {
    border-radius: var(--pill); border: 1px solid var(--glass-br);
    background: var(--glass); color: var(--text);
    padding: 11px 20px; font: 500 13px/1 var(--sans);
    letter-spacing: 0; text-transform: none;
    transition: background .2s var(--ease), border-color .2s, transform .2s var(--ease);
  }
  .btn:hover { background: var(--glass-2); border-color: var(--lilac-400); color: var(--text); transform: translateY(-1px); }
  .btn.primary {
    background: var(--ramp); background-size: 180% 100%; color: var(--sky-900);
    border-color: transparent; box-shadow: var(--shadow-pill); font-weight: 700;
  }

  /* ------------------------------------------------------------- score board */
  .scoreboard {
    display: grid; grid-template-columns: 1.12fr 1fr; gap: 18px;
    margin: 26px 0 22px; border: 0; background: none; overflow: visible;
  }

  .hero-stat {
    position: relative; overflow: hidden;
    display: grid; grid-template-columns: 1fr auto; gap: 26px; align-items: start;
    padding: 26px 28px; border-radius: var(--r-lg);
    background: linear-gradient(140deg,
      rgba(171, 146, 212, .24), rgba(119, 181, 232, .15) 55%, var(--glass));
    border: 1px solid var(--glass-br);
  }
  /* Bloom in the corner, so the panel has a light source. */
  .hero-stat::after {
    content: ''; position: absolute; right: -60px; top: -70px; width: 260px; height: 260px;
    border-radius: var(--pill); pointer-events: none;
    background: radial-gradient(circle, rgba(217, 143, 184, .3), transparent 68%);
  }
  .hero-stat .rule-label {
    display: inline-flex; align-items: center; gap: 8px;
    padding: 6px 13px; border-radius: var(--pill);
    background: var(--glass-2); border: 1px solid var(--glass-br);
    font-size: 10.5px; font-weight: 700; letter-spacing: .16em; text-transform: uppercase;
    color: var(--lilac-200);
  }
  .hero-stat .rule-label em {
    font-style: normal; color: var(--text-3); letter-spacing: .1em;
  }
  /* lilac-200 vanishes on a light panel — the tier name was invisible and the
     badge read as a bare "3 under". */
  @media (prefers-color-scheme: light) {
    .hero-stat .rule-label { color: var(--lilac-700); }
    .hero-stat .rule-label em { color: var(--text-2); }
    .hero-n {
      background: linear-gradient(120deg, var(--sky-900), var(--sky-600));
      -webkit-background-clip: text; background-clip: text;
    }
  }

  .hero-row { display: flex; align-items: baseline; gap: 12px; margin-top: 15px; flex-wrap: wrap; }
  .hero-n {
    font-family: var(--sans); font-size: clamp(52px, 7.4vw, 76px); font-weight: 900;
    letter-spacing: -.035em; line-height: .95;
    background: linear-gradient(120deg, var(--text), var(--sky-300));
    -webkit-background-clip: text; background-clip: text; color: transparent;
  }
  /* Share of the board sitting at this tier — turns a bare count into a rate. */
  .hero-trend {
    display: inline-flex; align-items: center; gap: 5px;
    padding: 5px 11px; border-radius: var(--pill);
    background: var(--good-bg); border: 1px solid var(--good-br);
    color: var(--good-fg); font-size: 12.5px; font-weight: 700;
  }
  .hero-trend svg { width: 14px; height: 14px; fill: currentColor; }
  .hero-note {
    margin-top: 12px; max-width: 34ch;
    font-size: 13.5px; line-height: 1.55; color: var(--text-2); text-wrap: pretty;
  }

  /* Deal quality: how the board is distributed across the ladder. Answers the
     question the raw counts don't — "is this a good day or a normal one?" */
  .quality { display: flex; flex-direction: column; gap: 11px; min-width: 158px; }
  .quality > .rule-label {
    font-size: 10px; font-weight: 700; letter-spacing: .16em; text-transform: uppercase;
    color: var(--text-3); background: none; border: 0; padding: 0;
  }
  .qrow { display: flex; flex-direction: column; gap: 5px; }
  .qrow .top { display: flex; justify-content: space-between; gap: 10px; font-size: 12px; color: var(--text-2); }
  .qrow .top b { color: var(--text); font-weight: 700; font-variant-numeric: tabular-nums; }
  .qbar { height: 5px; border-radius: var(--pill); background: var(--glass-2); overflow: hidden; }
  .qbar i {
    display: block; height: 100%; border-radius: var(--pill);
    background: var(--ramp); width: 0;
    transition: width .9s var(--ease-out);
  }

  /* Six stat cards. Was a flat divided strip; as cards they gain the icon and
     stop reading as a table. */
  .card-strip {
    display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 13px;
  }
  .card-strip .cell {
    display: flex; flex-direction: column; justify-content: space-between; gap: 14px;
    padding: 16px; border-radius: var(--r-md);
    background: var(--glass); border: 1px solid var(--glass-br);
    transition: border-color .22s var(--ease), background .22s, transform .22s var(--ease);
  }
  .card-strip .cell:hover {
    border-color: var(--lilac-400); background: var(--glass-2); transform: translateY(-2px);
  }
  .card-strip .cell .head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
  .card-strip .cell .l {
    font-size: 9.5px; font-weight: 700; letter-spacing: .15em; text-transform: uppercase;
    color: var(--text-3); line-height: 1.3;
  }
  .card-strip .cell .ico { width: 16px; height: 16px; flex: none; fill: var(--text-3); opacity: .75; }
  .card-strip .cell .n {
    font-size: 27px; font-weight: 700; letter-spacing: -.03em; line-height: 1; color: var(--text);
    font-variant-numeric: tabular-nums;
  }
  /* The two rarest rungs stay tinted even at zero — an empty Albatross is a
     fact about the data, not a broken card. */
  .card-strip .cell[data-tier="ace"] .n { color: var(--pink-300); }
  .card-strip .cell[data-tier="glitch"] .n { color: var(--lilac-300); }
  .card-strip .cell[data-tier="discount"] .n { color: var(--sky-300); }
  @media (prefers-color-scheme: light) {
    .card-strip .cell[data-tier="ace"] .n { color: var(--pink-600); }
    .card-strip .cell[data-tier="glitch"] .n { color: var(--lilac-600); }
    .card-strip .cell[data-tier="discount"] .n { color: var(--sky-600); }
  }

  /* ------------------------------------------------------------------ toolbar */
  .toolbar {
    display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
    margin-bottom: 16px; border-bottom: 0; padding-bottom: 0;
  }
  /* Segmented pill group. Horizontally scrollable so seven rungs never wrap
     into a second row on a phone. */
  .tabs {
    display: flex; align-items: center; gap: 5px; padding: 5px;
    border-radius: var(--pill); background: var(--glass); border: 1px solid var(--glass-br);
    overflow-x: auto; scrollbar-width: none; scroll-snap-type: x proximity;
    max-width: 100%;
  }
  .tabs::-webkit-scrollbar { display: none; }
  .tab {
    flex: none; scroll-snap-align: center;
    border: 0; border-radius: var(--pill); padding: 9px 15px;
    background: transparent; color: var(--text-2);
    font: 500 13px/1 var(--sans); letter-spacing: 0; text-transform: none;
    white-space: nowrap; cursor: pointer;
    transition: background .2s var(--ease), color .2s;
  }
  .tab::after { display: none; }        /* the old underline marker */
  .tab:hover { color: var(--text); background: var(--glass-2); }
  .tab.active {
    background: var(--ramp); color: var(--sky-900); font-weight: 700;
    box-shadow: var(--shadow-pill);
  }

  .filters { display: flex; align-items: center; gap: 10px; flex: 1; min-width: 220px; padding-bottom: 0; }
  .search-wrap {
    display: flex; align-items: center; gap: 9px; flex: 1; min-width: 0;
    height: 44px; padding: 0 16px; border-radius: var(--pill);
    background: var(--glass); border: 1px solid var(--glass-br);
    transition: border-color .2s;
  }
  .search-wrap:focus-within { border-color: var(--sky-400); }
  .search-wrap svg { width: 17px; height: 17px; flex: none; fill: var(--text-3); }
  #search {
    flex: 1; min-width: 0; height: 100%; border: 0; outline: 0; background: transparent;
    font: 400 14px var(--sans); color: var(--text); padding: 0;
  }
  #sort {
    height: 44px; padding: 0 38px 0 16px; border-radius: var(--pill);
    background: var(--glass); border: 1px solid var(--glass-br); color: var(--text);
    font: 400 14px var(--sans); cursor: pointer; appearance: none;
    background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%2363758a'%3E%3Cpath d='M7 10l5 5 5-5z'/%3E%3C/svg%3E");
    background-repeat: no-repeat; background-position: right 12px center; background-size: 18px;
  }

  /* ------------------------------------------------------------------ caution */
  /* NOT flex: the paragraph's inline children (strong, links, bare text) would
     each become a flex item and the copy would break into ragged columns.
     Block plus an absolutely positioned icon keeps it as prose. */
  .caution {
    position: relative; display: block; padding: 15px 17px 15px 46px; margin: 0 0 16px;
    border-radius: var(--r-sm); border: 1px solid var(--warn-br); border-left-width: 1px;
    background: linear-gradient(100deg, var(--warn-bg), var(--glass));
    font-size: 12.5px; line-height: 1.6; color: var(--text-2); text-wrap: pretty;
  }
  .caution::before {
    content: ''; position: absolute; left: 17px; top: 16px; width: 19px; height: 19px;
    background: var(--warn);
    -webkit-mask: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2a10 10 0 100 20 10 10 0 000-20zm0 5a1.3 1.3 0 110 2.6A1.3 1.3 0 0112 7zm1.2 11h-2.4v-6.6h2.4z'/%3E%3C/svg%3E") center/contain no-repeat;
    mask: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2a10 10 0 100 20 10 10 0 000-20zm0 5a1.3 1.3 0 110 2.6A1.3 1.3 0 0112 7zm1.2 11h-2.4v-6.6h2.4z'/%3E%3C/svg%3E") center/contain no-repeat;
  }
  .caution strong { color: var(--text); font-weight: 700; }

  /* The disclaimer is a <details>. The summary carries the essential warning so
     the collapsed state is never a way of hiding liability copy. */
  .caution summary {
    list-style: none; cursor: pointer; color: var(--text-2);
    font-size: 12.5px; line-height: 1.6;
  }
  .caution summary::-webkit-details-marker { display: none; }
  .caution summary::after {
    content: 'Read the full note';
    display: block; margin-top: 6px;
    font-size: 11.5px; font-weight: 700; color: var(--sky-300);
  }
  .caution[open] summary { display: none; }   /* the full text repeats it */
  .caution-full { margin: 0; }
  @media (prefers-color-scheme: light) {
    .caution summary::after { color: var(--sky-700); }
  }

  .count-row {
    display: flex; align-items: center; justify-content: space-between; gap: 14px;
    margin-bottom: 14px; font-size: 12.5px; color: var(--text-3);
  }
  .result-count { margin: 0; font-family: var(--sans); font-size: 12.5px; color: var(--text-3); }
  .result-count b { color: var(--text); font-weight: 700; }
  .updated { white-space: nowrap; }

  /* ------------------------------------------------------------------ sidebar */
  .layout { grid-template-columns: 262px minmax(0, 1fr); gap: 22px; }
  .sidebar {
    position: sticky; top: 88px;
    border-radius: var(--r-md); background: var(--glass);
    border: 1px solid var(--glass-br); overflow: hidden;
    max-height: calc(100vh - 108px); overflow-y: auto;
  }
  .side-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: 15px 17px; border-bottom: 1px solid var(--glass-br);
  }
  .side-head .rule-label {
    display: flex; align-items: center; gap: 9px;
    font-size: 12px; font-weight: 700; letter-spacing: .13em; text-transform: uppercase;
    color: var(--text);
  }
  .side-head .rule-label svg { width: 17px; height: 17px; fill: var(--lilac-300); }
  .facet { border-bottom: 1px solid var(--glass-br); padding: 14px 17px; }
  .facet:last-child { border-bottom: 0; }
  .facet summary {
    font-size: 10.5px; font-weight: 700; letter-spacing: .15em; text-transform: uppercase;
    color: var(--text-3); margin-bottom: 10px;
  }
  .opt {
    display: flex; align-items: center; gap: 10px;
    padding: 7px 8px; margin: 0 -8px; border-radius: 9px;
    font-size: 14px; cursor: pointer;
    transition: background .18s;
  }
  .opt:hover { background: var(--glass-2); }
  .opt .label { flex: 1; font-size: 14px; color: var(--text); }
  .opt .n { font-size: 12px; color: var(--text-3); font-variant-numeric: tabular-nums; }

  .chips { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 14px; }
  .chip {
    display: inline-flex; align-items: center; gap: 7px;
    padding: 6px 8px 6px 13px; border-radius: var(--pill);
    background: var(--glass-2); border: 1px solid var(--glass-br);
    font-size: 12.5px; color: var(--text);
  }

  /* ------------------------------------------------------------------- cards */
  /* align-items: start stops rows stretching every card to the tallest in the
     row. Listings carry wildly different amounts of evidence — one has both
     percentages and a was-price, its neighbour has neither — and stretching
     opened a 150px hole in the shorter card. */
  .grid {
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: 16px; align-items: start;
  }

  .card {
    display: flex; flex-direction: column; border-radius: 18px; overflow: hidden;
    background: var(--glass); border: 1px solid var(--glass-br);
    transition: transform .24s var(--ease-out), border-color .24s, box-shadow .24s;
  }
  .card:hover {
    transform: translateY(-4px); border-color: var(--lilac-400); box-shadow: var(--shadow-card);
  }
  /* Gradient cap — the tier's colour, so the rung is legible before you read. */
  .card::before {
    content: ''; display: block; height: 4px; flex: none; background: var(--ramp);
  }
  .card.tier-ace::before    { background: linear-gradient(90deg, var(--pink-400), var(--lilac-400)); }
  .card.tier-glitch::before { background: linear-gradient(90deg, var(--lilac-400), var(--sky-300)); }
  .card.tier-bogey::before,
  .card.tier-par::before    { background: var(--rule); }

  /* The image sits on a light panel: golf gear is photographed on white, and a
     dark tile made every cut-out look like a hole in the card. Taller than the
     old 168px because a driver is a tall object and was being cropped. */
  /* The earlier rule sets aspect-ratio AND min-height: 210px, so a bare `height`
     is silently ignored — that is what kept mobile cards 503px tall. Both have
     to be unset explicitly. Clubs still get a tall frame here; they are long,
     narrow objects and were being cropped at 148px. */
  .thumb {
    position: relative; margin: 14px 14px 0; border-radius: 12px;
    aspect-ratio: auto; min-height: 0; height: 210px;
    /* Near-white rather than a grey gradient: product shots are cut out on
       white, and a grey panel framed every one of them in a visible box. */
    background: #fafbfd;
    border-bottom: 0;
    display: grid; place-items: center; overflow: hidden;
  }
  .thumb img { width: 100%; height: 100%; object-fit: contain; padding: 10px; }
  .thumb svg { width: 54px; height: 54px; opacity: .3; }

  .badges { position: absolute; top: 24px; left: 24px; z-index: 2; display: flex; gap: 6px; flex-wrap: wrap; max-width: calc(100% - 48px); }
  .badge {
    padding: 5px 11px; border-radius: var(--pill);
    font-size: 9.5px; font-weight: 700; letter-spacing: .12em; text-transform: uppercase;
    color: #fff; border: 0; box-shadow: 0 6px 18px rgba(0, 0, 0, .35);
    background: var(--lilac-600);
  }
  .badge.ace      { background: linear-gradient(140deg, var(--pink-600), var(--lilac-600)); }
  .badge.glitch   { background: linear-gradient(140deg, var(--lilac-600), var(--sky-600)); }
  .badge.extreme  { background: var(--sky-600); }
  .badge.discount { background: var(--sky-700); }
  .badge.par, .badge.bogey { background: #5a6b85; }
  .badge.src, .badge.used, .badge.sample {
    background: rgba(8, 12, 19, .74); backdrop-filter: blur(6px);
    color: var(--sky-100); font-weight: 500; letter-spacing: .06em;
  }

  .card .body { display: flex; flex-direction: column; gap: 9px; padding: 13px 15px 15px; }
  .card .title {
    font-size: 13.5px; font-weight: 500; line-height: 1.4; color: var(--text);
    text-wrap: pretty; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
    overflow: hidden;
  }
  /* Where it is and what condition — the two things that decide whether a
     percentage means anything. */
  .card .seller-row {
    display: flex; align-items: center; gap: 7px;
    font-size: 11px; color: var(--text-3); min-width: 0;
  }
  .card .seller-row svg { width: 13px; height: 13px; flex: none; fill: currentColor; }
  .card .seller-row .who { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .card .seller-row .dot { color: var(--rule); }

  /* NOT ".foot" — that is the page footer's class, and reusing it here gave
     every card a 52px margin-top, opening a void above the price. */
  .card-foot {
    display: flex; align-items: flex-end; justify-content: space-between; gap: 8px;
    padding-top: 11px; border-top: 1px solid var(--glass-br);
  }
  .prices { display: flex; flex-direction: column; gap: 2px; align-items: flex-start; }
  /* Circular, not the old editorial serif. The price is the most-read number on
     the card and a serif numeral next to a Circular title read as a bug. */
  .prices .now {
    font-family: var(--sans); font-size: 21px; font-weight: 700;
    letter-spacing: -.025em; color: var(--text); font-variant-numeric: tabular-nums;
  }
  .prices .was { font-size: 12px; color: var(--text-3); text-decoration: line-through; }
  .prices .saved { display: none; }        /* the vs-market pill carries this now */

  .pcts { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; text-align: right; }
  .pct {
    display: inline-flex; align-items: baseline; gap: 4px;
    padding: 4px 9px; border-radius: var(--pill);
    background: var(--good-bg); border: 1px solid var(--good-br); color: var(--good-fg);
    font-size: 10.5px; font-weight: 700; white-space: nowrap;
  }
  .pct b { font-weight: 700; }
  .pct i { font-style: normal; font-weight: 500; opacity: .8; }
  /* "vs site" is the stronger figure but the quieter one: it is often empty, and
     a pill that says nothing is worse than a line of text. */
  .pct.site {
    background: none; border: 0; padding: 0; color: var(--text-3);
    font-size: 10.5px; font-weight: 500;
  }
  .pct.pending { background: none; border: 0; padding: 0; color: var(--text-3); font-weight: 500; font-size: 10px; }

  .card .meta-row.ident, .card .why { display: none; }   /* moved into the title tooltip */
  .card .meta-row {
    display: flex; justify-content: space-between; gap: 8px;
    font-size: 10.5px; color: var(--text-3);
  }

  .skel { border-radius: 18px; height: 372px; }

  /* -------------------------------------------------------------------- join */
  /* The original rail set its heading in the serif face. The rebuild is all
     Circular, and one serif line in the middle of the page read as a leftover. */
  .join { border-radius: var(--r-md); }
  .join::before { height: 3px; }
  .join-copy strong { font-family: var(--sans); font-weight: 700; letter-spacing: -.01em; }

  /* Typography holdouts from the previous design. The rebuild is one typeface. */
  .cell .n, .empty p, .consent h2, .ace-stamp {
    font-family: var(--sans); letter-spacing: -.02em;
  }
  .ace-stamp { font-weight: 900; }
  .consent h2 { font-weight: 700; }

  /* --------------------------------------------------------------- mobile bar */
  /* Hidden on desktop; on a phone this is the only filter entry point, pinned
     within thumb reach at the bottom rather than buried above the results.
     The scrim and the done button sit outside .layout so they cannot become
     grid children, which means they must be explicitly hidden here. */
  .mobile-bar, .scrim, .side-done { display: none; }

  /* ═══════════════════════════════════════════════════ mobile ═════════════ */
  @media (max-width: 1080px) {
    .scoreboard { grid-template-columns: 1fr; }
  }

  @media (max-width: 900px) {
    .masthead { padding: 11px clamp(14px, 4vw, 20px); }
    .masthead-right .status { display: none; }   /* the stat cards say it better */
    .mark { width: 34px; height: 34px; }
    .wordmark { font-size: 21px; }
    .tagline { font-size: 8.5px; letter-spacing: .2em; }
    .actions .btn { padding: 9px 15px; font-size: 12px; }

    /* The lane is decorative and eats 48px above the fold on a phone. */
    .lane { display: none; }

    /* Tightened hard, because on a phone this card is the only thing between the
       fold and the first product. Every 100px here is a product the visitor
       doesn't have to scroll past. */
    .hero-stat { grid-template-columns: 1fr; gap: 13px; padding: 16px 17px; }
    .hero-row { margin-top: 11px; }
    .hero-n { font-size: clamp(40px, 13vw, 54px); }
    .hero-note { max-width: none; margin-top: 8px; font-size: 12.5px; }
    .hero-stat .rule-label { padding: 5px 11px; font-size: 9.5px; }
    .quality { min-width: 0; gap: 7px; }
    .quality > .rule-label { font-size: 9.5px; }
    .qrow { gap: 3px; }
    .qrow .top { font-size: 11.5px; }
    .qbar { height: 4px; }

    /* Stat cards become a snap rail: six cards stacked would be a screen and a
       half before the first product. */
    .card-strip {
      display: flex; gap: 10px; overflow-x: auto; scroll-snap-type: x mandatory;
      margin: 0 calc(clamp(14px, 4vw, 20px) * -1); padding: 2px clamp(14px, 4vw, 20px);
      scrollbar-width: none;
    }
    .card-strip::-webkit-scrollbar { display: none; }
    .card-strip .cell { flex: 0 0 132px; scroll-snap-align: start; }

    /* Products first. The sidebar leaves the flow entirely and becomes a
       drawer — this is the single biggest mobile fix: it used to occupy a full
       screen of checkboxes above the first card. */
    .layout, .layout.collapsed { grid-template-columns: minmax(0, 1fr); }
    .filter-toggle { display: none; }        /* replaced by the bottom bar */

    .sidebar {
      position: fixed; z-index: 55; inset: 0 auto 0 0;
      width: min(87vw, 340px); max-height: none; height: 100%;
      margin: 0; border-radius: 0 var(--r-lg) var(--r-lg) 0;
      border: 0; border-right: 1px solid var(--glass-br);
      background: var(--panel);
      transform: translateX(-101%);
      transition: transform .32s var(--ease-out);
      overflow-y: auto; overscroll-behavior: contain;
      padding-bottom: 88px;                  /* clear of the done button */
    }
    .sidebar.open { transform: none; box-shadow: 26px 0 60px rgba(0, 0, 0, .5); }
    .side-head {
      position: sticky; top: 0; z-index: 2;
      background: var(--panel); padding: 16px 18px;
    }
    .scrim {
      display: block;
      position: fixed; inset: 0; z-index: 50; background: rgba(4, 7, 12, .62);
      backdrop-filter: blur(3px);
      opacity: 0; pointer-events: none; transition: opacity .3s var(--ease);
    }
    .scrim.on { opacity: 1; pointer-events: auto; }
    /* Full-width targets: 44px minimum, and the count on the right where a
       thumb is not covering it. */
    .opt { padding: 12px 10px; margin: 0 -10px; font-size: 15px; }
    .opt .label { font-size: 15px; }

    /* Sticky action bar. Two jobs only — filter and sort — because those are
       the two things a phone user actually changes. */
    .mobile-bar {
      display: flex; gap: 10px; align-items: center;
      position: fixed; z-index: 45; left: 0; right: 0; bottom: 0;
      padding: 10px clamp(14px, 4vw, 20px) calc(10px + env(safe-area-inset-bottom));
      background: color-mix(in srgb, var(--ink) 88%, transparent);
      backdrop-filter: blur(18px);
      border-top: 1px solid var(--glass-br);
    }
    .mobile-bar .btn { flex: 1; height: 46px; display: inline-flex; align-items: center; justify-content: center; gap: 8px; }
    .mobile-bar .btn svg { width: 17px; height: 17px; fill: currentColor; }
    .mobile-bar .n {
      min-width: 20px; height: 20px; padding: 0 6px; border-radius: var(--pill);
      background: var(--ramp); color: var(--sky-900);
      font-size: 11px; font-weight: 700; display: grid; place-items: center;
    }
    /* display:grid above would otherwise beat the UA's [hidden] rule and show a
       permanent "0" next to Filters. */
    .mobile-bar .n[hidden] { display: none; }
    .side-done {
      display: block;
      position: fixed; z-index: 56; left: 0; bottom: 0;
      width: min(87vw, 340px);
      padding: 12px 18px calc(12px + env(safe-area-inset-bottom));
      background: var(--panel); border-top: 1px solid var(--glass-br);
      transform: translateX(-101%); transition: transform .32s var(--ease-out);
    }
    .side-done.open { transform: none; }
    .side-done .btn { width: 100%; height: 46px; }

    body { padding-bottom: 96px; }
    .toolbar { gap: 10px; }
    .filters { width: 100%; }
    #sort { display: none; }                 /* lives in the bottom bar now */
    .grid { grid-template-columns: repeat(auto-fill, minmax(158px, 1fr)); gap: 12px; }
    .thumb { height: 152px; min-height: 0; aspect-ratio: auto; margin: 10px 10px 0; }
    .card .body { padding: 11px 12px 13px; gap: 7px; }
    .card .title { font-size: 12.5px; }
    .prices .now { font-size: 17px; }
    .badges { top: 18px; left: 18px; }
    .badge { padding: 4px 8px; font-size: 8.5px; }
    /* Two figures side by side don't fit a 158px card, so the layout turns. */
    .card-foot { flex-direction: column; align-items: flex-start; gap: 7px; }
    .pcts { align-items: flex-start; text-align: left; }
    .caution { font-size: 12px; padding: 13px 14px 13px 41px; }
    .caution::before { left: 14px; top: 14px; }

    /* The CTA earns its place but not 259px of it. */
    .join { gap: 12px; padding: 14px 15px; margin: 16px 0 4px; }
    .join-glyph { width: 36px; height: 36px; }
    .join-glyph svg { width: 21px; height: 21px; }
    .join-copy strong { font-size: 15px; }
    .join-copy span { font-size: 12px; line-height: 1.45; }
    .join-btn { padding: 11px 18px; font-size: 12.5px; }
    .count-row { font-size: 11.5px; }
    .skel { height: 250px; }
  }

  @media (max-width: 420px) {
    .grid { grid-template-columns: 1fr 1fr; gap: 10px; }
    .hero-stat { padding: 17px; }
  }

  @media (prefers-reduced-motion: reduce) {
    .card:hover { transform: none; }
    .qbar i { transition: none; }
  }
}

@layer motion {
  @keyframes live-pulse {
    0%, 100% { opacity: 1; transform: scale(1); }
    50%      { opacity: .45; transform: scale(.82); }
  }
}
