/* ==========================================================================
   PM Countdown: ticker.css (tk-)
   The "PERPS · SPOT" strip that sits under the header and above <main>.
   Spec: research/14-hero-and-ticker-design.md section 2 (Build 1, the spot strip).
   Data: Coinbase spot via /api/coinbase/ticker; max leverage via /api/kalshi-perps when it
   answers, else Kalshi's listed table dated 2026-09-20 (assets/ticker.js LISTED_LEV).
   Load order: site.css, dock.css, ticker.css, then page CSS.

   v28 (2026-09-20, Jay: "make the scroll look better and more distinct with logo of each coin"):
   every cell opens with a 22px coin mark (an original monogram in the coin's brand color, or a
   licensed logo file once one is listed in ticker.js COIN_ICONS), the 24h change sits on a soft
   tinted chip, cells are divided by a hairline.
   v29 (2026-09-21, Jay: live prices, "some graphic when the number change by a digit or 2 ... green
   or red"): the coin PNGs in assets/img/coins/ render inside the same 22px (18px phone) disc; the
   v28 600ms background flash is replaced by the DIGIT TAIL: ticker.js wraps the digits that changed
   in .tk-d[data-dir] (green up / red down, 1px underline, fades back to ink over 700ms) and sets
   data-tick on the price slot, which draws a tiny ▲ / ▼ above its right edge. The unchanged leading
   digits never change color. Reduced motion: no fade, the colored tail holds until ticker.js clears
   it (MARK_MS). Nothing else moves on a tick, and nothing takes layout space (the glyph is absolute).

   v30 (2026-09-21, Jay: sticky, no Coinbase-spot line, speed, a second reversed row, drag):
   the strip is PINNED under the sticky header (top: --hd-h + the header's 1px border, z-index just
   under it) and html scroll-padding-top grows by its height, so nothing lands behind it. --tk-row is
   one row's height and --tk-rows (written on <html> by ticker.js) is 1 or 2, so --tk-h stays the one
   number both the strip and the scroll padding read. The provenance line (.tk-ts) is GONE from the
   strip: ticker.js puts it in the strip's accessible name and in a title on the lead and the tail, and
   .tk-age prints the age in the strip only while the feed is delayed. The marquee's loop distance is
   now ONE SET (--tk-set), not half the track, because .tk-row carries a drag offset wrapped into one
   set and the tape needs that much slack. .tk-row is the drag layer (touch-action:pan-y: a vertical
   swipe still scrolls the page); .tk-track keeps the keyframe and nothing else.

   v31 (2026-09-21, Jay: a maximize button, and speed in .25 steps up to 3): [data-max="1"] hides
   .tk-lead and every CONTROL in .tk-tail with display:none (out of the tab order and out of the
   accessible tree in one move) and takes .tk-tail out of the flow, so .tk-vp gets the whole bar. The
   toggle itself stays behind as a 44px chip over the tape's right edge, on its own --tk-bg chip and a
   gradient scrim, so it is legible in both themes over moving content and the way back is never lost.
   The strip's height does not change: the tail is absolutely positioned inside a fixed-height bar.
   .tk-age survives maximizing on purpose, because a delayed feed has to say so in visible words.

   Three rules this file exists to keep:
     1. The strip's height is fixed at every state, so it contributes 0 to CLS.
     2. Only `transform` on .tk-track is animated, and only by the compositor.
     3. Under prefers-reduced-motion the track must NOT be left at its end state.
        site.css section 18 sets animation-duration:.001ms!important on everything,
        which would snap a marquee to translate3d(-50%) and hide the first symbols.
        `animation-name:none` is not overridden by that !important, so that is what
        we use, with an explicit transform:none behind it.
   ========================================================================== */

/* One row's height, the row count, and the strip's height derived from both. They live on :root, not
   on .tk, because html's scroll-padding-top has to read the same number: an in-page anchor must land
   below the header AND below the strip, at one row or two. ticker.js writes --tk-rows on <html>. */
:root{--tk-row:40px; --tk-rows:1; --tk-h:calc(var(--tk-row) * var(--tk-rows))}
html{scroll-padding-top:calc(var(--hd-h) + 1px + var(--tk-h) + 16px)}

