/* ══════════════════════════════════════════════════════════════════════════
   tb-shared.css — the ONE place both views read their look from.

   James, 2026-08-29: "the 2d and 3d stylistically should still feel like the
   same product - right now tey feel like 2 different apps."

   They were two systems: the 2D board ran a purple-black chrome with a
   near-WHITE accent, the 3D board ran a neutral near-black with an ORANGE
   accent. Nothing was shared. The house values below are the 3D ones,
   because those were read off _docs/STYLE-DIRECTION.md, which outranks both.

   The 2D board's own variable names are aliased to the same values, so it
   inherits the house palette without a rewrite and nothing regresses.
   ══════════════════════════════════════════════════════════════════════════ */
:root{
  /* ── house tokens (canonical names) ── */
  --tb-ground:#05070a;      /* the ground everything sits on */
  --tb-panel:#0b0f15;       /* chrome surfaces */
  --tb-el:#131a23;          /* a raised control */
  --tb-edge:#1b2430;        /* every border */
  --tb-ink:#e8eef7;         /* body copy — near-white, never dim grey */
  --tb-ink-dim:#8b97a8;     /* small uppercase LABELS only */
  --tb-accent:#f97316;      /* the ONE accent */
  --tb-on-accent:#0a0500;   /* text on the accent */
  --tb-teamA:#ff2a3a; --tb-teamB:#2f9bff; --tb-keeper:#ffd23d;
  --tb-radius:10px;
  --tb-chrome:600 11px/1 Inter,system-ui,sans-serif;

  /* ── aliases so the 2D board inherits the house palette unchanged ── */
  --bg:var(--tb-ground);  --panel:var(--tb-panel);
  --el:var(--tb-el);      --el-hi:#1c2531;
  --border:var(--tb-edge); --border2:#131a23;
  --txt:var(--tb-ink);    --dim:var(--tb-ink-dim);
  --accent:var(--tb-accent); --accent-d:var(--tb-on-accent);
  --red:var(--tb-teamA);  --gold:var(--tb-keeper);

  /* ── aliases so the 3D board keeps its own names ── */
  --ground:var(--tb-ground); --edge:var(--tb-edge);
  --ink:var(--tb-ink); --ink-dim:var(--tb-ink-dim);
  --teamA:var(--tb-teamA); --teamB:var(--tb-teamB); --keeper:var(--tb-keeper);
}

/* ══════════════════════════════════════════════════════════════════════════
   THE VIEW SWITCH — one component, byte-identical in both views.

   James: "the 2d>3d switch button should be in the middle at the top in the
   ui big and bold as its the best part!"  It was a small `3D` button buried
   in the second toolbar row of the 2D board, and a different small `2D|3D`
   pair at the top right of the 3D board. Two controls for one action.
   ══════════════════════════════════════════════════════════════════════════ */
.tb-viewswitch{
  position:fixed; top:10px; left:50%; transform:translateX(-50%);
  z-index:60; display:flex; gap:4px; padding:4px;
  background:rgba(11,15,21,.92);
  border:1px solid var(--tb-edge); border-radius:var(--tb-radius);
  box-shadow:0 8px 28px rgba(0,0,0,.55);
}
.tb-viewswitch a,
.tb-viewswitch button{
  appearance:none; cursor:pointer; text-decoration:none;
  font:700 15px/1 Inter,system-ui,sans-serif;
  letter-spacing:.14em; text-transform:uppercase;
  padding:11px 26px; border-radius:7px;
  border:1px solid transparent; background:transparent;
  color:var(--tb-ink-dim);
}
.tb-viewswitch a:hover,
.tb-viewswitch button:hover{ color:var(--tb-ink); border-color:var(--tb-edge) }
.tb-viewswitch .on{
  background:var(--tb-accent); border-color:var(--tb-accent);
  color:var(--tb-on-accent); cursor:default;
}
@media (max-width:900px){
  .tb-viewswitch a,.tb-viewswitch button{padding:9px 18px;font-size:13px}
}

