/* ============================================================
   Scorecard grid — the core Excel-like RAG grid.
   Rebuilt to artboard 02 (Paper "Cadry.ai" / A2-0): centered 1280
   column on fog, joined KPI strip, chronological period columns
   ending at the (tinted) current period, trailing trend column,
   floating status bar.
   ============================================================ */

/* ---- page scaffold ---- */
/* 1280px content column (mock: 1280 @ 1440) + the page's own gutters. */
.page.grid-page{
  max-width:1336px; margin:0 auto; padding:36px 28px 48px;
  display:flex; flex-direction:column;
}
/* 20px rhythm between sections (mock gap 20). The AI banner collapses to
   zero height but stays display:flex, so a flex `gap` would double up around
   it — margins with a collapsed-banner exception keep the rhythm exact. */
.grid-page > * + *{ margin-top:20px; }
.grid-page > .rt-ai-banner:not(.rt-on){ margin-top:0; }
/* An empty KPI wrapper (no metrics yet) must not hold a 20px slot open. */
.grid-page > .team-kpis-wrap:not(:has(*)){ margin-top:0; }

.grid-page .page-head{ padding:0; border-bottom:0; }
.grid-page .page-sub{ margin-top:6px; }
.grid-page .page-meta{ gap:12px; padding-bottom:4px; }
/* Phone (#275): title/sub absorb leftover width (min-width:0 / flex:1)
   while +Group / +New metric stay flex-shrink:0 at max-content. At ~390
   the subtitle collapses to a ~40px one-word-per-line ribbon. Wrap the
   actions below the heading; keep every control. Desktop stays a
   heading + action row. */
