/* The shell and components of every page in this deployment that uses the
 * sidebar/topbar chrome — the PM app under /pm/ and the Data Entry app under
 * /data-entry/.
 *
 * Rebranded with the executive "Dark Navy & Champagne Gold" theme to give
 * the Project Management platform a distinct, elite identity while maintaining
 * exact layout consistency with the LIBRA suite.
 *
 * Loaded by templates/pm/base.html, which owns the markup these rules target.
 *
 * ── Themeable, deliberately ──
 * Every brand colour below is a custom property, and the ones that appear
 * inside an rgba() are *also* published as a bare "r,g,b" triplet
 * (--gold-rgb and friends), used as rgba(var(--gold-rgb),.2) throughout. That
 * extra indirection is the whole reason a second app can wear a different
 * accent without a second copy of this file: a theme sheet loaded after this
 * one redefines ~10 properties on :root and every one of the 48 translucent
 * accents here follows. Writing rgba(197,155,78,.2) by hand instead — as this
 * file used to — pins the accent in place and makes a theme a fork.
 *
 * So: no raw brand hex or rgba() past this :root block. See
 * static/data_entry/css/theme.css for the consumer side.
 */

:root{
  /* ── Dark Navy Palette ── */
  --navy-deep:#080f1d;
  --navy-dark:#14213d;
  --navy-mid:#1e2f52;
  --navy-tint:#f0f4f9;
  --navy-subtle:#e2e8f4;

  /* The same three navies as "r,g,b", for the rgba() uses below. */
  --navy-deep-rgb:8,15,29;
  --navy-dark-rgb:20,33,61;
  --navy-mid-rgb:30,47,82;

  /* ── Champagne Gold Accent Palette ── */
  --gold-rgb:197,155,78;
  --gold:#c59b4e;
  --gold-light:#dfbe76;
  --gold-dark:#9e792f;
  --gold-tint:#faf4e8;
  --gold-border:rgba(var(--gold-rgb),.35);
  --gold-glow:0 0 22px -4px rgba(var(--gold-rgb),.35);

  /* ── Neutrals & Semantic Colors ── */
  --ink:#0e172a;
  --body-c:#2d3748;
  --muted:#64748b;
  --border:#dbe2ea;
  --white:#ffffff;
  --bg:#f4f6fa;
  --ok:#2e8b57;
  --ok-tint:#eaf6f0;
  --warn:#d9a441;
  --warn-tint:#fdf6e8;
  --err:#c9524f;
  --err-tint:#fbeceb;

  /* ── Typography & Chrome Metrics ── */
  --font-heading:'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-body:'Source Sans 3', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --shadow-std:0px 12px 28px -6px rgba(var(--navy-dark-rgb),.16), 0px 4px 12px -2px rgba(var(--gold-rgb),.08);
  --shadow-soft:0px 4px 16px -4px rgba(15,23,42,.08);
  --sidebar-w:236px;
  --topbar-h:56px;

  /* ── Work surface ──
     Tool pages use these, not the gold accents: on a grid of numbers every
     accent colour competes with the data for attention. Gold is reserved for
     the shell (sidebar/topbar) and the landing tiles. */
  --line:#e7ebf0;          /* hairline rules inside a grid */
  --surface-alt:#f7f8fa;   /* table head, footer, action bars */
  --stale-row-bg:#fdeeee;  /* row not updated in more than one week */
  --stale-row-border:#f3c4c4;
  --stale-row-hover:#fbe4e4;
  --focus:#3f5f96;         /* one focus colour, everywhere */
  --focus-ring:0 0 0 3px rgba(63,95,150,.18);
}
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%}
body{
  font-family:var(--font-body);font-size:16px;line-height:1.6;
  color:var(--body-c);background:var(--bg);-webkit-font-smoothing:antialiased;
}
button{font-family:inherit;cursor:pointer}
h1,h2,h3{font-family:var(--font-heading);color:var(--ink);letter-spacing:-.01em}
a{text-decoration:none;color:inherit}

/* Inline SVG icons */
.ico{width:24px;height:24px;display:inline-block;flex:0 0 auto;fill:currentColor;user-select:none}

