/* =========================================================
   LOCKHERN TOOLS — design tokens

   THE PALETTE IS CLOSED. These eight values are the entire colour
   system and nothing else may be added — no derived tints, no darker
   steps, no generated hues, no semantic red or green. If a state needs
   distinguishing, use weight, opacity of a palette colour, a border, an
   arrow or a word. Not a new colour.

   Every rule below resolves to one of these eight. The only variation
   permitted is opacity of a palette colour (rgba of the blue, the navy
   or the black) and CSS filters on hover, both of which are shades of a
   palette colour rather than new entries in it.
   ========================================================= */
:root{
  /* ---- The brand palette. Nothing else. ---- */
  --blue:       #317AE0;   /* primary blue  */
  --navy:       #0E2970;   /* tertiary blue — see the note below */
  --white:      #FFFFFF;
  --black:      #0E0E0E;
  --grey-dark:  #6B7A99;
  --grey-mid:   #BAC3D6;
  --grey-light: #E7ECF4;

  /* ---- Semantic aliases. Each one IS a palette colour above. ----
     Kept so the intent of a rule is readable, not to smuggle in extras. */
  --canvas:     var(--grey-light);   /* page background */
  --surface:    var(--white);        /* cards, rail, hero */
  --line:       var(--grey-mid);     /* card outlines, real edges */
  --line-soft:  var(--grey-light);   /* dividers inside a white card */
  --text:       var(--black);
  --text-2:     var(--grey-dark);    /* secondary copy */
  --text-3:     var(--grey-dark);    /* was a lighter invented grey; mid
                                        grey fails as text, so secondary
                                        and tertiary share dark grey and
                                        separate by size and weight */
  --disabled:   var(--grey-mid);     /* placeholders, disabled only */
  --signal:     var(--blue);

  /* Blue at low opacity — still the brand blue, just less of it. Used
     for the active nav pill, focus glow and the Live chip, all of which
     need a blue-tinted ground and the palette has no light blue. */
  --blue-wash:  rgba(49,122,224,.10);
  --blue-edge:  rgba(49,122,224,.28);

  /* Navy is used ONLY as the tint inside shadows and the mobile scrim,
     where it shades black rather than reading as a colour of its own.
     It is never a surface, never text, never a fill. */

  /* ---- Type ----
     Two families, both soft and both sans.

     Montserrat is the geometric sans the logo is drawn in, so headings
     and the wordmark now speak with the same voice instead of the page
     saying one thing and the mark another.

     DM Sans carries body, UI and labels: humanist, open apertures, low
     stroke contrast — easy to read at 12px, which is where most of this
     interface lives. It replaces both Figtree and the monospace that
     used to set every label and chip; small letterspaced monospace caps
     read as a terminal, not as a tool people enjoy using. */
  --display: "Montserrat", "Segoe UI", -apple-system, sans-serif;   /* headings */
  --ui:      "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --label:   "DM Sans", -apple-system, "Segoe UI", sans-serif;      /* eyebrows, chips, meta */
  --logo:    "Montserrat", "Segoe UI", -apple-system, sans-serif;   /* wordmark only */

  /* Metrics */
  --rail-w: 244px;
  --r: 10px;
  --r-lg: 14px;
  /* Shadows carry a navy tint so they read as depth in a blue-grey UI
     rather than as neutral grime. These and the mobile scrim are the only
     places the tertiary navy appears, and in both it is a shading of black
     rather than a colour on its own. */
  --shadow: 0 1px 2px rgba(14,41,112,.05), 0 6px 20px -8px rgba(14,41,112,.12);
  --shadow-lift: 0 2px 4px rgba(14,41,112,.06), 0 16px 34px -12px rgba(14,41,112,.20);
}

*,*::before,*::after{ box-sizing:border-box; }
[hidden]{ display:none !important; }
html{ -webkit-text-size-adjust:100%; }
body{
  margin:0; background:var(--canvas); color:var(--text);
  font-family:var(--ui); font-size:15px; line-height:1.5;
  -webkit-font-smoothing:antialiased;
}
h1,h2,h3,h4,p{ margin:0; }
a{ color:inherit; text-decoration:none; }
button,input,select,textarea{ font:inherit; color:inherit; }
button{ cursor:pointer; background:none; border:none; padding:0; }
:focus-visible{ outline:2px solid var(--signal); outline-offset:2px; border-radius:4px; }

.skip{
  position:absolute; left:-999px; top:0; z-index:99;
  background:var(--blue); color:var(--white); padding:10px 16px; border-radius:0 0 var(--r) 0;
}
.skip:focus{ left:0; }

.eyebrow{
  font-family:var(--label); font-size:11px; font-weight:500;
  letter-spacing:.14em; text-transform:uppercase; color:var(--text-3);
}

/* =========================================================
   RAIL
   ========================================================= */
/* Light rail on a white surface, separated from the canvas by a real
   edge rather than by being a black slab. */
.rail{
  position:fixed; inset:0 auto 0 0; width:var(--rail-w); z-index:40;
  background:var(--surface); color:var(--text);
  border-right:1px solid var(--line);
  display:flex; flex-direction:column; padding:22px 0 18px;
}
.rail-top{ display:flex; align-items:flex-start; justify-content:space-between; padding:0 20px 22px; }

/* Logo lockup — mark, divider, wordmark, in the proportions of the
   Lockhern Digital logo. The two text lines stay config-driven from the
   admin Brand tab, so this renders "LOCKHERN / TOOLS" here and follows
   whatever the brand fields say. */
/* overflow:hidden because the two text lines are admin-editable — a long
   brand name should clip at the rail edge, not spill across the page. */