@media (max-width: 720px) {
  .grid-page .page-head { flex-direction: column; align-items: stretch; gap: 14px; }
  .grid-page .page-meta { flex-wrap: wrap; }
}
.grid-page .page-meta .pill{
  height:24px; padding:0 10px; gap:6px; border-radius:12px;
  text-transform:uppercase;
}
.grid-page .page-meta .btn{
  height:34px; padding:0 14px; border-radius:7px;
  font-size:13px; color:var(--ink); box-shadow:none;
}
.grid-page .page-meta .btn.primary{ padding:0 16px; color:#FFFFFF; }

/* ---- attention rail: one joined container, internal dividers ---- */
/* Overrides the shared .team-kpis/.tk-tile (design.css) for this page only —
   class names stay so realtime flash + broadcasts keep targeting them. */
.grid-page .team-kpis{
  display:flex; flex-wrap:wrap; gap:0;
  background:var(--surface); border:1px solid var(--rule-2);
  border-radius:var(--radius-lg); overflow:hidden;
}
.grid-page .tk-tile{
  flex:1 1 0; min-width:240px; min-height:0;
  padding:18px 22px; gap:8px;
  background:var(--surface); border-right:1px solid var(--rule);
}
.grid-page .tk-tile:last-child{ border-right:0; }
.grid-page .tk-tile .tk-label{ letter-spacing:0.12em; }
.grid-page .tk-tile .tk-rag.empty{ background:transparent; border:1px dashed var(--ink-4); }
.grid-page .tk-tile .tk-val{ font-size:28px; line-height:34px; display:block; }
.grid-page .tk-tile .tk-sub{ color:var(--ink-4); }

/* The derived rail: three ranked tiles, each carrying the reading, its signed
   gap, the trend, and the sentence a reviewer would say out loud. The wrapper
   holds the section label above and the healthy-summary line below, so the
   .grid-page 20px rhythm applies to the group rather than to each part. */
.grid-page .attn-rail{ display:flex; flex-direction:column; }
.attn-rail .attn-head{
  display:flex; align-items:baseline; flex-wrap:wrap; gap:10px; margin-bottom:12px;
  font-family:var(--mono); font-size:10.5px; text-transform:uppercase;
}
.attn-rail .attn-title{ color:var(--ink-3); font-weight:500; letter-spacing:0.14em; }
.attn-rail .attn-hint{ color:var(--ink-4); letter-spacing:0.06em; }

.grid-page .attn-rail .tk-tile{ padding:16px 20px 15px; gap:6px; }
/* Reading + trend on one baseline: value, signed gap, then the 132×34 spark.
   min-width:0 + a shrinkable spark wrapper let the row fit a 390px tile
   (and the 240px two-up tiles) instead of clipping under overflow:hidden. */
.attn-rail .attn-read{
  display:flex; align-items:flex-end; justify-content:space-between;
  gap:16px; flex-wrap:wrap; min-width:0;
}
.attn-rail .attn-now{ display:flex; align-items:baseline; gap:9px; min-width:0; }
.grid-page .attn-rail .tk-val{ font-size:28px; line-height:32px; }
/* nowrap: the unit must not break away from its number ("−4.1 / pts"). */
.attn-rail .attn-gap{ font-family:var(--mono); font-size:12px; font-weight:500; white-space:nowrap; }
.attn-rail .attn-spark{
  flex:0 1 132px; width:132px; min-width:88px;
  margin-left:auto; line-height:0;
}
.attn-rail .attn-spark svg{ display:block; width:100%; height:auto; margin-bottom:2px; }
.attn-rail .attn-reason{ margin:0; font-size:12.5px; line-height:17px; color:var(--ink-2); text-wrap:pretty; }
.grid-page .attn-rail .tk-foot{ margin-top:2px; letter-spacing:0.08em; }

/* Everything healthy in one grey line — the "nothing to see here" gets a line,
   not four tiles. */
.attn-rail .attn-summary{
  display:flex; align-items:center; flex-wrap:wrap; gap:8px;
  margin:10px 0 0; font-size:12.5px; color:var(--ink-4);
}
.attn-rail .attn-summary .pill{ height:20px; flex:none; }
/* A rail with no tiles is just the summary line — no 12px label gap to hold. */
.attn-rail:not(:has(.team-kpis)) .attn-summary{ margin-top:0; }

/* Tone colours for the gap numbers, on the tiles and in the current column.
   Amber = at risk, red = off track; a healthy gap reads green. */
.attn-gap.t-ok,   .cur-gap.t-ok  { color:var(--ok); }
.attn-gap.t-warn, .cur-gap.t-warn{ color:var(--warn); }
.attn-gap.t-bad,  .cur-gap.t-bad { color:var(--bad); }
.attn-gap.t-empty,.cur-gap.t-empty{ color:var(--ink-4); }

/* ---- toolbar ---- */
.toolbar{
  display:flex; align-items:center; gap:12px; flex-wrap:wrap;
  padding:0; border:0; background:transparent;
}
.toolbar .sp{ flex:1; }
.toolbar .scorecard-group-filter{ display:contents; }

/* read-only cadence indicator (not a switcher — cadence changes in settings) */
.cadence-badge{
  display:inline-flex; align-items:center; gap:7px; height:32px; padding:0 12px;
  background:var(--surface); border:1px solid var(--rule-2); border-radius:7px;
  font-family:var(--mono); font-size:11px; font-weight:500; letter-spacing:0.1em;
  color:var(--ink-3);
}

.grid-page .toolbar .seg{ padding:3px; border-radius:8px; }
.grid-page .toolbar .seg .si{ height:26px; padding:0 14px; border-radius:6px; }

.filter-chip{
  display:inline-flex; align-items:center; gap:7px;
  height:32px; padding:0 12px; font-size:12.5px; font-weight:500; color:var(--ink);
  background:var(--surface); border:1px solid var(--rule-3); border-radius:7px; cursor:pointer;
}
.filter-chip:hover{ background:var(--surface-2); }
.filter-chip .fc-val{ color:var(--ink); font-weight:500; }
.filter-chip.open{ background:var(--surface-2); border-color:var(--rule-3); }
.fc-wrap{ position:relative; }

/* caps-mono status counts: ● 4 ON TRACK ● 1 AT RISK ● 2 OFF TRACK. Live counts
   rather than a static key, and each one filters the rows it counts. */
.legend{
  display:flex; align-items:center; gap:14px;
  font-size:10.5px; color:var(--ink-3); font-family:var(--mono);
  text-transform:uppercase;
}
/* These read as text but they're buttons now, so they need a real hit area —
   32px matches the toolbar's other controls, and a thumb gets 44px. */
.legend .lg{
  display:flex; align-items:center; gap:5px; min-height:32px;
  background:none; border:0; padding:0 1px 3px; cursor:pointer;
  font:inherit; letter-spacing:inherit; text-transform:inherit; color:inherit;
}
@media (max-width:700px){
  .legend{ gap:16px; }
  .legend .lg{ min-height:44px; }
}
.legend .lg.t-warn{ color:var(--warn-ink); }
.legend .lg.t-bad{ color:var(--bad-ink); }
/* The chosen count is the active-filter cue until click-again or All.
   Underline (not a new chip bar) so All / Flagged / Mine stay the segment. */
.legend .lg.on{ color:var(--ink); font-weight:600; box-shadow:inset 0 -2px 0 currentColor; }
.legend .lg:hover{ color:var(--ink); }
.legend .lg:disabled,
.legend .lg.is-inert{
  opacity:0.38; cursor:default;
}
.legend .lg:disabled:hover,
.legend .lg.is-inert:hover{ color:inherit; }
.legend .sw{ display:inline-block; width:8px; height:8px; border-radius:50%; flex-shrink:0; }
.legend .sw.ok{ background:var(--ok); }
.legend .sw.warn{ background:var(--warn); }
.legend .sw.bad{ background:var(--bad); }
.legend .sw.empty{ background:transparent; border:1px dashed var(--ink-4); }

/* ---- popovers ---- */
.pop{
  position:absolute; top:calc(100% + 6px); left:0; z-index:50; min-width:200px;
  background:var(--surface); border:1px solid var(--rule-2);
  border-radius:7px; box-shadow:var(--shadow-pop); padding:4px; display:none;
}
.pop.on{ display:block; }
.pop-opt{ display:flex; align-items:center; gap:8px; padding:6px 8px; border-radius:4px; font-size:12.5px; color:var(--ink-2); cursor:pointer; user-select:none; text-decoration:none; }
.pop-opt:hover{ background:var(--surface-2); color:var(--ink); }
.pop-opt.on{ color:var(--ink); font-weight:500; }
.pop-check{ width:12px; display:inline-block; text-align:center; color:var(--accent); font-size:11px; font-weight:600; }
.pop-av{ width:18px; height:18px; border-radius:50%; font-size:8.5px; font-weight:600; color:white; display:grid; place-items:center; flex-shrink:0; }
.pop-count{ margin-left:auto; font-family:var(--mono); font-size:10.5px; color:var(--ink-4); }
.pop-sep{ height:1px; background:var(--rule); margin:4px 2px; }

/* ---- grid shell ---- */
/* Card: 1px border, radius 12, NO shadow (shadows are for popovers only).
   Do not set margin here — `.grid-page > * + *` owns the 20px section
   rhythm, and a later `margin:0` collapsed the toolbar/grid gap (#188). */
.grid-wrap{
  background:var(--surface);
  border:1px solid var(--rule-2); border-radius:var(--radius-lg); overflow:hidden;
}
/* Thin, always-painted scrollbar so overlay platforms still show that the
   months can move. Overflow classes force a reserved bar when a side
   still has more. Sticky edges keep their 1px rules — no gradient wash. */
.grid-scroll{
  overflow:auto; max-height:calc(100vh - 300px);
  scrollbar-width:thin;
  scrollbar-color:var(--ink-3) var(--surface-2);
}
.grid-scroll.has-start-overflow,
.grid-scroll.has-end-overflow{ overflow-x:scroll; }
.grid-scroll::-webkit-scrollbar{ height:12px; width:12px; }
.grid-scroll::-webkit-scrollbar-thumb{
  background:var(--ink-3); border-radius:8px;
}
.grid-scroll::-webkit-scrollbar-track{ background:var(--surface-2); }

/* display:table is load-bearing: the class is literally "grid", which collides
   with Tailwind's `.grid{display:grid}` utility. Without this, the table renders
   as a CSS grid — thead/tbody stop sharing a column model and the month headers
   drift out of alignment with their values. Keep it explicit. */
table.grid{ display:table; border-collapse:separate; border-spacing:0; width:max-content; min-width:100%; font-size:13px; }
table.grid th, table.grid td{
  height:var(--row-h); padding:0 12px; text-align:right; white-space:nowrap;
  border-bottom:1px solid var(--rule);
  vertical-align:middle; background:var(--surface);
}
table.grid thead th{
  position:sticky; top:0; z-index:5; height:36px;
  font-family:var(--mono); font-size:10.5px; font-weight:500; color:var(--ink-4);
  letter-spacing:0.08em; text-transform:uppercase; background:var(--bg);
  border-bottom:1px solid var(--rule-2);
}
table.grid thead th .yr{ display:inline-block; margin-left:4px; font-size:9.5px; color:var(--ink-4); font-weight:400; }
/* Current period — the LAST period column (columns run oldest→newest). The
   header carries an accent-soft fill + accent label; the column cells get a
   faint accent-tinted wash. Hue comes from the workspace accent tokens, never
   a hardcoded blue, so warm workspaces tint warm. */
table.grid thead th.cur{ color:var(--accent); font-weight:600; background:var(--accent-soft); }
table.grid td.cur{ background:color-mix(in oklch, var(--accent-soft) 45%, var(--surface)); }
table.grid td.cell.cur .v{ font-weight:500; }

/* The current period is a column, not just a tint: wide enough to be labelled
   ("JUL · CURRENT") and to carry the signed gap to target beside the value.
   --grid-cur-min is set by grid_controller when a neighbor would otherwise
   sit mid-glyph under the sticky column (#178 / #199) — grow to the last
   full period. */
table.grid thead th.cur, table.grid td.cell.cur{ min-width:max(132px, var(--grid-cur-min, 132px)); }
table.grid td.cell.cur .cur-read{
  display:flex; align-items:baseline; justify-content:flex-end; gap:10px;
}
table.grid td.cell.cur .cur-read .v{ font-size:13.5px; font-weight:500; }
table.grid td.cell.cur .cur-gap{ font-size:11px; font-weight:500; min-width:56px; text-align:right; }
/* The editor covers the whole cell — the gap must not show through beside it. */
td.cell.editing .cur-gap{ visibility:hidden; }

/* The metric column now carries the trend too, so it needs the width the
   retired trailing column gave back (300 → 392). On a phone that floor is
   wider than the scroller: the sticky names cover every month, scrollLeft
   moves, and values never paint (#229). --grid-metric-max is set when the
   392px column would leave no seat for CURRENT; desktop leaves it unset. */
table.grid th.metric-h, table.grid td.metric-c{
  text-align:left; position:sticky; left:0; z-index:4; background:var(--surface);
  border-right:1px solid var(--rule-2); padding:0 16px;
  min-width:min(392px, var(--grid-metric-max, 392px));
  max-width:var(--grid-metric-max, none);
  width:var(--grid-metric-max, auto);
  overflow:hidden;
}
table.grid thead th.metric-h{ z-index:6; background:var(--bg); color:var(--ink-3); letter-spacing:0.12em; }
/* One cell, two labels, each over the part it names: the name label at the left
   rail, the trend label right-aligned over the 84px sparkline. As one string at
   the far left, "12-mo trend" sat ~124px from the trend it named. */
table.grid thead th.metric-h .mh{
  display:flex; align-items:baseline; justify-content:space-between; gap:16px;
  min-width:0; max-width:100%;
}
table.grid thead th.metric-h .mh-trend{ flex:none; }

/* ---- the current period is frozen to the right edge ---- */
/* The mirror of the metric column: periods scroll between two fixed reads, so
   the column the page is about cannot slide off (it used to clip as soon as you
   scrolled back through the year). Under 700px the two frozen columns would
   leave no room for the periods between them, so the pin lifts there. */
table.grid thead th.cur, table.grid td.cur{ border-left:1px solid var(--rule-2); }
@media (min-width:700px){
  table.grid thead th.cur, table.grid td.cur{ position:sticky; right:0; }
  table.grid thead th.cur{ z-index:6; }
  table.grid td.cell.cur{ z-index:4; }
  table.grid tr.group td.cur{ z-index:3; }
  /* Selecting or editing a cell sets position:relative for its outline — it must
     not knock the frozen column back into the scroll flow. */
  table.grid td.cell.cur.editing{ z-index:6; }
}

/* ---- group separator row ---- */
tr.group td{
  height:32px; background:var(--bg);
  border-bottom:1px solid var(--rule);
  padding:0 16px; font-family:var(--mono); font-size:10.5px; font-weight:600;
  color:var(--ink-3); letter-spacing:0.14em; text-transform:uppercase; text-align:left;
}
tr.group td.metric-c{ position:sticky; left:0; z-index:3; background:var(--bg); }
/* A group row carries the frozen current column too, so the right edge runs
   unbroken through the sections. It stays --bg: the accent tint belongs to the
   readings, not to a section header. */
table.grid tr.group td.cur{ background:var(--bg); }
tr.group td .gcell{ display:flex; align-items:center; gap:8px; min-width:0; }
tr.group td .gcount{ color:var(--ink-4); font-weight:400; font-size:10.5px; letter-spacing:0; }
/* the synthetic "Ungrouped" section header reads quieter than real groups */
tr.group td .gcell-ungrouped{ color:var(--ink-4); font-weight:500; }
tr.group td.span-rest{ background:var(--bg); }

/* Group CRUD lives on the header. Viewers keep the label; editors get the
   drag handle (name + grip), click-to-rename, and delete. Empty Ungrouped
   stays in the DOM as a drop slot but is off-page until a drag starts
   (#267) — then is-dragging-item reveals it and is-ungrouped-revealing
   plays the fade once. Named empty groups keep their idle placeholder. */
.grid-page[data-can-edit="true"] tr.group .gcell-label{ display:none; }
.grid-page:not([data-can-edit="true"]) tr.group .gcell-rename,
.grid-page:not([data-can-edit="true"]) tr.group .gcell-tools,
.grid-page:not([data-can-edit="true"]) .gcell-handle,
.grid-page:not([data-can-edit="true"]) .mcell-handle .mcell-grip{ display:none; }
.grid-page:not([data-can-edit="true"]) .mcell-handle{
  cursor:default; user-select:text;
}
.grid-page:not(:has(tr.group)) .mcell-handle{ cursor:default; }
.grid-page:not(:has(tr.group)) .mcell-handle .mcell-grip{ display:none; }
.grid-page:not(.is-dragging-item) tr.group.g-ungrouped-empty{ display:none; }
/* One-shot: added when empty Ungrouped is first shown for a drag, cleared
   on drag end. Do not key this off g-ungrouped-empty — retally re-adds
   that class mid-drag and would replay the fade (#268). */
.grid-page tr.group.g-ungrouped.is-ungrouped-revealing > td{
  animation:ungrouped-reveal 180ms ease;
}
@keyframes ungrouped-reveal{ from{ opacity:0; } }
@media (prefers-reduced-motion: reduce){
  .grid-page tr.group.g-ungrouped.is-ungrouped-revealing > td{ animation:none; }
}

tr.group .gcell-rename{ display:flex; min-width:0; }
tr.group .gcell-name{
  width:auto; min-width:6ch; max-width:18ch; height:22px; padding:0 4px;
  border:1px solid transparent; border-radius:4px; background:transparent;
  font:inherit; letter-spacing:inherit; text-transform:inherit; color:inherit;
}
.grid-page[data-can-edit="true"] .gcell:has(> .gcell-rename:focus-within) .gcell-name{
  max-width:none; width:100%;
}
tr.group .gcell-name:hover{ border-color:var(--rule-2); }
tr.group .gcell-name:focus{
  outline:none; border-color:var(--rule-3); background:var(--surface);
}

tr.group .gcell-tools{
  display:flex; align-items:center; gap:4px; margin-left:auto;
  font-weight:500; letter-spacing:0; text-transform:none;
}
tr.group .gcell-act{
  display:block; position:relative; width:44px; height:44px; margin:0; padding:0;
}
tr.group .gcell-btn{
  position:absolute; inset:0; width:100%; height:100%; box-sizing:border-box;
  padding:0; margin:0; display:grid; place-items:center;
  border:1px solid transparent; border-radius:4px; background:transparent;
  color:var(--ink-4); font:inherit; line-height:1; cursor:pointer;
}
tr.group .gcell-btn:hover{ border-color:var(--rule-2); color:var(--ink); background:var(--surface); }
tr.group .gcell-btn-danger:hover,
tr.group .gcell-btn-danger.is-armed{
  border-color:var(--bad-edge); color:var(--bad-ink); background:var(--bad-soft);
}

/* Name-column handles — period cells stay free for scroll and select.
   The handle (grip + name) is the HTML5 drag source; the decorative grip
   does not capture the pointer. */
.gcell-handle, .mcell-handle{
  display:flex; align-items:center; gap:8px; min-width:0; min-height:32px;
  margin-left:-4px; padding:0 4px; cursor:grab; user-select:none;
}
.gcell-handle{ flex:1; }
.mcell-handle{ flex:1; height:100%; }
.gcell-drag-name{
  min-width:6ch; max-width:18ch; overflow:hidden; text-overflow:ellipsis;
}
.gcell-grip, .mcell-grip{
  display:inline-flex; align-items:center; justify-content:center;
  width:22px; height:22px; padding:0; flex:none;
  color:var(--ink-4); pointer-events:none;
}
.gcell-handle:hover .gcell-grip, .mcell-handle:hover .mcell-grip{ color:var(--ink-2); }
.gcell-handle:active, .mcell-handle:active{ cursor:grabbing; }
.gcell-handle:focus-visible{
  outline:2px solid var(--accent); outline-offset:1px;
}

/* Click-to-rename: the visible name lives on the handle so it can be
   dragged. The input is parked off-screen until focused, then overlaid
   on the name's box (left/width set in beginRename) so it does not
   jump into a later flex slot. */
.grid-page[data-can-edit="true"] .gcell{ position:relative; }
.grid-page[data-can-edit="true"] .gcell-rename:not(:focus-within){
  position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0);
}
.grid-page[data-can-edit="true"] .gcell:has(> .gcell-rename:focus-within) .gcell-rename{
  position:absolute; top:0; bottom:0; height:auto; overflow:visible; clip:auto;
  display:flex; align-items:center; z-index:1;
}
.grid-page[data-can-edit="true"] .gcell:has(> .gcell-rename:focus-within) .gcell-drag-name{
  display:none;
}