.tk{
  /* geometry */
  --tk-fade:14px; --tk-dur:30s; --tk-set:0px;
  --tk-cell-px:13px;          /* cell side padding: the "air" between the hairline and the mark */
  --tk-mark:22px;             /* coin mark diameter */
  /* dark palette (default) */
  --tk-bg:#070c1a; --tk-line:rgba(240,246,252,.09); --tk-sep:rgba(240,246,252,.12);
  --tk-ink:var(--dink); --tk-ink-2:var(--dink-2); --tk-ink-3:var(--dink-3);
  --tk-up:#2fd98f; --tk-dn:#ff7c88; --tk-flat:var(--dink-3);
  --tk-up-soft:rgba(47,217,143,.13); --tk-dn-soft:rgba(255,124,136,.13); --tk-flat-soft:rgba(240,246,252,.07);
  --tk-amber:var(--amber);
  --tk-lev-ink:var(--dink-2); --tk-lev-line:rgba(240,246,252,.24);
  --tk-skel:rgba(240,246,252,.08);
  --tk-hover:rgba(240,246,252,.06);
  --tk-mark-ring:rgba(255,255,255,.30);   /* the thin inner ring on every monogram */
  --tk-mark-ink:#ffffff;

  /* Pinned under the site header (site.css .site-hd: sticky, top 0, height --hd-h, 1px bottom border),
     below it in the stack so the header and the mobile drawer always win. */
  position:sticky;top:calc(var(--hd-h) + 1px);z-index:calc(var(--z-hd) - 1);
  /* v31b: its own compositor layer and stacking context, so the sticky bar is composited, not
     repainted, as the page scrolls under the header. Without this Chromium promoted and demoted the
     layer as the stick state changed, and text anti-aliasing flipped with it. */
  isolation:isolate;will-change:transform;backface-visibility:hidden;-webkit-backface-visibility:hidden;
  display:flex;align-items:center;gap:14px;
  height:var(--tk-h);box-sizing:border-box;   /* fixed, never min-height: every state is the same height */
  padding:0 var(--gutter);
  background:var(--tk-bg);
  border-top:1px solid var(--tk-line);
  border-bottom:1px solid var(--tk-line);
  overflow:hidden;
  color:var(--tk-ink-2);
  font:600 12.5px/1 var(--sans);
}
/* Light theme. --tk-up is darker than the site's --gain on purpose: this is 12px text.
   Measured on white: #087043 = 6.16:1, #b42333 = 6.50:1 (research/14 section 5.1). */
:root[data-theme="light"] .tk{
  --tk-bg:var(--surface-2); --tk-line:var(--line); --tk-sep:var(--line-2);
  --tk-ink:var(--ink); --tk-ink-2:var(--ink-2); --tk-ink-3:var(--ink-3);
  --tk-up:#087043; --tk-dn:#b42333; --tk-flat:var(--ink-3);
  --tk-up-soft:rgba(8,112,67,.10); --tk-dn-soft:rgba(180,35,51,.09); --tk-flat-soft:rgba(10,18,38,.06);
  --tk-amber:var(--warn-ink);
  --tk-lev-ink:var(--ink-2); --tk-lev-line:var(--line-2);
  --tk-skel:rgba(10,18,38,.07);
  --tk-hover:rgba(36,89,240,.06);
  --tk-mark-ring:rgba(255,255,255,.34);
}

/* --- coin marks ----------------------------------------------------------
   Original monograms, never a traced logo: a filled circle in the coin's brand hue with the
   ticker's first letters. Fills are the widely used brand color, darkened where white text
   would not reach 4.5:1 on it (measured 2026-09-20 with the WCAG formula):
     BTC  #b85606  4.80   orange         LINK #2a5ada  5.88   blue (brand exact)
     ETH  #4257e0  5.72   indigo         LTC  #345d9d  6.57   steel blue (brand exact)
     SOL  #7c3aed  5.70   purple, green ring #14f195     ADA  #0033ad 10.11  blue (brand exact)
     XRP  #23292f 14.69   graphite (dark theme #3a4148, 10.35, so it has an edge on the dark strip)
     DOGE #8a6f12  4.81   gold           BCH  #0a8560  4.63   green
     SUI  #1a6fd8  4.87   sky blue       other #4b5563 7.56   neutral
   The tokens live on .tk-mark so a mark works anywhere (strip, board), in both themes. */