.wordmark{ display:flex; align-items:center; gap:9px; line-height:1; min-width:0; overflow:hidden; }
.wordmark-mark{ flex:none; width:33px; height:33px; display:block; }
/* The mark is an <img> pointing at assets/logo.svg, so its colours live
   in that file rather than here — that is what makes swapping in the
   official artwork a one-file change. */
.wordmark-rule{
  flex:none; width:1.5px; height:30px; background:var(--grey-dark);
  border-radius:1px;
}
.wordmark-text{ min-width:0; }
/* Montserrat ExtraBold is the closest widely-available match to the
   geometric sans in the logo, so the wordmark beside the mark reads as
   part of it rather than as Figtree pretending. */
.wordmark-l{
  display:block; font-family:var(--logo); font-weight:800; font-size:15px;
  letter-spacing:.005em; text-transform:uppercase; color:var(--blue);
  white-space:nowrap;
}
.wordmark-s{
  display:block; font-family:var(--logo); font-size:8.5px; font-weight:700;
  letter-spacing:.36em; text-transform:uppercase; color:var(--grey-dark);
  margin-top:4px; white-space:nowrap;
}
.rail-close{ display:none; color:var(--text-2); font-size:26px; line-height:1; padding:0 2px; }

.rail-nav{ flex:1; overflow-y:auto; padding:0 12px; }
.rail-group{ margin-bottom:2px; }
.rail-link{
  display:flex; align-items:center; gap:10px; width:100%;
  padding:8px 10px; border-radius:8px; color:var(--text-2);
  font-size:13.5px; font-weight:500; text-align:left;
  transition:background .14s, color .14s;
}
.rail-link:hover{ background:var(--grey-light); color:var(--text); }
.rail-link.is-on{ background:var(--blue-wash); color:var(--signal); font-weight:600; }
.rail-link.is-on .rail-tick{ background:var(--signal); }
.rail-tick{
  width:3px; height:15px; border-radius:2px; background:var(--grey-mid);
  flex:none; transition:background .14s;
}
.rail-count{ margin-left:auto; font-family:var(--label); font-size:11.5px; color:var(--text-2);
  font-variant-numeric:tabular-nums; }
.rail-head{
  font-family:var(--label); font-size:10px; letter-spacing:.16em; text-transform:uppercase;
  color:var(--text-2); padding:16px 12px 7px;
}

.rail-foot{ padding:16px 22px 0; border-top:1px solid var(--line-soft); margin:14px 12px 0; }
.creed{
  font-family:var(--ui); font-size:12.5px; font-weight:500;
  color:var(--text-2); line-height:1.5; margin-bottom:14px;
}
.rail-admin{
  display:inline-flex; align-items:center; gap:8px;
  font-family:var(--label); font-size:11px; letter-spacing:.1em; text-transform:uppercase;
  color:var(--text-2); transition:color .14s;
}
.rail-admin:hover{ color:var(--signal); }
.rail-admin .dot{ width:5px; height:5px; border-radius:50%; background:var(--signal); }
.rail-scrim{ display:none; }

/* =========================================================
   SHELL / HERO
   ========================================================= */
.shell{ margin-left:var(--rail-w); min-height:100vh; display:flex; flex-direction:column; }

/* Padding is tighter than it looks like it wants to be, and that is the
   point: everything below has to clear the fold. Every 10px of air here is
   10px of the board someone has to scroll for. */
.hero{
  position:relative; background:var(--surface);
  border-bottom:1px solid var(--line);
  padding:30px 40px 26px;
}
/* Matches the board's 1500px cap minus its 40px gutters, so the stat
   tiles line up with the panels underneath them. */
.hero-inner{ max-width:1420px; }
.hero-title{
  font-family:var(--display); font-weight:700; font-size:clamp(23px,2.6vw,30px);
  letter-spacing:-.015em; line-height:1.1; margin:8px 0 20px;
}
/* Was an italic serif. Same family now, lighter and grey — the two-tone
   effect survives without switching voice mid-sentence. */
.hero-title em{ font-style:normal; color:var(--text-2); font-weight:600; }

.menu-btn{ display:none; color:var(--text-2); margin-bottom:14px; }

/* Command console — the signature element */
.console{
  position:relative; display:flex; align-items:center; gap:12px;
  max-width:620px; background:var(--surface);
  border:1.5px solid var(--line); border-radius:var(--r-lg);
  padding:0 14px; height:56px;
  transition:border-color .16s, box-shadow .16s;
}
.console:focus-within{
  border-color:var(--signal);
  box-shadow:0 0 0 4px var(--signal-soft);
}
.console-icon{ color:var(--text-3); flex:none; transition:color .16s; }
.console:focus-within .console-icon{ color:var(--signal); }
.console input{
  flex:1; border:none; background:none; outline:none;
  font-size:16.5px; font-weight:500; letter-spacing:-.005em; min-width:0;
}
.console input::placeholder{ color:var(--text-3); font-weight:400; }
.console-kbd{
  font-family:var(--label); font-size:11px; color:var(--text-3);
  border:1px solid var(--line); border-bottom-width:2px; border-radius:5px;
  padding:2px 7px; background:var(--grey-light); flex:none;
}
.console:focus-within .console-kbd{ display:none; }
.console-clear{ font-size:20px; color:var(--text-3); line-height:1; padding:0 4px; }
.console-clear:hover{ color:var(--text); }
.console-hint{
  font-family:var(--label); font-size:11px; color:var(--text-3);
  margin-top:10px; height:16px; opacity:0; transition:opacity .18s;
}
.console-hint b{ color:var(--text-2); font-weight:500; }
.console:focus-within ~ .console-hint{ opacity:1; }

/* =========================================================
   MAIN GRID
   ========================================================= */