/* Empty groups are header-only (32px) and read as an invisible drop.
   Give the row a real landing pad and a hint under the name. */
tr.group.g-empty td{ height:72px; }
tr.group.g-empty .gcell{
  min-height:72px; flex-wrap:wrap; align-content:center; row-gap:2px;
}
.gcell-drop-hint{
  flex:1 1 100%; margin-left:30px;
  font-weight:500; letter-spacing:0; text-transform:none;
  color:var(--ink-4); font-size:12px;
  pointer-events:auto; position:relative; z-index:1;
  min-height:24px;
}
.grid-page:not([data-can-edit="true"]) .gcell-drop-hint{ display:none; }
.grid-page:not([data-can-edit="true"]) tr.group.g-empty td{ height:32px; }
.grid-page:not([data-can-edit="true"]) tr.group.g-empty .gcell{ min-height:0; }

tr.group.is-drop-target td,
table.grid tr.group.is-drop-target td.metric-c,
table.grid tr.group.is-drop-target td.span-rest,
table.grid tr.group.is-drop-target td.cur{
  background:var(--accent-soft);
}
tr.group.is-drop-target td.metric-c{
  box-shadow:inset 3px 0 0 var(--accent);
}
tr.group.is-dragging, tr.metric-row.is-dragging{ opacity:0.45; }
.mcell-drag-ghost{
  position:absolute !important; left:-9999px !important; top:-9999px !important;
  pointer-events:none !important;
}