/* ══════════════════════════════════════════════════════════════════════════
   THE TOAST — one component, byte-identical in both views.

   Gate verdict, 2026-09-01: "a person pressing [PNG / EXPORT VIDEO] is told
   absolutely nothing." The 3D board already had this toast; the 2D board had
   no feedback element at all, so PNG export gave zero confirmation of any
   kind. Moved here rather than duplicated so the two views cannot drift.
   3200ms, not the original 1800ms — a blind reader's own note: "toast() at
   1.8 seconds is too fast to read for anything that explains a refusal."

   `critique-round14` (2026-09-01, Rivet): first placed top-centre, `#toast`
   sat in the EXACT rect `.tb-viewswitch` already owns (same `left:50%`
   anchor, 6px apart) — measured 100% overlap, both directions: buried under
   the switch on 2D (0 visible px on every label), burying the switch on 3D
   for the toast's whole 3200ms. Moved to bottom-centre instead, clear of
   both boards' top chrome. Measured live before picking the offset: on 3D,
   `.bar.bottom` (the play/loop/speed toolbar) + `#caption` (the narration
   box) together occupy the bottom 84px of the viewport at 1600x1000
   (`.bar.bottom` top=938, `#caption` top=916, viewport bottom=1000) — so
   `bottom:104px` clears both with a 20px margin. 2D has nothing fixed at
   the bottom at all (confirmed by enumerating every `position:fixed`
   element on the page), so the same rule is free there too. One rule,
   both boards inherit it — no new component, no new tokens.
   ══════════════════════════════════════════════════════════════════════════ */
/* z-index:9500, not 20 (2026-09-02, closed-19). ROOT CAUSE of "export gives no
   visible feedback", confirmed by driving three ways: three prior shifts (gate,
   whats-left-10, whats-left-11) all reproduced the failure on a BRAND-NEW browser
   profile — and a brand-new profile is exactly when the first-run tour is active.
   The tour's dimming layer (#tbtour-hole, z-index:9001, a 9999px box-shadow
   covering the whole viewport outside its spotlight hole) painted OVER this toast
   at z-index:20, so the confirmation fired every time but was crushed to
   near-invisible under near-black. Photographed both states on the same build,
   same click, same fixture: tour running -> "PNG SAVED" is a barely-visible smear
   at (799,880); tour dismissed -> the identical click shows it bright and legible
   in the same spot. Nothing about toast() or its opacity transition was ever
   broken. A confirmation toast must always be able to answer "did that work?",
   including mid-onboarding — so it now sits above every tour layer (block 9000,
   hole 9001, bubble 9002, ghost 9003) instead of introducing a tour-aware
   exception. */
#toast{position:fixed;left:50%;bottom:104px;transform:translateX(-50%);z-index:9500;
  background:var(--tb-accent);color:var(--tb-on-accent);font:600 11px Inter,system-ui,sans-serif;
  letter-spacing:.06em;text-transform:uppercase;padding:8px 14px;border-radius:6px;opacity:0;
  transition:opacity .25s;pointer-events:none}
#toast.show{opacity:1}

/* ── 2D board only: reserve a top strip so the switch never covers the toolbar.
      `#app` exists only on the 2D page. Measured 2026-08-29: without this the
      switch sat on top of Dashed, Ball, Clear-drawings and Clear-home — a fixed
      element is a claim about everything around it. `box-sizing:border-box` is
      global on that page, so the 100vh height is unchanged.                  */
#app{ padding-top:56px }
body:has(#app)::before{
  content:''; position:fixed; top:0; left:0; right:0; height:56px;
  background:var(--tb-panel); border-bottom:1px solid var(--tb-edge); z-index:55;
}

/* ══════════════════════════════════════════════════════════════════════════
   THE GUIDED TOUR — one component, byte-identical in both views.

   James, 1 Sep: "there needs to be some kind of tutorial, onboarding … like
   where the screen goes down and dark and then you point out each feature one
   by one." His acceptance test is a stranger who is told nothing and is not
   confused.

   ZERO new tokens. Every colour, font, radius and border below is one of the
   house variables already declared at the top of this file. The bubble is the
   same surface as `.tb-viewswitch` (panel at .92 alpha, --tb-edge border,
   --tb-radius), the small caps are the same `--tb-chrome` face used by every
   label in both views, and the one accent is the same --tb-accent.

   The dimming is done with a giant spread box-shadow on a transparent
   rectangle, so the control being explained keeps its real pixels — a
   screenshot of a step shows the actual button, not a copy of it.
   ══════════════════════════════════════════════════════════════════════════ */