/* =========================================================
   THE DAY'S LINE
   ========================================================= */
/* A strip, not a card. One sentence in a quarter of the board would have
   priced a nice-to-read above the numbers people came for. */
.dayline{
  display:flex; align-items:baseline; gap:16px; flex-wrap:wrap;
  background:var(--surface); border-bottom:1px solid var(--line);
  padding:13px 40px; border-left:3px solid var(--blue);
}
.dayline-k{
  font-family:var(--label); font-size:10px; letter-spacing:.16em;
  text-transform:uppercase; color:var(--text-3); flex:none;
}
.dayline-t{
  font-size:14.5px; font-weight:500; letter-spacing:-.004em; line-height:1.45;
  flex:1; min-width:240px;
}
.dayline-a{
  font-family:var(--label); font-size:11px; letter-spacing:.06em;
  color:var(--text-2); flex:none;
}

/* ---- Movie club ----
   A column: heading, poster, meta. The poster is the flexible one, so the
   card is always visually full and never the panel that sets the row
   height — the board stretches all four to the tallest, and a 2:3 poster at
   its natural size in a 277px column would have dragged the whole row past
   the fold. min-height keeps it recognisable as a poster on a short row. */
#clubPanel{ display:flex; flex-direction:column; }
#clubPanel .club-body{ display:flex; flex-direction:column; flex:1; min-height:0; }
.club-poster{
  /* basis 0, not auto: with auto the poster's own height counts towards the
     card's content height, the card then sets the row height, and the poster
     grows to fill the row it just defined. That loop rendered a 1807px card
     next to three 400px ones. At basis 0 only min-height contributes, so the
     row is sized by the other panels and the poster fills what is left. */
  flex:1 1 0; min-height:150px; margin-bottom:14px;
  border-radius:var(--r); overflow:hidden; background:var(--grey-light);
  border:1px solid var(--line-soft);
}
.club-poster img{
  width:100%; height:100%; object-fit:cover;
  /* Posters put the title and the branding in the top two thirds; a centre
     crop on a tall one cuts the title off. */
  object-position:center 30%;
  display:block;
}
.club-meta{ flex:none; }
.club-t{ font-size:15.5px; font-weight:600; letter-spacing:-.01em; line-height:1.3; }
.club-t a{ text-decoration:underline; text-underline-offset:2px; text-decoration-thickness:1px; }
.club-t a:hover{ color:var(--signal); }
.club-kind{ font-size:12.5px; color:var(--text-2); margin-top:3px; }
.club-when{ font-size:12.5px; color:var(--text-2); margin-top:8px; }
.club-when b{ color:var(--text); font-weight:600; }
/* wrap, because three links beside a poster do not fit a 320px phone —
   "IMDb · Rotten Tomatoes · Our ratings" is wider than the column left over. */
.club-links{
  display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-top:12px;
  padding-top:11px; border-top:1px solid var(--line-soft);
  font-family:var(--label); font-size:11.5px; font-weight:600; color:var(--text-2);
}
.club-links a:hover{ color:var(--signal); }
/* The same treatment as the club links, so a panel footer link reads the
   same wherever it appears. */
.panel-link{ display:flex; }
.club-dot{ color:var(--grey-mid); }

/* =========================================================
   THE BOARD — status, above the fold
   ========================================================= */
.board{ padding:22px 40px 0; max-width:1500px; width:100%; }
.board-grid{
  display:grid; gap:16px;
  grid-template-columns:repeat(4,minmax(0,1fr));
  /* Stretch, not start: four panels of slightly different length left a
     ragged bottom edge that read as broken rather than as a masonry. */
  align-items:stretch;
}
/* A panel that hides itself must not leave a column of air behind it, so
   the grid counts what is actually on screen rather than what is declared. */
.board-grid .panel[hidden]{ display:none; }

/* =========================================================
   MAIN GRID
   ========================================================= */
.main{
  flex:1; display:flex; flex-direction:column; gap:26px;
  padding:30px 40px 52px; max-width:1500px; width:100%;
}
.panel-wide{ max-width:620px; }

/* ---- Catalog ---- */
.catalog{ display:flex; flex-direction:column; gap:38px; }

.sect-head{ display:flex; align-items:baseline; gap:14px; margin-bottom:16px; }
.sect-name{
  font-family:var(--display); font-weight:700; font-size:18px; letter-spacing:-.01em;
 
}
.sect-blurb{ font-size:13px; color:var(--text-3); }
.sect-rule{ flex:1; height:1px; background:var(--line); min-width:20px; }

/* Min raised from 224px now the catalog has the full page width instead of
   a column beside a rail. At 224 it packed five thin cards a row; the
   description then wrapped to five lines and the grid read as a wall. */
.cards{
  display:grid; gap:16px;
  grid-template-columns:repeat(auto-fill,minmax(252px,1fr));
}

.card{
  position:relative; display:flex; flex-direction:column;
  background:var(--surface); border:1px solid var(--line);
  border-radius:var(--r-lg); overflow:hidden;
  box-shadow:var(--shadow);
  transition:transform .16s cubic-bezier(.2,.7,.3,1), box-shadow .16s, border-color .16s;
}
.card::before{
  content:""; position:absolute; inset:0 0 auto 0; height:2px;
  background:var(--signal); opacity:0; transition:opacity .16s;
}
.card:hover, .card.is-cursor{
  transform:translateY(-2px); box-shadow:var(--shadow-lift); border-color:var(--grey-mid);
}
.card:hover::before, .card.is-cursor::before{ opacity:1; }
.card.is-soon{ box-shadow:none; border-style:dashed; background:transparent; cursor:default; }
.card.is-soon .card-plate{ background:transparent; border-bottom:1px dashed var(--line); }
.card.is-soon .card-mono{ color:var(--disabled) !important; }
.card.is-soon:hover{ transform:none; box-shadow:none; }
.card.is-soon::before{ display:none; }