/* Live-shift: siblings translate to open a hole while the ghost follows
   the pointer. Invert is applied inline; this transition plays it out.
   Drop adds is-settling so opacity eases back instead of snapping. */
.grid-page.is-dragging-item tbody tr.group > td,
.grid-page.is-dragging-item tbody tr.metric-row > td,
.grid-page.is-live-shifting tbody tr.group > td,
.grid-page.is-live-shifting tbody tr.metric-row > td,
.grid-page.is-settling tbody tr.group > td,
.grid-page.is-settling tbody tr.metric-row > td{
  transition:transform 180ms ease;
}
.grid-page.is-settling tr.group,
.grid-page.is-settling tr.metric-row{
  transition:opacity 180ms ease;
}
@media (prefers-reduced-motion: reduce){
  .grid-page.is-dragging-item tbody tr.group > td,
  .grid-page.is-dragging-item tbody tr.metric-row > td,
  .grid-page.is-live-shifting tbody tr.group > td,
  .grid-page.is-live-shifting tbody tr.metric-row > td,
  .grid-page.is-settling tbody tr.group > td,
  .grid-page.is-settling tbody tr.metric-row > td,
  .grid-page.is-settling tr.group,
  .grid-page.is-settling tr.metric-row{
    transition:none;
  }
}

