/* Media Pollution CRM */

:root {
  --bg: #f6f6f4;
  --panel: #ffffff;
  --panel-2: #fbfbfa;
  --ink: #16161a;
  --ink-2: #5c5c66;
  --ink-3: #8b8b96;
  --line: #e2e2dd;
  --accent: #d8492b;
  --accent-ink: #ffffff;
  --ok: #2f7d54;
  --warn: #b26b00;
  --danger: #c0392b;
  --radius: 10px;
  --shadow: 0 1px 2px rgba(0,0,0,.05), 0 8px 24px rgba(0,0,0,.06);
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  --sans: system-ui, -apple-system, "Segoe UI", Inter, Roboto, sans-serif;
}

:root[data-theme="dark"] {
  --bg: #111114;
  --panel: #17171b;
  --panel-2: #1d1d22;
  --ink: #eceff1;
  --ink-2: #a8a8b3;
  --ink-3: #74747f;
  --line: #2a2a31;
  --accent: #ff6a3d;
  --accent-ink: #17171b;
  --ok: #4cc38a;
  --warn: #e0a33e;
  --danger: #f0645a;
  --shadow: 0 1px 2px rgba(0,0,0,.4), 0 10px 30px rgba(0,0,0,.35);
}

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0; background: var(--bg); color: var(--ink);
  font-family: var(--sans); font-size: 14px; line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
.hidden { display: none !important; }
a { color: inherit; text-decoration: none; }
button, input, select, textarea { font: inherit; color: inherit; }

/* ------------------------------------------------------------------ login
   A CRT set built entirely in CSS. The screen is one surface: it holds the
   form, then plays the static clip in the same place on sign-in.
   Sizing hangs off --tv so the whole set scales as one object.            */