/* Light plate. This used to be a near-black panel washed with a hue
   generated per tool — colours that were nowhere in the brand palette.
   Now it is light grey, and the only thing that varies per tool is which
   palette colour the monogram takes (see plateStyle in app.js). */
/* The plate sits on the card, not on the page, so it is white with a
   rule under it — grey-light here would match the canvas exactly and the
   card would read as if its top were punched out. The monogram carries
   the colour. */
.card-plate{
  height:84px; display:flex; align-items:center; justify-content:center;
  overflow:hidden; position:relative;
  background:var(--surface); border-bottom:1px solid var(--line-soft);
}
.card-plate img{ position:relative; z-index:1; }
.card-plate img{ width:100%; height:100%; object-fit:cover; display:block; }
.card-mono{
  position:relative; z-index:1;
  font-family:var(--display); font-weight:700; font-size:27px; letter-spacing:-.01em;
 
}
.card-body{ padding:14px 16px 16px; display:flex; flex-direction:column; flex:1; gap:6px; }
.card-name{ font-size:15px; font-weight:600; letter-spacing:-.01em; }
.card-desc{ font-size:13px; color:var(--text-2); line-height:1.45; flex:1; }
.card-meta{
  display:flex; align-items:center; gap:8px; margin-top:auto; padding-top:10px;
  font-family:var(--label); font-size:10.5px; letter-spacing:.06em; text-transform:uppercase;
  color:var(--text-3);
}
.card-go{ margin-left:auto; color:var(--signal); font-weight:500; }
.card.is-soon .card-go{ color:var(--text-3); }

.chip{
  display:inline-flex; align-items:center; gap:5px;
  font-family:var(--label); font-size:10px; font-weight:500;
  letter-spacing:.08em; text-transform:uppercase;
  padding:3px 7px; border-radius:5px;
}
/* Palette has no green or amber, so the three states step down in
   emphasis instead of changing hue: filled blue, outlined blue, outlined
   grey. Each chip renders its own word, so the styling reinforces the
   label rather than carrying the meaning alone. */
.chip-live{ background:var(--blue-wash); color:var(--signal); border:1px solid transparent; }
.chip-beta{ background:transparent;      color:var(--signal); border:1px solid var(--blue-edge); }
.chip-soon{ background:transparent;      color:var(--text-2); border:1px solid var(--line); }

/* Compact rows for links/docs */
.links{ display:grid; gap:8px; grid-template-columns:repeat(auto-fill,minmax(268px,1fr)); }
.link-row{
  display:flex; align-items:center; gap:11px;
  background:var(--surface); border:1px solid var(--line); border-radius:var(--r);
  padding:11px 13px; transition:border-color .14s, transform .14s;
}
.link-row:hover, .link-row.is-cursor{ border-color:var(--signal); transform:translateX(2px); }
.link-glyph{
  width:26px; height:26px; border-radius:7px; flex:none;
  display:flex; align-items:center; justify-content:center;
  font-family:var(--label); font-size:11px; font-weight:600;
}
.link-name{ font-size:13.5px; font-weight:500; }
.link-desc{ font-size:12px; color:var(--text-3); }
.link-arrow{ margin-left:auto; color:var(--text-3); font-size:13px; }

.empty{ text-align:center; padding:60px 20px; }
.empty-h{ font-family:var(--display); font-size:18px; font-weight:700; }
.empty-p{ color:var(--text-2); font-size:14px; margin:6px 0 16px; }

/* =========================================================
   SIDE PANELS
   ========================================================= */
.panel{
  background:var(--surface); border:1px solid var(--line);
  border-radius:var(--r-lg); padding:18px;
}
.panel-h{
  font-family:var(--label); font-size:10px; letter-spacing:.16em; text-transform:uppercase;
  color:var(--text-3); margin-bottom:14px; display:flex; align-items:center; gap:8px;
}
.panel-h::after{ content:""; flex:1; height:1px; background:var(--line-soft); }

.quote-t{
  font-family:var(--ui); font-size:15.5px; line-height:1.5; font-weight:500;
  letter-spacing:-.004em;
}
.quote-a{
  font-family:var(--label); font-size:11px; letter-spacing:.06em;
  color:var(--text-2); margin-top:12px;
}

.kpi{ display:flex; flex-direction:column; gap:14px; }
.kpi-row{ display:flex; align-items:flex-end; gap:10px; }
.kpi-v{
  font-family:var(--display); font-size:26px; font-weight:700; line-height:1;
  font-variant-numeric:tabular-nums;
  letter-spacing:-.025em;
}
/* No green or red in the palette, so direction is carried by an arrow
   and the colour only sets emphasis. This is what the numbers needed
   anyway — up is not always good, and green said it was. */
.kpi-d{ font-family:var(--label); font-size:11.5px; font-weight:700; padding-bottom:2px;
  font-variant-numeric:tabular-nums; }
.kpi-d::before{ font-weight:400; margin-right:3px; }
.kpi-d.up{ color:var(--signal); }
.kpi-d.up::before{ content:"\2191"; }        /* ↑ */
.kpi-d.down{ color:var(--text-2); }
.kpi-d.down::before{ content:"\2193"; }      /* ↓ */
.kpi-d.flat{ color:var(--text-2); font-weight:500; }
.kpi-d.flat::before{ content:"\2013"; }      /* – */
.kpi-l{ font-size:12.5px; color:var(--text-2); margin-top:3px; }
.kpi-n{ font-size:11.5px; color:var(--text-3); margin-top:2px; }
.kpi + .kpi{ border-top:1px solid var(--line-soft); padding-top:14px; }