.tk-mark{
  --tk-c-btc:#b85606; --tk-c-eth:#4257e0; --tk-c-sol:#7c3aed; --tk-c-sol-ring:rgba(20,241,149,.85);
  --tk-c-xrp:#3a4148; --tk-c-doge:#8a6f12; --tk-c-link:#2a5ada; --tk-c-ltc:#345d9d;
  --tk-c-ada:#0033ad; --tk-c-bch:#0a8560; --tk-c-sui:#1a6fd8; --tk-c-x:#4b5563;
  --tk-c-dot:#1f2329; --tk-c-xlm:#23292f; --tk-c-hbar:#111111; --tk-c-hype:#0b3b33; --tk-c-shib:#1f2329;
  --tk-c-near:#3a4148;
  flex:none;width:var(--tk-mark);height:var(--tk-mark);border-radius:50%;
  display:inline-flex;align-items:center;justify-content:center;overflow:hidden;
  background:var(--tk-c-x);color:var(--tk-mark-ink);
  box-shadow:inset 0 0 0 1px var(--tk-mark-ring);
  font:800 9.5px/1 var(--cond);letter-spacing:.01em;text-transform:uppercase;
  -webkit-font-smoothing:antialiased;user-select:none;
}
:root[data-theme="light"] .tk-mark{--tk-c-xrp:#23292f;--tk-c-near:#23292f}
.tk-mark[data-coin="btc"]{background:var(--tk-c-btc)}
.tk-mark[data-coin="eth"]{background:var(--tk-c-eth)}
.tk-mark[data-coin="sol"]{background:var(--tk-c-sol);box-shadow:inset 0 0 0 1.5px var(--tk-c-sol-ring)}
.tk-mark[data-coin="xrp"]{background:var(--tk-c-xrp)}
.tk-mark[data-coin="doge"]{background:var(--tk-c-doge)}
.tk-mark[data-coin="link"]{background:var(--tk-c-link)}
.tk-mark[data-coin="ltc"]{background:var(--tk-c-ltc)}
.tk-mark[data-coin="ada"]{background:var(--tk-c-ada)}
.tk-mark[data-coin="bch"]{background:var(--tk-c-bch)}
.tk-mark[data-coin="sui"]{background:var(--tk-c-sui)}
/* v31a: coins whose logo is a bare mark need their own ground; the ones whose logo fills the chip
   keep a matching hue underneath so a slow-loading image never flashes grey. */
.tk-mark[data-coin="dot"]{background:var(--tk-c-dot)}
.tk-mark[data-coin="xlm"]{background:var(--tk-c-xlm)}
.tk-mark[data-coin="hbar"]{background:var(--tk-c-hbar)}
.tk-mark[data-coin="hype"]{background:var(--tk-c-hype)}
.tk-mark[data-coin="shib"]{background:var(--tk-c-shib)}
.tk-mark[data-coin="near"]{background:var(--tk-c-near)}
.tk-mark-t{display:block;line-height:1;transform:translateY(.5px)}   /* optical centre for caps */
/* A logo file (ticker.js COIN_ICONS, assets/img/coins/<sym>.png, 64x64 transparent): the image fills
   the same reserved box, disc background transparent so the PNG's own disc shows, plus a faint ring
   so a dark disc (XRP, HBAR, XLM) still has an edge on the dark strip. The box is reserved by
   .tk-mark's width/height whether or not the file has loaded: no shift. */
.tk-mark.tk-mark--img{background:transparent;box-shadow:inset 0 0 0 1px rgba(255,255,255,.14)}   /* two classes: outranks the [data-coin] fills above */
:root[data-theme="light"] .tk-mark.tk-mark--img{box-shadow:inset 0 0 0 1px rgba(10,18,38,.08)}
/* ...except where the artwork is a BARE WHITE mark. XLM and XRP ship black, and NEAR ships with no
   fill attribute at all (so it renders black), and the pack inverts all three to white for the dark
   chip. On the light strip a transparent chip would leave a white mark on a near-white bar: measured
   1.06:1, which is nothing at all. These three keep their own dark ground in BOTH themes, which is
   how each brand shows its mark. Two classes plus the attribute, so this beats the blanket
   transparent above wherever it sits in the file. Every other coin's artwork carries its own disc or
   its own colour and is left transparent; tk31a measures every shipped mark either way. */
.tk-mark.tk-mark--img[data-coin="xrp"]{background:var(--tk-c-xrp)}
.tk-mark.tk-mark--img[data-coin="xlm"]{background:var(--tk-c-xlm)}
.tk-mark.tk-mark--img[data-coin="near"]{background:var(--tk-c-near)}
.tk-mark--img img{display:block;width:100%;height:100%;object-fit:contain;border-radius:50%}

/* --- lead ---------------------------------------------------------------- */
.tk-lead{display:flex;align-items:center;gap:6px;flex:none;margin:0;height:100%;padding-right:10px;border-right:1px solid var(--tk-sep)}
.tk-dot{width:8px;height:8px;border-radius:50%;flex:none;color:var(--tk-ink-3);background:currentColor;position:relative}   /* v32: the dot owns `color` and paints with it, so the mark inside the lead inherits the state colour */
.tk[data-state="live"] .tk-dot{color:var(--tk-up)}
.tk[data-state="live"] .tk-dot::after{content:"";position:absolute;inset:0;border-radius:50%;background:inherit;animation:tk-ping 1.8s ease-out infinite}
@keyframes tk-ping{0%{transform:scale(1);opacity:.7}80%,100%{transform:scale(3);opacity:0}}
.tk[data-state="stale"] .tk-dot{color:var(--tk-amber)}
.tk[data-state="error"] .tk-dot,.tk[data-state="empty"] .tk-dot{color:var(--tk-ink-3)}
.tk-lead-t{font:800 10.5px/1 var(--sans);letter-spacing:.09em;text-transform:uppercase;color:var(--tk-ink-3);white-space:nowrap}

/* --- viewport and track --------------------------------------------------- */
.tk-vp{position:relative;flex:1 1 auto;min-width:0;height:100%;overflow:hidden}
/* v31b (2026-09-21, Jay: "the main page flickers a bit" while scrolling). The edge fade used to be a
   mask-image on this viewport. A mask forces Chromium to draw the masked subtree into an offscreen
   surface every frame, and once the strip became sticky (v30) that surface was re-rasterised on every
   scroll step under a moving compositor layer, which reads as flicker. The fade is now two gradient
   overlays in the strip's own background colour: same look, no offscreen surface, pointer-events off
   so the drag and the links at the edges still work. */
.tk-vp::before,.tk-vp::after{content:"";position:absolute;top:0;bottom:0;width:var(--tk-fade);z-index:3;pointer-events:none}
.tk-vp::before{left:0;background:linear-gradient(90deg,var(--tk-bg),transparent)}
.tk-vp::after{right:0;background:linear-gradient(270deg,var(--tk-bg),transparent)}
/* Two layers, two jobs: .tk-row carries the DRAG offset that ticker.js writes, .tk-track carries the
   marquee keyframe and nothing else, so a drag never fights the animation and the animation stays on
   the compositor. touch-action:pan-y means a vertical swipe scrolls the page and only sideways drags. */
.tk-row{display:block;width:max-content;height:100%;touch-action:pan-y;cursor:grab;will-change:transform}
.tk[data-drag="1"] .tk-row{cursor:grabbing;user-select:none;-webkit-user-select:none}
.tk[data-rows="2"] .tk-row{height:50%}
.tk-track{display:flex;width:max-content;height:100%;align-items:center;will-change:transform}
/* Cells butt against each other and each <li> carries the hairline on its right edge, so the
   seam between two sets is one more hairline, identical to the divider between two cells, and
   translate3d(-50%) lands on it exactly. The lead-in equals the mask fade so the first mark is
   never eaten by the edge gradient. */
.tk-set{display:flex;align-items:center;gap:0;flex:0 0 auto;list-style:none;margin:0;height:100%;
  padding-left:var(--tk-fade);padding-right:0}
.tk-set>li{display:flex;align-items:center;height:100%;border-right:1px solid var(--tk-sep)}

/* The loop distance is ONE SET (--tk-set, written by ticker.js from the measured width), not half the
   track: .tk-row adds a drag offset wrapped into one set, so the track has to carry the viewport plus
   two sets of slack or a hard flick would pull its own edge into view. measure() clones until it does.
   The trailing padding puts the gap inside the measured width, so the seam is one more hairline. */
@keyframes tk-roll{from{transform:translate3d(0,0,0)}to{transform:translate3d(calc(-1 * var(--tk-set)),0,0)}}
@keyframes tk-roll-rev{from{transform:translate3d(calc(-1 * var(--tk-set)),0,0)}to{transform:translate3d(0,0,0)}}
.tk[data-state="live"] .tk-track,
.tk[data-state="stale"] .tk-track{animation:tk-roll var(--tk-dur) linear infinite}
.tk[data-state="live"] .tk-track--rev,
.tk[data-state="stale"] .tk-track--rev{animation-name:tk-roll-rev}
.tk[data-static="1"] .tk-track{animation:none}
.tk-vp:hover .tk-track,
.tk-vp:focus-within .tk-track,
.tk[data-paused="1"] .tk-track,
.tk[data-drag="1"] .tk-track,
.tk[data-off="1"] .tk-track{animation-play-state:paused}

/* --- a cell: mark · SYM · price · 24h chip · leverage badge ------------------- */
.tk-cell{display:flex;align-items:center;gap:8px;height:100%;padding:0 var(--tk-cell-px);
  text-decoration:none;color:inherit;white-space:nowrap}
.tk-cell:hover{background:var(--tk-hover)}
.tk-cell:focus-visible{outline:3px solid #8fb4ff;outline-offset:-3px}
:root[data-theme="light"] .tk-cell:focus-visible{outline-color:#1d4ed8}
.tk-sym{font:800 12.5px/1 var(--sans);letter-spacing:.04em;color:var(--tk-ink);min-width:3.1em;
  font-variant-numeric:tabular-nums}
.tk-last{box-sizing:content-box;   /* the reserved min-width is a content width, measured in tabular digits */
  position:relative;   /* anchors the ▲ / ▼ tick glyph (v29), which is absolute so it takes no width */
  font-variant-numeric:tabular-nums;font-weight:700;font-size:13px;color:var(--tk-ink);text-align:right;padding:3px 4px;border-radius:4px}
.tk-chg{display:inline-flex;align-items:center;justify-content:flex-end;gap:4px;min-width:calc(8.4ch + 12px);
  font-variant-numeric:tabular-nums;font-weight:700;font-size:12px;text-align:right;
  padding:3px 6px;border-radius:4px;background:var(--tk-flat-soft);color:var(--tk-flat)}
.tk-chg[data-dir="up"]{color:var(--tk-up);background:var(--tk-up-soft)}
.tk-chg[data-dir="down"]{color:var(--tk-dn);background:var(--tk-dn-soft)}
.tk-chg[data-dir="flat"]{color:var(--tk-flat);background:var(--tk-flat-soft)}
.tk-gly{font-style:normal;font-size:8.5px;line-height:1}
.tk-lev{font:700 10.5px/1 var(--sans);letter-spacing:.02em;color:var(--tk-lev-ink);text-align:right;min-width:calc(5.2ch + 12px);
  border:1px solid var(--tk-lev-line);border-radius:4px;padding:3px 6px;font-variant-numeric:tabular-nums}
/* A listed figure (Kalshi's own page, dated) is an outline chip with a dashed edge and a leading "≈";
   a live figure from Kalshi's API gets a solid edge. Same size either way, so a swap shifts nothing. */
.tk-lev[data-src="listed"]{border-style:dashed}
.tk-lev[data-src="live"]{border-style:solid;color:var(--tk-ink)}

/* --- tail ----------------------------------------------------------------- */
.tk-tail{display:flex;align-items:center;gap:2px;flex:none;height:100%;padding-left:8px;border-left:1px solid var(--tk-sep)}
/* v30: the provenance line is gone from here (it is the strip's accessible name and the lead / tail
   title now). This is the one thing that still prints in the strip, and only when the feed is old:
   a delayed price has to SAY it is delayed in words, not only by dimming and an amber dot. */
.tk-age{display:none;margin:0;font:700 12px/1 var(--sans);color:var(--tk-amber);white-space:nowrap;
  font-variant-numeric:tabular-nums;padding-right:4px}
.tk[data-state="stale"] .tk-age{display:block}
.tk-pause,.tk-speed,.tk-rows{width:44px;height:44px;flex:none;display:inline-flex;align-items:center;justify-content:center;
  background:none;border:0;border-radius:8px;color:var(--tk-ink-2);cursor:pointer;padding:0}
.tk-pause:hover,.tk-speed:hover,.tk-rows:hover{background:var(--tk-hover);color:var(--tk-ink)}
.tk-pause svg,.tk-rows svg{width:15px;height:15px;fill:currentColor}
/* The speed step, as a number the reader can read: 0.5x, 1x, 2x of the 40px/s base. */
.tk-speed-v{font:700 12px/1 var(--sans);font-variant-numeric:tabular-nums;letter-spacing:.01em}
.tk-rows[aria-pressed="true"]{color:var(--tk-ink);background:var(--tk-hover)}
.tk-rows[aria-pressed="true"] .tk-ic-one{display:none}
.tk-rows .tk-ic-two{display:none}
.tk-rows[aria-pressed="true"] .tk-ic-two{display:block}
/* Reduced motion: the control stays, and says it is off, rather than pretending to change a speed
   nobody can see (ticker.js writes the words into its .sr label and "off" into the value). */
.tk-speed[aria-disabled="true"]{opacity:.55;cursor:default}
.tk-speed[aria-disabled="true"] .tk-speed-v{font-size:10.5px;letter-spacing:.06em;text-transform:uppercase}
.tk-pause .tk-ic-play{display:none}
.tk[data-paused="1"] .tk-pause .tk-ic-play{display:block}
.tk[data-paused="1"] .tk-pause .tk-ic-pause{display:none}
.tk-all{font:700 11.5px/1 var(--sans);color:var(--tk-ink-2);text-decoration:none;
  border:1px solid var(--tk-lev-line);border-radius:999px;padding:7px 10px;white-space:nowrap}
.tk-all:hover{color:var(--tk-ink);background:var(--tk-hover)}
.tk-all:focus-visible,.tk-pause:focus-visible,.tk-speed:focus-visible,.tk-rows:focus-visible{outline:3px solid #8fb4ff;outline-offset:-2px}
:root[data-theme="light"] .tk-all:focus-visible,:root[data-theme="light"] .tk-pause:focus-visible,
:root[data-theme="light"] .tk-speed:focus-visible,:root[data-theme="light"] .tk-rows:focus-visible,
:root[data-theme="light"] .tk-max:focus-visible{outline-color:#1d4ed8}

/* --- maximize (v31) -------------------------------------------------------
   The toggle is an ordinary 44px tail control until it is pressed; then everything else in the strip's
   chrome goes and it is the only thing left, parked over the tape's right edge. It is NEVER hidden by
   a state rule: it is the only way back, so it outlives an error, an empty feed and reduced motion. */
.tk-max{width:44px;height:44px;flex:none;display:inline-flex;align-items:center;justify-content:center;
  background:none;border:0;border-radius:8px;color:var(--tk-ink-2);cursor:pointer;padding:0}
.tk-max:hover{background:var(--tk-hover);color:var(--tk-ink)}
.tk-max:focus-visible{outline:3px solid #8fb4ff;outline-offset:-2px}
.tk-max svg{width:14px;height:14px;fill:currentColor}
.tk-max .tk-ic-min{display:none}
.tk-max[aria-pressed="true"] .tk-ic-min{display:block}
.tk-max[aria-pressed="true"] .tk-ic-max{display:none}
/* maximized: the lead and every control go; the tail leaves the flow so .tk-vp takes the whole bar */
.tk[data-max="1"] .tk-lead,
.tk[data-max="1"] .tk-pause,
.tk[data-max="1"] .tk-speed,
.tk[data-max="1"] .tk-rows,
.tk[data-max="1"] .tk-all{display:none}
.tk[data-max="1"] .tk-tail{position:absolute;top:0;right:0;bottom:0;z-index:4;   /* above the v31b edge fade (z 3) */
  gap:4px;padding-left:20px;padding-right:0;border-left:0;
  background:linear-gradient(90deg,transparent,var(--tk-bg) 20px)}
/* the chip: a solid disc of the strip's own background under the icon, so it reads over moving cells */
.tk[data-max="1"] .tk-max{background:var(--tk-bg);box-shadow:inset 0 0 0 1px var(--tk-sep);color:var(--tk-ink)}
.tk[data-max="1"] .tk-max:hover{background:var(--tk-hover)}

/* --- states --------------------------------------------------------------- */
.tk-skel{display:block;width:104px;height:13px;border-radius:5px;background:var(--tk-skel);
  animation:tk-sk 1.2s ease-in-out infinite alternate}
.tk-set>li>.tk-skel{margin:0 var(--tk-cell-px)}   /* the skeleton sits at the real cells' geometry, hairlines included */
@keyframes tk-sk{from{opacity:.5}to{opacity:1}}
.tk-msg{margin:0;font:600 12.5px/1 var(--sans);color:var(--tk-ink-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.tk-retry{margin-left:10px;font:700 11.5px/1 var(--sans);color:var(--tk-ink-2);background:none;
  border:1px solid var(--tk-lev-line);border-radius:999px;padding:7px 10px;cursor:pointer}
.tk-retry:hover{color:var(--tk-ink);background:var(--tk-hover)}
.tk[data-state="error"] .tk-pause,.tk[data-state="empty"] .tk-pause,.tk[data-state="loading"] .tk-pause,.tk[data-static="1"] .tk-pause,
.tk[data-state="error"] .tk-speed,.tk[data-state="empty"] .tk-speed,.tk[data-state="loading"] .tk-speed,.tk[data-static="1"] .tk-speed,
.tk[data-state="error"] .tk-rows,.tk[data-state="empty"] .tk-rows,.tk[data-state="loading"] .tk-rows,.tk[data-static="1"] .tk-rows{display:none}
/* .tk-max is deliberately absent from that list: it is the way back out of the maximized state, so it
   survives an error, an empty feed, the loading state and reduced motion. */
/* ...except the speed control under reduced motion, which stays to say it is off (v30). */
.tk[data-reduced="1"][data-static="1"][data-state="live"] .tk-speed,
.tk[data-reduced="1"][data-static="1"][data-state="stale"] .tk-speed{display:inline-flex}
.tk[data-state="stale"] .tk-last,.tk[data-state="stale"] .tk-chg{opacity:.72}
.tk-nojs{display:none;margin:0;font:600 12.5px/1 var(--sans);color:var(--tk-ink-2)}
.tk-nojs a{color:inherit}

/* --- the digit tail (v29): the digits that changed, and only those, in green or red --------
   ticker.js: <span class="tk-last" data-tick="up">81,34<span class="tk-d" data-dir="up">3.02</span></span>
   The tail starts in the direction color with a 1px underline and fades back to the price's ink over
   700ms (color and underline together), holding the color for the first 40% so a 4/s tick rate still
   reads. The leading, unchanged digits are plain text in --tk-ink and never animate. The ▲ / ▼ is a
   7px glyph above the slot's right edge (::after, absolute): a shape as well as a color, taking no
   layout width. Both are removed by ticker.js MARK_MS (800ms) after the last change. Tabular
   numerals are inherited, so wrapping the tail moves nothing. */
@keyframes tk-dg-up{0%,40%{color:var(--tk-up);text-decoration-color:var(--tk-up)}100%{color:var(--tk-ink);text-decoration-color:transparent}}
@keyframes tk-dg-dn{0%,40%{color:var(--tk-dn);text-decoration-color:var(--tk-dn)}100%{color:var(--tk-ink);text-decoration-color:transparent}}
@keyframes tk-tick-out{0%,40%{opacity:1}100%{opacity:0}}
.tk-d{text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:2px;text-decoration-skip-ink:none}
.tk-d[data-dir="up"]{color:var(--tk-up);text-decoration-color:var(--tk-up);animation:tk-dg-up .7s ease-out both}
.tk-d[data-dir="down"]{color:var(--tk-dn);text-decoration-color:var(--tk-dn);animation:tk-dg-dn .7s ease-out both}
.tk-last[data-tick]::after{position:absolute;right:0;top:-4px;font:700 7px/1 var(--sans);letter-spacing:0;pointer-events:none;
  animation:tk-tick-out .7s ease-out both}
.tk-last[data-tick="up"]::after{content:"▲";color:var(--tk-up)}
.tk-last[data-tick="down"]::after{content:"▼";color:var(--tk-dn)}

/* --- reduced motion: the section 2.9 trap ---------------------------------- */
@media (prefers-reduced-motion:reduce){
  .tk-track{animation-name:none;transform:none}
  .tk-set[aria-hidden="true"]{display:none}
  .tk-vp{overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:thin;
    -webkit-mask-image:none;mask-image:none}
  .tk-pause,.tk-rows{display:none}   /* .tk-max stays: maximizing buys the scroller room here too */
  .tk-row{width:auto;transform:none!important;cursor:default}   /* the viewport is a real scroller here: a drag scrolls it */
  .tk-dot::after{animation:none;display:none}
  .tk-skel{animation:none}
  /* v29: no fade. animation-name:none (not duration, see the section 18 trap) leaves the static
     direction color on the tail and the glyph until ticker.js clears them. */
  .tk-d[data-dir],.tk-last[data-tick]::after{animation-name:none}
}

/* --- widths --------------------------------------------------------------- */
@media (max-width:720px){
  :root{--tk-row:46px}   /* v33: 46px so each cell (a link) is a 44px tap target; still fixed at every state */
  .tk{--tk-fade:12px;--tk-cell-px:10px;--tk-mark:18px;gap:6px;padding:0 12px}   /* v30: three 44px controls in the tail, so the gutters give way first */
  .tk-lead-t{display:none}
  .tk-lead{padding-right:10px}
  .tk-tail{border-left:0;padding-left:0;gap:0}   /* the edge fade already separates the last cell from the controls */
  .tk-all{display:none}
  .tk-sym{min-width:0}
  .tk-cell{gap:7px}
  .tk-mark{font-size:8px}
  .tk-last{font-size:12.5px}
}
/* 2026-10-21 (Jay: "why does the Kalshi perpetual numbers not showing anymore on the ticker? It's supposed to show like how much
   leverage you can use"): the leverage badge was hidden under 420px since v25 (Sep 20), so phones never showed it. The strip scrolls,
   so width is not the constraint: on a phone the badge stays, smaller, with no reserved width. */
@media (max-width:420px){ .tk-lev{min-width:0;font-size:10px;padding:0 4px} }

/* v30a (2026-09-21, Jay's "so it doesn't take up so much room"): on a phone the tail was
   eating ~130px of a 390px bar, so one coin fitted. Speed is a motion nicety and goes; the
   row toggle and pause stay, both still 44px, and the tail gives its padding back. */
@media (max-width:759px){
  .tk-speed{display:none}
  .tk-tail{gap:0;padding-left:4px}   /* v31: the maximize toggle is a third 44px control, so the padding goes */
}


/* v32 — the strip's two ends give their width back to the tape. Jay: "get rid of
   'Perps · Spot' so there is more room on the ticker and let's make the right side
   narrower too and have a way to show that this is coinbase spot pricing."

   The lead said "Perps · Spot", which named the product and not the source. The prices
   are Coinbase spot and have been since 2026-09-20 (api/kalshi-perps.js: the Kalshi call
   503s from Vercel, so the strip's prices moved to api/coinbase/ticker.js and that
   endpoint was cut down to the leverage list). So the lead now says where the numbers
   come from, in the slot a reader already checks for a source. The full sentence -
   Coinbase for the price, Kalshi for the leverage multiple - stays in the strip's
   accessible name and in the title ticker.js writes on the lead and the tail.

   On desktop the five tail controls drop from 44px to 38px wide. They keep the full 44px
   HEIGHT, and below 760px they go back to 44 wide, so the touch target is never smaller
   than the rule allows on a device that uses touch. */
@media (min-width:760px){
  .tk-pause,.tk-speed,.tk-rows,.tk-max{width:38px}
  /* Ross wanted .tk-all{display:none} here for ~72px. Not taken: test_group_a asserts on
     that element (a contrast check at :3726 and a visibility map at :4784), the suite needs
     a browser this environment does not have, and 72px is 0.22 of a coin cell. Losing a
     route and breaking assertions I cannot run is a bad price for a fifth of one coin. */
  .tk-all{padding-left:9px;padding-right:9px}
}

/* ==========================================================================
   v32 (2026-09-22): the Coinbase source card, and our own live-feed mark.
   Jay approved the look in pmc-coinbase-card-v10.html. What differs from that preview is
   invisible and came from the Ross / Mary review:
   - NO wrapper. The preview wrapped .tk in a positioned .tk-shell, which made the shell the
     sticky containing block and the strip scrolled away with the page. The card is a sibling of
     .tk in <body> and is position:fixed just under the pinned strip, so the pin is untouched and
     tests/test_group_a.py "tk 1" (.tk is a child of <body>) still holds.
   - It stays open while focus is inside it (Tab reaches the button), closes on Escape and on a
     tap outside (ticker.js), and a transparent bridge covers the 9px gap so a slow pointer can
     travel from the word to the card (WCAG 1.4.13).
   - The "Live" pill only shows while the strip is live. The glow follows the feed colour.

   tk-sig: ORIGINAL artwork, not a Coinbase mark or a trace of one: a three-segment price line
   with a filled leading point, the point being "the price right now".
   ========================================================================== */
.tk-lead .tk-src{display:inline-flex;align-items:center;gap:7px;height:100%;padding:0 2px;position:relative;
  border:0;background:none;color:inherit;font:inherit;cursor:pointer;border-radius:6px}
.tk-lead .tk-src::after{content:"";position:absolute;inset:-2px -10px}   /* hit area: 44px wide on phones, where the word is hidden */
.tk-lead .tk-src:focus-visible{outline:2px solid var(--brand);outline-offset:2px}
.tk-lead .tk-src .tk-lead-t{text-decoration:underline;text-decoration-style:dotted;
  text-underline-offset:3px;text-decoration-color:var(--ink-3)}

/* in the lead the dot stops being a dot and becomes the frame for the mark */
.tk-lead .tk-src .tk-dot{width:15px;height:15px;border-radius:0;background:none;
  display:grid;place-items:center;overflow:visible;position:relative}
.tk-lead .tk-src .tk-dot::after{display:none}   /* the dot's own ping; the mark breathes instead */
.tk-sig{width:15px;height:15px;display:block;overflow:visible;
  fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round}
.tk-sig-l{opacity:.82}
/* v34 (2026-09-24, Jay: "flickering"): the point used to breathe on its own (`r` 1.9 to 2.4 and opacity .55 to 1,
   keyframes tk-sig-p). Geometry and opacity on an element INSIDE an svg can only be animated by the main thread,
   so the pinned strip was repainted on every frame, all day (traced: span.tk-dot painted 110 times in 5 s), and the
   main thread never went idle. The point now keeps the markup's r="2.2" (the middle of that range) and the glow
   behind it (.tk-dot::before, tk-breathe: transform and opacity, compositor-only) carries the breathing on its own. */
.tk-sig-c{fill:currentColor;stroke:none}
.tk-lead .tk-src .tk-dot::before{content:"";position:absolute;inset:-7px;border-radius:50%;
  background:radial-gradient(circle,color-mix(in srgb,currentColor 52%,transparent) 0%,transparent 68%);
  opacity:.75;animation:tk-breathe 2.6s ease-in-out infinite;pointer-events:none;z-index:-1}
@keyframes tk-breathe{0%,100%{opacity:.42;transform:scale(.88)}50%{opacity:.92;transform:scale(1.12)}}
@media (prefers-reduced-motion:reduce){
  .tk-lead .tk-src .tk-dot::before{animation:none;opacity:.55}
}

/* the card */
.tk-card{position:fixed;top:calc(var(--hd-h) + 1px + var(--tk-h) + 9px);left:8px;width:min(404px,calc(100vw - 20px));
  z-index:calc(var(--z-hd) - 1);padding:16px 17px 14px;border-radius:14px;box-sizing:border-box;
  background:var(--paper);border:1px solid var(--line);
  box-shadow:0 1px 2px rgba(10,18,38,.07),0 18px 46px -12px rgba(10,18,38,.34);
  opacity:0;visibility:hidden;transform:translateY(-6px);
  transition:opacity .16s ease,transform .16s ease,visibility 0s linear .16s}
.tk:has(.tk-src:hover) ~ .tk-card,
.tk:has(.tk-src:focus-visible) ~ .tk-card,
.tk-card:hover,
.tk-card:focus-within,
.tk-card[data-open="1"]{opacity:1;visibility:visible;transform:translateY(0);transition-delay:0s}
.tk-card[data-hush="1"]{opacity:0 !important;visibility:hidden !important;transition-delay:0s !important}   /* Escape wins over hover */
.tk[data-max="1"] ~ .tk-card{display:none}
.tk-card::before{content:"";position:absolute;top:-6px;left:18px;width:11px;height:11px;   /* the beak */
  background:var(--paper);border-left:1px solid var(--line);border-top:1px solid var(--line);
  transform:rotate(45deg);border-radius:2px 0 0 0}
.tk-card::after{content:"";position:absolute;left:0;right:0;top:-12px;height:12px}   /* hover bridge over the gap */

.tk-card-h{margin:0 0 3px;display:flex;align-items:center;gap:9px;font:800 15.5px/1.2 var(--sans);color:var(--ink)}
.tk-card-live{display:inline-flex;align-items:center;gap:5px;font:800 9.5px/1 var(--sans);
  letter-spacing:.11em;text-transform:uppercase;color:var(--gain);
  padding:4px 7px 4px 6px;border-radius:999px;background:var(--gain-soft)}
.tk-card-live::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--gain)}
.tk:not([data-state="live"]) ~ .tk-card .tk-card-live{display:none}
.tk-card-sub{margin:0 0 13px;font:400 13px/1.5 var(--sans);color:var(--ink-2);text-wrap:pretty}
.tk-nb{white-space:nowrap}   /* "as they are traded." travels whole */
.tk-keys{margin:0;display:grid;grid-template-columns:auto 1fr;gap:9px 12px;align-items:baseline;
  padding-bottom:14px;border-bottom:1px solid var(--line-2)}
.tk-k{font:800 9.5px/1.35 var(--sans);letter-spacing:.1em;text-transform:uppercase;white-space:nowrap}
.tk-k--price{color:var(--k-price)}
.tk-k--lev{color:var(--k-lev)}
.tk-k--set{color:var(--k-set)}
.tk-keys dd{margin:0;font:400 12.8px/1.55 var(--sans);color:var(--ink-2);text-wrap:pretty}
.tk-keys dd b{font-weight:700;color:var(--ink)}
.tk-card-cta{display:flex;align-items:center;justify-content:center;gap:7px;margin:14px 0 0;
  min-height:44px;padding:0 16px;border-radius:10px;text-decoration:none;
  background:var(--ink);color:var(--paper);font:800 13.5px/1 var(--sans);
  transition:transform .14s ease,box-shadow .14s ease,background-color .14s ease;box-shadow:0 1px 2px rgba(10,18,38,.16)}
.tk-card-cta:hover{background:color-mix(in srgb,var(--ink) 84%,var(--gain));
  transform:translateY(-1.5px);box-shadow:0 8px 18px -6px color-mix(in srgb,var(--gain) 55%,transparent),0 2px 5px rgba(10,18,38,.2)}
.tk-card-cta:active{transform:translateY(0);box-shadow:0 1px 2px rgba(10,18,38,.22)}
.tk-card-cta:focus-visible{outline:2px solid var(--brand);outline-offset:2px}
@media (prefers-reduced-motion:reduce){.tk-card{transition:none}.tk-card-cta{transition:background-color .14s ease}
  .tk-card-cta:hover,.tk-card-cta:active{transform:none}}
.tk-card-note{margin:8px 0 0;font:400 11.5px/1.4 var(--sans);color:var(--ink-3);text-align:center}
.tk-card-note b{font-weight:700;color:var(--ink-2)}
/* the three key colours, tuned per theme for AA on the card surface */
:root{--k-price:#1257b8;--k-lev:#8a4b04;--k-set:#166a4e}
:root[data-theme="dark"]{--k-price:#7fb2ff;--k-lev:#f0ad4c;--k-set:#4fd0a0}
@media (max-width:520px){
  .tk-card{left:6px;right:6px;width:auto}
  .tk-keys{grid-template-columns:1fr;gap:3px 0}
  .tk-keys dd{margin-bottom:9px}
}
@media print{.tk-card{display:none}}