/* FOUR dark panels, not one scrim (2026-09-02). It looks identical — same dim,
   same rounded hole — but the hole is now a REAL hole, so the control being
   explained is genuinely clickable underneath it. That is the whole of James's
   "go ahead, try it": with one full-screen scrim on top, every click the tour
   invited was eaten by the tour. Same load-bearing display:none as below. */
.tbtour-block{position:fixed;z-index:9000;display:none;cursor:pointer}
/* display:none is LOAD-BEARING, not tidiness. The dimming is a 9999px spread
   box-shadow on this element, so if the hole renders, the whole screen is dark
   — and it does not care whether the tour is running. Measured 2026-09-02 by
   driving the board cold: pressing SKIP removed the scrim and the bubble (both
   correctly gated on .tbtour-on) and left the hole behind, so the entire app
   sat at 16% brightness with an orange ring round the team picker and no way
   to clear it. The tour was "off" and the product was unusable.
   Every one of the tour's three parts is gated on .tbtour-on for this reason. */
#tbtour-hole{position:fixed;z-index:9001;border-radius:8px;pointer-events:none;
  display:none;
  border:1px solid var(--tb-accent);
  /* .95, not .84 (2026-09-02). James: "even more focused darkness around
     everything else but the thing that they're doing." Measured against a real
     screenshot first: at 84% over an app that is ALREADY near-black, all
     twenty-two players and every toolbar button stayed perfectly readable, so
     the eye never landed. .95 takes everything outside the ring down to a
     suggestion. The lit area pulled in to match — the tour's own PAD went 6px
     to 4px, and while a demo is running the hole is the size of the PLAYER
     being dragged, not the size of the board he is being dragged on. */
  box-shadow:0 0 0 9999px rgba(5,7,10,.95);
  transition:top .22s ease,left .22s ease,width .22s ease,height .22s ease}
.tbtour-on #tbtour-hole{display:block}

/* The light that follows a PLAYER rather than sitting on a control. A hard
   rectangle was measured on a real frame and it was wrong: the square edge
   sliced the two team-mates either side of the man being dragged clean in half
   and left three red fragments pinned to the corners, which reads as a crop,
   not as a spotlight. A circle with a feathered inside edge reads as what it
   is — a pool of light on the grass — and a circle crossing a shirt looks like
   a vignette rather than a cut. The inset shadow does the feathering: an OUTER
   shadow is clipped to the outside of the box and can never soften the edge
   from within. */
#tbtour-hole.soft{
  box-shadow:inset 0 0 34px 14px rgba(5,7,10,.92), 0 0 0 9999px rgba(5,7,10,.95)}
#tbtour-hole.soft.round{border-radius:50%}
#tbtour-hole.soft:not(.round){border-radius:20px}

/* ── THE GHOST CURSOR ──────────────────────────────────────────────────────
   The hand that does the demonstrating. Drawn in code so there is no image to
   go missing, in the house ink outlined in the house ground so it reads on the
   bright grass and on the dark chrome alike. The ring is the press: it grows
   out of the point on mouse-down in the one accent. Gated on .tbtour-on for
   the same reason as everything above — a cursor left on screen after the tour
   closed would be a second mouse pointer nobody can get rid of. */
#tbtour-ghost{position:fixed;left:0;top:0;z-index:9003;pointer-events:none;
  display:none;will-change:transform}
.tbtour-on #tbtour-ghost.on{display:block}
#tbtour-ghost svg{display:block;position:relative;z-index:2;
  filter:drop-shadow(0 2px 5px rgba(0,0,0,.85))}
#tbtour-ghost svg path{fill:var(--tb-ink);stroke:var(--tb-ground);stroke-width:1.4;
  stroke-linejoin:round}
#tbtour-ghost .tbtour-ring{position:absolute;left:1px;top:1px;width:34px;height:34px;
  margin:-17px 0 0 -17px;border-radius:50%;
  border:2px solid var(--tb-accent);opacity:0;transform:scale(.35);
  transition:opacity .16s ease,transform .16s ease}
#tbtour-ghost.press .tbtour-ring{opacity:1;transform:scale(1)}

#tbtour-bubble{position:fixed;z-index:9002;display:none;width:min(320px,calc(100vw - 24px));
  background:rgba(11,15,21,.97);border:1px solid var(--tb-edge);
  border-radius:var(--tb-radius);box-shadow:0 14px 40px rgba(0,0,0,.6);
  padding:14px 15px 12px;cursor:default}