.quote-ai{ color:var(--disabled); }

/* A ratio or share, not a change — no arrow, because there is no direction. */
.kpi-t{
  font-family:var(--label); font-size:11.5px; font-weight:600; padding-bottom:2px;
  color:var(--text-2); font-variant-numeric:tabular-nums;
}

/* ---- Suggestion box: trigger + dialog ----
   Anchored top-right of the hero rather than sitting in the page flow. A
   box people have to scroll past the whole catalog to find is a box nobody
   uses, and it was previously the last thing on the page. */
.sg-open{
  position:absolute; top:26px; right:34px;
  display:inline-flex; align-items:center; gap:8px;
  font-family:var(--label); font-size:12.5px; font-weight:600;
  color:var(--text-2); background:var(--surface);
  border:1px solid var(--line); border-radius:999px; padding:8px 14px;
  transition:color .14s, border-color .14s, box-shadow .14s;
}
.sg-open:hover{ color:var(--signal); border-color:var(--blue-edge); box-shadow:var(--shadow); }

.sg-scrim{
  position:fixed; inset:0; z-index:60; background:rgba(14,41,112,.44);
}
.sg-pop{
  position:fixed; z-index:61; top:50%; left:50%; transform:translate(-50%,-50%);
  width:min(460px, calc(100vw - 32px)); max-height:calc(100vh - 32px);
  overflow:auto; box-shadow:var(--shadow-lift);
}
.sg-close{
  position:absolute; top:12px; right:14px; font-size:22px; line-height:1;
  color:var(--text-2); padding:2px 6px;
}
.sg-close:hover{ color:var(--text); }

@media (max-width:860px){
  .sg-open{ top:auto; right:20px; margin-top:14px; position:static; }
}

/* ---- Per-platform spend split ----
   Sits under the total and must visibly add up to it, so the rows carry the
   same tabular numerals as the headline figure. */
/* The spend figure follows the client lists in the same panel, and a 28px
   number directly under a client's name reads as that client's number. */
.team-list + .kpi{
  margin-top:16px; padding-top:16px; border-top:1px solid var(--line-soft);
}

.split{ list-style:none; margin:12px 0 0; padding:12px 0 0; border-top:1px solid var(--line-soft); }
.split li{
  display:flex; justify-content:space-between; gap:12px; align-items:baseline;
  font-size:13px; padding:4px 0; color:var(--text-2);
}
.split li em{ font-style:normal; font-size:11.5px; }
.split-v{
  font-weight:600; color:var(--text); font-variant-numeric:tabular-nums; white-space:nowrap;
}

/* ---- Business stats, top of page ----
   Four headline figures. Single numbers, so tiles rather than a chart. */
/* Same column count as the board below it, so the four figures sit directly
   over the four panels instead of huddling in the left two-thirds while the
   board runs the full width. auto-fit rather than a fixed 4, because with no
   end-date column mapped there are only three tiles and a hard four would
   leave a hole where the missing one used to be. */
.statbar{
  display:grid; gap:16px; margin-top:22px;
  grid-template-columns:repeat(auto-fit,minmax(200px,1fr));
}
.stat{
  background:var(--surface); border:1px solid var(--line);
  border-radius:var(--r); padding:14px 16px;
}
.stat-v{
  font-family:var(--display); font-weight:700; font-size:28px; line-height:1;
  letter-spacing:-.02em; font-variant-numeric:tabular-nums; color:var(--text);
}
.stat-l{ font-size:13px; font-weight:600; margin-top:7px; }
.stat-n{ font-size:12px; color:var(--text-2); margin-top:2px; }

/* ---- Client flow chart ----
   Direction is the encoding: above the line joined, below it left.

   Both series are solid, and that is a correction. The lower half was the
   brand blue at .45 opacity — which measures 1.81:1 against the page, under
   the 3:1 floor a graphical object needs, so a month where one client left
   was a sliver you could not see. Solid grey-dark is 4.31:1; solid blue is
   4.21:1. Both are legible on their own, which is the constraint that
   actually binds here.

   Two hues this close would normally fail a discriminability check — blue
   against grey-dark is ΔE 12.1 for normal vision, under the 15 floor. That
   floor governs when colour is the ONLY thing telling two series apart. It
   isn't: position is, and position is unambiguous. Colour is a redundant
   second cue, so being hard to tell apart costs nothing, while being too
   faint to see costs the whole bar. See flowChart(). */
.flow-up path{ fill:var(--blue); }
.flow-down path{ fill:var(--grey-dark); }
.flow-zero{ fill:var(--grey-mid); }
.flow:hover .flow-up path, .flow:hover .flow-down path{ fill:var(--black); }
.flow-key{
  font-family:var(--label); font-size:9px; font-weight:600; fill:var(--text-2);
  letter-spacing:.08em; text-transform:uppercase;
}

/* ---- Team panel ---- */
.team-next{ padding-bottom:12px; }
.team-next-v{ font-size:16px; font-weight:600; letter-spacing:-.01em; }
.team-next-n{ font-size:12.5px; color:var(--text-2); margin-top:3px; }
.team-sub{
  font-family:var(--label); font-size:10px; letter-spacing:.14em;
  text-transform:uppercase; color:var(--text-2); margin:14px 0 8px;
  padding-top:12px; border-top:1px solid var(--line-soft);
}
.team-list{ list-style:none; margin:0; padding:0; }
.team-list li{
  display:flex; align-items:baseline; gap:10px; justify-content:space-between;
  font-size:13px; padding:6px 0; border-top:1px solid var(--line-soft);
}
.team-list li:first-child{ border-top:none; }
.team-when{ color:var(--text-2); font-size:12px; white-space:nowrap; flex:none; }