@media (max-width:700px){
  tr.group td{ height:auto; min-height:44px; padding-top:6px; padding-bottom:6px; }
  tr.group .gcell{ flex-wrap:wrap; row-gap:6px; }
  tr.group .gcell-name{ height:44px; }
  tr.group .gcell-act{ width:44px; height:44px; }
  tr.group .gcell-btn, .gcell-grip, .mcell-grip, .gcell-handle, .mcell-handle{ min-height:44px; }
  .gcell-grip, .mcell-grip{ width:44px; height:44px; }
}

.grid-page .page-meta .g-new .btn{ color:var(--ink); }
.g-new-pop{ min-width:220px; padding:8px; }
.g-new-form{ display:flex; align-items:center; gap:6px; }
.g-new-form input[type="text"]{
  flex:1; min-width:0; height:28px; padding:0 8px;
  border:1px solid var(--rule-2); border-radius:6px; background:var(--bg);
  font-size:13px;
}
.g-new-form input[type="text"]:focus{ outline:none; border-color:var(--rule-3); }

/* ---- metric name cell: owner chip, name over caps-mono sub, then the trend ---- */
.mcell{ display:flex; align-items:center; gap:12px; height:100%; min-width:0; max-width:100%; }
.mcell .mtext{ min-width:0; flex:1; display:flex; flex-direction:column; justify-content:center; gap:1px; }
/* The 84×22 trend, right-aligned in the cell: shape and label in one fixation
   instead of at opposite ends of a 12-column scan. */