/* ── Sidebar ───────────────────────────────────────────────────────────── */
.sidebar{
  position:fixed;left:0;top:0;bottom:0;width:var(--sidebar-w);z-index:60;
  background:linear-gradient(180deg, var(--navy-dark) 0%, var(--navy-dark) var(--topbar-h), var(--navy-deep) 100%);
  color:#fff;display:flex;flex-direction:column;padding:0 8px 8px;
  transform:translateX(-100%);transition:transform .22s ease;
  border-right:1px solid rgba(var(--gold-rgb),.2);
}
.sidebar.open{transform:none}
.sidebar .brand{
  height:var(--topbar-h);flex:0 0 auto;margin:0 -8px;padding:0 16px;
  display:flex;align-items:center;gap:10px;border-bottom:1px solid rgba(var(--gold-rgb),.3);
  background:rgba(var(--navy-deep-rgb),.25);
}
.brand-link{display:flex;line-height:0;text-decoration:none;color:inherit;flex:0 0 auto}
.brand-link:hover{opacity:.88}
.sidebar .brand img{height:20px;display:block;flex:0 0 auto}
.sidebar .brand .app-name{font-family:var(--font-heading);font-size:14px;font-weight:700;color:#fff;line-height:1.2}
.sidebar .brand .app-sub{font-size:11px;color:var(--gold-light);line-height:1.2;font-weight:600;letter-spacing:.04em}
.sidebar nav{flex:1;min-height:0;overflow-y:auto;margin-top:14px}
.sidebar nav ul{list-style:none;display:flex;flex-direction:column;gap:3px}
.side-link{
  display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:8px;
  font-size:14px;font-weight:600;color:rgba(255,255,255,.88);
  border-left:3px solid transparent;
  transition:background .15s,color .15s,border-color .15s;
  width:100%;border-top:0;border-right:0;border-bottom:0;background:transparent;text-align:left;
}
.side-link .ico{width:20px;height:20px;color:rgba(255,255,255,.65);transition:color .15s}
.side-link:hover{background:rgba(255,255,255,.08);color:#fff}
.side-link:hover .ico{color:var(--gold-light)}
.side-link.active{
  background:linear-gradient(90deg, rgba(var(--gold-rgb),.22) 0%, rgba(var(--gold-rgb),.05) 100%);
  color:#fff;
  border-left-color:var(--gold-light);
}
.side-link.active .ico{color:var(--gold-light)}

/* ── Sidebar Submenus ── */
.side-group{display:flex;flex-direction:column}
.side-group-toggle{
  justify-content:space-between;cursor:pointer;
}
.side-group-toggle .side-title-wrap{
  display:flex;align-items:center;gap:10px;flex:1;min-width:0;
}
.side-chevron{
  width:18px;height:18px;display:flex;align-items:center;justify-content:center;
  margin-left:auto;transition:transform .2s ease;opacity:.7;color:rgba(255,255,255,.8);
}
.side-chevron .ico{width:18px;height:18px}
.side-group.open .side-chevron{transform:rotate(180deg);opacity:1;color:var(--gold-light)}

.side-submenu{
  list-style:none;display:none;flex-direction:column;gap:2px;
  padding:3px 0 4px 10px;margin:2px 0 4px 14px;
  border-left:1.5px solid rgba(var(--gold-rgb),.25);
}
.side-group.open .side-submenu{display:flex}

.side-sublink{
  display:flex;align-items:center;gap:8px;padding:7px 10px;border-radius:6px;
  font-size:13px;font-weight:500;color:rgba(255,255,255,.78);
  transition:background .15s,color .15s,transform .12s;
}
.side-sublink .sub-ico{width:18px;height:18px;display:flex;align-items:center;justify-content:center}
.side-sublink .sub-ico .ico{width:17px;height:17px;color:rgba(255,255,255,.55);transition:color .15s}
.side-sublink .sub-dot{
  width:5px;height:5px;border-radius:50%;background:rgba(var(--gold-rgb),.45);
  transition:background .15s,transform .15s;
}
.side-sublink:hover{background:rgba(255,255,255,.07);color:#fff;transform:translateX(2px)}
.side-sublink:hover .sub-ico .ico{color:var(--gold-light)}
.side-sublink:hover .sub-dot{background:var(--gold-light);transform:scale(1.3)}
.side-sublink.active{
  background:rgba(var(--gold-rgb),.18);color:#fff;font-weight:700;
}
.side-sublink.active .sub-ico .ico{color:var(--gold-light)}
.side-sublink.active .sub-dot{background:var(--gold-light);box-shadow:0 0 6px rgba(var(--gold-rgb),.8)}
.side-foot{
  margin-top:auto;border-top:1px solid rgba(var(--gold-rgb),.25);padding:10px 12px 6px;
  display:flex;align-items:center;gap:10px;
}
.avatar{
  width:32px;height:32px;border-radius:50%;flex:0 0 auto;
  background:linear-gradient(135deg, var(--gold-light) 0%, var(--gold) 100%);
  color:var(--navy-deep);font-family:var(--font-heading);font-weight:800;font-size:12.5px;
  display:flex;align-items:center;justify-content:center;
  box-shadow:0 2px 8px rgba(var(--gold-rgb),.4);
}
.side-foot .who{min-width:0}
.side-foot .who .name{font-size:13.5px;font-weight:700;color:#fff;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.side-foot .who .role{font-size:11px;color:var(--gold-light);text-transform:uppercase;letter-spacing:.08em;font-weight:700}

.backdrop{position:fixed;inset:0;background:rgba(var(--navy-deep-rgb),.45);z-index:55;opacity:0;pointer-events:none;transition:opacity .22s}
.backdrop.show{opacity:1;pointer-events:auto}

/* ── Top Bar ───────────────────────────────────────────────────────────── */
.main{min-height:100%;display:flex;flex-direction:column}
.topbar{
  position:sticky;top:0;z-index:40;height:var(--topbar-h);flex:0 0 auto;
  background:linear-gradient(135deg, var(--navy-dark) 0%, #0e172a 100%);color:#fff;
  border-bottom:2px solid rgba(var(--gold-rgb),.35);
  display:flex;align-items:center;gap:14px;padding:0 20px;
  box-shadow:0 4px 16px rgba(var(--navy-deep-rgb),.15);
}
.topbar .brand-link img{height:24px;display:block}
.topbar img{height:24px;display:block}
.topbar .divider{width:1px;height:16px;background:rgba(var(--gold-rgb),.4)}
.topbar .app-name{font-size:13px;text-transform:uppercase;letter-spacing:.12em;font-weight:700;color:rgba(255,255,255,.95)}
.menu-btn{
  border:1px solid rgba(var(--gold-rgb),.3);background:rgba(255,255,255,.08);color:#fff;border-radius:8px;
  width:34px;height:34px;display:flex;align-items:center;justify-content:center;
}
.menu-btn:hover{background:rgba(var(--gold-rgb),.2)}
.topbar-right{margin-left:auto;display:flex;align-items:center;gap:10px}
.home-btn{
  border:1px solid rgba(var(--gold-rgb),.3);background:rgba(255,255,255,.08);color:#fff;border-radius:8px;
  width:34px;height:34px;display:flex;align-items:center;justify-content:center;
  transition:background .15s,transform .15s;
}
.home-btn .ico{width:19px;height:19px}
.home-btn:hover{background:rgba(var(--gold-rgb),.2);transform:translateY(-1px)}
.home-btn:active{transform:translateY(0)}
.svc{
  display:inline-flex;align-items:center;gap:7px;font-size:12px;font-weight:700;
  background:rgba(255,255,255,.08);border:1px solid rgba(var(--gold-rgb),.25);border-radius:20px;padding:5px 11px;color:#fff;
}
.svc .dot{width:7px;height:7px;border-radius:50%;background:#fff;opacity:.6}
.svc.ok .dot{background:#4ade80;opacity:1;box-shadow:0 0 6px #4ade80}
.svc.err .dot{background:#ff8080;opacity:1;box-shadow:0 0 6px #ff8080}
.lang-select{
  border:1px solid rgba(var(--gold-rgb),.35);background:rgba(255,255,255,.08);color:#fff;
  padding:6px 9px;border-radius:6px;font-size:12px;font-weight:700;cursor:pointer;
}
.lang-select:hover{background:rgba(var(--gold-rgb),.2)}
.lang-select option{color:#0e172a;background:#fff;font-weight:400}

/* ── Page Content ──────────────────────────────────────────────────────── */
/* The cap is for reading comfort, and what this app puts on a page is mostly
   tables: a wide screen should go into columns rather than into margins. Hence
   a high cap here and a much higher one once the sidebar is permanent — a
   still-generous limit rather than none, so a 4K screen does not stretch a row
   of numbers across a metre of glass. */
.page{flex:1;min-height:0;max-width:1600px;width:100%;margin:0 auto;padding:18px 16px 28px;
  display:flex;flex-direction:column}

/* ── Hero ──────────────────────────────────────────────────────────────── */
.hero{
  position:relative;overflow:hidden;background:var(--white);flex:0 0 auto;
  border:1px solid var(--border);border-top:3px solid var(--gold);
  border-radius:16px;box-shadow:var(--shadow-std);
  padding:20px 22px;margin-bottom:16px;
  display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:16px;
}
.hero::after{
  content:"";position:absolute;right:-90px;top:-140px;width:340px;height:340px;border-radius:50%;
  background:radial-gradient(circle at center, rgba(var(--gold-rgb),.18), rgba(var(--navy-dark-rgb),.02) 70%);pointer-events:none;
}
.hero .intro{position:relative;z-index:1;min-width:260px;flex:1}
.hero-hub{
  justify-content:center;text-align:center;
}
.hero-hub .intro{flex:0 1 auto;max-width:640px}
.hero-hub p{margin-left:auto;margin-right:auto}
.hero h1{font-size:26px;line-height:1.15;margin-bottom:4px;color:var(--navy-dark)}
.hero p{color:var(--muted);font-size:14.5px;line-height:1.5;max-width:560px}

/* ── Bento Grid / Tiles ────────────────────────────────────────────────── */
/* `align-items`/`align-content: center` — not the flex default `stretch` — so
   a tile is only as tall as its own content. Without it a single tile (today,
   this app has exactly one) stretches to fill the hub's whole locked-viewport
   height, turning one short card into one very tall one with empty space
   under its text instead of a card sized to what it says. */
.bento{
  display:flex;flex-wrap:wrap;gap:14px;justify-content:center;align-items:center;
  align-content:center;flex:1;min-height:0;
}
.tile{
  position:relative;overflow:hidden;display:flex;flex-direction:column;min-height:0;cursor:pointer;
  background:var(--white);border:1px solid var(--border);border-radius:14px;
  box-shadow:var(--shadow-soft);padding:16px 18px;flex:1 1 300px;max-width:420px;
  transition:transform .18s cubic-bezier(.2,.8,.3,1.15),box-shadow .18s ease,border-color .18s ease;
}
.tile .tile-category{
  display:inline-block;font-size:10.5px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;
  color:var(--gold-dark);margin-bottom:4px;position:relative;z-index:1;
}
.tile:hover{
  transform:translateY(-5px);border-color:var(--gold);
  box-shadow:0 20px 36px -12px rgba(14,23,42,.22), 0 0 0 1px var(--gold-border);
}
.tile:active{
  transform:translateY(-1px) scale(.97);box-shadow:0 6px 14px -8px rgba(var(--navy-dark-rgb),.3);
  transition-duration:.05s;
}
.tile:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.tile::before{
  content:"";position:absolute;inset:0;pointer-events:none;z-index:2;
  background:linear-gradient(115deg,transparent 30%,rgba(var(--gold-rgb),.15) 45%,transparent 60%);
  transform:translateX(-120%);transition:transform .5s ease;
}
.tile:hover::before{transform:translateX(120%)}
.tile .ghost{
  position:absolute;top:-16px;right:-12px;color:var(--gold);
  opacity:.06;pointer-events:none;transition:opacity .2s,transform .3s ease;
}
.tile .ghost .ico{width:92px;height:92px}
.tile:hover .ghost{opacity:.16;transform:rotate(8deg) scale(1.08)}
.tile .icon{
  width:42px;height:42px;border-radius:11px;display:flex;align-items:center;justify-content:center;
  background:var(--navy-tint);color:var(--navy-dark);border:1px solid var(--navy-subtle);
  margin-bottom:12px;position:relative;z-index:1;
  transition:background .2s,color .2s,border-color .2s,transform .28s cubic-bezier(.2,.8,.3,1.4);
}
.tile .icon .ico{width:22px;height:22px}
.tile:hover .icon{
  background:linear-gradient(135deg, var(--navy-dark) 0%, var(--navy-deep) 100%);
  color:var(--gold-light);border-color:var(--gold);
  transform:scale(1.1) rotate(-6deg);
  box-shadow:0 4px 12px rgba(var(--navy-dark-rgb),.3);
}
.tile h3{font-size:17.5px;margin-bottom:4px;position:relative;z-index:1;color:var(--navy-dark)}
.tile p{font-size:13.5px;line-height:1.45;color:var(--muted);position:relative;z-index:1}
.tile .open{
  margin-top:12px;display:flex;align-items:center;gap:6px;position:relative;z-index:1;
  font-size:12.5px;font-weight:700;letter-spacing:.04em;color:var(--gold-dark);
  opacity:0;transform:translateY(4px);transition:opacity .18s,transform .18s,color .15s;
}
.tile:hover .open,.tile:focus-visible .open{opacity:1;transform:none}
.tile:hover .open .ico{animation:nudge .5s ease}
@keyframes nudge{50%{transform:translateX(4px)}}
.tile .open .ico{width:16px;height:16px}

.foot{margin-top:14px;font-size:11.5px;color:var(--muted);text-align:center;flex:0 0 auto}

@media (max-width:420px){
  .page{padding:14px 12px 20px}
  .hero{padding:16px 18px}
  .hero h1{font-size:22px}
}

@media (min-width:900px){
  .sidebar{transform:none}
  .main{margin-left:var(--sidebar-w)}
  .menu-btn{display:none}
  .backdrop{display:none}
  .topbar .brand-link,.topbar .divider{display:none}
  .hero{padding:22px 26px}
  .hero h1{font-size:29px}
  .page{padding:20px 26px 20px}
}

@media (min-width:1440px){
  .page{max-width:2100px;padding:26px 32px}
  .hero{padding:28px 34px}
  .hero h1{font-size:33px}
  .hero p{font-size:15.5px}
  .tile{padding:20px 22px}
  .tile h3{font-size:18.5px}
  .tile p{font-size:14px}
}

@media (min-width:900px) and (min-height:620px){
  body.locked{overflow:hidden}
  body.locked .main{height:100vh;overflow:hidden}
  body.locked .tile{justify-content:center}
}

/* ── Tool Pages / Data Grids ───────────────────────────────────────────── */
.page-head{
  display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;
  gap:14px;margin-bottom:8px;
}
.page-head h1{font-size:16px;line-height:1.25;color:var(--navy-dark)}
.page-head .sub{color:var(--muted);font-size:11.5px;margin-top:1px}

/* The edited week's date span. Previously the largest text on the entry page
   (a 22–30px clamp) — on a page a team reads every day, every pixel of that
   was pixels not spent on rows of actual numbers, so it now reads at roughly
   the weight of a card header rather than a banner. Tabular figures keep the
   two dates the same width, so a changed week is visible as a changed shape,
   not just changed digits. */
.week-dates{
  display:flex;flex-wrap:wrap;align-items:baseline;gap:.3em;
  margin-top:2px;font-family:var(--font-heading);
  font-size:15px;font-weight:700;line-height:1.3;
  color:var(--ink);letter-spacing:-.01em;font-variant-numeric:tabular-nums;
}
/* Each date stays whole; only the gap between them may break. */
.week-dates > span{white-space:nowrap}
.week-dates-dash{color:var(--muted);font-weight:700}

.card{
  background:var(--white);border:1px solid var(--border);border-radius:14px;
  box-shadow:var(--shadow-soft);margin-bottom:16px;overflow:hidden;
}
.card > header{
  padding:14px 18px;border-bottom:1px solid var(--line);
  display:flex;flex-wrap:wrap;align-items:center;gap:10px;background:var(--surface-alt);
}
.card > header h2{font-size:15px;letter-spacing:.01em;color:var(--navy-dark)}
.card > header .spacer{margin-left:auto}
.card .body{padding:16px 18px}

/* ── Buttons ── */
.btn{
  display:inline-flex;align-items:center;gap:7px;border:1px solid var(--border);
  background:var(--white);color:var(--ink);border-radius:8px;padding:8px 14px;
  font-size:13.5px;font-weight:700;font-family:var(--font-body);line-height:1.2;
  transition:background .15s,border-color .15s,color .15s,transform .05s,box-shadow .15s;
}
.btn:hover{background:var(--surface-alt);border-color:#c8d2de;color:var(--navy-dark)}
.btn:active{transform:translateY(1px)}
.btn:focus-visible{outline:2px solid var(--focus);outline-offset:2px}
.btn .ico{width:18px;height:18px}
/* Flat, not a gradient, and no gold on hover: this is the one action a form
   wants you to take, and it should read as a button rather than as decoration. */
.btn.primary{background:var(--navy-dark);border-color:var(--navy-dark);color:#fff}
.btn.primary:hover{background:var(--navy-mid);border-color:var(--navy-mid);color:#fff}
.btn.ghost{border-color:transparent;background:transparent;color:var(--navy-dark)}
.btn.ghost:hover{background:var(--surface-alt);color:var(--navy-dark)}
.btn[disabled],.btn.disabled{opacity:.45;pointer-events:none}

/* ── Week Stepper ── */
.weeknav{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.weeknav .btn{padding:7px 10px}

/* The week is stated once, here, at a size you can read without it shouting.
   It replaced a full-width banner in 34px type — clarity did not need that. */
.weeknav-label{
  font-family:var(--font-heading);font-size:17px;font-weight:800;
  color:var(--navy-dark);white-space:nowrap;padding:0 4px;
  font-variant-numeric:tabular-nums;
}
.btn.icon-only{padding:7px 9px}
.btn.icon-only .ico{width:18px;height:18px}
.btn.xs{font-size:12px;padding:4px 6px;border-radius:6px}
.btn.xs.icon-only{padding:4px 5px}
.btn.xs.icon-only .ico{width:16px;height:16px}
.complete-row-btn{color:var(--muted)}
.complete-row-btn:hover{color:var(--navy-dark);background:var(--surface-alt)}

/* Reachable by a screen reader, invisible on screen — used for the column
   header above the delete checkboxes, which needs no visible label. */
.sr-only{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0,0,0,0);white-space:nowrap;border:0;
}

/* ── Entry page: collapsible HP / BC/HAS sections ── */
.entry-section{
  margin-bottom:16px;border:1px solid var(--border);border-radius:14px;
  background:var(--white);box-shadow:var(--shadow-soft);overflow:hidden;
}
.entry-section-toggle{
  width:100%;display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:14px 18px;border:0;background:var(--white);
  border-bottom:1px solid transparent;cursor:pointer;text-align:left;
  transition:background .15s,border-color .15s;
}
.entry-section.open .entry-section-toggle{border-bottom-color:var(--line)}
.entry-section-toggle:hover{background:var(--surface-alt)}
.entry-section-title{
  display:flex;align-items:center;gap:10px;
  font-family:var(--font-heading);font-size:15.5px;font-weight:700;color:var(--navy-dark);
}
/* Both tags are the same neutral grey. Colour-coding HP navy and BC/HAS gold
   made the page look like two different apps; the label already says which. */
.entry-section-tag{
  display:inline-flex;align-items:center;justify-content:center;
  min-width:42px;padding:3px 8px;border-radius:6px;font-size:11px;font-weight:800;
  letter-spacing:.04em;background:var(--surface-alt);color:var(--muted);
  border:1px solid var(--line);
}
.entry-section-meta{display:flex;align-items:center;gap:10px}
.entry-chevron{
  width:22px;height:22px;display:flex;align-items:center;justify-content:center;
  color:var(--muted);transition:transform .2s ease,color .15s;
}
.entry-chevron .ico{width:22px;height:22px}
.entry-section.open .entry-chevron{transform:rotate(180deg);color:var(--navy-dark)}
.entry-section-body{display:none;padding:0 14px 14px}
.entry-section.open .entry-section-body{display:block}
.entry-section .entry-card{margin-bottom:0;border:0;box-shadow:none;border-radius:0}
.entry-section .entry-card > header{background:transparent;border-top:0;padding-top:8px}
.entry-section form{margin:0}

.modal-overlay{
  position:fixed;inset:0;z-index:80;display:flex;align-items:center;justify-content:center;
  padding:16px;background:rgba(14,23,42,.45);
}
.modal-overlay[hidden]{display:none !important}
.modal-card{
  width:min(100%,440px);background:var(--white);border-radius:14px;
  border:1px solid var(--border);box-shadow:var(--shadow-soft);overflow:hidden;
}
.modal-head{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding:16px 18px;border-bottom:1px solid var(--line);
}
.modal-head h2{font-size:18px;margin:0}
.modal-form{display:flex;flex-direction:column;gap:12px;padding:16px 18px 18px}
.modal-form label{display:flex;flex-direction:column;gap:6px;font-size:13px;font-weight:600;color:var(--muted)}
.modal-form input{width:100%}
.modal-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:4px}

/* ── Column picker (Data Entry) ──
   A baulist is 73 to 134 columns wide, so the picker is a scrolling multi-column
   grid rather than a list: 103 checkboxes in one column is a 40-screen dialog
   nobody can scan. Wider than the default .modal-card for the same reason, and
   the list — not the card — is what scrolls, so the actions and the Übernehmen
   button stay on screen while you work down the columns.

   Names are prefixed de- because this is the Data Entry app's picker and the
   reporting table already has its own (.heat-chooser-*) with different
   ergonomics; sharing one class here would couple two dialogs that are free to
   diverge. */
#columns-modal .modal-card{width:min(100%,56rem)}
.de-columns-actions{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
.de-columns-list{
  max-height:min(52vh,30rem);overflow:auto;
  border:1px solid var(--line);border-radius:10px;padding:10px 12px;
  display:grid;grid-template-columns:repeat(auto-fill,minmax(13rem,1fr));gap:2px 14px;
}
/* Overrides .modal-form label, which stacks its children for the text inputs
   the other modals are made of — here the checkbox belongs beside its name. */
.de-columns-list .de-column-option{
  flex-direction:row;align-items:center;gap:7px;
  font-size:12.5px;font-weight:600;color:var(--body-c);
  padding:3px 2px;cursor:pointer;min-width:0;
}
.de-column-option input{width:auto;flex:0 0 auto;margin:0;cursor:pointer}
.de-column-option code{
  min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  font-size:12px;color:var(--navy-dark);
}
.de-column-option:hover code{color:var(--gold-dark)}
.de-column-label{flex:0 0 auto;white-space:nowrap}
.de-column-label + code{font-size:11px;opacity:.7}

/* ── Row table (Data Entry) ──
   Up to 44 columns of a 52k-row table, thirty rows at a time. The whole table
   scrolls inside .table-wrap rather than taking the page with it, and the
   header row pins to the top of that scroller.

   No pinned data column. One was pinned at first and it earned its removal:
   `left` had to be hard-coded to the width of the # cell, so any change to that
   width slid the two apart, and the column it froze is whichever happens to be
   first in the person's own selection — which is not necessarily anything
   identifying.

   Cells do not wrap. A baulist holds free text people typed into a spreadsheet,
   and one long comment would otherwise set the row height for every column. */
.de-rows table.grid{white-space:nowrap}
.de-rows table.grid th,.de-rows table.grid td{
  max-width:22rem;overflow:hidden;text-overflow:ellipsis;
}
.de-rows table.grid thead th{position:sticky;top:0;z-index:2;background:var(--surface-alt)}
/* The row's number within the whole list. It scrolls away sideways with every
   other column — it used to be pinned to the left edge, and that was removed on
   request: a column that slides across the data while you scroll is a thing
   covering the sheet rather than a thing helping you read it.
   `position:relative` only so the colleague tag below has something to sit in;
   the header row keeps its own `position:sticky` top from the thead rule. */
.de-rows .de-rownum{
  position:relative;
  background:var(--surface-alt);text-align:right;
  font-variant-numeric:tabular-nums;color:var(--muted);font-size:12px;
  border-right:1px solid var(--line);
  /* Room for the colleague tag (.de-row-who), reserved on every row whether or
     not one is showing: letting this column grow the moment somebody else saves
     would shift all forty-four columns sideways under the hands of everybody
     looking at the page. */
  padding-left:34px;
}

/* ── Row selection ──
   The tick box left of the row number. Same surface as the number column so the
   two read as one gutter rather than as a column of data, and centred because a
   check box has no reading direction to align to. */
.de-rows .de-pick{
  background:var(--surface-alt);text-align:center;
  border-right:1px solid var(--line);
  width:1%;white-space:nowrap;padding:0 6px;
}
.de-rows .de-pick input{
  margin:0;cursor:pointer;vertical-align:middle;
  /* The browser default is ~13px and sits in a 44-column table of 12px text as
     a dot nobody can hit; this is the smallest that stays a target. */
  width:15px;height:15px;accent-color:var(--gold-dark);
}
/* The header's box, which is the select-all. Sticky like the rest of the header
   row — the thead rule provides the top offset. */
.de-rows table.grid thead th.de-pick{background:var(--surface-alt)}

/* ── Editable cells ──
   Every cell of the entry grid is an input that saves itself when it is left
   (static/data_entry/js/grid.js). The input is deliberately invisible until it
   is touched: a grid of 44 bordered boxes per row reads as a form to fill in,
   when what people are doing is reading a spreadsheet and correcting some of
   it. So the input looks exactly like the text it replaced, and only focus,
   hover and state say otherwise.

   `width:100%` with the cell's own max-width doing the sizing: an input's
   default width is 20 characters whatever column it is in, which on a
   44-column table is several screens of nothing. */
.de-rows td.de-cell{padding:0;position:relative}
.de-rows .de-cell-input{
  width:100%;min-width:4.5rem;padding:7px 10px;
  border:1px solid transparent;border-radius:0;background:transparent;
  font:inherit;color:inherit;
  text-overflow:ellipsis;
}
.de-rows .de-cell-input:hover:not(:disabled){
  background:var(--surface-alt);border-color:var(--line);
}
.de-rows .de-cell-input:focus{
  outline:none;background:var(--surface);
  border-color:var(--gold);box-shadow:var(--focus-ring);
  /* Lifted over its neighbours so the focus ring is not clipped by the next
     cell's overflow:hidden. */
  position:relative;z-index:1;
  text-overflow:clip;
}
/* Somebody who may read but not write. Not greyed into illegibility — the value
   is still the point of the page. */
.de-rows .de-cell-input:disabled{
  color:inherit;cursor:default;opacity:1;
}

/* ── Columns that are not an entry point ──
   The row's identity (city, street, hn …) plus area_pop and dp. Read-only
   rather than disabled, so the text stays selectable and copyable — people do
   paste an address into a ticket — but hatched and out of the tab order so it
   never reads as a field somebody forgot to fill in. */
.de-rows td.de-cell-locked{
  background:repeating-linear-gradient(
    135deg, transparent, transparent 5px,
    rgba(var(--navy-dark-rgb),.035) 5px, rgba(var(--navy-dark-rgb),.035) 10px);
}
.de-rows td.de-cell-locked .de-cell-input{
  color:var(--muted);cursor:default;
}
.de-rows td.de-cell-locked .de-cell-input:hover,
.de-rows td.de-cell-locked .de-cell-input:focus{
  background:transparent;border-color:transparent;box-shadow:none;
}

/* A status dropdown. Sized like every other cell — a <select> would otherwise
   be as wide as its longest option, and "PNAS - Denied za naplata" would set
   the column width for the whole table. */
.de-rows .de-cell-select{
  width:100%;min-width:7rem;
  appearance:none;-webkit-appearance:none;
  padding-right:18px;
  background-image:linear-gradient(45deg,transparent 50%,var(--muted) 50%),
                   linear-gradient(135deg,var(--muted) 50%,transparent 50%);
  background-position:calc(100% - 11px) calc(50% - 1px),calc(100% - 7px) calc(50% - 1px);
  background-size:4px 4px,4px 4px;background-repeat:no-repeat;
}
.de-rows .de-cell-select:hover{background-color:var(--surface-alt)}

/* A date column whose value is not a date — a tick (x, F, Ja) left over from
   the spreadsheet. Marked, not corrected: it is somebody's record that the step
   is done, and the only honest fix is for a person to put the real date in. */
.de-rows td.de-cell-stale-date .de-cell-input{
  color:var(--warn);font-style:italic;
}
.de-rows td.de-cell-stale-date::after{
  content:"";position:absolute;top:3px;right:3px;
  width:5px;height:5px;border-radius:50%;background:var(--warn);
  pointer-events:none;
}

/* ── The expanded comment editor ──
   A cell holding a running history opens over the table rather than inside its
   column: a comment is 200 characters and the column is 12 wide, so editing it
   in place is the problem, not the layout.

   Absolute, so the row keeps its height and the thirty rows below do not jump.
   It deliberately overflows its cell to the right and down — anchored at the
   cell so you can still see which row you are in. */
.de-rows td.de-cell.is-expanded{z-index:7}
.de-rows .de-cell-expand{
  position:absolute;left:0;top:0;z-index:8;
  width:30rem;max-width:min(30rem,70vw);min-height:7.5rem;
  padding:8px 10px;
  font:inherit;line-height:1.5;color:var(--ink);
  background:var(--surface);
  border:1px solid var(--gold);border-radius:0 8px 8px 8px;
  box-shadow:var(--shadow-std);
  resize:both;white-space:pre-wrap;
}
.de-rows .de-cell-expand:focus{outline:none;box-shadow:var(--shadow-std),var(--focus-ring)}
/* The cell underneath keeps its own highlight while the editor is open, so a
   conflict or a remote change on it is still visible behind the panel. */
.de-rows td.de-cell.is-expanded .de-cell-input{visibility:hidden}

/* The native picker indicator is dark and heavy next to 44 text cells; keep it
   quiet until the cell is hovered or focused. */
.de-rows .de-cell-date::-webkit-calendar-picker-indicator{opacity:.3}
.de-rows .de-cell-date:hover::-webkit-calendar-picker-indicator,
.de-rows .de-cell-date:focus::-webkit-calendar-picker-indicator{opacity:1}

/* An empty date cell has to *look* empty. A date input with no value is not
   blank on screen: the browser fills it with its own format hint — dd.mm.yyyy
   in Chrome, tt.mm.jjjj in a German Firefox — and in a column of thirty cells
   that is indistinguishable from thirty filled ones at a glance. People read
   the grid as "this is already done" and skip the row.

   `color:transparent` rather than `::-webkit-datetime-edit{opacity:0}`, which
   was the obvious way and only works in Chrome and Safari: Firefox has no
   equivalent pseudo-element, so the hint would have stayed exactly where the
   problem was reported. Colour is a property every engine has.

   Revealed on hover and on focus, so the format is there the moment somebody
   means to type — and the faint calendar indicator above stays visible either
   way, which is what still says "this is a date column" rather than a cell
   nobody bothered to label. The class is kept in step by grid.js. */
/* `input.` is load-bearing: the <td> carries `de-cell-date` too (it renders as
   de-cell-{kind}), and `color` inherits — so without it a marked cell turned
   every date in it transparent, filled ones included. */
.de-rows input.de-cell-date.is-empty:not(:focus):not(:hover){color:transparent}

/* Cell state, as a left edge rather than a background: a filled cell still has
   to be readable, and four background colours across a dense grid is noise. */
.de-rows td.de-cell.is-saving .de-cell-input{opacity:.55}
/* Saved. The tint on the *input* is the part that matters and was missing:
   a focused input paints `background:var(--surface)` over the cell, so an
   edge-only indicator is invisible for exactly the cell somebody just pressed
   Enter in — which is the one they are looking at. is-error and is-conflict
   below always tinted their input, which is why failures showed and successes
   did not. Specificity beats the :focus rule above on purpose. */
.de-rows td.de-cell.is-saved{box-shadow:inset 3px 0 0 var(--ok)}
.de-rows td.de-cell.is-saved .de-cell-input{background:var(--ok-tint)}
.de-rows td.de-cell.is-error{box-shadow:inset 3px 0 0 var(--err)}
.de-rows td.de-cell.is-error .de-cell-input{background:var(--err-tint)}
.de-rows td.de-cell.is-conflict{box-shadow:inset 3px 0 0 var(--warn)}
.de-rows td.de-cell.is-conflict .de-cell-input{background:var(--warn-tint)}
.de-rows td.de-cell.is-merged{box-shadow:inset 3px 0 0 var(--navy-mid)}
/* Somebody else's save, just applied. The accent rather than a semantic colour:
   nothing is wrong, and green/amber/red here would read as a verdict on a value
   a colleague entered. Hover gives the name. */
.de-rows td.de-cell.is-remote{box-shadow:inset 3px 0 0 var(--gold)}
.de-rows td.de-cell.is-remote .de-cell-input{background:var(--gold-tint)}
.de-rows td.de-cell.is-remote{transition:box-shadow .6s ease-out}
/* The flash fades; the error and conflict edges stay until they are resolved. */
.de-rows td.de-cell.is-saved,
.de-rows td.de-cell.is-saved .de-cell-input{transition:box-shadow .5s ease-out,background .5s ease-out}

/* The conflict note, under the cell it belongs to. Absolute so it cannot change
   the row's height — thirty rows jumping down to make room for one message is
   worse than the message being an overlay. */
.de-rows .de-cell-conflict{
  position:absolute;left:0;top:100%;z-index:6;
  display:flex;align-items:center;gap:8px;
  min-width:17rem;padding:8px 10px;
  background:var(--surface);border:1px solid var(--warn);
  border-left:3px solid var(--warn);border-radius:0 8px 8px 0;
  box-shadow:var(--shadow-soft);
  white-space:normal;
}
.de-rows .de-cell-conflict-who{font-size:12px;color:var(--navy-dark);flex:1}

/* ── Save status ──
   Where a Speichern button would be. Three states, and the only one that must
   be impossible to miss is the one that says something is not saved. */
.de-save-status{
  margin-left:auto;font-size:12.5px;white-space:nowrap;
  display:inline-flex;align-items:center;gap:6px;
}
.de-save-status::before{
  content:"";width:7px;height:7px;border-radius:50%;background:currentColor;
  flex:none;
}
.de-save-status.is-idle{color:var(--muted)}
.de-save-status.is-saving{color:var(--gold-dark)}
.de-save-status.is-error{color:var(--err);font-weight:700}

/* ── Filtered export ──
   Sits at the right end of the Erfassung header, after the save status (which
   carries the margin-left:auto that pushes both over). A form rather than a
   link because it POSTs with the page's CSRF token, so it needs its own
   zero-margin box to not disturb the header's baseline. */
.de-export-form{margin:0;display:inline-flex;align-items:center}
/* Same reason as .de-export-form above: a <form> is block-level by default, so
   without this the push-back button breaks onto its own line and drops out of
   the header's row of controls — which is how it came to be "invisible". */
.de-push-form{margin:0;display:inline-flex;align-items:center}
/* Reconnected after a drop: messages were missed, so the page is knowingly
   behind. Not an error — nothing failed — but not silent either. */
.de-save-status.is-stale{color:var(--gold-dark)}
@media (prefers-reduced-motion:reduce){
  .de-rows td.de-cell.is-saved,
  .de-rows td.de-cell.is-saved .de-cell-input{transition:none}
}

/* ── Comment cells and their dialog (Data Entry) ──
   The komentar columns hold a log, not a value — years of appended notes in a
   cell twelve characters wide (see static/data_entry/js/grid.js). So the cell
   is a button onto the dialog rather than a field: read-only, obviously
   clickable, and carrying the one fact the column cannot show, which is how
   many notes are in there. */
.de-rows td.de-cell-comment .de-cell-input{
  cursor:pointer;
  /* The text fades out before the badge instead of being padded away from it:
     `overflow:hidden` clips a textarea at its *padding* box, so padding-right
     reserves the space without keeping the text out of it — the value simply
     ran on underneath the number. A mask is independent of what is behind the
     cell, which matters because that changes with hover and with every save
     state, and the fade reads as "there is more", which there always is. */
  -webkit-mask-image:linear-gradient(to right,
    #000 calc(100% - 2.6rem), transparent calc(100% - 1.6rem));
  mask-image:linear-gradient(to right,
    #000 calc(100% - 2.6rem), transparent calc(100% - 1.6rem));
}
/* It is a <textarea rows="1">, because an <input> cannot hold a value with a
   newline in it (see the template). Everything here is to make it read as the
   single-line cell it sits in a row of: no resize grabber, no wrapping, no
   scrollbars, and a height pinned to one line rather than left to the browser's
   two-row default. */
.de-rows .de-cell-comment-text{
  display:block;height:2.1rem;
  white-space:pre;overflow:hidden;resize:none;
  vertical-align:middle;
}
.de-rows td.de-cell-comment .de-cell-input:hover{
  background:var(--gold-tint);border-color:var(--gold-border);
}
/* The count, in the cell's right edge. Pointer-events off so the click lands on
   the cell and opens the dialog from here as well. */
.de-comment-badge{
  position:absolute;top:50%;right:5px;transform:translateY(-50%);
  min-width:1.15rem;padding:1px 4px;border-radius:5px;
  font-size:10.5px;font-weight:800;line-height:1.5;text-align:center;
  font-variant-numeric:tabular-nums;
  background:var(--navy-subtle);color:var(--navy-dark);
  pointer-events:none;
}
.de-rows td.de-cell-comment:hover .de-comment-badge{
  background:var(--gold);color:var(--white);
}
/* A note typed into the dialog and never added. Visible on purpose: the draft
   is kept in memory and beforeunload warns about it, and a warning about
   something nobody can see is worse than no warning. */
.de-rows td.de-cell-comment.has-draft{box-shadow:inset 3px 0 0 var(--gold)}
.de-rows td.de-cell-comment.has-draft .de-comment-badge{
  background:var(--gold);color:var(--white);
}
.de-rows td.de-cell-comment.has-draft::after{
  content:"";position:absolute;top:3px;left:6px;
  width:5px;height:5px;border-radius:50%;background:var(--gold);
  pointer-events:none;
}

/* ── The dialog ──
   Wider than the default .modal-card and capped in height, because the thing
   it shows is a list that can be forty entries long. The card is the column:
   head, writing box, status, the scrolling history, then the footer — so the
   writing box and the Hinzufügen button never scroll away. */
#comment-modal .modal-card{
  width:min(100%,42rem);max-height:min(88vh,46rem);
  display:flex;flex-direction:column;
}
#comment-modal .modal-head{align-items:flex-start}
.de-comment-ident{min-width:0}
.de-comment-ident h2{font-size:15px;margin:0}
.de-comment-ident code{
  font-size:14px;font-weight:700;color:var(--navy-dark);
}
.de-comment-where{
  margin:3px 0 0;font-size:12.5px;color:var(--muted);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}

.de-comment-compose{
  display:flex;flex-direction:column;gap:8px;
  padding:14px 18px;border-bottom:1px solid var(--line);
  background:var(--surface-alt);
}
.de-comment-input{
  width:100%;padding:9px 11px;
  font:inherit;font-size:14px;line-height:1.55;color:var(--ink);
  background:var(--white);border:1px solid var(--border);border-radius:9px;
  resize:vertical;
}
.de-comment-input:focus{
  outline:none;border-color:var(--gold);box-shadow:var(--focus-ring);
}
.de-comment-compose-foot{
  display:flex;flex-wrap:wrap;align-items:center;gap:10px;
}
/* The exact prefix the note will be stored with, in the same monospace the
   column names use — it is literal text that is about to land in the sheet,
   not a label about it. */
.de-comment-byline{
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:11.5px;font-weight:700;color:var(--gold-dark);
  background:var(--gold-tint);border:1px solid var(--gold-border);
  border-radius:6px;padding:2px 6px;white-space:nowrap;
}
.de-comment-hint{font-size:11.5px;color:var(--muted)}
.de-comment-compose-foot .btn{margin-left:auto}

.de-comment-note{
  margin:0;padding:9px 18px;font-size:12.5px;font-weight:600;
  border-bottom:1px solid var(--line);
}
.de-comment-note[hidden]{display:none}
.de-comment-note.is-busy{background:var(--surface-alt);color:var(--muted)}
.de-comment-note.is-ok{background:var(--ok-tint);color:var(--ok)}
.de-comment-note.is-warn{background:var(--warn-tint);color:var(--navy-dark)}
.de-comment-note.is-err{background:var(--err-tint);color:var(--err)}

/* The history. The one part that scrolls, so a forty-entry cell still has its
   writing box on screen. */
.de-comment-list{
  flex:1 1 auto;min-height:0;overflow:auto;
  display:flex;flex-direction:column;gap:7px;
  padding:14px 18px;
}

.de-comment-none{
  margin:0;padding:10px 2px;font-size:13px;color:var(--muted);
}

/* One note. Newest first, which is the order this column is already written
   in — so the top card is the one somebody is looking for. */
.de-note{
  padding:8px 11px;
  background:var(--white);border:1px solid var(--line);
  border-left:3px solid var(--navy-subtle);border-radius:0 9px 9px 0;
}
.de-note-head{
  display:flex;flex-wrap:wrap;align-items:baseline;gap:8px;margin-bottom:3px;
}
.de-note-date{
  font-size:11.5px;font-weight:800;color:var(--navy-dark);
  font-variant-numeric:tabular-nums;
}
.de-note-who{
  font-size:11.5px;font-weight:700;color:var(--gold-dark);
  text-transform:uppercase;letter-spacing:.03em;
}
.de-note-body{
  margin:0;font-size:13.5px;line-height:1.55;color:var(--body-c);
  /* pre-wrap: a note of its own often runs over several lines, and the dialog
     is the first place anybody has been able to see that. */
  white-space:pre-wrap;overflow-wrap:anywhere;
}
/* A status flip the POM import wrote — "(29.03.2025 denied)". Quieter than a
   note a person typed, because the history is mostly these and the human notes
   are what somebody opened the dialog to read. */
.de-note.is-system{
  background:var(--surface-alt);border-left-color:var(--border);
}
.de-note.is-system .de-note-body{color:var(--muted);font-size:12.5px}

.de-comment-foot{
  display:flex;align-items:center;gap:10px;
  padding:11px 18px;border-top:1px solid var(--line);background:var(--surface-alt);
}
.de-comment-count{font-size:12px;color:var(--muted);font-variant-numeric:tabular-nums}
.de-comment-foot .btn{margin-left:auto}

/* The whole cell as one string. Second step behind a button, and it looks like
   one: a plain monospace textarea, because what it edits is the stored text
   and nothing about it should suggest otherwise. */
.de-comment-rawbox{
  padding:14px 18px;border-top:1px solid var(--line);
}
.de-comment-rawbox[hidden]{display:none}
.de-comment-rawbox .de-comment-input{
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:12.5px;line-height:1.6;
}


/* ── Resizable columns (Data Entry) ──
   Drag the divider on a header's right edge, Excel-style; the width is stored
   per person per baulist (models.CustomTable.column_widths) and comes back on
   the next load. Mechanics in static/data_entry/js/resize.js.

   `is-sized` is the layout switch and it is only ever on a table that has
   widths to honour: a `width` under automatic layout is a suggestion the
   browser overrules to fit content, so dragging only sticks under
   `table-layout:fixed` — and under fixed layout every column needs a width, so
   a table nobody has resized is deliberately left alone. */
.de-rows table.grid.is-sized{table-layout:fixed}
/* The row-number column keeps sizing itself. It is the sticky left edge every
   other position on the page is measured from, and it has exactly one thing in
   it. */
.de-rows table.grid.is-sized col.de-col-num{width:auto}
/* The tick-box column, beside it, for the same reason. */
.de-rows table.grid.is-sized col.de-col-pick{width:auto}
/* Under fixed layout the cell is the authority on width, so an input that
   insists on 4.5rem is simply wider than its cell and gets clipped mid-word
   rather than ellipsised. Letting it shrink is what makes a narrow column
   readable instead of cut. */
.de-rows table.grid.is-sized .de-cell-input,
.de-rows table.grid.is-sized .de-cell-select{min-width:0}

/* The grip: the divider itself, not a button beside it. Sits in the header's
   right padding and overlaps the gap between the columns, which is where a
   spreadsheet puts it and where people reach for it. */
.de-rows table.grid thead th{position:relative}
.de-col-grip{
  /* Entirely inside its own header, and that is load-bearing rather than
     tidy. The obvious version straddled the divider (`right:-3px`) so the grab
     area was centred on it — but every `thead th` is `position:sticky` with the
     same z-index, so the next header, being later in the DOM, painted over the
     half that overhung it. The grip looked 7px wide and was 4px wide, and which
     4px depended on which column: the first drag of a column landed on dead
     pixels about half the time, which reads as resizing being broken rather
     than as having missed by three pixels.

     So the box stops at the header's edge and the visible line is drawn on that
     edge instead — the grip still looks like it sits on the divider, and all of
     it is clickable. */
  position:absolute;top:0;right:0;bottom:0;width:9px;
  cursor:col-resize;z-index:3;
  /* Invisible until the header is approached: forty-four vertical lines across
     a header that already has column names and filter arrows in it is noise,
     and the gap between columns already reads as a divider. */
  opacity:0;transition:opacity .12s;
}
.de-col-grip::after{
  content:"";position:absolute;top:18%;bottom:18%;right:0;width:2px;
  border-radius:1px;background:var(--gold);
}
.de-rows table.grid thead th:hover .de-col-grip,
.de-col-grip:hover,
.de-col-grip.is-dragging{opacity:1}
.de-col-grip.is-dragging::after{top:0;bottom:0;background:var(--gold-dark)}

/* While a drag is running. On <html> so it holds wherever the pointer goes:
   people drag past the table, and a cursor that reverts halfway makes the
   gesture feel dropped. `user-select` stops the drag from selecting the header
   text of every column it crosses. */
html.de-resizing,
html.de-resizing *{cursor:col-resize !important;user-select:none}

/* ── Where you are, and where everybody else is (Data Entry) ──
   Forty-four near-identical columns and thirty rows of the same street: the one
   mistake this grid makes easy is working the row above the one you meant, or
   the row a colleague is already in. Two marks, driven from
   static/data_entry/js/grid.js.

   `background-color` and never the `background` shorthand: the locked columns
   are hatched with a background-image, and the shorthand would quietly erase
   the hatch on whichever row happened to have the cursor in it.

   ── Why the live row is blue, which is in neither theme ──

   The first attempt tinted it with `--navy-tint`, on the reasoning that the
   chrome's own colour is the safe choice. It was invisible. The Data Entry
   theme repaints every `--navy-*` token warm (#fdf3ec), the locked columns are
   already hatched in warm grey, and a colleague's row is gold — so the page a
   clerk looks at is cream from edge to edge, and one more pale warm band in it
   reads as nothing at all.

   So the live row gets a colour that is in *neither* palette and is reserved
   for this one meaning. It is not brand chrome, it is a cursor: it has to be
   impossible to confuse with a value, with a status, with a locked column, or
   with a colleague, and the only way to guarantee that is to keep it out of the
   range the rest of the page uses. Stated literally rather than through a token
   for the same reason — a token is something another theme can repaint, and
   this must survive that. */
:root{
  --live-row-bg:#e4efff;
  --live-row-bg-strong:#cde0fb;
  --live-row-edge:#2a78d6;
  --live-row-ink:#17365e;
}

/* ── The crosshair: the row and column of the cell with focus ──
   The row is a band, the column a wash, their intersection the strongest of the
   three, and the row number filled solid.

   The band across the whole row is what carries this now that the number no
   longer travels with the scroll: forty columns to the right you are still
   inside a blue stripe with a blue rule above and below it. */
.de-rows table.grid tbody tr.is-active > td{
  background-color:var(--live-row-bg);
  /* A band, not just a fill: the rules above and below are what make it read
     as one row lifted out of thirty rather than a cell that changed colour.
     2px, because against a fogged table this edge is the sharpest thing on the
     page and is doing most of the work. */
  border-top:2px solid var(--live-row-edge);
  border-bottom:2px solid var(--live-row-edge);
}
/* Close the band at both ends, so it is a box around the row rather than two
   rules that run off the sides. */
.de-rows table.grid tbody tr.is-active > td:first-child{
  border-left:2px solid var(--live-row-edge);
}
.de-rows table.grid tbody tr.is-active > td:last-child{
  border-right:2px solid var(--live-row-edge);
}
/* Lifted clear of the fog: a blurred row paints over its neighbours' edges
   otherwise, and the one row that must stay crisp is this one. */
.de-rows table.grid tbody tr.is-active{position:relative;z-index:2}
.de-rows table.grid td.is-active-col{background-color:var(--live-row-bg)}
.de-rows table.grid tbody tr.is-active > td.is-active-col{
  background-color:var(--live-row-bg-strong);
}
.de-rows table.grid tbody tr.is-active > td .de-cell-input{color:var(--live-row-ink)}
.de-rows table.grid tbody tr.is-active > td.de-rownum{
  background-color:var(--live-row-edge);
  color:#fff;font-weight:800;
}

/* The fog (blurring/dimming every row but the live one while a cell has
   focus) was removed on request: the live row's own band above is the
   highlight, on its own, with no dimming of the rest of the table beside it. */

/* ── A colleague's row ──
   Gold, which is already this grid's colour for "somebody else did this"
   (.is-remote on a single cell). The row-level version says it for the whole
   row and for columns this viewer has hidden, where the cell mark cannot.

   Gold against the live row's blue, and that pairing is the point: the two
   marks have to be told apart at a glance, because the dangerous case is a row
   that is both. */
.de-rows table.grid tbody tr.has-colleague > td{
  background-color:var(--gold-tint);
  border-top:1px solid var(--gold);
  border-bottom:1px solid var(--gold);
}
.de-rows table.grid tbody tr.has-colleague > td.de-rownum{
  background-color:var(--gold);color:#fff;font-weight:800;
}
/* Both at once — you are in the row somebody else just wrote. The collision
   this whole feature exists to prevent, so it is allowed to look like neither
   of the two calm states: their gold band, your blue fill under the number. */
.de-rows table.grid tbody tr.has-colleague.is-active > td{
  background-color:var(--gold-tint);
}
.de-rows table.grid tbody tr.has-colleague.is-active > td.is-active-col{
  background-color:#fbe6c8;
}
.de-rows table.grid tbody tr.has-colleague.is-active > td.de-rownum{
  background-color:var(--live-row-edge);
  box-shadow:inset 0 -3px 0 var(--gold);
}

/* Their initials, in the row-number cell. The space is reserved on every row
   whether or not there is a tag in it: letting the column grow when somebody
   saves would shift all forty-four columns sideways under the hands of
   everybody else looking at the page. */
.de-row-who{
  position:absolute;left:4px;top:50%;transform:translateY(-50%);
  padding:1px 4px;border-radius:5px;
  font-size:9.5px;font-weight:800;line-height:1.5;letter-spacing:.02em;
  font-family:var(--font-heading);
  background:var(--navy-dark);color:#fff;
  pointer-events:none;
}
/* On a gold row-number the gold tag would vanish into it; on the blue one it
   has to stop being dark navy for the same reason. */
.de-rows table.grid tbody tr.has-colleague.is-active > td.de-rownum .de-row-who{
  background:var(--gold);
}

/* ── Sticky horizontal scrollbar (Data Entry) ──
   .table-wrap's own scrollbar sits at the table's foot, a full screen of
   scrolling down on a page of thirty rows. This is that same scroll range,
   mirrored into a track pinned to the bottom of the viewport by hscroll.js,
   so the far-right columns are reachable without leaving the row you are in.

   `fixed`, not `sticky`: .card has `overflow:hidden` for its rounded corners
   (used on every card in the app), and that ancestor becomes a sticky
   element's containing block whether or not it ever actually scrolls — which
   this one doesn't, the page does — so a sticky child of it never sticks at
   all. `fixed` is anchored to the viewport regardless, same as .de-filter-panel
   elsewhere on this page; hscroll.js positions and hides it to match
   .table-wrap's own left edge, width and in-view state, recomputed on scroll
   and resize since nothing here is still once the sidebar can expand. */
.de-hscroll{
  position:fixed;bottom:0;z-index:5;
  height:12px;
  background:var(--surface-alt);border-top:1px solid var(--line);
}
.de-hscroll.is-hidden{display:none}
/* Drawn by hand rather than left to this div's own overflow: macOS and iOS
   draw an overlay scrollbar that stays invisible until a scroll gesture is
   already under way, which defeats a bar whose whole point is to be seen
   before that gesture starts. */
.de-hscroll-thumb{
  position:absolute;top:2px;height:8px;border-radius:4px;
  background:var(--muted);opacity:.5;cursor:grab;
}
.de-hscroll-thumb:hover,.de-hscroll-thumb.is-dragging{
  background:var(--navy-mid);opacity:.85;cursor:grabbing;
}
/* The table's own scrollbar still exists underneath — same scroll range,
   just no longer the only way to reach it — so it is hidden rather than
   removed: the mirror above remains the single bar the eye finds, instead of
   two close together whenever the table's own foot happens to be in view. */
.de-rows.table-wrap,.has-hscroll.table-wrap{scrollbar-width:none}
.de-rows.table-wrap::-webkit-scrollbar,.has-hscroll.table-wrap::-webkit-scrollbar{display:none}

.de-pager{
  display:flex;flex-wrap:wrap;align-items:center;gap:10px;
  padding:12px 18px;border-top:1px solid var(--line);background:var(--surface-alt);
}
.de-pager .de-pager-state{
  margin-left:auto;font-size:12.5px;color:var(--muted);
  font-variant-numeric:tabular-nums;white-space:nowrap;
}
.de-pager [aria-disabled="true"]{opacity:.45;pointer-events:none}

/* ── Search bar + chips (Data Entry) ──
   Same chip vocabulary the Termin app's search bar uses (.search-chip /
   .zip-mark, see static/assets/js/baulist-search.js) so the two read as one
   feature — but styled here, because that one lives in the other service's
   static tree and this app never loads it. No JS behind these: the chips are
   server-rendered from the URL's ?q= terms. */
/* The search is the page's opening move on a 52k-row table, so it is sized like
   one: its own banner under the heading, a tall input and a 17px value. The
   gold focus ring is the chrome's accent — in the Data Entry app theme.css
   repaints that orange, so this picks it up without naming a colour. */
.de-searchbar{
  margin:0 0 16px;padding:16px 18px;
  background:var(--white);border:1px solid var(--border);border-radius:14px;
  box-shadow:var(--shadow-soft);
}
.de-search{display:flex;flex-wrap:wrap;align-items:center;gap:10px}
.de-search .de-search-input{
  flex:1 1 360px;min-width:220px;
  height:48px;padding:0 16px;font-size:17px;font-weight:600;
  border-radius:10px;
}
.de-search .de-search-input::placeholder{font-weight:500;color:var(--muted)}
.de-search .de-search-input:focus-visible{
  outline:2px solid var(--gold);outline-offset:1px;border-color:var(--gold);
}
.de-search .de-search-go{height:48px;padding:0 22px;font-size:15px;font-weight:700}
.de-search .btn.ghost{height:48px;padding:0 16px}
.de-search-chips{
  display:flex;flex-wrap:wrap;align-items:center;gap:6px;
  margin-top:12px;
}
@media (max-width:640px){
  .de-search .de-search-input,.de-search .de-search-go,.de-search .btn.ghost{height:44px}
  .de-search .de-search-input{font-size:16px}
}
.search-chip{
  display:inline-flex;align-items:center;gap:6px;
  font-size:12px;font-weight:700;color:var(--navy-dark);
  background:var(--surface-alt);border:1px solid var(--line);
  border-radius:20px;padding:4px 6px 4px 10px;max-width:22rem;
}
.search-chip .search-chip-value{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.search-chip .zip-mark{
  font-size:9.5px;font-weight:800;letter-spacing:.06em;
  background:var(--navy-dark);color:#fff;border-radius:4px;padding:2px 4px;
}
/* A real link, so middle-click and Back behave; sized like a button because
   that is what it is for. */
.search-chip .search-chip-x{
  display:inline-flex;align-items:center;justify-content:center;
  width:17px;height:17px;border-radius:50%;flex:0 0 auto;
  font-size:13px;line-height:1;text-decoration:none;
  color:var(--muted);background:rgba(var(--navy-dark-rgb),.07);
}
.search-chip .search-chip-x:hover{background:var(--err);color:#fff}
.search-chip.is-filter .zip-mark{
  background:var(--gold-dark);font-family:var(--font-mono,ui-monospace,monospace);
  text-transform:none;letter-spacing:0;
}

/* ── Per-column filter panel (Data Entry) ──
   Excel's autofilter dropdown. position:fixed because the table scrolls inside
   .table-wrap — anchored to its header cell it would ride off-screen with the
   column — so the open handler sets left/top from the button's viewport rect. */
.de-th{display:inline-flex;align-items:center;gap:6px;width:100%}
/* The workbook's header over the field name — see data_entry/labels.py. The
   field name stays, smaller, because "Name" heads five Telekom columns. */
.de-th-name{display:flex;flex-direction:column;min-width:0;line-height:1.15}
.de-th-label{font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.de-th-name code{font-size:10.5px;opacity:.65;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.de-filter-btn{
  flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
  width:18px;height:18px;padding:0;border:0;border-radius:4px;cursor:pointer;
  background:transparent;color:var(--muted);opacity:.5;
}
.de-filter-btn .ico{width:14px;height:14px}
.de-filter-btn:hover{opacity:1;background:rgba(var(--navy-dark-rgb),.08);color:var(--navy-dark)}
.de-filter-btn.is-on{opacity:1;color:var(--gold-dark);background:rgba(var(--gold-rgb),.16)}
.de-filter-btn[aria-expanded="true"]{opacity:1;background:rgba(var(--navy-dark-rgb),.12)}
/* The header's own sort arrow — set once, server-rendered, independent of
   whether the filter button next to it exists for this column. */
.de-sort-mark{flex:0 0 auto;font-size:9px;line-height:1;color:var(--gold-dark)}

.de-filter-panel{
  position:fixed;z-index:90;width:min(22rem,calc(100vw - 16px));
  display:flex;flex-direction:column;gap:8px;padding:12px;
  background:var(--white);border:1px solid var(--border);border-radius:12px;
  box-shadow:0 12px 32px rgba(14,23,42,.18);
}
.de-filter-panel[hidden]{display:none !important}
.de-filter-head{display:flex;align-items:center;justify-content:space-between;gap:8px}
.de-filter-head code{font-size:12.5px;font-weight:700;color:var(--navy-dark)}
/* Excel's sort entries, above the search box and the value list they share a
   panel with. */
.de-filter-sort{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.de-filter-sort .btn{display:inline-flex;align-items:center;gap:5px}
.de-filter-sort .btn.is-on{color:var(--gold-dark);background:rgba(var(--gold-rgb),.16)}
.de-filter-sort .de-sort-mark{font-size:10px}
#filter-panel-sort-clear{margin-left:auto}
.de-filter-sep{border:0;border-top:1px solid var(--line);margin:2px 0}
.de-filter-search{width:100%}
.de-filter-actions{display:flex;align-items:center;gap:6px}
.de-filter-count{margin-left:auto;font-size:11.5px;color:var(--muted);font-variant-numeric:tabular-nums}
.de-filter-values{
  max-height:min(44vh,22rem);overflow:auto;
  border:1px solid var(--line);border-radius:8px;padding:6px 8px;
}
.de-filter-values .hint{padding:8px 2px}
.de-filter-option{
  display:flex;align-items:center;gap:8px;padding:3px 2px;cursor:pointer;
  font-size:12.5px;color:var(--body-c);min-width:0;
}
.de-filter-option input{flex:0 0 auto;margin:0;cursor:pointer}
.de-filter-option .de-filter-value{
  flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.de-filter-option .de-filter-n{
  flex:0 0 auto;font-size:11px;color:var(--muted);font-variant-numeric:tabular-nums;
}
.de-filter-option:hover{background:var(--surface-alt);border-radius:4px}
.de-filter-foot{display:flex;align-items:center;justify-content:space-between;gap:8px}
table.grid tr.row-flagged{background:var(--status-flagged-bg,#fef2f2)}
table.grid .leader-email-form{display:flex;align-items:center;gap:8px}
table.grid .leader-email-input{flex:1 1 220px;min-width:160px}
table.grid .row-project-name{display:block;font-size:12px;color:var(--muted);margin-top:2px}
table.grid .row-project-name-primary{display:block;font-weight:700;font-size:13px;color:var(--navy-dark);margin-bottom:2px}
table.grid td.w-code:has(.row-project-name-primary) input{color:var(--muted);font-size:12px}
/* Entry column headers: owner on top, metric label, small column total — fixed
   three-row stack so every column lines up (Projektleiter included). */
table.grid thead th.col-stacked{vertical-align:bottom;padding-bottom:9px}
table.grid thead th.col-stacked .col-header-stack{
  display:grid;grid-template-rows:1.35em 1.15em 1.15em;align-content:end;
  gap:2px;min-height:3.75em;
}
table.grid th.col-stacked .col-owner{
  display:block;font-weight:800;font-size:13px;color:var(--navy-dark);letter-spacing:.01em;
  line-height:1.35;align-self:end;
}
table.grid th.col-stacked .col-metric{
  display:block;font-size:10.5px;font-weight:700;text-transform:uppercase;
  letter-spacing:.05em;color:var(--muted);align-self:end;line-height:1.15;
}
table.grid th.col-stacked .col-total{
  display:block;font-size:11px;font-weight:700;font-variant-numeric:tabular-nums;
  color:var(--body-c);align-self:end;line-height:1.15;opacity:.85;
}
table.grid th.col-stacked .col-stack-spacer{visibility:hidden;pointer-events:none;user-select:none}
table.grid td.w-leader-entry{min-width:9rem;vertical-align:middle}

/* The goal is a target the counts are read against, so it sits slightly apart
   from them rather than looking like a fourth weekly figure. */
table.grid td.goal-cell{border-right:1px solid var(--border);background:var(--surface-alt)}
table.grid td.goal-cell input{background:#fff}

table.grid td.w-leader-entry .leader-cell{
  display:flex;align-items:flex-start;gap:8px;
}
table.grid td.w-leader-entry .leader-cell-text{flex:1;min-width:0}
table.grid td.w-leader-entry .leader-display-name{
  display:block;font-weight:700;font-size:13px;color:var(--navy-dark);line-height:1.3;
}
table.grid td.w-leader-entry .leader-display-email{
  display:block;font-size:11px;color:var(--muted);margin-top:2px;word-break:break-all;
}
table.grid td.w-leader-entry .edit-leader-btn{flex:0 0 auto;margin-top:1px}

.entry-legend{
  display:flex;align-items:center;gap:10px;margin:0 0 14px;
  padding:10px 14px;border-radius:10px;
  background:var(--surface-alt);border:1px solid var(--line);
  font-size:13px;line-height:1.45;color:var(--body-c);
}
.entry-legend-swatch{
  flex:0 0 auto;width:16px;height:16px;border-radius:4px;
  background:var(--stale-row-bg);border:1px solid var(--stale-row-border);
  box-shadow:inset 0 0 0 1px rgba(255,255,255,.35);
}

.entry-toolbar{
  display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin:0 0 10px;
}
.entry-toolbar .entry-search{flex:1 1 240px;min-width:160px}
.entry-toolbar .entry-result-count{
  margin-left:auto;font-size:12px;color:var(--muted);white-space:nowrap;
}

/* ── Data Grid / Table ── */
/* position:relative makes the wrap the containing block for absolutely
   positioned bits inside cells (.sr-only labels, the band expand buttons).
   Without it they anchor to the page, escape the wrap's clip, and widen the
   whole document — on Reporting with all columns on the page scrolled ~1300px
   right into blank space. */
.table-wrap{position:relative;overflow-x:auto;-webkit-overflow-scrolling:touch}
/* Entry HP / BC/HAS: freeze the column header while the rows scroll.
   Sticky thead cannot pin to the page here — .table-wrap already scrolls
   horizontally, and overflow-x:auto makes the wrap the sticky containing
   block — so the wrap itself is the vertical scroller. */
.entry-table-wrap{
  max-height:calc(100vh - var(--topbar-h) - 13.5rem);
  max-height:calc(100dvh - var(--topbar-h) - 13.5rem);
  overflow:auto;
  overscroll-behavior:contain;
}
.entry-table-wrap table.grid{
  border-collapse:separate;
  border-spacing:0;
}
.entry-table-wrap table.grid thead th{
  position:sticky;top:0;z-index:4;
  background:var(--surface-alt);
  box-shadow:0 1px 0 var(--line);
}
.entry-table-wrap table.grid thead th.sortable:hover,
.entry-table-wrap table.grid thead th.sortable:focus-visible{
  background:var(--surface-alt);
}
.entry-table-wrap table.grid tbody td{border-bottom:1px solid var(--line)}
.entry-table-wrap table.grid tbody tr{border-bottom:0}
.entry-table-wrap table.grid tbody tr:last-child td{border-bottom:0}
table.grid th.sortable{cursor:pointer;user-select:none}
table.grid th.sortable:hover,table.grid th.sortable:focus-visible{background:var(--surface-alt)}
table.grid th.sortable .col-metric::after{content:"⇅";margin-left:5px;font-size:10px;color:var(--muted);opacity:.55}
table.grid th.sortable.sort-asc .col-metric::after{content:"▲";opacity:1}
table.grid th.sortable.sort-desc .col-metric::after{content:"▼";opacity:1}
table.grid{width:100%;border-collapse:collapse;font-size:14px;min-width:660px}
table.grid th,table.grid td{padding:9px 12px;text-align:left;vertical-align:middle}
/* A light head with muted labels. The numbers are the content; a dark bar with
   a gold underline pulled the eye to the headings instead. */
table.grid thead th{
  font-family:var(--font-heading);font-size:11px;font-weight:700;
  text-transform:uppercase;letter-spacing:.07em;color:var(--muted);
  background:var(--surface-alt);border-bottom:1px solid var(--line);white-space:nowrap;
}
table.grid tbody tr{border-bottom:1px solid var(--line)}
table.grid tbody tr:last-child{border-bottom:0}
table.grid tbody tr:hover{background:var(--surface-alt)}
table.grid td.num,table.grid th.num{text-align:right;font-variant-numeric:tabular-nums}
table.grid tfoot td{
  padding:11px 12px;border-top:1px solid var(--border);background:var(--surface-alt);
  font-family:var(--font-heading);font-weight:700;color:var(--navy-dark);
}
table.grid .row-meta{font-size:12px;color:var(--muted);white-space:nowrap}
/* An unsaved row is marked, not highlighted — a tinted band with a dashed rule
   read as an error state. */
table.grid tr.new-row{background:transparent}
table.grid tr.new-row td:first-child{box-shadow:inset 2px 0 0 var(--border)}
table.grid tr.stale-row{background:var(--stale-row-bg)}
table.grid tr.stale-row:hover{background:var(--stale-row-hover)}
table.grid tr.stale-row td:first-child{box-shadow:inset 2px 0 0 var(--stale-row-border)}

/* Form Inputs */
input[type=text],input[type=number],input[type=search],input[type=email],select,textarea{
  font-family:var(--font-body);font-size:14px;color:var(--ink);
  background:var(--white);border:1px solid var(--border);border-radius:8px;
  padding:7px 9px;width:100%;transition:border-color .15s,box-shadow .15s;
}
/* Chromium/WebKit draw their own search-field chrome (a round cancel button
   and extra inset padding) that fights the shared look above — stripped so a
   search input reads as the same control as every other field. */
input[type=search]{appearance:textfield;-webkit-appearance:textfield}
input[type=search]::-webkit-search-decoration,
input[type=search]::-webkit-search-cancel-button,
input[type=search]::-webkit-search-results-button,
input[type=search]::-webkit-search-results-decoration{-webkit-appearance:none;appearance:none}
input:focus,select:focus,textarea:focus{
  outline:0;border-color:var(--focus);box-shadow:var(--focus-ring);
}
input[type=number]{text-align:right;font-variant-numeric:tabular-nums;
  -moz-appearance:textfield;appearance:textfield}
input[type=number]::-webkit-outer-spin-button,
input[type=number]::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}
table.grid input{border-color:transparent;background:transparent;padding:6px 8px}
table.grid tr:hover input,table.grid input:hover{border-color:var(--border);background:var(--white)}
table.grid input:focus{border-color:var(--focus);background:var(--white)}
table.grid td.w-code input{max-width:120px}
table.grid td.num input{max-width:110px;margin-left:auto}
table.grid td.num.entry-metric-cell{vertical-align:top}
table.grid td.num .cell-metric-stamp{margin-top:1px;line-height:1.2}
table.grid td.num .cell-metric-stamp-date{display:block;font-variant-numeric:tabular-nums}
table.grid td.num .cell-metric-stamp-ago{display:block;font-size:8px;opacity:.85}
.cell-stamp{
  display:block;font-size:9px;color:var(--muted);white-space:nowrap;
  text-align:right;margin-top:1px;overflow:hidden;text-overflow:ellipsis;
}
.cell-stamp-empty{color:var(--border)}
table.grid th.w-del,table.grid td.w-del{width:40px;text-align:center;padding-left:6px;padding-right:6px}
table.grid th.w-done,table.grid td.w-done{width:auto;min-width:88px;text-align:center;padding-left:6px;padding-right:6px}
table.grid th.w-done{white-space:nowrap}

.field-error{display:block;font-size:12px;font-weight:700;color:var(--err);margin-top:3px}
input.has-error,select.has-error{border-color:var(--err) !important;background:var(--err-tint) !important}

/* Colour still carries the meaning, but from a 3px edge rather than a saturated
   panel — several notices at once used to dominate the page. */
.notice{
  border-radius:8px;padding:10px 14px;font-size:13.5px;font-weight:600;
  margin-bottom:12px;background:var(--surface-alt);color:var(--body-c);
  border:1px solid var(--line);border-left:3px solid var(--muted);
}
.notice.ok{background:var(--ok-tint);border-color:rgba(46,139,87,.18);border-left-color:var(--ok);color:#1b5e3b}
.notice.err{background:var(--err-tint);border-color:rgba(201,82,79,.18);border-left-color:var(--err);color:#8d2b29}
.notice.info{background:var(--surface-alt);border-left-color:var(--navy-mid);color:var(--navy-dark)}

.empty{padding:34px 18px;text-align:center;color:var(--muted)}
.empty .ico{width:40px;height:40px;color:var(--border);margin-bottom:8px}
.empty p{font-size:14px;margin-bottom:14px}

.hint{font-size:12.5px;color:var(--muted)}
.chip{
  display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:700;
  background:var(--surface-alt);border:1px solid var(--line);border-radius:20px;
  padding:4px 10px;color:var(--muted);
}
button.chip{
  cursor:pointer;font:inherit;line-height:inherit;
  transition:background .12s,border-color .12s,box-shadow .12s;
}
button.chip:hover{background:var(--surface-alt);filter:brightness(.97)}
button.chip.heat-filter-btn.is-active,
button.chip.heat-filter-btn[aria-pressed="true"]{
  box-shadow:inset 0 0 0 2px var(--status-flagged-edge);
}

.form-actions{
  display:flex;flex-wrap:wrap;align-items:center;gap:10px;
  padding:14px 18px;border-top:1px solid var(--line);background:var(--surface-alt);
}
.form-actions .spacer{margin-left:auto}

@media (max-width:560px){
  .page-head h1{font-size:15px}
  .weeknav-label{font-size:15px}
  .card .body,.card > header,.form-actions{padding-left:14px;padding-right:14px}
}

/* Reporting and data entry: the wide banded grid uses the full content width —
   no side padding or card chrome eating horizontal space for a 30-column
   table. Both pages draw the same table, so both get the same treatment. */
body.page-reporting .page,
body.page-entry .page{padding-left:0;padding-right:0}
/* The entry grid caps its own height (.entry-table-wrap's max-height, so the
   sticky header has something to scroll under) well short of a full viewport
   on most screens. ``.main{min-height:100%}`` does not know that, and kept
   stretching anyway — the table ended, the footer printed, and then a slab of
   plain background ran on to the bottom of the viewport for no reason. The
   table manages its own height already, so this page does not need the
   stretch that page relies on. */
body.page-entry .main{min-height:0}
body.page-reporting .page-head,
body.page-entry .page-head{padding-left:16px;padding-right:16px}
body.page-reporting .foot,
body.page-entry .foot{padding-left:16px;padding-right:16px}
body.page-reporting .card.heat-card,
body.page-entry .card.heat-card{
  border:none;border-radius:0;box-shadow:none;margin-bottom:0;background:transparent;
}
body.page-reporting .card.heat-card > header{
  border:1px solid var(--border);border-bottom:0;border-radius:0;
  background:var(--surface-alt);
}
body.page-reporting .card.heat-card .table-wrap,
body.page-entry .card.heat-card .table-wrap{
  border-left:1px solid var(--border);border-right:1px solid var(--border);
}
body.page-reporting .card.heat-card .scale-legend{
  border:1px solid var(--border);border-top:1px solid var(--line);border-radius:0;
}
@media (min-width:900px){
  body.page-reporting .page-head,
  body.page-reporting .foot,
  body.page-entry .page-head,
  body.page-entry .foot{padding-left:26px;padding-right:26px}
}
@media (min-width:1440px){
  body.page-reporting .page-head,
  body.page-reporting .foot,
  body.page-entry .page-head,
  body.page-entry .foot{padding-left:32px;padding-right:32px}
}

/* Expand-all switch in the reporting page head — off shows the default column
   set, on shows every column. Driven by a real checkbox so :checked animates
   the knob without waiting on JS attribute updates. */
.heat-expand-toggle{
  display:inline-flex;align-items:center;gap:12px;
  font-size:13px;font-weight:600;color:var(--muted);
  cursor:pointer;user-select:none;
}
.heat-expand-toggle .heat-expand-label{transition:color .15s,font-weight .15s}
.heat-expand-toggle:has(.toggle-switch-input:checked) .heat-expand-label{
  color:var(--navy-dark);font-weight:800;
}
.heat-expand-toggle .toggle-switch-input{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;
}
.toggle-switch-ui{
  position:relative;display:block;width:54px;height:30px;border-radius:15px;
  background:#d8dee8;border:2px solid #b8c2d0;
  transition:background .2s ease,border-color .2s ease,box-shadow .2s ease;
  flex-shrink:0;box-shadow:inset 0 1px 3px rgba(14,23,42,.12);
}
.toggle-switch-input:checked + .toggle-switch-ui{
  background:var(--navy-dark);border-color:var(--navy-dark);
  box-shadow:inset 0 1px 2px rgba(0,0,0,.22);
}
.toggle-knob{
  position:absolute;top:2px;left:2px;width:22px;height:22px;border-radius:50%;
  background:#fff;box-shadow:0 2px 5px rgba(14,23,42,.28);
  transition:transform .2s ease;
}
.toggle-switch-input:checked + .toggle-switch-ui .toggle-knob{transform:translateX(24px)}
.toggle-switch-input:focus-visible + .toggle-switch-ui{
  outline:2px solid var(--focus);outline-offset:2px;
}

/* ── Reporting Dashboard & Analytics ─────────────────────────────────── */
.kpi-grid{
  display:grid;grid-template-columns:repeat(auto-fit, minmax(220px, 1fr));
  gap:14px;margin-bottom:18px;
}
.kpi-card{
  background:var(--white);border:1px solid var(--border);border-top:3px solid var(--navy-dark);
  border-radius:14px;padding:16px 18px;box-shadow:var(--shadow-soft);
  display:flex;flex-direction:column;gap:6px;position:relative;overflow:hidden;
}
.kpi-card.gold{border-top-color:var(--gold)}
.kpi-card.ok{border-top-color:var(--ok)}
.kpi-card .kpi-label{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.06em;color:var(--muted)}
.kpi-card .kpi-value{font-family:var(--font-heading);font-size:26px;font-weight:800;color:var(--navy-dark);line-height:1.1}
.kpi-card.gold .kpi-value{color:var(--gold-dark)}
.kpi-card.ok .kpi-value{color:var(--ok)}
.kpi-card .kpi-meta{
  display:flex;align-items:center;justify-content:space-between;
  font-size:12px;color:var(--muted);font-weight:600;margin-top:2px;
}

.badge{
  display:inline-flex;align-items:center;gap:4px;padding:3px 9px;border-radius:12px;
  font-size:11.5px;font-weight:700;line-height:1.2;white-space:nowrap;
}
.badge.ok{background:var(--ok-tint);color:var(--ok);border:1px solid rgba(46,139,87,.3)}
.badge.gold{background:var(--gold-tint);color:var(--gold-dark);border:1px solid var(--gold-border)}
.badge.warn{background:var(--warn-tint);color:var(--warn);border:1px solid rgba(217,164,65,.3)}
.badge.muted{background:var(--navy-tint);color:var(--muted);border:1px solid var(--border)}

.filter-bar{
  display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;
  gap:12px;margin-bottom:14px;
}
.filter-tabs{
  display:inline-flex;align-items:center;gap:4px;
  background:var(--white);padding:4px;border-radius:10px;border:1px solid var(--border);
  box-shadow:var(--shadow-soft);
}
.filter-tab{
  padding:5px 12px;border-radius:7px;font-size:12.5px;font-weight:700;
  color:var(--muted);transition:background .15s,color .15s;text-decoration:none;
}
.filter-tab:hover{color:var(--navy-dark);background:var(--navy-tint)}
.filter-tab.active{background:var(--navy-dark);color:#fff}

.search-box{
  position:relative;display:flex;align-items:center;min-width:220px;
}
.search-box input{
  padding-left:32px;padding-right:10px;height:36px;
}
.search-box .search-ico{
  position:absolute;left:9px;top:50%;transform:translateY(-50%);
  color:var(--muted);pointer-events:none;display:flex;align-items:center;
}
.search-box .search-ico .ico{width:16px;height:16px}

/* ── Reporting: dataset cards, status grid, trend chart ───────────────────
 *
 * Two different colour jobs on one page, kept apart on purpose:
 *
 *   The week grid encodes STATUS, not magnitude. A metric is flagged when it
 *   falls below 75% of the metric to its left, so the question the colour
 *   answers is "is this ratio healthy?" — one flagged step and one clear step,
 *   never a ramp. (It began as a sequential blue ramp; a ramp answers "how big
 *   is this?", which the numbers in the cells already say.)
 *
 *   Red against LIBRA blue rather than red against green: a red/green pair is
 *   the classic colourblind trap. These two fills differ in hue but barely in
 *   lightness, so colour is never the only channel — a flagged cell also
 *   carries a "!" glyph, a left accent rule, and the percentage as text.
 *   Every text/fill pair here was measured at or above 4.5:1.
 *
 *   The trend uses CATEGORICAL slots 1-3 (blue, orange, aqua) for identity.
 *   Validated as an adjacent set: worst CVD ΔE 9.2 (deuteranopia), worst
 *   normal-vision ΔE 27.6. Aqua is 2.82:1 on white, under the 3:1 mark, so this
 *   page ships the relief the palette requires — a legend, direct end labels
 *   and a table view.
 *
 * Series colour belongs to the metric, not to its rank, so switching dataset
 * never repaints a metric the reader has already learned.
 */
:root{
  --status-ok-bg:#dbe9fb;
  --status-ok-ink:#14213d;
  --status-flagged-bg:#fbe3e3;
  --status-flagged-ink:#8d2b29;
  --status-flagged-edge:#d03b3b;
  --series-1:#2a78d6;
  --series-2:#eb6834;
  --series-3:#1baf7a;
}

.section-label{
  font-family:var(--font-heading);font-size:11.5px;font-weight:700;
  text-transform:uppercase;letter-spacing:.08em;color:var(--muted);
  margin-bottom:8px;
}

/* ── Dataset picker ── */
.pick-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:12px;margin-bottom:18px}
.pick-card{
  display:flex;align-items:center;gap:12px;padding:14px 16px;
  background:var(--white);border:1px solid var(--border);border-radius:12px;
  box-shadow:var(--shadow-soft);transition:border-color .15s,box-shadow .15s,transform .1s;
}
.pick-card:hover{border-color:#c8d2de;transform:translateY(-2px)}
.pick-card:focus-visible{outline:2px solid var(--focus);outline-offset:2px}
/* The selected card is marked by weight and a left rule, not by a fill — a
   filled card competes with the charts it is scoping. */
.pick-card.active{border-color:var(--navy-dark);box-shadow:inset 3px 0 0 var(--navy-dark),var(--shadow-soft)}
.pick-card-tag{
  display:inline-flex;align-items:center;justify-content:center;min-width:52px;
  padding:5px 8px;border-radius:7px;font-family:var(--font-heading);
  font-size:11.5px;font-weight:800;letter-spacing:.04em;
  background:var(--surface-alt);color:var(--muted);border:1px solid var(--line);
}
.pick-card.active .pick-card-tag{background:var(--navy-dark);color:#fff;border-color:var(--navy-dark)}
.pick-card-body{display:flex;flex-direction:column;min-width:0;gap:2px}
.pick-card-title{font-family:var(--font-heading);font-size:15px;font-weight:700;color:var(--navy-dark)}
.pick-card-desc{font-size:12.5px;color:var(--muted);line-height:1.35}
.pick-card-figure{margin-left:auto;text-align:right;flex:0 0 auto}
/* Proportional figures, not tabular: equal-width digits make a standalone
   number look loose at this size. */
.pick-card-value{
  display:block;font-family:var(--font-heading);font-size:24px;font-weight:800;
  color:var(--navy-dark);line-height:1.1;
}
.pick-card-label{display:block;font-size:10.5px;font-weight:700;text-transform:uppercase;
  letter-spacing:.05em;color:var(--muted);margin-top:2px}

/* ── Per-card filters ──
   Each control sits on the card it scopes: the week on the grid, the project on
   the trend. (The general dashboard rule is one filter row above everything —
   it applies when a control scopes every panel, which these do not.) */
.card-filter{display:flex;align-items:flex-end;gap:8px}
.filter-inline{display:flex;align-items:center;gap:7px}
.filter-inline .filter-label{
  font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;
  color:var(--muted);white-space:nowrap;
}
.filter-inline select{min-width:200px;padding:6px 8px;font-size:13px}
.card-filter .btn{padding:6px 12px;font-size:12.5px}

/* ── Control Center upload ── */
.page-sub{margin:6px 0 0;font-size:14px;color:var(--muted);max-width:52rem;line-height:1.45}
.upload-form{display:flex;flex-direction:column;gap:14px;max-width:32rem;margin-top:12px}
.upload-field{display:flex;flex-direction:column;gap:6px;font-size:13px;font-weight:600;color:var(--muted)}
.upload-field input[type=file],
.upload-field select{max-width:100%}

/* One row instead of a column: the import is a rare job and should cost a
   strip, not a screen. */
.upload-form.is-inline{
  flex-direction:row;flex-wrap:wrap;align-items:flex-end;
  gap:10px 14px;max-width:none;margin-top:0;
}
.upload-form.is-inline .upload-field{flex:1 1 15rem;min-width:0}

/* Project → Projektleiter list: capped height, its own scroller, frozen head.
   Same reasoning as .entry-table-wrap, but a fixed cap rather than a viewport
   one — this card is one of four on the page and must not eat the screen. */
.leader-assign-wrap{
  max-height:22rem;overflow:auto;overscroll-behavior:contain;
  border-top:1px solid var(--line);
}
.leader-assign-wrap table.grid{border-collapse:separate;border-spacing:0;min-width:480px}
.leader-assign-wrap table.grid thead th{
  position:sticky;top:0;z-index:4;background:var(--surface-alt);
  box-shadow:0 1px 0 var(--line);
}
.leader-assign-wrap table.grid tbody td{border-bottom:1px solid var(--line)}
.leader-assign-wrap table.grid tbody tr{border-bottom:0}
.leader-assign-wrap table.grid tbody tr:last-child td{border-bottom:0}
.leader-assign-search{flex:0 1 18rem;min-width:9rem;font-size:13px;padding:6px 10px}

/* A <details> wearing .card: the summary is the header, the fold is the body. */
.card-collapsible > summary{
  padding:11px 18px;background:var(--surface-alt);cursor:pointer;
  display:flex;align-items:center;gap:8px;list-style:none;
  font-family:var(--font-heading);font-size:14px;font-weight:700;color:var(--navy-dark);
}
.card-collapsible > summary::-webkit-details-marker{display:none}
.card-collapsible > summary::before{
  content:"▸";display:inline-block;font-size:11px;color:var(--muted);
  transition:transform .12s;
}
.card-collapsible[open] > summary::before{transform:rotate(90deg)}
.card-collapsible > summary:hover{filter:brightness(.98)}
.card-collapsible[open] > summary{border-bottom:1px solid var(--line)}

/* ── Week grid (status) ── */
/* Columns size to their content and the wrap scrolls. Sharing one screen width
   between them equally is what used to make the numbers spill into the cell
   next door: with five bands of columns there is no width left to divide. */
table.grid.heat{width:100%;min-width:100%;table-layout:auto}
table.grid.heat th.num,table.grid.heat td.heat-cell{width:auto}

/* A line on every column, so a wall of figures reads as a grid, and a heavier
   darker one where one band of columns ends and the next begins. */
/* A hidden column must actually collapse, not just paint blank.
   ``!important`` because this has to beat any rule that gives a cell an
   explicit ``display`` — the Projektleiter cell is ``display:flex`` (further
   down this file) and ties this selector on specificity, so source order alone
   let it win: unticking Projektleiter in the chooser removed its header and
   left 102 cells on screen. A column the chooser has turned off has to go,
   whatever else a cell wants to be. */
table.grid.heat [hidden]{display:none !important}

/* ── Column chooser (Wochenübersicht) ──
   A native <details> with the panel floated over the table, same mechanics as
   any dropdown: closes on an outside click (JS), on Escape (native), and works
   before the page's JS has run at all. */
.heat-chooser{position:relative}
.heat-chooser > summary{list-style:none;cursor:pointer}
.heat-chooser > summary::-webkit-details-marker{display:none}
.heat-chooser-panel{
  position:absolute;right:0;top:calc(100% + 6px);z-index:20;width:min(30rem,90vw);
  max-height:60vh;overflow:auto;
  background:var(--white);border:1px solid var(--border);border-radius:12px;
  box-shadow:var(--shadow-soft);padding:12px 14px;
  display:grid;grid-template-columns:repeat(auto-fit,minmax(12rem,1fr));gap:10px 16px;
}
.heat-chooser-actions{grid-column:1/-1;display:flex;flex-wrap:wrap;align-items:center;gap:8px}
.heat-save-columns-form{display:inline-flex;margin:0}
.rep-notify-all-form{display:inline-flex;margin:0}
.card > header .btn.sm{padding:5px 10px;font-size:12.5px}
.heat-chooser-group{border:0;padding:0;margin:0;min-width:0}
.heat-chooser-group legend{
  font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;
  color:var(--muted);padding:0 0 4px;
}
.heat-chooser-item{display:flex;align-items:center;gap:6px;font-size:12.5px;padding:2px 0;cursor:pointer}

/* ── Per-band colour ──
   Seven bands, seven hues, low alpha: enough to tell at a glance which few
   columns of a 30-column row belong together, never enough to compete with
   red/blue status colour. Headers (no status of their own) always show the
   tint, at a stronger alpha so the band name reads as a bar of colour; body
   and footer cells show it only where nothing has been rated yet — a real
   ok/flagged/base cell keeps its own colour untouched. */
:root{
  --band-hp:76,140,207;
  --band-bchas:47,150,138;
  --band-activation:141,109,201;
  --band-pnas:206,102,146;
  --band-nas:214,133,74;
  --band-status:99,116,158;
  --band-open:96,150,99;
}
/* Header bars match the sidebar shell: navy gradient, gold type like
   "Project Management" under the brand — not per-band header tints. */
table.grid.heat thead th{
  background:linear-gradient(180deg, var(--navy-dark) 0%, var(--navy-deep) 100%);
  color:var(--gold-light);text-align:center;vertical-align:middle;
}

table.grid.heat tbody td.band-hp:not(.status-ok):not(.status-flagged),
table.grid.heat tfoot td.band-hp:not(.status-ok):not(.status-flagged){background:rgba(var(--band-hp),.07)}
table.grid.heat tbody td.band-bchas:not(.status-ok):not(.status-flagged),
table.grid.heat tfoot td.band-bchas:not(.status-ok):not(.status-flagged){background:rgba(var(--band-bchas),.07)}
table.grid.heat tbody td.band-activation:not(.status-ok):not(.status-flagged),
table.grid.heat tfoot td.band-activation:not(.status-ok):not(.status-flagged){background:rgba(var(--band-activation),.07)}
table.grid.heat tbody td.band-pnas:not(.status-ok):not(.status-flagged),
table.grid.heat tfoot td.band-pnas:not(.status-ok):not(.status-flagged){background:rgba(var(--band-pnas),.07)}
table.grid.heat tbody td.band-nas:not(.status-ok):not(.status-flagged),
table.grid.heat tfoot td.band-nas:not(.status-ok):not(.status-flagged){background:rgba(var(--band-nas),.07)}
table.grid.heat tbody td.band-status:not(.status-ok):not(.status-flagged),
table.grid.heat tfoot td.band-status:not(.status-ok):not(.status-flagged){background:rgba(var(--band-status),.07)}
table.grid.heat tbody td.band-open:not(.status-ok):not(.status-flagged),
table.grid.heat tfoot td.band-open:not(.status-ok):not(.status-flagged){background:rgba(var(--band-open),.07)}
/* The leader cell (band-status) carries no status class at all, so it always
   gets the tint — no guard needed, but the selector above already covers it
   since :not(.status-ok):not(.status-flagged) matches an element with neither. */

table.grid.heat th,table.grid.heat td{border-right:1px solid rgba(14,23,42,.07)}
table.grid.heat thead th{border-right:1px solid rgba(255,255,255,.1)}
table.grid.heat th:last-child,table.grid.heat td:last-child{border-right:0}
table.grid.heat th.block-end,table.grid.heat td.block-end{
  border-right:1px solid rgba(14,23,42,.13);
}
table.grid.heat thead th.block-end{border-right:1px solid rgba(255,255,255,.22)}
table.grid.heat th.num .col-metric-label{display:block}
table.grid.heat th.sortable .col-metric{display:inline-flex;align-items:center;gap:4px}
table.grid.heat th.sortable .col-metric::after{
  content:"⇅";font-size:10px;color:var(--gold-light);opacity:.45;font-weight:400;
}
table.grid.heat th.sortable.sort-asc .col-metric::after{content:"▲";opacity:1}
table.grid.heat th.sortable.sort-desc .col-metric::after{content:"▼";opacity:1}
table.grid.heat th.num .col-updated-stamp,
table.grid.heat thead th .col-updated-stamp{
  display:block;font-size:8px;font-weight:500;color:rgba(223,190,118,.45);
  margin-top:1px;line-height:1.1;font-variant-numeric:tabular-nums;white-space:nowrap;
}
table.grid.heat .w-project .heat-project-name{
  display:block;font-weight:700;font-size:14px;color:var(--navy-dark);line-height:1.3;
}
table.grid.heat .w-project .heat-project-code{
  display:block;font-size:12px;color:var(--muted);margin-top:2px;
  font-variant-numeric:tabular-nums;
}
table.grid.heat td.heat-cell .cell-paid-label{font-size:10.5px;white-space:nowrap}
table.grid.heat td.heat-cell{
  text-align:right;font-weight:700;cursor:default;
  transition:filter .12s;vertical-align:top;
}
table.grid.heat td.heat-cell .cell-value,
table.grid.heat td.heat-cell .cell-ratio{white-space:nowrap}
table.grid.heat tbody td.heat-cell .cell-value{font-size:18px}
table.grid.heat tfoot td.heat-cell .cell-value{
  font-size:16px;font-weight:800;
}
table.grid.heat td.heat-cell .cell-updated-stamp{
  display:block;font-size:10px;font-weight:600;color:var(--muted);
  opacity:.88;margin-top:3px;line-height:1.35;
}
table.grid.heat td.heat-cell .cell-updated-date{
  display:block;font-variant-numeric:tabular-nums;
}
table.grid.heat td.heat-cell .cell-updated-relative{
  display:block;font-size:9.5px;opacity:.85;
}
/* Two-row header: the dataset a block of columns belongs to, then the columns.
   The group row is the only thing saying which numbers are HP and which are
   BC/HAS, so it reads as a band rather than as another row of headers. */
table.grid.heat thead .heat-group-row th{
  padding:5px 8px;font-size:10.5px;font-weight:700;text-transform:uppercase;
  letter-spacing:.06em;
}
table.grid.heat thead .heat-group-project{
  font-family:var(--font-heading);font-size:11px;font-weight:700;
  letter-spacing:.06em;min-width:9rem;
}
.heat-group-row-inner{
  display:flex;align-items:center;justify-content:center;gap:6px;position:relative;
  min-height:18px;padding:0 18px 0 0;
}
.heat-group-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:center}
/* Expands this one band's own columns; leaves every other band's visibility
   alone. Small and quiet on purpose — it lives in a 10px-type header bar next
   to six others just like it. */
.heat-group-expand{
  position:absolute;right:0;top:50%;transform:translateY(-50%);
  flex:0 0 auto;width:14px;height:14px;padding:0;border:0;border-radius:3px;
  background:rgba(223,190,118,.18);color:var(--gold-light);font-size:11px;font-weight:800;
  line-height:1;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;
  transition:background .12s;
}
.heat-group-expand:hover,.heat-group-expand:focus-visible{background:rgba(223,190,118,.32)}
.heat-group-expand:focus-visible{outline:1px solid var(--gold-light);outline-offset:1px}
.heat-group-expand[aria-expanded="true"]{background:rgba(223,190,118,.4)}

/* Lower header row: minimal type, date alone (no "Neueste" label). */
table.grid.heat thead .heat-col-row th{padding:2px 5px;line-height:1.1}
table.grid.heat thead .heat-col-row .col-metric{
  font-size:10px;font-weight:700;color:var(--gold-light);letter-spacing:.03em;
}
table.grid.heat thead .heat-col-row th.sortable .col-metric{
  display:inline-flex;align-items:center;justify-content:center;gap:2px;
}
table.grid.heat thead .heat-col-row th.sortable .col-metric::after{font-size:8px}
/* Only the leading corner (above the Projekt column) is a real empty cell —
   the Projektleiter column no longer trails the row as its own empty header,
   it lives inside the Status band's own colspan (see with_leader). A
   :last-child rule here would now hit that band's real header instead of an
   empty corner, painting it the wrong colour. */
/* ── Data entry, in the reporting table's clothes ──
   ``.entry-grid`` is ``.grid.heat`` with inputs in the cells. Everything above
   — band colours, the two-row header, the dividers between bands — applies
   unchanged; what follows is only what an input needs that a rated figure does
   not. */

/* No column chooser on this page, so no band carries an expand button and the
   18px the reporting header reserves for one would push every band name
   off-centre. */
.entry-grid .heat-group-row-inner{padding:0}

/* The owner's name, not a timestamp: it has to be read, so it is a step up
   from the 8px the reporting header's freshness stamp sits at. */
.entry-grid thead th .col-updated-stamp{
  font-size:9.5px;font-weight:700;color:rgba(223,190,118,.7);letter-spacing:.01em;
}

/* The sticky header keeps the navy band rather than reverting to the light
   entry-grid head: .entry-table-wrap paints every sticky th --surface-alt,
   which on this table would hide the band colours the header exists to show. */
.entry-table-wrap table.grid.heat thead th{
  background:linear-gradient(180deg, var(--navy-dark) 0%, var(--navy-deep) 100%);
  box-shadow:none;
}
/* The group row scrolls to the top edge, the column row sits right under it,
   so both stay readable while the rows move. Its own height, measured from the
   band row's type and padding. */
.entry-table-wrap table.grid.heat thead .heat-group-row th{top:0;z-index:5}
.entry-table-wrap table.grid.heat thead .heat-col-row th{top:28px;z-index:4}
.entry-table-wrap table.grid.heat thead th.sortable:hover,
.entry-table-wrap table.grid.heat thead th.sortable:focus-visible{
  filter:brightness(1.18);
}

/* Pin the Projekt column while the bands scroll sideways — the same idea as
   the sticky thead, but on the x axis so entry never loses which row a cell
   belongs to. */
.entry-table-wrap table.grid.heat thead .heat-group-row th.w-project{
  left:0;z-index:7;
  box-shadow:1px 0 0 rgba(255,255,255,.14);
}
.entry-table-wrap table.grid.heat tbody td.w-project{
  position:sticky;left:0;z-index:3;background:var(--white);
  box-shadow:1px 0 0 var(--line);padding-left:8px;
}
.entry-table-wrap table.grid.heat tbody tr.stale-row td.w-project{
  background:var(--stale-row-bg);
}
.entry-table-wrap table.grid.heat tbody tr.stale-row:hover td.w-project{
  background:var(--stale-row-hover);
}
/* Reporting pins the same column: thirty-odd columns to the right, a row of
   colours is no use without the project it belongs to. Only `left` here — the
   page scrolls vertically, not the wrap, so there is no sticky top to pair it
   with. The edge is a shadow, not a border: under border-collapse the border
   belongs to the table and stays behind when the cell slides. */
body.page-reporting table.grid.heat thead th.w-project{
  position:sticky;left:0;z-index:3;
  box-shadow:1px 0 0 rgba(255,255,255,.14);
}
body.page-reporting table.grid.heat tbody td.w-project{
  position:sticky;left:0;z-index:2;background:var(--white);
  box-shadow:1px 0 0 var(--line);
}
body.page-reporting table.grid.heat tr.highlighted td.w-project{
  box-shadow:1px 0 0 var(--line),inset 0 1px 0 var(--navy-dark),inset 0 -1px 0 var(--navy-dark);
}

/* A cell is as wide as its input needs and no wider: with ~25 columns the
   table scrolls, and every millimetre a count cell does not spend is one the
   next band gets on screen. */
table.grid.heat td.entry-cell{padding:5px 6px;vertical-align:top}
table.grid.heat td.entry-cell input[type=number]{
  max-width:5.5rem;min-width:3.4rem;padding:5px 6px;font-size:13.5px;font-weight:700;
}
/* Same "quiet until you reach for it" treatment the entry grid has always had,
   restated here because the band tint is the cell's own background: an input
   painted --white at rest would draw a box around every number on the page. */
table.grid.heat td.entry-cell input{border-color:transparent;background:transparent}
table.grid.heat tbody tr:hover td.entry-cell input,
table.grid.heat td.entry-cell input:hover{border-color:var(--border);background:var(--white)}
table.grid.heat td.entry-cell input:focus{border-color:var(--focus);background:var(--white)}
/* One line per cell, not two: who/when moved to the cell's own ``title`` (a
   native hover tooltip, see week.html) instead of a permanent second line, so
   a hundred-odd rows of a dozen metric columns each do not carry a visible
   timestamp under every single number. The stamp markup stays — hidden, not
   removed — so nothing else that reads ``.cell-metric-stamp`` (the live
   refresh after a cell write, see week.html's ``refreshStamp``) has to change. */
table.grid.heat td.entry-cell .cell-metric-stamp{display:none}

/* The goal is a target the counts are read against, so it sits slightly apart
   from them rather than looking like a fourth weekly figure. */
table.grid.heat td.entry-goal-cell{background:rgba(14,23,42,.05)}
table.grid.heat td.entry-goal-cell input{background:var(--white);border-color:var(--border)}

/* A column with no source yet: present, aligned, and plainly empty. */
table.grid.heat td.entry-pending-cell{
  text-align:center;color:var(--border);font-weight:700;cursor:default;
}

/* ── Status switches ──
   Four yes/no columns between two bands of counts. A checkbox would read as
   "tick to select", which is what the export column means one screen to the
   left, so these are switches — and small enough that four of them cost less
   width than one count column. */
table.grid.heat th.col-switch,
table.grid.heat td.entry-switch-cell{width:2.6rem;text-align:center;padding-left:4px;padding-right:4px}
table.grid.heat td.entry-switch-cell{vertical-align:middle}
.entry-switch{
  display:inline-flex;align-items:center;justify-content:center;
  cursor:pointer;position:relative;line-height:0;
}
.entry-switch .entry-switch-input{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;
}
.entry-switch-ui{
  position:relative;display:block;width:28px;height:15px;border-radius:8px;
  background:#d8dee8;border:1px solid #b8c2d0;
  transition:background .15s ease,border-color .15s ease;
}
.entry-switch-ui::after{
  content:"";position:absolute;top:1px;left:1px;width:11px;height:11px;border-radius:50%;
  background:#fff;box-shadow:0 1px 2px rgba(14,23,42,.3);transition:transform .15s ease;
}
.entry-switch-input:checked + .entry-switch-ui{background:var(--ok);border-color:var(--ok)}
.entry-switch-input:checked + .entry-switch-ui::after{transform:translateX(13px)}
.entry-switch-input:focus-visible + .entry-switch-ui{outline:2px solid var(--focus);outline-offset:2px}
/* A switch nobody may change still shows its state; it just does not invite a
   click it would have to refuse. */
.entry-switch-input:disabled + .entry-switch-ui{opacity:.5;cursor:not-allowed}

/* ── The tri-state milestone (Teilschlussrechnung) ──
   The same slider as the switches beside it, with three stops instead of two,
   so the Status band still reads as one row of sliders rather than one row of
   sliders and one odd control. The knob sits left for "nein", centre for
   "versendet" and right for "ja" — how far right it is, is how far along the
   milestone is, which is what makes the column scannable down a hundred rows.

   Wider than a switch (36px against 28px) because three stops need the room to
   read as three; the geometry below is that width minus the knob and its two
   1px margins, so the end stops sit flush the way the switch's do. */
table.grid.heat th.col-cycle,
table.grid.heat td.entry-cycle-cell{
  width:3.2rem;text-align:center;vertical-align:middle;padding-left:4px;padding-right:4px;
}
.entry-cycle{
  display:inline-flex;align-items:center;justify-content:center;
  padding:0;border:0;background:none;cursor:pointer;line-height:0;
}
.entry-cycle-ui{
  position:relative;display:block;width:36px;height:15px;border-radius:8px;
  background:#d8dee8;border:1px solid #b8c2d0;
  transition:background .15s ease,border-color .15s ease;
}
.entry-cycle-ui::after{
  content:"";position:absolute;top:1px;left:1px;width:11px;height:11px;border-radius:50%;
  background:#fff;box-shadow:0 1px 2px rgba(14,23,42,.3);transition:transform .15s ease;
}
/* The two centre stop-marks, so an empty track still shows it has three
   positions rather than looking like a switch that failed to line up. */
.entry-cycle-ui::before{
  content:"";position:absolute;top:50%;left:50%;width:2px;height:2px;
  margin:-1px 0 0 -1px;border-radius:50%;
  background:rgba(14,23,42,.25);
}
/* Left / centre / right. 21px is the full travel: 34px of inner track, less
   the 11px knob and its 1px margin at each end. */
.entry-cycle[data-state="no"] .entry-cycle-ui::after{transform:translateX(0)}
.entry-cycle[data-state="sent"] .entry-cycle-ui::after{transform:translateX(10.5px)}
.entry-cycle[data-state="yes"] .entry-cycle-ui::after{transform:translateX(21px)}
/* The track takes the colour of the state it has reached: amber part-way,
   green at the end, and the neutral grey until it has started. Matches the
   switches beside it (--ok when on) and the reporting table's marks. */
.entry-cycle[data-state="sent"] .entry-cycle-ui{background:var(--warn);border-color:var(--warn)}
.entry-cycle[data-state="yes"] .entry-cycle-ui{background:var(--ok);border-color:var(--ok)}
/* Once the track is filled the centre marks would only muddy it. */
.entry-cycle[data-state="sent"] .entry-cycle-ui::before,
.entry-cycle[data-state="yes"] .entry-cycle-ui::before{background:rgba(255,255,255,.45)}
.entry-cycle:focus-visible .entry-cycle-ui{outline:2px solid var(--focus);outline-offset:2px}
/* Same courtesy as a switch nobody may change: the state still reads, the
   click just is not offered. */
.entry-cycle:disabled{cursor:not-allowed}
.entry-cycle:disabled .entry-cycle-ui{opacity:.5}
/* The save flash, on the track rather than on an input this cell does not
   show — the hidden field carrying the value has nothing to tint. */
table.grid.heat td.entry-cell.cell-saved .entry-cycle-ui,
table.grid.heat td.entry-cell.cell-saving .entry-cycle-ui{
  box-shadow:0 0 0 2px var(--ok-tint);
}
table.grid.heat td.entry-cell.cell-error .entry-cycle-ui{
  box-shadow:0 0 0 2px var(--err-tint);
}

/* The Projekt cell: the name leads, the code is the second line, and the
   hidden ids/snapshots/codes behind them take no room at all. */
table.grid.heat td.w-project{vertical-align:middle}
table.grid.heat .entry-row-keys{display:none}
table.grid.heat td.w-leader-entry{min-width:8rem;vertical-align:middle;padding:5px 8px}
table.grid.heat td.w-leader-entry input[type=hidden]{display:none}

/* The card carries no header, so the toolbar is the top edge of the frame the
   table sits in — one line of the page back for the rows. */
body.page-entry .card.heat-card .entry-toolbar{
  margin:0;padding:10px 16px;
  border:1px solid var(--border);border-bottom:1px solid var(--line);
  background:var(--white);
}
/* ── Per-cell save state ──
   The cell is where the feedback belongs: at a hundred-odd rows, a banner at
   the top of the page would report on a row scrolled out of sight. Colour on
   the input's own edge, so it reads without moving anything. */
table.grid.heat td.entry-cell.cell-saving input{
  border-color:var(--border) !important;background:var(--white) !important;opacity:.7;
}
table.grid.heat td.entry-cell.cell-saved input{
  border-color:var(--ok) !important;background:var(--ok-tint) !important;
}
table.grid.heat td.entry-cell.cell-saved .entry-switch-ui,
table.grid.heat td.entry-cell.cell-saving .entry-switch-ui{
  box-shadow:0 0 0 2px var(--ok-tint);
}
table.grid.heat td.entry-cell.cell-error input{
  border-color:var(--err) !important;background:var(--err-tint) !important;
}
/* The green fades; the red does not. A rejected value is still sitting in the
   box unsaved, and that has to keep saying so. */
table.grid.heat td.entry-cell.cell-saved input{transition:border-color .5s,background .5s}

/* Read-only by permission: the value still reads, it just does not invite a
   keystroke the server would refuse (see views._entry_cell). */
table.grid.heat td.entry-cell input:disabled{
  color:var(--muted);cursor:not-allowed;border-color:transparent !important;
  background:transparent !important;
}
/* The rows take the height the card header used to: the shared 13.5rem
   allowance above it counted a header this page no longer draws. */
body.page-entry .entry-table-wrap{
  max-height:calc(100vh - var(--topbar-h) - 11.5rem);
  max-height:calc(100dvh - var(--topbar-h) - 11.5rem);
}

/* The band tint has to survive the row states the entry grid shades by — a
   stale row is the page's loudest signal and must not be painted over by
   seven band colours, so the band keeps only a hint of itself there. */
table.grid.heat tbody tr.stale-row td{background:var(--stale-row-bg)}
table.grid.heat tbody tr.stale-row:hover td{background:var(--stale-row-hover)}
table.grid.heat tbody tr.new-row td:first-child{box-shadow:inset 2px 0 0 var(--border)}
table.grid.heat tbody tr.stale-row td:first-child{box-shadow:inset 2px 0 0 var(--stale-row-border)}

/* ── Dense rows ──
   This table is read as a whole — "which projects are behind" — so fitting a
   portfolio on one screen beats decorating each row: ~30 projects now share a
   view where 12 used to. Nothing is dropped, only un-printed. Every cell's
   reported-at stamp is in the hover/focus readout, which is where the rest of
   the cell's detail already lives, and each column's newest stamp stays in the
   header. */
table.grid.heat tbody td{
  padding:3px 10px;line-height:1.25;
  border-bottom:1px solid rgba(14,23,42,.06);
}
table.grid.heat tbody td.heat-cell{vertical-align:middle}
table.grid.heat tbody td.heat-cell .cell-value{display:inline;font-size:14px}
table.grid.heat tbody td.heat-cell .cell-ratio{display:inline;margin:0 0 0 6px}
table.grid.heat tbody td.heat-cell .cell-updated-stamp{display:none}
/* Name and code on one line — two stacked lines doubled every row's height. */
table.grid.heat tbody .w-project{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* Capped at roughly "FTTH Gelsenkirchen Resse" and clipped past it: one
   project with four place names in brackets was setting the width of the whole
   column, and the code beside it identifies the row anyway. Full name in the
   title attribute. */
table.grid.heat tbody .w-project .heat-project-name{
  display:inline-block;max-width:26ch;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;vertical-align:bottom;font-size:13px;
}
table.grid.heat tbody .w-project .heat-project-code{display:inline;margin:0 0 0 6px;font-size:11px}
/* Hidden: the tooltip on the name already carries it, and dropping it lets
   this column shrink and the rest of the table slide left. */
table.grid.heat .w-project .heat-project-code.is-hidden{display:none}
table.grid.heat tbody .w-leader .leader-name{font-size:13px}
table.grid.heat tbody .w-leader .mail-stamp{font-size:11px}
table.grid.heat tbody .w-leader .btn.icon-only{padding:3px}

table.grid.heat tbody tr:hover{background:transparent}
/* A filter, not a colour change — the cell's colour is data. */
table.grid.heat td.heat-cell:hover,
table.grid.heat td.heat-cell:focus-visible{filter:brightness(.97);outline:2px solid var(--focus);outline-offset:-2px}
table.grid.heat tr.highlighted td{box-shadow:inset 0 1px 0 var(--navy-dark),inset 0 -1px 0 var(--navy-dark)}
table.grid.heat .mono{font-variant-numeric:tabular-nums;color:var(--body-c)}
table.grid.heat .w-leader{display:flex;align-items:center;justify-content:flex-start;gap:8px;white-space:nowrap;text-align:left}
table.grid.heat .w-leader .leader-name{font-weight:700;font-size:14px;color:var(--navy-dark);text-align:left}
/* The form, not the button, is now the flex item pushed to the right — the
   button sits inside it so the send is a POST. */
table.grid.heat .w-leader .mail-form{margin-left:auto;display:flex;flex:0 0 auto}
table.grid.heat .w-leader .btn.icon-only{padding:5px;flex:0 0 auto}
table.grid.heat .w-leader .btn.icon-only .ico{width:16px;height:16px}
/* Disabled means "no address on file" or "nothing to report" — legible as
   unavailable without going invisible, since the title explains which. */
table.grid.heat .w-leader .btn.icon-only:disabled{opacity:.35;cursor:not-allowed}
table.grid.heat .w-leader .mail-stamp{font-size:12px;font-variant-numeric:tabular-nums;color:var(--muted)}
table.grid.heat .w-leader .mail-stamp.is-flagged{color:var(--status-flagged-ink);font-weight:700}
table.grid.heat .w-leader .mail-stamp.is-neutral{color:var(--muted);font-style:italic}
table.grid.heat .w-stand{white-space:nowrap}
table.grid.heat .w-stand .row-stamp{display:block;font-weight:700;color:var(--navy-dark);font-size:13px}
table.grid.heat .w-stand .cell-stamp{text-align:left;margin-top:1px}

.cell-value{display:block;font-variant-numeric:tabular-nums;font-size:14px}
.cell-ratio{
  display:block;font-size:10.5px;font-weight:700;letter-spacing:.02em;
  font-variant-numeric:tabular-nums;opacity:.72;margin-top:1px;
}
.cell-flag{
  display:inline-flex;align-items:center;justify-content:center;
  width:14px;height:14px;margin-right:4px;border-radius:50%;
  background:var(--status-flagged-edge);color:#fff;font-size:10px;font-weight:800;
  line-height:1;vertical-align:1px;
}

/* ── Milestone cells (Status band) ──
   Yes or no, so a mark rather than a number. Both states get a glyph and not
   only a hue: ✓/✗ survive greyscale and colour blindness, which two fills of
   similar lightness would not. Green against slate, never green against red —
   red is this table's "below the threshold" signal, and a milestone that has
   not happened yet is not a problem, only earlier in the project. */
table.grid.heat td.heat-flag-cell{
  text-align:center;vertical-align:middle;padding:6px 8px;
}
table.grid.heat td.heat-flag-cell .flag-mark{
  display:inline-flex;align-items:center;justify-content:center;
  width:20px;height:20px;border-radius:50%;
  font-size:12px;font-weight:800;line-height:1;
}
/* The mark carries the state; the cell keeps its band tint like every other
   cell in the Status band. Tinting the cell as well would mean fighting the
   band rule for specificity to end up saying the same thing twice. */
table.grid.heat td.heat-flag-cell.status-flag-on .flag-mark{
  background:var(--ok);color:#fff;
  box-shadow:0 1px 2px rgba(14,23,42,.18);
}
table.grid.heat td.heat-flag-cell.status-flag-off .flag-mark{
  background:transparent;color:var(--muted);opacity:.55;
  box-shadow:inset 0 0 0 1.5px currentColor;
}
/* Sent, and not yet settled (Teilschlussrechnung only). Filled solid amber, the
   same weight as the green "done" disc rather than the faded ring "not yet"
   gets: at a hundred rows a tinted outline disappears into the band, and this
   is the state somebody scanning the column is actually looking for.

   Still the ✗ glyph, not a tick: nothing has arrived yet, and a tick on an
   unsettled invoice would say it had. So the three marks differ by fill as well
   as hue — faded ring, solid amber, solid green — and the sr-only text beside
   each says which is which for anyone colour does not reach. */
table.grid.heat td.heat-flag-cell.status-flag-sent .flag-mark{
  background:var(--warn);color:#fff;opacity:1;
  box-shadow:0 1px 2px rgba(14,23,42,.18);
}
/* The stamp only appears on a ticked milestone, so keep it tight under the
   mark rather than reserving room on every cell. */
table.grid.heat td.heat-flag-cell .cell-updated-stamp{margin-top:2px;text-align:center}

/* base = the first column, nothing to its left to judge it against.
   unknown = a ratio that cannot be computed (no reading, or a zero base). */
td.status-base{background:var(--surface-alt);color:var(--ink)}
td.status-unknown{background:var(--white);color:var(--muted)}
td.status-ok{background:var(--status-ok-bg);color:var(--status-ok-ink)}
td.status-flagged{
  background:var(--status-flagged-bg);color:var(--status-flagged-ink);
  box-shadow:inset 3px 0 0 var(--status-flagged-edge);
}
td.status-missing{background:var(--white);color:var(--border);font-weight:400}

.status-swatch{
  width:22px;height:16px;border-radius:3px;display:inline-flex;align-items:center;
  justify-content:center;border:1px solid rgba(14,23,42,.10);font-size:10px;font-weight:800;
}
.status-swatch.is-ok{background:var(--status-ok-bg)}
.status-swatch.is-flagged{background:var(--status-flagged-bg);color:#fff;
  box-shadow:inset 3px 0 0 var(--status-flagged-edge)}
.status-swatch.is-flagged{color:var(--status-flagged-ink)}
.status-swatch.is-base{background:var(--surface-alt)}
.status-swatch.is-neutral{background:var(--surface-alt);color:var(--muted);font-weight:600}
.status-swatch.is-missing{background:var(--white);color:var(--border)}


.scale-legend{
  display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;
  gap:10px;padding:12px 18px;border-top:1px solid var(--line);background:var(--surface-alt);
}
.scale{display:flex;flex-wrap:wrap;align-items:center;gap:4px;font-size:11.5px;color:var(--muted);font-weight:600}
.scale-key{display:inline-flex;align-items:center;gap:5px;margin-left:10px}

/* Status chips in a card header. Tinted, not saturated — a header badge should
   report the state, not compete with the grid it summarises. */
.chip.is-flagged{background:var(--status-flagged-bg);border-color:rgba(208,59,59,.3);color:var(--status-flagged-ink)}
.chip.is-ok{background:var(--status-ok-bg);border-color:rgba(var(--navy-dark-rgb),.18);color:var(--status-ok-ink)}

/* ── Trend chart ── */
.legend{list-style:none;display:flex;flex-wrap:wrap;gap:14px;margin-bottom:12px}
.legend-item{display:inline-flex;align-items:center;gap:7px;font-size:12.5px;font-weight:600;color:var(--body-c)}
/* Lines get a line key, mirroring the mark they identify. */
.legend-key{width:16px;height:3px;border-radius:2px;flex:0 0 auto;background:currentColor}
.legend-item .series-1{color:var(--series-1)}
.legend-item .series-2{color:var(--series-2)}
.legend-item .series-3{color:var(--series-3)}

.chart{position:relative}
.chart svg{display:block;width:100%;height:auto;overflow:visible;touch-action:pan-y}
.chart:focus-visible{outline:2px solid var(--focus);outline-offset:3px;border-radius:8px}

.chart-grid line{stroke:var(--line);stroke-width:1}
.chart-tick{font-family:var(--font-body);font-size:11px;fill:var(--muted);font-variant-numeric:tabular-nums}
.chart-line{fill:none;stroke-width:2;stroke-linejoin:round;stroke-linecap:round}
/* The 2px surface ring keeps a dot legible where lines cross. */
.chart-dot{stroke:var(--white);stroke-width:2}
/* Text wears text tokens — a light hue like aqua is illegible as a label. */
.chart-end-label{font-family:var(--font-heading);font-size:11.5px;font-weight:700;
  fill:var(--ink);font-variant-numeric:tabular-nums}
.chart-crosshair{stroke:var(--muted);stroke-width:1;opacity:0;pointer-events:none;transition:opacity .1s}
.chart-crosshair.visible{opacity:.5}

g.series-1 .chart-line{stroke:var(--series-1)}
g.series-1 .chart-dot{fill:var(--series-1)}
g.series-2 .chart-line{stroke:var(--series-2)}
g.series-2 .chart-dot{fill:var(--series-2)}
g.series-3 .chart-line{stroke:var(--series-3)}
g.series-3 .chart-dot{fill:var(--series-3)}

.chart-tooltip{
  position:absolute;transform:translate(-50%,-100%);pointer-events:none;
  background:var(--navy-deep);color:#fff;border-radius:8px;padding:8px 10px;
  font-size:12px;line-height:1.45;box-shadow:0 8px 20px -6px rgba(var(--navy-deep-rgb),.5);
  opacity:0;transition:opacity .1s;z-index:70;white-space:nowrap;
  display:flex;flex-direction:column;gap:3px;
}
.chart-tooltip.visible{opacity:1}
.chart-tooltip .tip-head{font-weight:700;color:rgba(255,255,255,.7);font-size:11px;
  text-transform:uppercase;letter-spacing:.05em}
/* Values lead, names follow — here the reader has the series and wants the number. */
.chart-tooltip .tip-row{display:flex;align-items:center;gap:7px}
.chart-tooltip .tip-row strong{font-variant-numeric:tabular-nums;font-size:13px}
.chart-tooltip .tip-name{color:rgba(255,255,255,.72);font-size:11.5px}
.chart-tooltip .tip-key{width:12px;height:3px;border-radius:2px;flex:0 0 auto;background:currentColor}
.chart-tooltip .tip-key.series-1{color:var(--series-1)}
.chart-tooltip .tip-key.series-2{color:var(--series-2)}
.chart-tooltip .tip-key.series-3{color:var(--series-3)}
.chart-tooltip strong{color:#fff}

@media (max-width:560px){
  .pick-card-value{font-size:20px}
  .filter,.filter select{min-width:100%}
  .scale-legend{padding:12px 14px}
}

/* ── PM login (not Django admin) ─────────────────────────────────────────── */
.login-page{
  min-height:100vh;color:#fff;background:var(--navy-deep);
  -webkit-font-smoothing:antialiased;
}
.login-bg{
  position:fixed;inset:0;z-index:0;overflow:hidden;pointer-events:none;
  background:
    radial-gradient(ellipse 90% 60% at 50% 0%, rgba(var(--gold-rgb),.14), transparent 58%),
    linear-gradient(165deg, #060a14 0%, var(--navy-deep) 42%, var(--navy-dark) 100%);
}
.login-bg-orb{
  position:absolute;border-radius:50%;filter:blur(60px);opacity:.55;
  animation:loginOrb 18s ease-in-out infinite alternate;
}
.login-bg-orb-a{
  width:min(520px, 70vw);height:min(520px, 70vw);
  top:-12%;left:-8%;background:rgba(var(--gold-rgb),.22);
}
.login-bg-orb-b{
  width:min(420px, 55vw);height:min(420px, 55vw);
  bottom:-18%;right:-6%;background:rgba(var(--navy-mid-rgb),.85);
  animation-delay:-6s;
}
@keyframes loginOrb{
  from{transform:translate(0,0) scale(1)}
  to{transform:translate(24px,-18px) scale(1.06)}
}
.login-bg-grid{
  position:absolute;inset:0;opacity:.22;
  background-image:
    linear-gradient(rgba(var(--gold-rgb),.08) 1px, transparent 1px),
    linear-gradient(90deg, rgba(var(--gold-rgb),.08) 1px, transparent 1px);
  background-size:48px 48px;
  mask-image:radial-gradient(ellipse 85% 75% at 50% 45%, #000 20%, transparent 72%);
}
.login-bg-vignette{
  position:absolute;inset:0;
  background:radial-gradient(ellipse 80% 70% at 50% 50%, transparent 35%, rgba(4,8,16,.82) 100%);
}
.login-shell{
  position:relative;z-index:1;min-height:100vh;display:flex;flex-direction:column;
  align-items:center;padding:clamp(16px,3vw,28px) clamp(16px,4vw,40px) 24px;
}
.login-top{
  width:100%;max-width:920px;display:flex;align-items:center;justify-content:flex-end;
  margin-bottom:clamp(20px,4vh,36px);
}
.login-stage{
  flex:1;display:flex;align-items:center;justify-content:center;width:100%;
}
.login-card{
  width:100%;max-width:920px;display:grid;grid-template-columns:minmax(280px, 1fr) minmax(320px, 1.05fr);
  border-radius:22px;overflow:hidden;
  border:1px solid rgba(var(--gold-rgb),.24);
  box-shadow:
    0 28px 60px -24px rgba(0,0,0,.65),
    0 0 0 1px rgba(255,255,255,.04) inset,
    0 0 80px -20px rgba(var(--gold-rgb),.18);
}
.login-brand{
  position:relative;overflow:hidden;
  background:
    radial-gradient(ellipse 120% 90% at 20% 0%, rgba(var(--gold-rgb),.18), transparent 55%),
    linear-gradient(160deg, #0a1224 0%, var(--navy-dark) 55%, #101e38 100%);
  border-right:1px solid rgba(var(--gold-rgb),.18);
  padding:clamp(28px,5vw,44px) clamp(24px,4vw,36px);
  display:flex;align-items:center;justify-content:center;
}
.login-brand-inner{
  position:relative;z-index:1;text-align:center;max-width:320px;
}
.login-logo-wrap{
  position:relative;display:inline-flex;align-items:center;justify-content:center;
  margin-bottom:18px;
}
.login-logo-halo{
  position:absolute;inset:-18px -24px;border-radius:999px;
  background:radial-gradient(circle, rgba(var(--gold-rgb),.28) 0%, transparent 68%);
  filter:blur(8px);
}
.login-logo{
  position:relative;height:clamp(44px, 8vw, 58px);width:auto;display:block;
  filter:drop-shadow(0 0 22px rgba(var(--gold-rgb),.45));
}
.login-wordmark{
  font-family:var(--font-heading);font-size:clamp(1.35rem, 2.4vw, 1.75rem);
  font-weight:800;letter-spacing:.06em;text-transform:uppercase;color:#fff;
  margin-bottom:14px;
}
.login-rule{
  display:block;width:56px;height:2px;margin:0 auto 14px;border-radius:999px;
  background:linear-gradient(90deg, transparent, var(--gold-light), transparent);
  box-shadow:0 0 16px rgba(var(--gold-rgb),.55);
}
.login-suite{
  font-size:11px;font-weight:800;letter-spacing:.18em;text-transform:uppercase;
  color:var(--gold-light);margin-bottom:12px;
}
.login-brand-desc{
  font-size:14px;line-height:1.6;color:rgba(255,255,255,.68);max-width:28ch;margin:0 auto;
}
.login-brand-accent{
  position:absolute;right:-40px;bottom:-40px;width:min(280px, 70%);height:auto;
  color:var(--gold);opacity:.16;pointer-events:none;
}
.login-panel{
  background:
    linear-gradient(180deg, rgba(255,255,255,.99) 0%, rgba(248,250,252,.98) 100%);
  color:var(--ink);padding:clamp(28px,5vw,44px) clamp(24px,4vw,40px);
  display:flex;flex-direction:column;justify-content:center;
}
.login-panel-head{margin-bottom:24px}
.login-panel h1{
  font-family:var(--font-heading);font-size:clamp(1.55rem, 2.8vw, 2rem);
  letter-spacing:-.03em;color:var(--navy-dark);margin-bottom:8px;
}
.login-sub{color:var(--muted);font-size:14.5px;line-height:1.55;max-width:36ch}
.login-form{display:flex;flex-direction:column;gap:18px}
.login-form .field{display:flex;flex-direction:column;gap:7px}
.login-form .field > span{
  font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);
}
.login-form input{
  width:100%;padding:13px 14px;border:1px solid #cfd8e3;border-radius:10px;
  font:inherit;background:#fff;color:var(--ink);
  transition:border-color .15s, box-shadow .15s, background .15s;
}
.login-form input::placeholder{color:#94a3b8}
.login-form input:focus{
  outline:none;border-color:var(--gold);
  box-shadow:0 0 0 3px rgba(var(--gold-rgb),.22);
}
.login-form input.invalid{border-color:var(--err);background:var(--err-tint)}
.login-form .field-error{font-size:12px;color:var(--err);font-weight:600}
.login-error{margin-bottom:18px}
.login-submit{
  width:100%;justify-content:center;padding:14px 18px;margin-top:6px;
  border:1px solid var(--gold-dark);border-radius:10px;
  background:linear-gradient(135deg, var(--gold-light) 0%, var(--gold) 52%, var(--gold-dark) 100%);
  color:var(--navy-deep);font-family:var(--font-heading);font-weight:800;letter-spacing:.04em;
  box-shadow:0 10px 24px -12px rgba(var(--gold-rgb),.65), inset 0 1px 0 rgba(255,255,255,.35);
  transition:transform .12s, box-shadow .15s, filter .15s;
}
.login-submit:hover{
  filter:brightness(1.03);
  box-shadow:0 14px 28px -12px rgba(var(--gold-rgb),.75), inset 0 1px 0 rgba(255,255,255,.4);
  color:var(--navy-deep);
}
.login-submit:active{transform:translateY(1px)}
.login-foot{
  margin-top:clamp(20px,4vh,32px);font-size:12px;font-weight:600;
  letter-spacing:.06em;text-transform:uppercase;color:rgba(255,255,255,.42);text-align:center;
}
.login-page .lang-select{
  border:1px solid rgba(var(--gold-rgb),.28);background:rgba(255,255,255,.06);color:#fff;
  padding:7px 12px;border-radius:999px;font-size:12px;font-weight:700;cursor:pointer;
  backdrop-filter:blur(6px);
}
.login-page .lang-select:hover{background:rgba(255,255,255,.12);border-color:rgba(var(--gold-rgb),.45)}
.login-page .lang-select option{color:var(--ink);background:#fff}
@media (max-width:820px){
  .login-card{grid-template-columns:1fr;max-width:460px}
  .login-brand{
    border-right:0;border-bottom:1px solid rgba(var(--gold-rgb),.18);
    padding:28px 24px 24px;
  }
  .login-brand-desc{max-width:none}
  .login-brand-accent{width:220px;right:-60px;bottom:-70px;opacity:.12}
  .login-panel{padding:28px 24px 32px}
}
@media (prefers-reduced-motion:reduce){
  .login-bg-orb{animation:none}
}
.side-logout{
  display:block;margin-top:6px;padding:0;border:0;background:none;
  font-size:12px;font-weight:700;color:rgba(255,255,255,.55);cursor:pointer;
  transition:color .15s;
}
.side-logout:hover{color:var(--gold-light)}