/* ---- Suggestion box ---- */
#suggestPanel textarea{
  width:100%; padding:10px 11px; border:1px solid var(--line); border-radius:8px;
  background:var(--grey-light); outline:none; resize:vertical; min-height:78px;
  font:14px/1.5 var(--ui); color:var(--text);
}
#suggestPanel textarea:focus{ border-color:var(--signal); background:var(--surface); }
.sg-row{ display:flex; align-items:center; gap:9px; margin-top:9px; }
.sg-fine{ font-size:11.5px; line-height:1.5; color:var(--text-2); margin-top:12px; }

/* ---- Bar chart ----
   One series, so the bars are the brand blue and nothing else needs a hue.
   Every number and label wears a text token: colour marks the data, text
   carries the meaning, and the chart still reads with colour removed. */
.chart{ margin:14px 0 2px; }
.chart svg{ display:block; width:100%; height:auto; overflow:visible; }
.chart .bar path{ fill:var(--blue); transition:fill .12s; }
.chart .bar.is-zero path{ fill:var(--grey-mid); }   /* a real zero, not a gap */
.chart .bar:hover path{ fill:var(--black); }
.chart-base{ stroke:var(--line); stroke-width:1; }  /* recessive: one hairline */
.chart-val{
  font-family:var(--label); font-size:10px; font-weight:700; fill:var(--text);
  text-anchor:middle; font-variant-numeric:tabular-nums;
}
.chart-tick{
  font-family:var(--label); font-size:9px; font-weight:500; fill:var(--text-2);
  text-anchor:middle;
}

.feed-item{ display:block; padding:11px 0; border-top:1px solid var(--line-soft); }
.feed-item:first-of-type{ border-top:none; padding-top:0; }
.feed-t{ font-size:13px; font-weight:500; line-height:1.35; }
.feed-item:hover .feed-t{ color:var(--signal); }
.feed-m{
  font-family:var(--label); font-size:10px; letter-spacing:.05em; text-transform:uppercase;
  color:var(--text-3); margin-top:5px;
}
.panel-note{ font-size:12.5px; color:var(--text-3); }

.foot{
  padding:20px 40px 30px; font-family:var(--label); font-size:11px;
  color:var(--text-3); letter-spacing:.04em;
}
.foot a:hover{ color:var(--signal); }
.foot-sep{ margin:0 8px; }

/* =========================================================
   ADMIN
   ========================================================= */
.admin{ background:var(--canvas); }
.gate{ flex:1; display:flex; align-items:center; justify-content:center; padding:40px 20px; }
.gate-card{
  width:100%; max-width:352px; background:var(--surface);
  border:1px solid var(--line); border-radius:var(--r-lg);
  padding:30px; box-shadow:var(--shadow);
}
.gate-h{ font-family:var(--display); font-size:22px; font-weight:700; margin:8px 0 6px; letter-spacing:-.015em; }
.gate-p{ font-size:13.5px; color:var(--text-2); margin-bottom:20px; }
.gate-card input{
  width:100%; height:44px; padding:0 13px; margin-bottom:10px;
  border:1.5px solid var(--line); border-radius:var(--r);
  background:var(--grey-light); outline:none; letter-spacing:.3em;
}
.gate-card input:focus{ border-color:var(--signal); background:var(--surface); }
.gate-card .btn{ width:100%; justify-content:center; }
/* No red in the palette. Errors are marked by weight and a leading glyph
   instead of colour — see the note at the top of the admin section. */
.gate-err{ font-size:12.5px; color:var(--text); font-weight:600; margin-top:10px; }
.gate-err::before{ content:"\26A0"; margin-right:6px; color:var(--text-2); }
.gate-back{ display:block; margin-top:18px; font-size:12.5px; color:var(--text-3); }
.gate-back:hover{ color:var(--text); }

.admin-head{
  display:flex; align-items:flex-end; justify-content:space-between; flex-wrap:wrap; gap:16px;
  padding:34px 40px 22px; background:var(--surface); border-bottom:1px solid var(--line);
}
.admin-h{ font-family:var(--display); font-size:25px; font-weight:700; letter-spacing:-.015em; margin-top:6px; }
.admin-actions{ display:flex; align-items:center; gap:9px; flex-wrap:wrap; }
.save-state{ font-family:var(--label); font-size:11px; color:var(--text-2); margin-right:4px; }
.save-state.dirty{ color:var(--signal); font-weight:600; }

/* The "you are in browser-only mode" warning. Was amber; the palette has
   no amber, so it leans on the blue wash and a heavier rule instead. */
.banner{
  margin:0 40px; margin-top:18px; padding:12px 15px;
  background:var(--blue-wash); border:1px solid var(--blue-edge);
  border-left:3px solid var(--blue); border-radius:var(--r);
  font-size:13px; color:var(--text);
}

.btn{
  display:inline-flex; align-items:center; gap:7px; height:36px; padding:0 15px;
  border-radius:8px; font-size:13.5px; font-weight:500; border:1px solid transparent;
  transition:background .14s, border-color .14s, color .14s;
}
/* Hover darkens the blue with a filter rather than introducing a second
   blue token — the palette has one blue and this keeps it that way. */
.btn-solid{ background:var(--signal); color:var(--white); }
.btn-solid:hover{ filter:brightness(.90); }
.btn-quiet{ background:var(--surface); border-color:var(--line); color:var(--text); }
.btn-quiet:hover{ border-color:var(--grey-dark); background:var(--grey-light); }
.btn-ghost{ color:var(--text-2); }
.btn-ghost:hover{ color:var(--text); }
/* Destructive actions get no red — the palette has none. They are marked
   by an outline and a bolder label, and every one of them already asks
   for confirmation before it deletes anything. */