.login {
  min-height: 100vh; display: grid; place-items: center; padding: 20px;
  background:
    radial-gradient(120% 90% at 50% 0%, #2a2a30 0%, #131316 55%, #0b0b0d 100%);
}

.tv { --tv: min(78vw, 44vh, 560px); }

.tv-set {
  display: flex; align-items: stretch; gap: calc(var(--tv) * .035);
  padding: calc(var(--tv) * .055);
  border-radius: calc(var(--tv) * .045);
  background:
    linear-gradient(163deg, #e8c88a 0%, #c99f52 22%, #8f6f34 55%, #b98f45 78%, #7d5f2c 100%);
  box-shadow:
    inset 0 2px 3px rgba(255,235,190,.55),
    inset 0 -3px 6px rgba(0,0,0,.45),
    0 30px 60px -18px rgba(0,0,0,.8),
    0 4px 14px rgba(0,0,0,.5);
}

/* recessed bay the tube sits in */
.tv-screen-bay {
  padding: calc(var(--tv) * .03);
  border-radius: calc(var(--tv) * .09) / calc(var(--tv) * .075);
  background: linear-gradient(160deg, #6d5326, #3d2e13);
  box-shadow: inset 0 3px 8px rgba(0,0,0,.75), inset 0 -1px 2px rgba(255,220,160,.25);
}

.tv-screen {
  position: relative; overflow: hidden;
  width: var(--tv); aspect-ratio: 4 / 3;
  /* the classic bulged rectangle */
  border-radius: 11% / 14%;
  background: radial-gradient(120% 120% at 50% 42%, #14201a 0%, #0a100d 60%, #050807 100%);
  box-shadow:
    inset 0 0 calc(var(--tv) * .09) calc(var(--tv) * .02) rgba(0,0,0,.95),
    inset 0 0 calc(var(--tv) * .02) rgba(120,255,190,.10);
  display: grid; place-items: center;
}

.tv-static {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; opacity: 0; pointer-events: none;
  transition: opacity .12s linear;
}
.tv-screen.on .tv-static { opacity: 1; }
.tv-screen.on .tv-form { opacity: 0; transition: opacity .1s linear; }

.tv-scanlines {
  position: absolute; inset: 0; pointer-events: none; opacity: .5;
  background: repeating-linear-gradient(
    to bottom, rgba(0,0,0,.32) 0 1px, transparent 1px 3px);
  mix-blend-mode: multiply;
}
.tv-glare {
  position: absolute; inset: 0; pointer-events: none;
  background:
    radial-gradient(75% 55% at 28% 12%, rgba(255,255,255,.10), transparent 60%),
    radial-gradient(120% 100% at 50% 50%, transparent 52%, rgba(0,0,0,.55) 100%);
}

/* the form, living inside the tube */
.tv-form {
  position: relative; z-index: 2;
  width: 78%; display: grid; gap: calc(var(--tv) * .022);
  color: #9fe8c4; font-family: var(--mono);
  text-shadow: 0 0 6px rgba(110,255,190,.45);
  animation: tv-flicker 5.5s infinite steps(1);
}
@keyframes tv-flicker {
  0%, 97.5%, 100% { opacity: 1 }
  98%   { opacity: .82 }
  98.6% { opacity: 1 }
  99.2% { opacity: .9 }
}
.tv-brand {
  font-size: calc(var(--tv) * .052); font-weight: 700; letter-spacing: .12em;
  text-align: center; line-height: 1.1;
}
.tv-sub {
  font-size: calc(var(--tv) * .026); letter-spacing: .32em; text-align: center;
  opacity: .6; text-transform: uppercase; margin-bottom: calc(var(--tv) * .015);
}
.tv-form label {
  display: grid; gap: calc(var(--tv) * .008);
  font-size: calc(var(--tv) * .024); letter-spacing: .16em; text-transform: uppercase;
  opacity: .78;
}
.tv-form input {
  width: 100%; padding: calc(var(--tv) * .022) calc(var(--tv) * .028);
  font-family: var(--mono); font-size: calc(var(--tv) * .032);
  color: #c8ffe4; background: rgba(0,0,0,.45);
  border: 1px solid rgba(140,255,200,.28); border-radius: 4px;
  letter-spacing: .06em; caret-color: #7dffc0;
}
.tv-form input:focus {
  outline: none; border-color: rgba(160,255,210,.75);
  box-shadow: 0 0 0 1px rgba(160,255,210,.35), 0 0 14px rgba(110,255,190,.25) inset;
}
.tv-form button {
  margin-top: calc(var(--tv) * .012);
  padding: calc(var(--tv) * .026); cursor: pointer;
  font-family: var(--mono); font-size: calc(var(--tv) * .028);
  letter-spacing: .22em; text-transform: uppercase; font-weight: 700;
  color: #06120c; background: #7dffc0; border: 0; border-radius: 4px;
  box-shadow: 0 0 18px rgba(110,255,190,.35);
}
.tv-form button:hover { filter: brightness(1.12); }
.tv-form button:disabled { opacity: .5; cursor: default; }
.login-error {
  margin: 0; min-height: 1.2em; text-align: center;
  font-size: calc(var(--tv) * .024); letter-spacing: .06em;
  color: #ff8f7a; text-shadow: 0 0 6px rgba(255,120,90,.5);
}

/* right-hand control panel: speaker grille, knobs, power lamp */
.tv-panel {
  width: calc(var(--tv) * .17); border-radius: calc(var(--tv) * .02);
  display: flex; flex-direction: column; align-items: center;
  gap: calc(var(--tv) * .026); padding: calc(var(--tv) * .035) 0;
  background:
    repeating-linear-gradient(to bottom, rgba(0,0,0,.55) 0 2px, transparent 2px 6px),
    repeating-linear-gradient(to right, rgba(0,0,0,.55) 0 2px, transparent 2px 6px),
    linear-gradient(160deg, #d9b978 0%, #b58f47 100%);
  background-color: #c49c50;
  box-shadow: inset 0 0 calc(var(--tv) * .03) rgba(0,0,0,.5);
}
.tv-knob {
  width: calc(var(--tv) * .07); height: calc(var(--tv) * .07); border-radius: 50%;
  background: radial-gradient(circle at 34% 30%, #5d5d63, #232327 62%, #121214);
  box-shadow: 0 2px 4px rgba(0,0,0,.6), inset 0 1px 1px rgba(255,255,255,.22);
  border: 0; padding: 0; flex: none;
}
.tv-knob-lg {
  width: calc(var(--tv) * .105); height: calc(var(--tv) * .105);
  cursor: pointer; position: relative;
  margin-bottom: calc(var(--tv) * .01);
}
.tv-knob-lg:hover { filter: brightness(1.25); }
.tv-pointer {
  position: absolute; top: 12%; left: 50%; width: 2px; height: 32%;
  background: #e8e8ee; border-radius: 2px; transform-origin: 50% 100%;
  transform: translateX(-50%) rotate(0deg); transition: transform .18s ease;
  box-shadow: 0 0 4px rgba(255,255,255,.5);
}
.tv-knob-lg[aria-pressed="false"] .tv-pointer { transform: translateX(-50%) rotate(-125deg); }
.tv-led {
  width: calc(var(--tv) * .028); height: calc(var(--tv) * .028); border-radius: 50%;
  margin-top: auto; background: #ff6a3d;
  box-shadow: 0 0 calc(var(--tv) * .035) rgba(255,106,61,.9), inset 0 0 3px rgba(255,255,255,.6);
  animation: led-breathe 3.4s ease-in-out infinite;
}
@keyframes led-breathe { 0%,100% { opacity: .75 } 50% { opacity: 1 } }

@media (prefers-reduced-motion: reduce) {
  .tv-form { animation: none }
  .tv-led  { animation: none }
}
@media (max-width: 560px) { .tv { --tv: min(84vw, 40vh, 420px) } }

/* ----------------------------------------------------------------- topbar */
.topbar {
  position: sticky; top: 0; z-index: 20; display: flex; align-items: center; gap: 20px;
  padding: 0 18px; height: 56px; background: var(--panel);
  border-bottom: 1px solid var(--line);
}
/* The logo, filled with the SMPTE test card — the picture a dead set shows.
   The artwork is a solid silhouette with POLLUTION knocked out, so masking by
   its alpha lets the bars through the letterforms while POLLUTION stays a
   hole: it reads against whichever theme is behind it. */
.brand {
  flex: none; display: block;
  width: 112px; aspect-ratio: 340 / 130;
  background-image: url('/media/testcard.png');
  background-size: 100% 100%;
  background-repeat: no-repeat;
  -webkit-mask-image: url('/media/logo.png');
          mask-image: url('/media/logo.png');
  -webkit-mask-size: contain;    mask-size: contain;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center;  mask-position: center;
  /* The bars open on a 75% grey, the weak spot against a light background;
     a tight outline carries it without dulling the colours. */
  filter: drop-shadow(0 0 .7px rgba(0,0,0,.7)) drop-shadow(0 1px 1px rgba(0,0,0,.25));
}
:root[data-theme="dark"] .brand {
  filter: drop-shadow(0 0 1px rgba(0,0,0,.9)) drop-shadow(0 0 7px rgba(255,255,255,.12));
}
/* Without mask support the logo would be an invisible box: show it plainly. */
@supports not ((mask-image: url(#a)) or (-webkit-mask-image: url(#a))) {
  .brand {
    background-image: url('/media/logo.png'); background-size: contain;
    filter: none;
  }
}
.tabs { display: flex; gap: 2px; flex: 1; overflow-x: auto; }
.depts { display: flex; gap: 2px; margin-left: 8px; flex: 1; overflow-x: auto; }

/* One SMPTE bar per item, in the order they appear on the test card, and now
   named by the colour rather than by the tab: the app has two Dashboards and
   two Pipelines since the rental board arrived, so a rule keyed on the tab's
   own name could no longer tell them apart. The 75% bars sit at 192, so black
   carries on grey/yellow/cyan/green and white on magenta/red/blue — every
   pairing clears 5:1. */
.depts a[data-tone="grey"], .tabs a[data-tone="grey"],
.sections a[data-tone="grey"]       { --tab: #c0c0c0; --tab-ink: #121212; }
.depts a[data-tone="yellow"], .tabs a[data-tone="yellow"],
.sections a[data-tone="yellow"]     { --tab: #c0c000; --tab-ink: #121212; }
.depts a[data-tone="cyan"], .tabs a[data-tone="cyan"],
.sections a[data-tone="cyan"]       { --tab: #00c0c0; --tab-ink: #121212; }
.depts a[data-tone="green"], .tabs a[data-tone="green"],
.sections a[data-tone="green"]      { --tab: #00c000; --tab-ink: #121212; }
.depts a[data-tone="magenta"], .tabs a[data-tone="magenta"],
.sections a[data-tone="magenta"]    { --tab: #c000c0; --tab-ink: #ffffff; }
.depts a[data-tone="red"], .tabs a[data-tone="red"],
.sections a[data-tone="red"]        { --tab: #c00000; --tab-ink: #ffffff; }
.depts a[data-tone="blue"], .tabs a[data-tone="blue"],
.sections a[data-tone="blue"]       { --tab: #3a3ad0; --tab-ink: #ffffff; }

.depts a, .tabs a {
  position: relative;
  padding: 7px 12px 8px; border-radius: 7px; color: var(--ink-2);
  font-weight: 500; white-space: nowrap;
}
/* A dim bar under every tab keeps the colour mapping learnable even when the
   tab is not the active one. */
.depts a::after, .tabs a::after {
  content: ''; position: absolute; left: 12px; right: 12px; bottom: 3px; height: 2px;
  border-radius: 2px; background: var(--tab); opacity: .38;
  transition: opacity .15s ease;
}
.depts a:hover, .tabs a:hover {
  color: var(--ink);
  background: color-mix(in srgb, var(--tab) 16%, transparent);
}
.depts a:hover::after, .tabs a:hover::after { opacity: .75; }
.depts a.on, .tabs a.on {
  background: var(--tab); color: var(--tab-ink); font-weight: 650;
}
.depts a.on::after, .tabs a.on::after { opacity: 0; }
.topbar-right { display: flex; align-items: center; gap: 8px; }
.who { color: var(--ink-3); font-size: 13px; }
.icon-btn {
  width: 30px; height: 30px; border: 1px solid var(--line); border-radius: 7px;
  background: var(--panel-2); cursor: pointer; line-height: 1;
}
.icon-btn:hover { border-color: var(--accent); color: var(--accent); }

/* On a phone the brand text crowded the tabs into an unusable sliver. */
@media (max-width: 700px) {
  .topbar { gap: 10px; padding: 0 10px; }
  /* The wordmark is decorative; the tabs are not. Below this width it gives
     up its room rather than squeezing them into a sliver. */
  .brand { display: none; }
  .depts { margin-left: 0; }
  .depts a, .tabs a { padding: 7px 10px; }
  .who { display: none; }
  .subbar { padding: 0 10px; gap: 10px; }
}

/* The strip has scrolled at phone width since it was written — with four tabs
   Playbook was already off the end, and nothing on screen said so. A fifth
   makes that worse, so below this width the labels give up a point and some
   padding: four of the five come into view and the fifth is visibly cut, which
   is at least an invitation to push it sideways. */
@media (max-width: 520px) {
  .topbar { gap: 8px; }
  .topbar-right { gap: 6px; }
  .depts a, .tabs a { padding: 6px 7px 7px; font-size: 12px; }
  .depts a::after, .tabs a::after { left: 7px; right: 7px; }
  /* Four of five still leaves one past the edge, and an edge that scrolls has
     to look like one — a hard cut reads as a bug. At this width the strip is
     always wider than its room, so the fade is never claiming a tab that is not
     there. */
  .depts, .tabs {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent);
            mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent);
  }
}

main { padding: 20px; max-width: 1500px; margin: 0 auto; }
h2 { font-size: 15px; margin: 26px 0 10px; letter-spacing: -.01em; }
h2:first-child { margin-top: 0; }
/* A heading sharing a row with the buttons that act on it. */
h2.flush { margin: 0; }
.muted { color: var(--ink-3); }
.sub { color: var(--ink-3); font-size: 12px; font-weight: 400; }

/* -------------------------------------------------------------------- kpi
   The numbers you check rather than work from: out of the dashboard, behind a
   button in the header. The button is a test-card ring around a legible face. */
.kpi-wrap { position: relative; display: flex; }
.kpi-btn {
  position: relative;
  width: 32px; height: 32px; flex: none; padding: 0; border: 0; cursor: pointer;
  border-radius: 50%; display: grid; place-items: center;
  background: conic-gradient(from -90deg,
    #c0c0c0 0 14.285%, #c0c000 0 28.57%, #00c0c0 0 42.855%, #00c000 0 57.14%,
    #c000c0 0 71.425%, #c00000 0 85.71%, #0000c0 0 100%);
  box-shadow: 0 1px 3px rgba(0,0,0,.25);
}
.kpi-btn span {
  width: 23px; height: 23px; border-radius: 50%; display: grid; place-items: center;
  background: var(--panel); color: var(--ink-2);
  font-size: 8px; font-weight: 700; letter-spacing: .02em;
}
/* The ring is 32px because that is what the header can spare. The target it
   answers to does not have to be: a thumb gets 46px. */
.kpi-btn::after { content: ''; position: absolute; inset: -7px; border-radius: 50%; }
.kpi-btn:hover { filter: brightness(1.1); }
.kpi-btn:hover span { color: var(--ink); }
.kpi-btn[aria-expanded="true"] span { background: var(--panel-2); color: var(--ink); }

.kpi-pop {
  position: absolute; right: 0; top: calc(100% + 9px); z-index: 60;
  width: min(430px, calc(100vw - 20px)); padding: 12px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow);
  display: grid; gap: 12px;
}
.kpi-pop .sheet-bars { margin: -2px 0 0; }

/* The button sits some 90px in from the right edge, so hanging the popup's
   right edge off it threw the whole left column past the left edge of a
   phone — three of the six numbers were cut in half. Narrow enough and it
   stops being a popover attached to a button and becomes a panel pinned
   under the header, which is where the room actually is. Fixed rather than
   absolute: the header is sticky, so the two stay together when scrolled. */
@media (max-width: 620px) {
  .kpi-pop {
    position: fixed; top: 63px; left: 10px; right: 10px; width: auto;
    max-height: calc(100vh - 73px);
    max-height: calc(100dvh - 73px);
    overflow-y: auto;
  }
}

.kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; }
.kpi-pop .kpis { grid-template-columns: 1fr 1fr; }
.kpi {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 12px 14px;
}
.kpi[data-tone] {
  border-left: 3px solid var(--tone);
  background: color-mix(in srgb, var(--tone) 7%, var(--panel));
}
:root[data-theme="dark"] .kpi[data-tone] { background: color-mix(in srgb, var(--tone) 14%, var(--panel)); }
.kpi .n { font-size: 24px; font-weight: 650; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.kpi .k { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-3); }

/* ------------------------------------------------------------------ table */
.card {
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden;
}
.scroll-x { overflow-x: auto; }
table { border-collapse: collapse; width: 100%; font-size: 13px; }
th, td { padding: 9px 12px; text-align: left; border-bottom: 1px solid var(--line); }
th {
  font-size: 11px; text-transform: uppercase; letter-spacing: .05em;
  color: var(--ink-3); font-weight: 600; background: var(--panel-2);
  position: sticky; top: 0;
}
tbody tr:last-child td { border-bottom: 0; }
tbody tr { cursor: pointer; }
/* A band on every second row: the pipeline runs eight columns wide and the eye
   loses its line halfway across one. The band is set on the row and never on a
   cell, because a row's background paints under its cells' — so the column
   tints and the heat ramp, alpha over transparent, keep compositing over it
   the way they already do over hover, and the ramp's steps stay the same
   distance apart on a banded row as on a bare one. The grey is --ink-3, which
   puts the band on the panel's own second surface in the dark theme and a
   little past it in the light one, where that surface is four levels off
   white and would not register. */
tbody tr:nth-child(even) { background: color-mix(in srgb, var(--ink-3) 7%, transparent); }
/* Hover was that second surface, which the band now sits at or beyond. It is
   the band's own grey at better than twice the strength instead, so the row
   under the cursor is one clear step above whichever kind of row it is. */
tbody tr:hover { background: color-mix(in srgb, var(--ink-3) 16%, transparent); }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
.mono { font-family: var(--mono); font-size: 12px; }

/* A header that sorts. The arrow is a character rather than an icon so it
   holds at the 11px these headings are set in, and its slot is reserved
   whether or not it is showing — otherwise every column shifts sideways as
   the sort moves between them. */
th.sortable { cursor: pointer; user-select: none; }
th.sortable:hover { color: var(--ink); }
th.sortable:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.sort-mark { display: inline-block; width: .95em; margin-left: 2px; font-size: 9px; }
th.num .sort-mark { width: auto; min-width: .6em; }

/* A cell that is its own drill-down: the row opens that lead's board, this
   opens it narrowed to the timeframe the column is headed with. */
td.drill:hover { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent); }

/* ------------------------------------------------------- deal table colour
   A bar marks the columns worth acting on, not every column there is. The
   header carries the colour at full strength; the cells keep a trace of it so
   the eye can follow a marked column down a wide table. Cell tints are alpha
   over transparent, never a solid, so row hover still reads through them.    */
/* The bar tokens, usable by anything that needs one colour per item. Scoped
   by value, so .sheet-sec's own tone names never collide with these. */
[data-tone="grey"]    { --tone: #9a9a9a; }
[data-tone="yellow"]  { --tone: #c0c000; }
[data-tone="cyan"]    { --tone: #00c0c0; }
[data-tone="green"]   { --tone: #00c000; }
[data-tone="magenta"] { --tone: #c000c0; }
[data-tone="red"]     { --tone: #c00000; }
[data-tone="blue"]    { --tone: #3a3ad0; }
:root[data-theme="dark"] [data-tone="blue"] { --tone: #6c6cff; }

.deals th[data-tone] {
  border-top: 2px solid var(--tone);
  background: color-mix(in srgb, var(--tone) 15%, var(--panel-2));
  color: var(--ink-2);
}
.deals td[data-tone] { background: color-mix(in srgb, var(--tone) 6%, transparent); }
/* The bars are dimmer against a dark panel and the bands stop registering. */
:root[data-theme="dark"] .deals td[data-tone] { background: color-mix(in srgb, var(--tone) 11%, transparent); }
/* An unmarked column still needs its 2px, or its heading sits higher than the
   marked ones; in the line colour it reads as the top rule of the table, which
   is all it is. A date column takes the ramp's own hue instead, so the heading
   predicts the colour appearing in the cells below it. */
.deals th:not([data-tone]) { border-top: 2px solid var(--line); }
.deals th.heat { border-top: 2px solid color-mix(in srgb, var(--warn) 55%, transparent); }

/* Nuance inside the date columns: one hue, five steps of nearness, plus a
   distinct red for dates already behind us. */
.deals td[data-heat] { --heat: var(--warn); }
.deals td[data-heat="past"] { --heat: var(--danger); background: color-mix(in srgb, var(--danger) 15%, transparent); }
.deals td[data-heat="h5"] { background: color-mix(in srgb, var(--warn) 30%, transparent); }
.deals td[data-heat="h4"] { background: color-mix(in srgb, var(--warn) 22%, transparent); }
.deals td[data-heat="h3"] { background: color-mix(in srgb, var(--warn) 15%, transparent); }
.deals td[data-heat="h2"] { background: color-mix(in srgb, var(--warn)  9%, transparent); }
.deals td[data-heat="h1"] { background: color-mix(in srgb, var(--warn)  4%, transparent); }
.deals td[data-heat="h5"], .deals td[data-heat="past"] { font-weight: 600; }

/* Only the name of the thing is allowed to wrap; everything else stays on one
   line so the columns keep their edges and the table stays scannable. Named
   twice because the two tables name it differently: the deals table says which
   column a cell is, the prospect tables still key it on the yellow bar their
   own Event and Person columns carry. */
.deals th, .deals td { white-space: nowrap; padding: 8px 10px; }
.deals td[data-col="name"], .deals td[data-tone="yellow"] { white-space: normal; min-width: 190px; }
.cell-note { color: var(--ink-3); font-size: 11px; }
.deals td[data-heat="past"] .cell-note { color: var(--danger); font-weight: 600; }

/* ------------------------------------------------------------------ chips */
.chip {
  display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 11px;
  font-weight: 600; background: var(--panel-2); border: 1px solid var(--line);
  color: var(--ink-2); white-space: nowrap;
}
.chip.overdue { background: color-mix(in srgb, var(--danger) 16%, transparent);
  border-color: color-mix(in srgb, var(--danger) 40%, transparent); color: var(--danger); }
.chip.won { background: color-mix(in srgb, var(--ok) 16%, transparent);
  border-color: color-mix(in srgb, var(--ok) 40%, transparent); color: var(--ok); }
.chip.lost { opacity: .65; }
.chip.soon { background: color-mix(in srgb, var(--warn) 16%, transparent);
  border-color: color-mix(in srgb, var(--warn) 40%, transparent); color: var(--warn); }

/* ---------------------------------------------------------------- filters */
.filters { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; align-items: center; }
.filters select, .filters input {
  padding: 7px 10px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--panel); font-size: 13px;
}
.filters input[type="search"] { min-width: 220px; flex: 1; max-width: 360px; }
.filters .spacer { flex: 1; }
.btn {
  padding: 7px 13px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--panel); cursor: pointer; font-size: 13px; font-weight: 500;
}
.btn:hover { border-color: var(--accent); color: var(--accent); }
.btn.primary { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
.btn.primary:hover { filter: brightness(1.08); color: var(--accent-ink); }
/* The tick in "overdue only" is the one input in a filter bar that must not be
   stretched into a slab by the rule above. */
.filters input[type="checkbox"] {
  width: 14px; height: 14px; padding: 0; margin: 0 6px 0 0;
  vertical-align: -2px; accent-color: var(--accent);
}

/* Five controls, a tick and two buttons do not fit on one line at 390px, and
   left to wrap on their own they came out a ragged stack of odd widths. The
   search takes a row of its own, the selects pair up, and the buttons share
   the last line — the bar stays a block you can read down. */
@media (max-width: 640px) {
  .pipe-filters .spacer, .client-filters .spacer { display: none; }
  .pipe-filters input[type="search"], .client-filters input[type="search"] {
    flex: 1 1 100%; min-width: 0; max-width: none;
  }
  .pipe-filters select, .pipe-filters .btn { flex: 1 1 calc(50% - 4px); min-width: 0; }
  .pipe-filters .chip, .client-filters .chip { flex: 1 1 100%; text-align: center; }
  .client-filters .btn { flex: 1 1 100%; }
}

/* ----------------------------------------------------------------- kanban */
.kanban { display: flex; gap: 12px; overflow-x: auto; padding-bottom: 12px; align-items: flex-start; }
.col {
  flex: 0 0 250px; background: var(--panel-2); border: 1px solid var(--line);
  border-radius: var(--radius); display: flex; flex-direction: column; max-height: calc(100vh - 210px);
}
.col.dragover { border-color: var(--accent); box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 35%, transparent); }
.col-head {
  padding: 10px 12px; border-bottom: 1px solid var(--line);
  display: flex; justify-content: space-between; align-items: center; gap: 8px;
  /* the stage's bar, same colour it has everywhere else */
  border-top: 3px solid var(--tone, var(--line));
  border-radius: calc(var(--radius) - 1px) calc(var(--radius) - 1px) 0 0;
  background: color-mix(in srgb, var(--tone, transparent) 12%, transparent);
}
.col-head b { font-size: 12px; letter-spacing: -.01em; }
.col-head .n { font-size: 11px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.col-body { overflow-y: auto; padding: 8px; display: grid; gap: 8px; }
.deal {
  background: var(--panel); border: 1px solid var(--line); border-radius: 8px;
  padding: 10px; cursor: grab; display: grid; gap: 6px;
}
.deal:hover { border-color: var(--accent); }
.deal.dragging { opacity: .45; }
/* Same ramp as the date columns, carried on the card's left edge. */
.deal[data-heat] { border-left-width: 3px; }
.deal[data-heat="past"] { border-left-color: var(--danger); }
.deal[data-heat="h5"] { border-left-color: color-mix(in srgb, var(--warn) 100%, transparent); }
.deal[data-heat="h4"] { border-left-color: color-mix(in srgb, var(--warn) 75%, transparent); }
.deal[data-heat="h3"] { border-left-color: color-mix(in srgb, var(--warn) 55%, transparent); }
.deal[data-heat="h2"] { border-left-color: color-mix(in srgb, var(--warn) 35%, transparent); }
.deal[data-heat="h1"] { border-left-color: color-mix(in srgb, var(--warn) 20%, transparent); }
.deal .t { font-weight: 600; font-size: 13px; line-height: 1.3; }
.deal .m { display: flex; gap: 6px; flex-wrap: wrap; align-items: center;
  font-size: 11px; color: var(--ink-3); }
.deal .owners { font-weight: 600; color: var(--ink-2); }

/* ------------------------------------------------------------------ charts
   Horizontal bars, built from the same bar tokens as everything else. The
   segments are solid --tone, not a tint of it: a 12% wash disappears against a
   dark panel, and these strips are too thin to survive that. The rail behind
   them is the tint, because it only has to hint at the row's full extent.    */
.chart-key {
  display: flex; flex-wrap: wrap; gap: 14px; padding: 9px 12px;
  border-bottom: 1px solid var(--line); background: var(--panel-2);
  font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-3);
}
.chart-key span { display: inline-flex; align-items: center; gap: 6px; }
.chart-key i { width: 10px; height: 10px; border-radius: 2px; background: var(--tone); }

.chart-row {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 12px; border-bottom: 1px solid var(--line);
}
.chart-row:last-child { border-bottom: 0; }
.chart-row[data-owner] { cursor: pointer; }
.chart-row[data-owner]:hover { background: var(--panel-2); }
/* A fixed label column, so every bar in a block starts at the same x and the
   lengths can be read against each other. Long city names lose their tail to
   the ellipsis; the title attribute gives it back. */
.chart-row .lab {
  flex: 0 0 96px; font-size: 13px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chart-row .track {
  flex: 1 1 auto; min-width: 0; display: flex; height: 14px;
  background: color-mix(in srgb, var(--ink-3) 14%, transparent);
  border-radius: 3px; overflow: hidden;
}
.chart-row .track i { background: var(--tone); }
/* Fixed too, and for the same reason: "8 · 28 · 3" is a shorter string than
   "53 · 39 · 28", so an auto-width count column handed the shorter row a wider
   track and its bar came out 3% long. Two bars that cannot be compared are
   worse than no chart. */
.chart-row .val {
  flex: 0 0 var(--val-w, 3.2em); text-align: right;
  font-size: 12px; color: var(--ink-2);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
@media (max-width: 480px) {
  .chart-row { gap: 8px; padding: 7px 10px; }
  .chart-row .lab { flex-basis: 88px; }
}

/* ------------------------------------------------------------------ grids */
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
@media (max-width: 900px) { .grid-2 { grid-template-columns: 1fr; } }

/* ----------------------------------------------------------------- drawer */
.drawer {
  position: fixed; top: 0; right: 0; height: 100%; width: min(560px, 100%);
  background: var(--panel); border-left: 1px solid var(--line); z-index: 40;
  box-shadow: var(--shadow); overflow-y: auto;
}
/* A project sheet is a column of fields and 560px is generous for it. The team
   is a six-column table with an email address in one of them, and cut to that
   width the pen — the way in to every row — sat off the right edge where the
   only way to reach it was a horizontal scrollbar nobody looks for. The panel
   that needs the room asks for it instead. */
.drawer.wide { width: min(820px, 100%); }
.drawer-inner { padding: 20px; display: grid; gap: 16px; }
.scrim { position: fixed; inset: 0; background: rgba(0,0,0,.35); z-index: 35; }
.drawer h3 { margin: 0; font-size: 17px; letter-spacing: -.01em; }
.drawer-head { display: flex; justify-content: space-between; gap: 12px; align-items: flex-start; }
.field-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
@media (max-width: 560px) { .field-grid { grid-template-columns: 1fr; } }
.field { display: grid; gap: 4px; }
.field span { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-3); }
.field select, .field input, .field textarea {
  padding: 8px 10px; border: 1px solid var(--line); border-radius: 8px;
  background: var(--panel-2); font-size: 13px; width: 100%;
}
.field textarea { min-height: 74px; resize: vertical; }
/* A yes/no field. The rule above stretches every input to the full cell, which
   would turn a checkbox into a slab, so the box keeps its own size and sits on
   a line with the word that says what ticking it means. */
.field-check > label {
  display: flex; align-items: center; gap: 8px; min-height: 35px;
  font-size: 13px; color: var(--ink-2); cursor: pointer;
}
.field-check input[type="checkbox"] {
  width: 15px; height: 15px; flex: none; padding: 0; margin: 0;
  accent-color: var(--accent);
}
/* Sits under a control and explains it. Sentence case on purpose: .field span
   is the shouted uppercase label, and a second one would read as another. */
.field-hint { font-size: 11px; color: var(--ink-3); line-height: 1.4; }
.timeline { display: grid; gap: 10px; }
.event {
  border-left: 2px solid var(--line); padding: 0 0 0 12px; font-size: 13px;
}
.event .when { font-size: 11px; color: var(--ink-3); }
.event .body { white-space: pre-wrap; }
.saved { color: var(--ok); font-size: 12px; }
.saved.err { color: var(--danger); }
.link { color: var(--accent); }
.link:hover { text-decoration: underline; }

/* ------------------------------------------------------------------ sheet
   The read view of a project. Colour is rationed: the test card across the
   top, then one bar per section as a tick beside its heading — enough to tell
   the blocks apart at a glance, not enough to compete with the content.     */
.sheet { display: grid; gap: 16px; }
/* A grid item refuses to shrink below its content's minimum width, so the
   projects table — which is built to scroll inside .scroll-x — was stretching
   the whole sheet past the drawer instead: on a client with a wide table the
   close button, and now the pen on each contact, sat off the right edge where
   no one could reach them. min-width: 0 hands the track back its own width and
   lets the table scroll, which is what it was always meant to do. */
.drawer-inner > *, .sheet > * { min-width: 0; }
.sheet-bars {
  height: 4px; border-radius: 2px;
  background: linear-gradient(to right,
    #c0c0c0 0 14.285%, #c0c000 0 28.57%, #00c0c0 0 42.855%, #00c000 0 57.14%,
    #c000c0 0 71.425%, #c00000 0 85.71%, #0000c0 0 100%);
}
.sheet-head { display: flex; justify-content: space-between; gap: 12px; align-items: flex-start; }
.sheet-head h3 { margin: 0 0 2px; font-size: 20px; line-height: 1.25; letter-spacing: -.015em; }
.sheet-actions { display: flex; gap: 6px; flex: none; }
/* What the Save in the head has to say, said under it rather than only at the
   foot of the form: a project carrying the whole money block puts several
   screens between the two, and a refusal down there is a refusal nobody who
   pressed the top button ever sees. It sits on its own line so a sentence
   cannot squeeze the title, and with nothing to say it is not in the layout at
   all — these panels are grids, and a grid pays its gap for a row whether or
   not there is anything in it. */
.head-msg { text-align: right; }
.head-msg:empty { display: none; }
.icon-btn svg {
  width: 15px; height: 15px; display: block; margin: 0 auto;
  fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}
.sheet-status { display: flex; flex-wrap: wrap; gap: 6px; }
.chip.stage { background: var(--ink); color: var(--bg); border-color: var(--ink); }

.sheet-sec { border-top: 1px solid var(--line); padding-top: 14px; }
.sheet-sec h4 {
  margin: 0 0 10px; display: flex; align-items: center; gap: 8px;
  font-size: 11px; font-weight: 600; text-transform: uppercase;
  letter-spacing: .09em; color: var(--ink-3);
}
.sheet-sec h4::before { content: ''; width: 3px; height: 12px; border-radius: 2px; background: var(--tone); }
.sheet-sec[data-tone="schedule"] { --tone: #c0c000; }
.sheet-sec[data-tone="client"]   { --tone: #00c0c0; }
.sheet-sec[data-tone="money"]    { --tone: #00c000; }
.sheet-sec[data-tone="project"]  { --tone: #c000c0; }
.sheet-sec[data-tone="notes"]    { --tone: #3a3ad0; }
.sheet-sec[data-tone="activity"] { --tone: #c00000; }
/* The blue bar is the one that disappears against a dark panel. */
:root[data-theme="dark"] .sheet-sec[data-tone="notes"] { --tone: #6c6cff; }

.sheet-sec dl {
  margin: 0; display: grid; grid-template-columns: 124px 1fr;
  gap: 7px 16px; align-items: baseline;
}
.sheet-sec dt { color: var(--ink-3); font-size: 12px; }
.sheet-sec dd { margin: 0; font-size: 14px; }
.sheet-sec dd b { font-weight: 650; }
.sheet-rel { color: var(--ink-3); font-size: 12px; }
.sheet-notes { white-space: pre-wrap; font-size: 13px; color: var(--ink-2); line-height: 1.55; }
.sheet-sec[data-tone="notes"] dl { grid-template-columns: 1fr; }
.sheet-sec[data-tone="notes"] dt { display: none; }
.sheet-add { margin-top: 12px; }
.sheet-sec .empty { padding: 12px 0; text-align: left; }

/* --------------------------------------------------------------- contacts
   Working data, edited where it is read. A client's people are a list of rows
   with a pen each; the same form serves adding one, and on a project the
   picker and the way to make a new one sit on one line, because "the person
   here changed" is a single thought.                                        */
.contact-pick { display: flex; gap: 6px; align-items: center; }
.contact-pick select { flex: 1; min-width: 0; }
.contact-list { display: grid; }
.contact-row {
  display: flex; gap: 10px; align-items: center; justify-content: space-between;
  padding: 6px 0; border-bottom: 1px solid var(--line);
}
.contact-row:last-child { border-bottom: 0; padding-bottom: 0; }
/* Being edited, the row is the form: it takes the full width rather than
   squeezing three fields into the space the pen left. */
.contact-row.editing { display: block; }
.c-main { display: flex; flex-wrap: wrap; gap: 2px 12px; align-items: baseline; font-size: 13px; }
.contact-form { display: grid; gap: 10px; padding: 2px 0 6px; }
.contact-form .filters { margin-bottom: 0; }
/* Opened under the project's Contact field, the form is three more inputs in a
   column of inputs. The rule down its side says it belongs to the field above
   rather than being the next question about the project. */
.field > div > .contact-form { border-left: 2px solid var(--line); padding-left: 12px; }

/* ----------------------------------------------------------------- cities
   The same affordance as the contact picker above, for the same reason: the
   list is closed, so the way to add to it belongs where the gap is noticed.
   The picker stacks its select over whatever it has to say about it — what
   the spreadsheet held here, or who to ask — at the gap .field gives its own
   children, which a nested div would otherwise swallow.                     */
.city-pick { display: grid; gap: 4px; }
.city-form { display: grid; gap: 10px; padding: 2px 0 6px; }
.city-form .filters { margin-bottom: 0; }
.field > div > .city-form { border-left: 2px solid var(--line); padding-left: 12px; }

/* ------------------------------------------------------------------- team
   Who may sign in, as a table in the drawer. It is the clients table with one
   row per person, so it inherits its rules and only says what is different:
   the columns hold their edges, and a deactivated row is dimmed — behind the
   word Deactivated in its own column, never instead of it.                  */
table.team th, table.team td { white-space: nowrap; padding: 9px 10px; }
table.team tr.off td { color: var(--ink-3); }
/* The address is the one column allowed to wrap, the way the project name is
   the one that wraps in the pipeline table. Nothing else here is long, and an
   address long enough to widen the table past the drawer takes the pen off the
   right-hand edge with it — where the only way back to it is a horizontal
   scrollbar in a panel nobody expects to scroll sideways. */
table.team td.mail { white-space: normal; overflow-wrap: anywhere; max-width: 220px; }
/* On a phone the drawer is the whole screen and six columns do not fit it
   whatever the address does — so the table scrolls sideways there, the way
   every other table in this app does, and the address goes back to one line
   rather than stacking itself six letters at a time. */
@media (max-width: 560px) {
  table.team td.mail { white-space: nowrap; max-width: none; }
}
/* The pen takes the width of the pen and nothing else. */
table.team td.pen { width: 1%; text-align: right; padding-left: 0; }
/* Under the date rather than beside it: inline, the widest cell in the table
   was the one saying nothing had happened yet. */
table.team td .chip.soon { display: block; width: fit-content; margin-top: 3px; }

/* Half of this form's fields carry a hint and half do not, and a grid row is
   as tall as its tallest cell — so the box next to a hinted one was being
   stretched half an inch taller than the box above it. Each field keeps the
   height of what is in it and the slack collects underneath. */
.team-form .field { align-content: start; }

/* ------------------------------------------------------------ prospecting
   Two lists of things that are not projects yet, in the pipeline's own table
   grammar — one bar per column, the heat ramp on the date — so that converting
   one does not feel like arriving from somewhere else.                       */
.chip-row { display: flex; gap: 6px; flex-wrap: wrap; }
.chip-row .chip { cursor: pointer; background: var(--panel); }
.chip-row .chip:hover { border-color: var(--accent); color: var(--accent); }
/* The chosen one inverts, which is what .chip.stage already does in the drawer:
   the strongest thing on the page that is still a chip. */
.chip-row .chip.on { background: var(--ink); color: var(--bg); border-color: var(--ink); }

/* A link that navigates nowhere. It opens the project this prospect became, in
   the drawer, the way every row in this app opens something — so it is a button
   with a link's face rather than an anchor to an address that does not exist. */
.link-btn {
  border: 0; background: none; padding: 0; font: inherit; font-size: inherit;
  color: var(--accent); cursor: pointer; text-align: left;
}
.link-btn:hover { text-decoration: underline; }

/* Notes are the one column here allowed to wrap, the way the project name is in
   the pipeline: a calendar note runs to a sentence, and nowrap would push the
   Status column and the Convert button in it off the right-hand edge. */
.prospects td.notes { white-space: normal; min-width: 190px; max-width: 340px; }
/* The same questions whether a prospect is being written down for the first
   time or corrected, so one form serves both — and it carries its own spacing
   rather than borrowing .sheet's, which would nest a sheet inside a sheet in
   the create drawer. */
.prospect-form { display: grid; gap: 14px; }

/* The TBD tick lives inside the date field rather than in a field of its own —
   one question, two controls — so it needs the rescue .field-check gives: the
   rule stretching every input in a .field to full width would otherwise turn the
   box into a slab. */
.tbd-line {
  display: flex; align-items: center; gap: 8px;
  font-size: 13px; color: var(--ink-2); cursor: pointer;
}
.tbd-line input[type="checkbox"] {
  width: 15px; height: 15px; flex: none; padding: 0; margin: 0;
  accent-color: var(--accent);
}

.prospects th.act, .prospects td.act { width: 1%; text-align: right; padding-left: 0; }
/* The amber rule over a heading belongs to the date columns, which is what it
   predicts the colour of. The action column has no heading and no cells to
   predict. */
.prospects th.act { border-top-color: transparent; }
.btn.tiny { padding: 4px 9px; font-size: 12px; }

/* The chips are the filter and the button is the one action: on a phone each
   takes a full line rather than wrapping into a ragged pair. */
@media (max-width: 640px) {
  .prospect-filters .spacer { display: none; }
  .prospect-filters .chip-row { flex: 1 1 100%; }
  .prospect-filters .chip-row .chip { flex: 1; text-align: center; padding: 6px 4px; }
  .prospect-filters .btn { flex: 1 1 100%; }
}

/* --------------------------------------------------------------- playbook */
.playbook dl { display: grid; grid-template-columns: 200px 1fr; gap: 0; margin: 0; }
.playbook dt { padding: 10px 12px; border-bottom: 1px solid var(--line); font-weight: 600; font-size: 13px; }
.playbook dd { padding: 10px 12px; border-bottom: 1px solid var(--line); margin: 0; color: var(--ink-2); font-size: 13px; }
@media (max-width: 700px) {
  .playbook dl { grid-template-columns: 1fr; }
  .playbook dt { border-bottom: 0; padding-bottom: 0; }
}
/* A heading that shares its row with the one button acting on its list, on a
   page of plain headings: it has to keep the air an h2 would have had above it. */
.playbook-head { margin-top: 26px; }
.note {
  background: color-mix(in srgb, var(--warn) 10%, var(--panel));
  border: 1px solid color-mix(in srgb, var(--warn) 35%, transparent);
  border-radius: var(--radius); padding: 12px 14px; font-size: 13px; color: var(--ink-2);
}
.empty { padding: 40px; text-align: center; color: var(--ink-3); }

.table-note {
  padding: 9px 12px; border-top: 1px solid var(--line);
  background: var(--panel-2); color: var(--ink-3); font-size: 12px; line-height: 1.45;
}


/* ============================================================== architecture
   Three levels of navigation, because the CRM has three: a department, a thing
   inside it (Projects or Rentals), and a screen. The department strip lives in
   the header beside the wordmark; the other two share the bar under it, which
   is only there while you are inside a department — the main page has nothing
   to put in it and would otherwise show an empty rule across the screen.

   The SMPTE bars carry all three levels, so a colour means the same thing
   wherever it is: Sales green, Rentals magenta, Projects cyan. */

.subbar {
  position: sticky; top: 56px; z-index: 19;
  display: flex; align-items: center; gap: 14px;
  height: 44px; padding: 0 18px;
  background: var(--panel-2); border-bottom: 1px solid var(--line);
  overflow-x: auto;
}
.sections { display: flex; gap: 4px; flex: none; }
/* Not a tab: a tab is a screen and these are which half of the department you
   are in. So it is the segmented control the shape suggests — a bordered pill
   carrying its own bar as a dot — and never the same object as the strip it
   sits next to. */
.sections a {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px; border-radius: 999px; font-size: 12.5px; font-weight: 600;
  color: var(--ink-2); border: 1px solid var(--line); background: var(--panel);
  white-space: nowrap;
}
.sections a::before {
  content: ''; width: 8px; height: 8px; border-radius: 2px;
  background: var(--tab); flex: none;
}
.sections a:hover { color: var(--ink); border-color: color-mix(in srgb, var(--tab) 60%, transparent); }
.sections a.on {
  color: var(--ink); background: color-mix(in srgb, var(--tab) 18%, var(--panel));
  border-color: color-mix(in srgb, var(--tab) 60%, transparent);
}
.subbar .rule { flex: none; width: 1px; height: 20px; background: var(--line); }

/* Work in progress. Everywhere it appears it is the same object: a small,
   quiet, non-shouting label. A greyed-out link nobody can explain is worse
   than a link that says outright it is not built yet. */
.chip.wip {
  background: transparent; border-style: dashed; color: var(--ink-3);
  font-weight: 600; letter-spacing: .04em; text-transform: uppercase; font-size: 10px;
}
.sections a.wip, .tabs a.wip, .depts a.wip { opacity: .62; }
.sections a.wip::after, .tabs a.wip::after, .depts a.wip::after { opacity: .2; }

/* ------------------------------------------------------------- the main page
   The outline of the whole CRM, as the thing itself rather than a picture of
   it: one card per department, each listing what is inside it, live links
   where there is something to open and a WIP label where there is not. It is
   the only screen that says what this app is going to be. */
.dept-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(310px, 1fr)); gap: 14px; }
.dept {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  border-top: 3px solid var(--tone); overflow: hidden;
  display: flex; flex-direction: column;
}
.dept-head { padding: 13px 15px 11px; border-bottom: 1px solid var(--line); }
.dept-head h3 {
  margin: 0 0 3px; font-size: 16px; letter-spacing: -.015em;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.dept-head p { margin: 0; font-size: 12px; color: var(--ink-3); line-height: 1.45; }
.dept-body { padding: 11px 15px 14px; display: grid; gap: 12px; }
.dept-sec > b {
  display: flex; align-items: center; gap: 7px;
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .08em;
  color: var(--ink-3); margin-bottom: 6px;
}
.dept-sec > b::before {
  content: ''; width: 3px; height: 12px; border-radius: 2px; background: var(--tone);
}
.dept-links { display: flex; flex-wrap: wrap; gap: 5px; }
.dept-links a, .dept-links span {
  font-size: 12.5px; padding: 4px 9px; border-radius: 7px;
  border: 1px solid var(--line); background: var(--panel-2); color: var(--ink-2);
}
.dept-links a:hover { border-color: var(--accent); color: var(--accent); }
.dept-links span { color: var(--ink-3); border-style: dashed; background: none; }
/* The count a live screen can put on itself. Nothing on a WIP one can. */
.dept-links a b { font-weight: 650; color: var(--ink); }

/* ---------------------------------------------------------------- wip screen
   Not a 404 and not an empty state: the tab exists in the architecture, it is
   simply not built. So it says what will be on it, and — where the answer is
   already in this database — what is standing in for it today. */
.wip-page { display: grid; gap: 16px; max-width: 760px; }
.wip-page ul { margin: 8px 0 0; padding-left: 20px; display: grid; gap: 5px; }
.wip-page li { font-size: 13px; color: var(--ink-2); }

/* -------------------------------------------------------------------- tiles
   The Rentals dashboard's schedule blocks: five counts across, each a filter on
   the board underneath. Past due is the one with an alarm on it — the sheet
   says both blocks "should be maintained and be at zero" — so it takes the same
   red every late thing in this app takes, and only while it is not zero. */
.tiles { display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px; }
/* Fewer than five, and they must not stretch to fill a 1440px row: two counts
   half a metre apart stop being a pair you read at a glance. */
.tiles-sm { grid-template-columns: repeat(auto-fit, minmax(160px, 230px)); justify-content: start; }
@media (max-width: 720px) {
  .tiles { grid-template-columns: repeat(2, 1fr); }
  .tiles-sm { grid-template-columns: repeat(2, 1fr); }
}
.tile {
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius);
  border-top: 3px solid var(--tone, var(--line));
  padding: 11px 13px; text-align: left; cursor: pointer; font: inherit; color: inherit;
  display: grid; gap: 2px;
}
.tile:hover { border-color: var(--accent); }
.tile .n { font-size: 23px; font-weight: 650; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.tile .k { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--ink-3); }
.tile.zero .n { color: var(--ink-3); }
.tile[data-alarm="1"] {
  --tone: var(--danger);
  background: color-mix(in srgb, var(--danger) 8%, var(--panel));
}
:root[data-theme="dark"] .tile[data-alarm="1"] { background: color-mix(in srgb, var(--danger) 15%, var(--panel)); }
.tile[data-alarm="1"] .n { color: var(--danger); }
/* The two tiles that overlap the three before them. Said once under the block
   rather than argued with per tile. */
.tiles + .table-note, .card + .tiles { margin-top: 10px; }

/* A rental's four Yes/No columns, as four letters that are either on or off.
   Four words would not fit the row and four ticks would not say which is which;
   the initial does both, and the title attribute spells it out. */
.docs { display: inline-flex; gap: 3px; }
.docs i {
  width: 17px; height: 17px; border-radius: 4px; font-style: normal;
  font-size: 10px; font-weight: 700; display: inline-grid; place-items: center;
  background: color-mix(in srgb, var(--ink-3) 16%, transparent); color: var(--ink-3);
}
.docs i.on { background: color-mix(in srgb, var(--ok) 22%, transparent); color: var(--ok); }

/* ------------------------------------------------------------------- print
   Handing a table to someone on paper is the other thing the workbook did for
   nothing. What prints is the content; the parts of an app you cannot press —
   the topbar, the controls in the filter bars, the buttons, the drawer and the
   scrim over the page behind it — go, because every one of them was landing in
   the middle of the sheet.                                                   */
/* =====================================================================
   toasts — what just happened, when there is no form to say it in
   ===================================================================== */

.toasts {
  position: fixed; left: 50%; bottom: 22px; transform: translateX(-50%);
  z-index: 60; display: flex; flex-direction: column; gap: 8px;
  align-items: center; pointer-events: none;
}
.toast {
  pointer-events: auto; cursor: pointer;
  background: var(--ink); color: var(--bg);
  padding: 9px 16px; border-radius: 999px; font-size: 13px; font-weight: 500;
  box-shadow: var(--shadow); max-width: min(90vw, 520px);
  animation: toast-in .18s ease-out;
}
.toast.err {
  background: var(--danger); color: #fff;
  /* Long enough to run to a second line, unlike the one-line confirmations:
     a refusal has a reason in it and the reason is the useful half. */
  border-radius: 12px; white-space: normal;
}
.toast.out { opacity: 0; transform: translateY(6px); transition: opacity .35s, transform .35s; }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } }
@media (prefers-reduced-motion: reduce) {
  .toast { animation: none; }
  .toast.out { transition: none; }
}

/* =====================================================================
   project types and tags
   ===================================================================== */

/* Six families down the box, their entries across. Grouped visually by a
   heading rather than by a border: twelve boxes in six bordered panels is a
   lot of furniture for one field. */
.type-picker {
  display: grid; gap: 10px; padding: 10px;
  border: 1px solid var(--line); border-radius: 8px; background: var(--panel);
}
.type-group-head {
  font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink-3); margin-bottom: 5px;
}
.type-group-body { display: flex; flex-wrap: wrap; gap: 6px; }

/* A checkbox wearing a chip. Ticked, it is the strongest thing in the box —
   the same treatment .chip-row.on already gives a chosen chip elsewhere. */
.chip.check {
  cursor: pointer; user-select: none; display: inline-flex; align-items: center;
  padding: 4px 10px; font-weight: 500;
}
.chip.check input { width: 13px; height: 13px; margin: 0 5px 0 0; accent-color: var(--accent); }
.chip.check:hover { border-color: var(--accent); }
.chip.check.on { background: var(--ink); color: var(--bg); border-color: var(--ink); font-weight: 600; }

/* On a sheet: the types a project carries, and the words it goes by. Two
   different kinds of fact, so they do not look alike — a type comes off the
   company's list and a tag is somebody's own word for this job. */
.chip.type { background: color-mix(in srgb, var(--tone, #00c0c0) 14%, transparent); }
.chip.tag::before { content: "#"; opacity: .5; }
.chip.tag.on { background: var(--ink); color: var(--bg); border-color: var(--ink); }

.tag-input {
  display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
  padding: 6px; border: 1px solid var(--line); border-radius: 8px; background: var(--panel);
}
.tag-list { display: contents; }
.tag-input input {
  flex: 1; min-width: 140px; border: 0; padding: 4px 6px; background: none; outline: none;
}
.tag-input:focus-within { border-color: var(--accent); }
.chip-x {
  border: 0; background: none; padding: 0 0 0 6px; cursor: pointer;
  color: inherit; opacity: .6; font-size: 13px; line-height: 1;
}
.chip-x:hover { opacity: 1; }

/* The type filter in the pipeline bar: a disclosure used as a menu, so the bar
   stays one line and still says what is ticked without being opened. */
.dropdown { position: relative; }
.dropdown > summary {
  list-style: none; cursor: pointer; padding: 7px 10px;
  border: 1px solid var(--line); border-radius: 8px; background: var(--panel);
  font-size: 13px; white-space: nowrap;
}
.dropdown > summary::-webkit-details-marker { display: none; }
.dropdown > summary::after { content: " ▾"; color: var(--ink-3); }
.dropdown > summary.on { border-color: var(--accent); color: var(--accent); }
.dropdown[open] > summary { border-color: var(--accent); }
.dropdown > .type-picker {
  position: absolute; z-index: 30; top: calc(100% + 4px); left: 0;
  min-width: 260px; box-shadow: var(--shadow);
}

/* =====================================================================
   photographs
   ===================================================================== */

.photo-grid {
  display: grid; gap: 10px; margin-bottom: 12px;
  grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
}
.photo {
  position: relative; margin: 0; border: 1px solid var(--line); border-radius: 8px;
  overflow: hidden; background: var(--panel-2);
}
/* A fixed frame so a portrait phone photo and a landscape camera one make an
   even grid; the picture itself is never cropped in the view that matters,
   which is the one the click opens. */
.photo img {
  display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover; cursor: zoom-in;
  background: var(--panel-2);
}
.photo figcaption {
  padding: 5px 7px; font-size: 11px; color: var(--ink-2);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.photo-fallback {
  display: grid; place-content: center; text-align: center; padding: 14px 8px;
  aspect-ratio: 4 / 3; font-size: 11px; color: var(--ink-2); word-break: break-all;
}
.photo-x {
  position: absolute; top: 4px; right: 4px; width: 22px; height: 22px;
  border: 0; border-radius: 50%; cursor: pointer; line-height: 1;
  background: color-mix(in srgb, var(--ink) 65%, transparent); color: var(--bg);
  opacity: 0; transition: opacity .12s;
}
.photo:hover .photo-x, .photo-x:focus { opacity: 1; }
/* On a touch screen there is no hover, so the button that is only revealed by
   one would never appear at all. */
@media (hover: none) { .photo-x { opacity: .85; } }

[data-tone="photos"] { --tone: #3a3ad0; }
:root[data-theme="dark"] [data-tone="photos"] { --tone: #6c6cff; }
.sheet-sec.dropping {
  outline: 2px dashed var(--accent); outline-offset: 3px;
}

.lightbox {
  position: fixed; inset: 0; z-index: 70; display: grid; place-items: center;
  gap: 10px; padding: 24px; background: rgba(0,0,0,.86); cursor: zoom-out;
}
.lightbox img { max-width: 100%; max-height: 82vh; object-fit: contain; }
.lightbox-cap { color: #ddd; font-size: 12px; text-align: center; }

/* =====================================================================
   editing in the table
   ===================================================================== */

/* A cell you can open says so on the way past, and only on a device with a
   pointer to say it to. Faint on purpose: every cell but two is editable now,
   and a whole table of affordances is a table of noise. */
@media (hover: hover) {
  .deals td.editable:hover {
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 45%, transparent);
    cursor: text;
  }
}
.deals td.editing { padding: 2px 4px; }
.cell-edit {
  width: 100%; min-width: 110px; font-size: 13px; padding: 4px 6px;
  border: 1px solid var(--accent); border-radius: 6px; background: var(--panel);
}
select.cell-edit[multiple] { min-height: 96px; }
/* The two link columns keep the pointer that says "this goes somewhere". */
.deals td .link-btn { cursor: pointer; }
.deals td .sub-link { color: var(--ink-2); }
.deals td .sep { color: var(--ink-3); padding: 0 1px; }

@media print {
  /* Both themes print the same page, and both print light. Taken from the dark
     theme this came out a black slab with the letters knocked out of it:
     unreadable at arm's length and most of a toner cartridge. Overriding the
     variables rather than the rules means every panel, rule and chip follows
     without being named twice — and the three signal colours go to ink,
     because the words beside them ("12d late", "overdue") are what carry the
     meaning anyway. */
  :root, :root[data-theme="dark"] {
    --bg: #fff;
    --panel: #fff;
    --panel-2: #fff;
    --ink: #000;
    --ink-2: #222;
    --ink-3: #444;
    --line: #767676;
    --accent: #000;
    --accent-ink: #fff;
    --ok: #000;
    --warn: #000;
    --danger: #000;
    --shadow: none;
  }
  /* Blue is the one bar with a value per theme, and two printers' worth of
     grey is not what "both themes print the same" means. */
  [data-tone="blue"], :root[data-theme="dark"] [data-tone="blue"] { --tone: #3a3ad0; }

  @page { margin: 12mm; }
  body { background: #fff; color: #000; }
  main { padding: 0; max-width: none; }

  /* The controls go rather than the bars holding them: on Clients and in the
     playbook the filter row is also where the heading lives, and a page that
     dropped its own title to lose a search box has lost the better half. */
  .topbar, .subbar, .drawer, .scrim,
  .filters input, .filters select, .filters label, .filters button,
  .filters .spacer, .filters .saved,
  .btn, .icon-btn { display: none !important; }

  /* A table on paper is the full width of it, ruled both ways — a printed grid
     is read across as much as down — and in a grey dark enough to survive a
     laser printer, where the screen's hairlines come out as nothing at all. A
     row split over a page break is a row nobody can read, and the head repeats
     on every page the table reaches. */
  .card { border: 1px solid #000; border-radius: 0; box-shadow: none; overflow: visible; }
  .scroll-x { overflow: visible; }
  table { width: 100%; font-size: 10px; }
  th, td { border: 1px solid var(--line); }
  th { position: static; background: none; color: #000; }
  thead { display: table-header-group; }
  tr { break-inside: avoid; }
  /* The banding goes the way the tints go: on paper the rules are what holds a
     row together, and a grey every second row would only compete with them. */
  tbody tr:nth-child(even) { background: none; }
  /* Every column on one line is a screen habit: paper cannot scroll sideways,
     so the cells wrap and the table fits the page instead of losing its last
     two columns off the edge. */
  .deals th, .deals td { white-space: normal; padding: 5px 6px; }

  /* The column tints and the heat ramp print as four shades of one grey, which
     says less than the "12d late" already sitting in the cell and hides the
     rules while it does it. The 2px bar over each heading stays — a single line
     per column reads where a wash does not — darkened, because the 75% bars
     land between 11% and 75% grey and the pale ones would come out blank. */
  .deals td[data-tone], .deals th[data-tone], .deals td[data-heat],
  :root[data-theme="dark"] .deals td[data-tone] { background: none; }
  .deals th[data-tone] { border-top: 2px solid color-mix(in srgb, var(--tone) 55%, #000); }
  .deals td[data-heat="past"], .deals td[data-heat="h5"] { font-weight: 700; }

  /* The board, flowed instead of scrolled: one stage after another down the
     page, each kept whole. Left as a row it printed two columns and cut the
     rest off at the paper's edge. */
  .kanban { display: block; overflow: visible; }
  .col { max-height: none; margin-bottom: 10px; break-inside: avoid; }
  .col-body { overflow: visible; }
  .deal { break-inside: avoid; }

  /* Blocks worth keeping whole wherever the page has room for them. */
  .tile { cursor: auto; }
  .kpi, .tile, .dept, .chart-row, .sheet-sec, .event, .note, .table-note { break-inside: avoid; }
  h2 { break-after: avoid; }

  /* The charts are their bars. Browsers drop background colour on paper, which
     would leave a column of empty rails; the counts printed beside them would
     still be true, but comparing the lengths is the whole point of the block,
     so the segments are forced through and the rail is given an outline to sit
     in. */
  .chart-row .track { border: 1px solid var(--line); }
  .chart-row .track i, .chart-key i {
    -webkit-print-color-adjust: exact; print-color-adjust: exact;
  }
}