.tbtour-on #tbtour-bubble{display:block}
#tbtour-bubble .tbtour-kicker{font:var(--tb-chrome);letter-spacing:.16em;
  text-transform:uppercase;color:var(--tb-ink-dim);margin-bottom:7px}
#tbtour-bubble h4{margin:0 0 6px;font:700 15px/1.25 Inter,system-ui,sans-serif;
  color:var(--tb-ink)}
#tbtour-bubble p{margin:0;font:400 13px/1.55 Inter,system-ui,sans-serif;
  color:var(--tb-ink)}
/* The one line that carries the hand-off: "watch this" while the app is
   demonstrating itself, "now you try" the moment you touch anything, and a
   tick when you have done it. Same face and colour as the kicker above it —
   it is the same KIND of thing, a quiet label, so it borrows the same spec
   rather than picking a new one. Only the tick is allowed the accent. */
#tbtour-bubble .tbtour-try{font:var(--tb-chrome);letter-spacing:.1em;
  text-transform:uppercase;color:var(--tb-ink-dim);margin-top:9px;
  line-height:1.5;min-height:1em}
#tbtour-bubble .tbtour-try.is-you{color:var(--tb-ink)}
#tbtour-bubble .tbtour-try.is-done{color:var(--tb-accent)}
#tbtour-bubble .tbtour-foot{display:flex;align-items:center;flex-wrap:wrap;gap:8px;
  row-gap:9px;margin-top:13px}
#tbtour-bubble .tbtour-dots{display:flex;gap:4px;margin-right:auto}
#tbtour-bubble .tbtour-dots i{width:5px;height:5px;border-radius:50%;
  background:var(--tb-edge);display:block}
#tbtour-bubble .tbtour-dots i.on{background:var(--tb-accent)}
#tbtour-bubble button{appearance:none;cursor:pointer;border-radius:6px;
  font:600 11px Inter,system-ui,sans-serif;letter-spacing:.06em;
  text-transform:uppercase;padding:7px 12px;
  background:transparent;border:1px solid var(--tb-edge);color:var(--tb-ink-dim)}
#tbtour-bubble button:hover{color:var(--tb-ink);border-color:#2b3746}
#tbtour-bubble button.tbtour-next{margin-left:auto;background:var(--tb-accent);
  border-color:var(--tb-accent);color:var(--tb-on-accent)}
#tbtour-bubble .tbtour-skip{border-color:transparent;padding-left:0}

/* The replay control. It is deliberately the quietest thing in either bar —
   it borrows whatever button class that bar already uses, so it inherits the
   house look rather than declaring one. This rule only sizes the glyph. */
.tbtour-help{font-weight:700}

/* ══════════════════════════════════════════════════════════════════════════
   TOOLBAR GROUPS — one component, both views.

   James, 1 Sep: "make it clearer what these things do, or GROUP THEM
   TOGETHER, because you've just put all these kind of buttons that are
   different … and it's just confusing to me."

   The 2D toolbar already had `.tsep` dividers between its runs of buttons.
   They were --border2 (#131a23) on a --panel (#0b0f15) ground — measured
   against a real screenshot, not one of them was visible. So fifteen buttons
   doing three completely different jobs read as one undifferentiated row.

   A divider alone was never going to be enough: it says "these are not those"
   but never says what either one IS. So a group carries a NAME. The type spec
   below is byte-identical to the `.lbl` rule board3d.html already uses for
   exactly this job (600 9px Inter / .16em / uppercase / --ink-dim) — reused
   rather than re-picked, so the two views agree by construction.
   ══════════════════════════════════════════════════════════════════════════ */
.tb-grp{display:flex;align-items:center;gap:4px;padding-left:7px;margin-left:0;
  border-left:1px solid var(--tb-edge)}
.tb-grp.first{border-left:0;padding-left:0;margin-left:0}
.tb-grplbl{font:600 9px Inter,system-ui,sans-serif;letter-spacing:.16em;
  text-transform:uppercase;color:var(--tb-ink-dim);white-space:nowrap;
  padding-right:3px;user-select:none}
@media (max-width:640px){ .tb-grp{padding-left:6px;margin-left:0} }