.btn-danger{ color:var(--text); background:transparent; border-color:var(--grey-dark); font-weight:600; }
.btn-danger:hover{ background:var(--grey-light); border-color:var(--black); }
.btn-sm{ height:29px; padding:0 10px; font-size:12.5px; }

.tabs{ display:flex; gap:2px; padding:0 40px; background:var(--surface); border-bottom:1px solid var(--line); overflow-x:auto; }
.tab{
  padding:13px 14px; font-size:13.5px; font-weight:500; color:var(--text-3);
  border-bottom:2px solid transparent; white-space:nowrap; margin-bottom:-1px;
}
.tab:hover{ color:var(--text); }
.tab.is-on{ color:var(--text); border-bottom-color:var(--signal); }

.admin-panes{ padding:26px 40px 60px; max-width:1120px; }
.pane-head{ display:flex; align-items:center; justify-content:space-between; gap:14px; margin-bottom:16px; flex-wrap:wrap; }
.pane-desc{ font-size:13.5px; color:var(--text-2); max-width:600px; }

.row-card{
  background:var(--surface); border:1px solid var(--line); border-radius:var(--r-lg);
  padding:16px; margin-bottom:11px;
}
.row-top{ display:flex; align-items:center; gap:10px; margin-bottom:13px; }
.row-title{ font-weight:600; font-size:14.5px; }
.row-tag{
  font-family:var(--label); font-size:10px; letter-spacing:.08em; text-transform:uppercase;
  color:var(--text-3); background:var(--grey-light); border:1px solid var(--line);
  padding:2px 6px; border-radius:5px;
}
.row-tools{ margin-left:auto; display:flex; gap:5px; }

.fields{ display:grid; gap:12px; grid-template-columns:repeat(auto-fit,minmax(190px,1fr)); }
.field{ display:flex; flex-direction:column; gap:5px; }
.field.wide{ grid-column:1/-1; }
.field label{
  font-family:var(--label); font-size:10px; letter-spacing:.1em; text-transform:uppercase; color:var(--text-3);
}
.field input, .field select, .field textarea{
  height:36px; padding:0 11px; border:1px solid var(--line); border-radius:8px;
  background:var(--grey-light); outline:none; font-size:13.5px; width:100%;
}
.field textarea{ height:auto; min-height:66px; padding:9px 11px; resize:vertical; line-height:1.45; font-family:var(--ui); }
.field input:focus, .field select:focus, .field textarea:focus{ border-color:var(--signal); background:var(--surface); }
.field-hint{ font-weight:400; text-transform:none; letter-spacing:0; color:var(--disabled); }

/* Card image picker. The preview is the real plate ratio (8:3) so what
   you see here is what the card gets. */