.mcell .mspark{ flex:none; display:flex; align-items:center; }
/* Phone squeeze: keep the spark, but let it give way so a month can sit
   beside the sticky names instead of disappearing under them. */
.grid-scroll.is-metric-squeezed .mcell,
.grid-scroll.is-metric-squeezed thead th.metric-h .mh{ width:100%; }
.grid-scroll.is-metric-squeezed .mcell .mspark{ flex:0 1 84px; min-width:40px; }
.grid-scroll.is-metric-squeezed .mcell .mspark svg{ display:block; width:100%; height:auto; }
.mcell .owner-chip{ cursor:default; }
/* Team-owned: dashed hollow chip — shared, not a person. */
.mcell .owner-chip.team{
  background:var(--surface); border:1.5px dashed var(--ink-4); color:var(--ink-3);
}
.mcell .name{ min-width:0; display:flex; align-items:center; gap:6px; }
.mcell .name .label{ font-size:13px; line-height:16px; color:var(--ink); font-weight:500; cursor:pointer; overflow:hidden; text-overflow:ellipsis; -webkit-user-drag:none; }
.mcell .name .label:hover{ color:var(--accent); }
.mcell .name .flag{ color:var(--warn); font-size:11px; }
.mcell .name .source-lock{display:inline-grid;place-items:center;width:14px;height:14px;color:#e45b3f;font-size:7px;line-height:1}
.mcell .msub{
  font-family:var(--mono); font-size:10px; line-height:12px; font-weight:400;
  color:var(--ink-3); letter-spacing:0.08em; text-transform:uppercase;
  overflow:hidden; text-overflow:ellipsis;
}

/* ---- RAG cell (stripe: 3px full-height left spine) ---- */
td.cell{ cursor:cell; position:relative; padding:0 12px; font-family:var(--mono); font-size:12.5px; color:var(--ink); font-weight:400; }
td.cell .v{ display:inline-block; }
td.cell::before{ content:''; position:absolute; left:0; top:0; bottom:0; width:3px; background:transparent; }
td.cell.t-ok::before{ background:var(--ok); }
td.cell.t-warn::before{ background:var(--warn); }
td.cell.t-bad::before{ background:var(--bad); }
td.cell.t-empty{ color:var(--ink-4); }
td.cell.t-empty::before{ background:repeating-linear-gradient(180deg, var(--ink-5) 0 3px, transparent 3px 6px); }
td.cell.readonly{cursor:default}td.cell.readonly::after{content:"";position:absolute;right:5px;top:5px;width:4px;height:4px;border-radius:50%;background:#e45b3f;opacity:.55}

/* Cells aren't text — a drag SELECTS cells, it must not paint a text range. */
td.cell{ user-select:none; -webkit-user-select:none; }
td.cell:hover{ background:var(--hover); }
td.cell.cur:hover{ background:color-mix(in oklch, var(--accent-soft) 70%, var(--surface)); }
td.cell:focus{ outline:none; }
td.cell.sel{ outline:2px solid var(--accent); outline-offset:-2px; z-index:3; position:relative; background:color-mix(in oklch, var(--accent-soft) 60%, var(--surface)); }

/* Multi-cell selection (drag / shift-extend). Cells in the range get a soft
   tint; the active cell keeps the bolder .sel ring + fill on top, so it stays
   the obvious focus within the block. :not(.sel) keeps the active cell darker
   than the range, and outranks the .cur tint via the extra :not() class. */
td.cell.in-range:not(.sel){ background:color-mix(in oklch, var(--accent-soft) 55%, var(--surface)); }

/* Off-screen clipboard proxy: ⌘C/⌘V focus this so the browser's native
   copy/paste fire here (see grid_controller). Must stay focusable — hence
   off-screen rather than display:none. */
textarea.grid-clip{
  position:fixed; left:-9999px; top:0; width:1px; height:1px;
  opacity:0; padding:0; border:0; margin:0; resize:none; pointer-events:none;
  /* must stay selectable — execCommand("copy") copies the proxy's selection. */
  user-select:text; -webkit-user-select:text;
}
td.cell.editing{ outline:2px solid var(--accent); outline-offset:-2px; background:var(--surface) !important; z-index:6; position:relative; box-shadow:var(--shadow-2); }
td.cell.editing .v{ visibility:hidden; }
td.cell input.cell-inp{ position:absolute; inset:0; width:100%; height:100%; border:none; background:transparent; outline:none; font-family:var(--mono); font-size:12.5px; font-weight:500; text-align:right; padding:0 12px; color:var(--ink); }
td.cell .cmt-dot{ position:absolute; top:5px; right:5px; width:5px; height:5px; border-radius:50%; background:var(--accent); }

/* The trailing trend column ("13 WK" / "12 MO") is retired — it was the same
   picture as the row's sparkline, twelve columns further away. The trend lives in
   .mcell .mspark above. */

/* ---- status bar: floating card under the grid ---- */
.statusbar{
  display:flex; align-items:center; gap:18px; height:36px; padding:0 16px;
  background:var(--surface); border:1px solid var(--rule-2); border-radius:9px;
  font-family:var(--mono); font-size:11px; color:var(--ink-3); letter-spacing:0.02em;
}
.statusbar .ref{ color:var(--accent); font-weight:500; text-transform:uppercase; }
.statusbar .v-now{ color:var(--ink); }
.statusbar .sb-stat{ display:flex; align-items:center; gap:5px; text-transform:uppercase; }
.statusbar .sb-dot{ width:6px; height:6px; border-radius:50%; flex-shrink:0; }
.statusbar .sb-dot.ok{ background:var(--ok); }
.statusbar .sb-dot.warn{ background:var(--warn); }
.statusbar .sb-dot.bad{ background:var(--bad); }
.statusbar .sb-dot.empty{ background:transparent; border:1px dashed var(--ink-4); }
.statusbar .sb-dot.none{ display:none; }
.statusbar .sp{ flex:1; }

/* The bindings used to be spelled out here permanently; they retreat behind this
   affordance and open upward (the status bar sits at the bottom of the page). */
.statusbar .sb-kbd{
  font-family:var(--mono); font-size:10.5px; letter-spacing:0.06em;
  text-transform:uppercase; white-space:nowrap;
}
.sb-shortcuts .pop.pop-up{ top:auto; bottom:calc(100% + 6px); left:auto; right:0; min-width:260px; }
.sb-shortcuts .pop-opt{ cursor:default; gap:10px; font-family:var(--sans); text-transform:none; letter-spacing:0; }
.sb-shortcuts .pop-opt:hover{ background:transparent; color:var(--ink-2); }
.sb-shortcuts .kb-keys{
  flex:none; min-width:76px; font-family:var(--mono); font-size:10.5px;
  color:var(--ink-4); text-transform:uppercase; letter-spacing:0.06em;
}
/* Phone (#256): a selected cell's status ("Off track · −4.1 pts vs target ·
   3rd decline") wraps inside the 36px strip — .sb-stat grows to ~80px and
   spills. Hiding SHORTCUTS made it 0×0. Grow and wrap the bar; keep the
   affordance on its own row so it stays tappable. Desktop stays one row. */
@media (max-width:700px){
  .statusbar{
    height:auto; min-height:36px; flex-wrap:wrap;
    align-items:center; align-content:center;
    gap:8px 12px; padding:8px 12px;
  }
  .statusbar .ref,
  .statusbar .v-now,
  .statusbar .sb-stat{ min-width:0; }
  .statusbar .sb-stat{ flex:1 1 12rem; }
  .statusbar .sp{ display:none; }
  .statusbar .sb-shortcuts{
    flex:1 1 100%; display:flex; justify-content:flex-end;
    min-width:min-content;
  }
  .statusbar .btn.sb-kbd{
    height:44px; min-height:44px; min-width:44px; padding:0 12px;
  }
  .sb-shortcuts .pop.pop-up{
    min-width:0; width:100%; max-width:none;
  }
}

/* row filter dim */
tr.metric-row.dim{ opacity:0.28; transition:opacity .15s; }
tr.group.dim{ opacity:0.45; }

/* Zero-height period sizer: participates in the column model so a filter
   that hides every metric row cannot collapse a month under CURRENT. */
tr.grid-col-sizer,
tr.grid-col-sizer td{
  height:0 !important; min-height:0 !important; padding:0 !important;
  border:0 !important; line-height:0; overflow:hidden; visibility:hidden;
}

/* Filtered-empty: the card stays, the blank header-only grid does not. */
.grid-filter-empty[hidden]{ display:none; }
.grid-wrap.is-filter-empty .grid-scroll{ display:none; }
.grid-wrap.is-filter-empty .grid-filter-empty{
  display:flex; padding:48px 24px;
}

/* ============================================================
   Grid micro-interactions (slice B1 — grid-motion).
   Additive polish on top of the grid above. All motion is gated
   behind prefers-reduced-motion.
   ============================================================ */

/* The selection ring snaps cell→cell, exactly like a real spreadsheet — no
   paint-tier outline-color/background-color transition (and faster to read). */

/* Crisp keyboard-only focus ring (mouse selection still uses .sel). */
td.cell:focus-visible{ outline:2px solid var(--accent); outline-offset:-2px; z-index:3; }

/* Sparkline reveal-on-load: fade the grid sparkline in once on render.
   Opacity is compositor-tier (S); animating stroke-dashoffset would repaint the
   SVG path every frame (C). Still gated now that the sparkline has moved into the
   metric column. */
@keyframes grid-spark-draw{ from{ opacity:0; } to{ opacity:1; } }
.mcell .mspark svg path{
  animation:grid-spark-draw .5s ease-out both;
}

@media (prefers-reduced-motion: reduce){
  .mcell .mspark svg path{ animation:none; opacity:1; }
}