.imgbox{ display:flex; gap:14px; align-items:flex-start; flex-wrap:wrap; }
.imgprev{
  flex:none; width:160px; aspect-ratio:8/3; border-radius:var(--r);
  border:1px solid var(--line); background:var(--surface);
  display:flex; align-items:center; justify-content:center; overflow:hidden;
}
.imgprev img{ width:100%; height:100%; object-fit:cover; display:block; }
.imgprev.is-empty{ border-style:dashed; background:var(--grey-light); }
.imgprev.is-empty span{
  font-family:var(--display); font-weight:700; font-size:22px; color:var(--disabled);
}
.imgctl{ flex:1; min-width:240px; display:flex; flex-direction:column; gap:9px; }
.imgctl .pane-desc{ font-size:12.5px; }
.imgbtns{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
/* The upload control is a <label> wrapping a hidden file input, so it
   needs the button's cursor and centring explicitly. */
.imgbtns label.btn{ cursor:pointer; }

.toast{
  position:fixed; left:50%; bottom:26px; transform:translateX(-50%);
  background:var(--black); color:var(--white); padding:11px 18px; border-radius:9px;
  font-size:13.5px; z-index:90; box-shadow:var(--shadow-lift);
}

/* =========================================================
   RESPONSIVE
   ========================================================= */
/* These board rules are the no-JavaScript fallback only. layoutBoard() in
   app.js sets grid-template-columns inline as soon as it runs, because the
   number of panels is not knowable here — Onboarding and Team hide
   themselves when they have nothing to show — and a fixed column count
   strands a lone panel on its own row. */
@media (max-width:1320px){
  .board-grid{ grid-template-columns:repeat(2,minmax(0,1fr)); }
}
/* Below this, four 200px tiles no longer fit on one line and auto-fit wraps
   them 3 + 1 — an orphan tile on a row of its own, burning ~110px of height
   in the one place height is scarce. Two-up wraps 2 + 2 instead. */
@media (max-width:1180px){
  .statbar{ grid-template-columns:repeat(2,minmax(0,1fr)); }
}
@media (max-width:1040px){
  .board-grid{ grid-template-columns:repeat(2,minmax(0,1fr)); }
}
@media (max-width:860px){
  .board{ padding:18px 20px 0; }
  .board-grid{ grid-template-columns:minmax(0,1fr); }
  .dayline{ padding:12px 20px; gap:6px 14px; }
  /* Two up, forced. The 200px minimum that lines the tiles up with the board
     on a laptop resolves to one column on a phone, and four full-width tiles
     push the board off the screen — the exact problem this layout exists to
     fix. Two-up keeps all four numbers in the first screen. */
  .statbar{ grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px; margin-top:18px; }
  .stat{ padding:12px 13px; }
  .stat-v{ font-size:24px; }
}
@media (max-width:860px){
  :root{ --rail-w:0px; }
  .shell{ margin-left:0; }
  .menu-btn{ display:block; }
  .rail{
    transform:translateX(-100%); width:260px;
    transition:transform .22s cubic-bezier(.2,.7,.3,1);
  }
  .rail.is-open{ transform:none; }
  .rail-close{ display:block; }
  .rail-scrim{ display:block; position:fixed; inset:0; background:rgba(14,41,112,.44); z-index:39; opacity:0; pointer-events:none; transition:opacity .2s; }
  .rail-scrim.is-open{ opacity:1; pointer-events:auto; }
  .hero{ padding:20px 20px 26px; }
  .main{ padding:24px 20px 40px; gap:24px; }
  .foot, .admin-head, .tabs, .admin-panes{ padding-left:20px; padding-right:20px; }
  .banner{ margin-left:20px; margin-right:20px; }
  /* Two-up on a phone — a launcher should fit on one or two screens. */
  .cards{ grid-template-columns:repeat(2,minmax(0,1fr)); gap:12px; }
  .card-plate{ height:58px; }
  .card-mono{ font-size:22px; }
  .card-body{ padding:11px 12px 13px; }
  .card-desc{
    display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical;
    overflow:hidden; font-size:12.5px; flex:none;
  }
  .card-meta{ flex-wrap:wrap; gap:6px; }
  .links{ grid-template-columns:1fr; }
  .sect-blurb{ display:none; }
  .console{ height:50px; }
  /* 16px is not a style choice: iOS Safari zooms the whole page when a
     focused input is smaller, and the page never zooms back. Every text
     input on a phone gets it, not just this one. */
  /* height:100% so the whole 50px console is the tap target. The input's
     own box was 26px tall, which is a miss waiting to happen on the one
     control this page exists for. */
  .console input{ font-size:16px; height:100%; }
  .console-kbd{ display:none; }
  /* .field wins on specificity, so the 16px rule has to match it. */
  .field input, .field select, .field textarea,
  #suggestPanel textarea, .gate-card input{ font-size:16px; }
  .field input, .field select{ height:44px; }
  .btn-sm{ height:36px; padding:0 12px; }
  /* The panel footer link IS a .club-links element rather than a link inside
     one, so it needs the padding in its own right. */
  .club-links a, a.club-links{ display:inline-block; padding:8px 0; }
  /* Negative margin cancels the padding, so the hit box grows to 33px while
     the title stays exactly where it was laid out. */
  .club-t a{ display:inline-block; padding-block:8px; margin-block:-8px; }

  /* ---- Tap targets ----
     A 26px control is a control people miss. 44px is the size a thumb
     actually hits; where the visible mark should stay small the box grows
     around it instead via padding, so nothing looks bigger than it was. */
  .menu-btn{
    display:flex; align-items:center; justify-content:center;
    width:44px; height:44px; margin:0 0 10px -10px;
  }
  .rail-close{ width:44px; height:44px; display:flex; align-items:center; justify-content:center; margin:-8px -10px 0 0; }
  .rail-admin{ padding:12px 10px; margin:0 -10px; }
  .rail-link{ padding:12px 10px; }
  .foot a{ display:inline-block; padding:10px 0; }
  .feed-item{ padding:14px 0; }
  .team-list li{ padding:9px 0; }

  /* The trigger sat below the search box as a stray pill because it is
     absolutely positioned on a laptop and static here. Full width and it
     reads as the deliberate action it is. */
  .sg-open{
    position:static; display:flex; width:100%; justify-content:center;
    margin:14px 0 0; padding:12px 14px;
  }

  /* Five tiles two-up leaves the fifth orphaned on a row of its own. A last
     odd tile spans instead, which reads as intended rather than as a
     layout that ran out. */
  .stat:last-child:nth-child(odd){ grid-column:1 / -1; }

  /* Poster beside the text rather than above it.

     Stacked single-column there is no row for the poster to fill, so it
     either collapses to a letterbox crop of a portrait image or, at its true
     2:3, eats about 470px of scroll for one card. Alongside the text it
     keeps the poster's real shape, stays obviously a poster, and costs a
     fifth of the height. */
  /* flex-direction MUST be restated. The desktop rule sets column, and
     without overriding it the 104px basis sized the poster's HEIGHT instead
     of its width — a full-width square where a portrait poster belongs. */
  #clubPanel .club-body{ display:flex; flex-direction:row; gap:14px; align-items:flex-start; }
  .club-poster{
    flex:0 0 104px; width:104px; aspect-ratio:2 / 3;
    min-height:0; margin-bottom:0;
  }
  .club-poster img{ object-position:center center; }
  .club-meta{ flex:1; min-width:0; }
  .club-links{ margin-top:10px; padding-top:9px; }

  /* ---- Admin ----
     row-top is title + tag + buttons on one line, which overflows its card
     below about 340px. */
  .row-top{ flex-wrap:wrap; row-gap:8px; }
  .row-tools{ margin-left:auto; }
  .tabs{ overflow-x:auto; -webkit-overflow-scrolling:touch; scrollbar-width:none; }
  .tabs::-webkit-scrollbar{ display:none; }
  .tab{ flex:none; padding:12px 12px; }
  .admin-head{ flex-direction:column; align-items:flex-start; gap:12px; }
  .admin-actions{ width:100%; }
  .fields{ grid-template-columns:1fr; }
}

@media (prefers-reduced-motion:reduce){
  *{ animation-duration:.001ms !important; transition-duration:.001ms !important; }
}

.sg-text{ font-size:14px; line-height:1.55; white-space:pre-wrap; }
