/* Mobile-first styles: cool off-white canvas, borderless shadow cards, an
   Industrial Blue primary, left sidebar on desktop, bottom nav on mobile.
   Light + dark via the `dark` class on <html> (a per-estate setting).

   The palette was a stock purple admin template, then deep forest green, and is
   Industrial Blue 175 (#648bb2 — hsl(210 34% 55%)) from 19/08/2026. Green was
   inherited wholesale from Storage Yard Manager, which is a different product
   for a different trade: a prospect who read the UnitLet marketing site (navy)
   and signed into a green app was looking at the yard's clothes.

   Every neutral is on the SAME hue, 210, rather than being grey: it is what
   makes the ground read as considered instead of default. The brand blue is
   kept dark where it carries text and left at full strength only where it is a
   large fill — #648bb2 holds white at just 3.57:1, so buttons use --btn-primary
   (6.01:1) and the swatch itself is reserved for the mark and the map lines.

   The status colours below are NOT branding and deliberately did not move.
   Green means paid, amber means due, red means late: that is a traffic light an
   operator reads without thinking, and repainting it to match a logo would cost
   more than it gained. */
:root {
  --bg: #f6f7f9;
  --card: #ffffff;
  --text: #1b2128;
  --heading: #131a20;
  --muted: #5d6974;
  --faint: #939ea9;
  --border: #dde3e9;
  --accent: #345779;
  --accent-hover: #274563;
  --accent-bg: #e9f0f7;
  --row-hover: #f5f7fa;
  --thead-bg: #eff2f6;
  --brand: #648bb2;
  --green: #15683d;
  --green-bg: #e3f3e9;
  --amber: #8a5a00;
  --amber-strong: #c47b12;
  --amber-bg: #fbeed8;
  --red: #b3261e;
  --red-bg: #fbe4e2;
  --cyan: #0d7d8c;
  --cyan-bg: #ddf1f4;
  --grey-bg: #edf0f2;
  --hatch-a: #edf0f2;
  --hatch-b: #d9dee4;
  /* Estate-map tile fills. Deliberately their own variables rather than the
     --*-bg pills: a tile is read across a whole estate at a glance and wants far
     more colour than a badge sitting inside a sentence.

     These stayed green on purpose. The tile ramp is a money signal — paid, due,
     late — not a brand surface, and it is the one place on the screen where
     colour IS the information. --tile-reserved and --tile-line are the
     exceptions: they mark chrome rather than money, so they carry the blue. */
  --tile-paid: #a9d9bd;
  --tile-due: #cfe9d9;
  --tile-overdue: #f7c3bd;
  --tile-vacant: #ffffff;
  --tile-vacant-edge: #b4bdc6;
  --tile-reserved: #dce8f2;
  --tile-line: #426b94;
  --tile-oos-a: #f6c58a;
  --tile-oos-b: #ffffff;
  --topbar-bg: rgba(255, 255, 255, 0.92);
  --shadow-sm: 0 2px 8px rgba(20, 32, 45, 0.08);
  --shadow-md: 0 4px 18px rgba(20, 32, 45, 0.12);
  --shadow-accent: 0 2px 6px rgba(52, 87, 121, 0.32);
}
/* Near-black rather than a blue-tinted dark: tinting the whole canvas blue
   makes the accent stop registering as an accent. Only the neutrals carry a
   faint blue bias, so the ground reads as considered rather than grey. */
html.dark {
  --bg: #0e1216;
  --card: #161c22;
  --text: #e1e6ea;
  --heading: #f2f5f7;
  --muted: #98a3ae;
  --faint: #687582;
  --border: #2b333b;
  --accent: #8badd0;
  --accent-hover: #acc7e2;
  --accent-bg: rgba(139, 173, 208, 0.18);
  --row-hover: rgba(255, 255, 255, 0.035);
  --thead-bg: #1d242a;
  --brand: #648bb2;
  --green: #4dd18c;
  --green-bg: rgba(45, 160, 100, 0.20);
  --amber: #f0b45e;
  --amber-strong: #f0b45e;
  --amber-bg: rgba(200, 130, 40, 0.20);
  --red: #f0908a;
  --red-bg: rgba(200, 60, 55, 0.22);
  --cyan: #5ecfdd;
  --cyan-bg: rgba(30, 160, 180, 0.18);
  --grey-bg: #23292f;
  --hatch-a: #23292f;
  --hatch-b: #2e353d;
  --tile-paid: rgba(45, 175, 108, 0.46);
  --tile-due: rgba(45, 175, 108, 0.24);
  --tile-overdue: rgba(214, 72, 64, 0.44);
  --tile-vacant: #f1f3f5;
  --tile-vacant-edge: rgba(0, 0, 0, 0.20);
  --tile-reserved: rgba(139, 173, 208, 0.16);
  --tile-line: #84a8cd;
  --tile-oos-a: #e2a463;
  --tile-oos-b: #f4f1ec;
  --topbar-bg: rgba(22, 28, 34, 0.94);
  --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.40);
  --shadow-md: 0 4px 18px rgba(0, 0, 0, 0.50);
  --shadow-accent: 0 2px 6px rgba(52, 87, 121, 0.45);
}
/* Solid primary buttons use one blue in both modes: it carries white text at
   6.01:1 on the light canvas and still reads as a raised control on the dark
   one. Kept separate from --accent, which has to work as small link TEXT and so
   needs to be dark in light mode and light in dark mode.

   This is Industrial Blue darkened, NOT the swatch. #648bb2 holds white at only
   3.57:1 — fine for a logo, not for a button somebody has to read. */
:root, html.dark { --btn-primary: #3f668d; --btn-primary-hover: #315477; }
/* A field's background matches the card it sits on, so its BORDER is the only
   thing that draws the box. --border gives 9:1 against a white card but only
   1.38:1 against the dark one, which left dark-mode fields nearly invisible —
   hence a separate field border that is lifted in dark mode (3.26:1). */
/* Light mode needed the same treatment and never got it: the old #e6e6e8 drew
   a field box at 1.2:1 against a white card, so the only thing separating an
   input from the card was a line almost nobody could see. 3.1:1 clears WCAG's
   3:1 for a control boundary. It reads heavier than a typical field border --
   that is the price of the box being visible at all. */
:root { --field-border: #8794a1; }
html.dark { --field-border: #607080; --field-placeholder: #98a3ae; }

* { box-sizing: border-box; }
html { font-size: 16px; }
html.dark { color-scheme: dark; }
body {
  margin: 0;
  font-family: Inter, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
  -webkit-font-smoothing: antialiased;
  font-size: 0.9375rem;
  background: var(--bg);
  color: var(--text);
  padding-bottom: calc(4.5rem + env(safe-area-inset-bottom)); /* room for bottom nav */
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
h1 { font-size: 1.35rem; font-weight: 650; letter-spacing: -0.01em; color: var(--heading); margin: 1.1rem 0 0.8rem; }
h2 { font-size: 1.02rem; font-weight: 600; color: var(--heading); margin: 0 0 0.7rem; }
h3 { font-size: 0.92rem; font-weight: 600; color: var(--heading); margin: 1rem 0 0.45rem; }
code { background: var(--grey-bg); padding: 0.1em 0.35em; border-radius: 5px; font-size: 0.85em; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.container { max-width: 64rem; margin: 0 auto; padding: 0 1rem 2rem; }

/* Top bar — mobile only; desktop uses the sidebar */
.topbar {
  background: var(--topbar-bg);
  -webkit-backdrop-filter: saturate(1.4) blur(10px);
  backdrop-filter: saturate(1.4) blur(10px);
  box-shadow: var(--shadow-sm);
  color: var(--text);
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.55rem 1rem;
  padding-top: calc(0.55rem + env(safe-area-inset-top));
  flex-wrap: wrap;
  position: sticky;
  top: 0;
  z-index: 20;
}
.brand {
  color: var(--heading); font-weight: 650; font-size: 1rem; letter-spacing: -0.01em;
  white-space: nowrap; display: flex; align-items: center; gap: 0.55rem;
}
.brand:hover { text-decoration: none; }
.brand-icon { width: 28px; height: 28px; border-radius: 8px; display: block; }
.logout-form { margin-left: auto; }
.linklike { background: none; border: none; color: var(--muted); cursor: pointer; font: inherit; font-size: 0.88rem; padding: 0.3rem; }
.linklike:hover { color: var(--text); }

/* Compact icon links in the mobile top bar (Reports / Maintenance / Settings /
   Billing — the pages the bottom tab bar doesn't cover) */
.topbar { gap: 0.6rem; }
.topnav-mobile { display: flex; gap: 0.1rem; margin-left: auto; align-items: center; }
.topnav-mobile a { position: relative; display: inline-flex; color: var(--muted); padding: 0.45rem; border-radius: 8px; }
.topnav-mobile a.active { color: var(--accent); background: var(--accent-bg); }
.topnav-mobile .badge { position: absolute; top: -1px; right: -3px; margin: 0; }
.topbar .logout-form { margin-left: 0.1rem; }
/* On narrow phones the wordmark would push Log out onto a second row —
   every page has its own heading, so the icon alone carries the brand. */
@media (max-width: 479px) { .topbar .brand span { display: none; } }

/* Sun/moon theme toggle (mobile top bar + desktop sidebar) */
.theme-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; padding: 0; flex: none; cursor: pointer;
  background: var(--card); color: var(--text);
  border: 1px solid var(--border); border-radius: 8px;
}
.theme-toggle svg { width: 18px; height: 18px; transition: transform 0.35s ease; }
.theme-toggle:hover svg { transform: rotate(35deg); }
.theme-toggle .moon { display: none; }
html.dark .theme-toggle .sun { display: none; }
html.dark .theme-toggle .moon { display: block; }
.sidebar-foot { display: flex; flex-direction: column; align-items: stretch; gap: 0.55rem; margin-top: 0.5rem; border-top: 1px solid var(--border); padding-top: 0.7rem; }
.sidebar-foot .foot-row { display: flex; align-items: center; gap: 0.5rem; }
/* Help sits with the theme toggle rather than in the menu: it is a thing you
   reach for at a moment of confusion, not a place you go, and every page already
   carries its own ? to the right bit of the manual. Same 38px box as the toggle
   so the footer reads as one row of small controls. */
.sidebar-foot .foot-row > a {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; flex: none;
  background: var(--card); color: var(--text);
  border: 1px solid var(--border); border-radius: 8px;
}
.sidebar-foot .foot-row > a:hover { text-decoration: none; border-color: var(--accent); color: var(--accent); }
.sidebar-foot .foot-row > a.active { color: var(--accent); border-color: var(--accent); background: var(--accent-bg); }
.sidebar-foot .foot-row > a svg { width: 18px; height: 18px; }
.sidebar-foot .sidebar-logout { margin: 0; border: 0; padding: 0; flex: 1; }

/* Which yard you're in, and who you are. One block, two placements: stacked
   above Log out in the desktop sidebar, and on its own wrapped row in the
   mobile top bar. It cannot share that bar's first row — below 480px there is
   so little width left that the wordmark is hidden outright, and a name would
   push Log out onto a second line anyway. min-width:0 lets the ellipsis work;
   without it a flex child refuses to shrink below its content. */
.who { display: flex; min-width: 0; }
.who-yard, .who-user { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.who-yard { font-weight: 620; color: var(--heading); }
.who-user { color: var(--muted); }
.sidebar-foot .who { flex-direction: column; padding: 0 0.6rem; line-height: 1.3; }
.sidebar-foot .who-yard { font-size: 0.92rem; }
.sidebar-foot .who-user { font-size: 0.82rem; }
.topbar .who { flex: 0 0 100%; align-items: baseline; gap: 0.4rem; font-size: 0.82rem; line-height: 1.2; }
.topbar .who-user::before { content: '·'; margin-right: 0.4rem; color: var(--faint); }

/* Sidebar (desktop) */
.sidebar {
  display: none;
  position: fixed; top: 0; left: 0; bottom: 0; width: 250px; z-index: 25;
  background: var(--card);
  box-shadow: var(--shadow-sm);
  flex-direction: column;
  padding: 1rem 0.85rem;
}
.sidebar .brand { padding: 0.25rem 0.6rem 1rem; font-size: 1.06rem; }
.sidenav { display: flex; flex-direction: column; gap: 0.15rem; flex: 1; overflow-y: auto; }
.sidenav a {
  display: flex; align-items: center; gap: 0.65rem;
  color: var(--muted); font-weight: 500; font-size: 0.92rem;
  padding: 0.55rem 0.75rem; border-radius: 8px; white-space: nowrap;
  transition: background 0.13s ease, color 0.13s ease;
}
.sidenav a svg { flex-shrink: 0; opacity: 0.9; }
.sidenav a:hover { color: var(--heading); text-decoration: none; background: var(--grey-bg); }
.sidenav a.active {
  color: #fff;
  /* Both ends carry white text: #3f668d is 6.0:1 and #4e7aa6 is 4.5:1, which
     is the lightest this hue goes before white stops being readable on it. */
  background: linear-gradient(72deg, #3f668d, #4e7aa6);
  box-shadow: var(--shadow-accent);
}
.sidenav a .badge { margin-left: auto; }
.sidebar-logout { margin-top: 0.5rem; border-top: 1px solid var(--border); padding-top: 0.7rem; }
.sidebar-logout .linklike {
  display: flex; align-items: center; gap: 0.65rem; width: 100%;
  padding: 0.55rem 0.75rem; border-radius: 8px; font-size: 0.92rem; font-weight: 500;
}
.sidebar-logout .linklike:hover { background: var(--grey-bg); }

/* Bottom nav (mobile) */
.bottomnav {
  position: fixed; bottom: 0; left: 0; right: 0; z-index: 20;
  display: flex;
  background: var(--topbar-bg);
  -webkit-backdrop-filter: saturate(1.4) blur(10px);
  backdrop-filter: saturate(1.4) blur(10px);
  box-shadow: 0 -2px 8px rgba(47, 43, 61, 0.08);
  padding-bottom: env(safe-area-inset-bottom);
}
.bottomnav a {
  flex: 1; text-align: center; padding: 0.5rem 0 0.45rem;
  color: var(--muted); font-size: 0.66rem; font-weight: 500; line-height: 1.4;
}
.bottomnav a:hover { text-decoration: none; }
.bottomnav a.active { color: var(--accent); }
.bottomnav .icon { display: block; position: relative; height: 24px; }
.bottomnav .icon svg { display: inline-block; vertical-align: top; margin-top: 1px; }
.bottomnav .icon .badge { position: absolute; top: -0.25rem; margin-left: 0.05rem; }

@media (min-width: 800px) {
  body { padding-bottom: 2rem; }
  .bottomnav { display: none; }
  .topbar { display: none; }
  .sidebar { display: flex; }
  .container { margin-left: 250px; max-width: 84rem; padding: 0.5rem 2rem 2.5rem; }
  h1 { font-size: 1.55rem; }
}
/* Above 1400 the container stops hugging the sidebar and centres instead, with
   the sidebar's width reserved as padding. That padding comes out of max-width,
   so the cap has to include it — otherwise the sidebar is subtracted from the
   content and the app gets NARROWER on a bigger screen: 1280px gave 966px of
   content, 1440px and every size above it gave 838px, a 2560px monitor
   included. The cap is expressed as content + chrome for that reason. */
@media (min-width: 1400px) {
  .container {
    margin-left: auto;
    padding-left: calc(250px + 2rem);
    max-width: calc(84rem + 250px + 2rem);
  }
}

/* Cards, stats */
.card {
  background: var(--card);
  border: none;
  border-radius: 12px;
  padding: 1.2rem 1.25rem;
  margin: 1.1rem 0;
  box-shadow: var(--shadow-sm);
}
@media (min-width: 800px) { .card { padding: 1.4rem 1.5rem; } }
.stat-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.8rem;
  margin: 1.1rem 0;
}
@media (min-width: 800px) { .stat-grid { grid-template-columns: repeat(3, 1fr); } }
.stat {
  background: var(--card);
  border: none;
  border-radius: 12px;
  padding: 1rem 1.05rem;
  color: var(--text);
  display: block;
  box-shadow: var(--shadow-sm);
  transition: box-shadow 0.13s ease, transform 0.13s ease;
}
.stat:hover { text-decoration: none; box-shadow: var(--shadow-md); transform: translateY(-2px); }
.stat .num { font-size: 1.6rem; font-weight: 650; letter-spacing: -0.01em; line-height: 1.25; color: var(--heading); }
.stat .num .sub { font-size: 1rem; color: var(--faint); font-weight: 500; letter-spacing: 0; }
.stat .label { color: var(--muted); font-size: 0.82rem; margin-top: 0.1rem; }
.stat.alert-stat .num { color: var(--red); }
.stat.alert-stat { box-shadow: var(--shadow-sm), inset 0 -3px 0 var(--red); }

/* Stat icon chips (Vuexy-style pastel squares), left of the figures.
   Only stats that have a chip flex into a row — report stats stay stacked. */
.stat:has(.stat-icon) { display: flex; align-items: center; gap: 0.85rem; }
.stat-body { min-width: 0; }
.stat-icon {
  display: inline-flex; align-items: center; justify-content: center;
  flex-shrink: 0; width: 42px; height: 42px; border-radius: 10px;
}
.stat-icon.purple { background: var(--accent-bg); color: var(--accent); }
.stat-icon.green { background: var(--green-bg); color: var(--green); }
.stat-icon.amber { background: var(--amber-bg); color: var(--amber-strong); }
.stat-icon.red { background: var(--red-bg); color: var(--red); }
.stat-icon.cyan { background: var(--cyan-bg); color: var(--cyan); }
.stat-icon.grey { background: var(--grey-bg); color: var(--muted); }

/* Buttons */
.btn {
  display: inline-block;
  background: var(--card);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 8px;
  padding: 0.5rem 1rem;
  font: inherit;
  font-size: 0.9rem;
  font-weight: 500;
  cursor: pointer;
  text-align: center;
  min-height: 44px; /* touch target */
  line-height: 1.8;
  transition: background 0.13s ease, border-color 0.13s ease, box-shadow 0.13s ease, transform 0.13s ease;
}
.btn:hover { text-decoration: none; background: var(--grey-bg); border-color: var(--faint); }
.btn.primary {
  background: var(--btn-primary); border-color: var(--btn-primary); color: #fff;
  box-shadow: var(--shadow-accent); font-weight: 550;
}
.btn.primary:hover { background: var(--btn-primary-hover); border-color: var(--btn-primary-hover); transform: translateY(-1px); }
.btn.danger-outline { border-color: color-mix(in srgb, var(--red) 55%, transparent); color: var(--red); background: transparent; }
.btn.danger-outline:hover { background: var(--red-bg); border-color: var(--red); }
.btn.small { padding: 0.28rem 0.7rem; min-height: 36px; font-size: 0.82rem; line-height: 1.7; }
.btn.big { padding: 0.65rem 1.4rem; font-size: 1rem; }
.btn.block { display: block; width: 100%; }
.btn-row { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0.7rem 0 0.2rem; align-items: center; }
.btn-row form { margin: 0; }
.quick-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; margin: 1.1rem 0; }
.btn.ghost { background: transparent; border-color: transparent; color: var(--muted); box-shadow: none; }
.btn.ghost:hover { background: var(--grey-bg); color: var(--text); }

/* Empty-yard dashboard prompt */
.setup-hero { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; border-left: 4px solid var(--accent); }
.setup-hero h2 { margin: 0 0 0.25rem; }
.setup-hero p { margin: 0; max-width: 42ch; }

/* Onboarding wizard */
.wiz { max-width: 640px; margin: 0 auto; }
.wiz-head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.5rem; flex-wrap: wrap; }
.wiz-skip { margin: 0; }
.wiz-steps { display: flex; gap: 0.4rem; list-style: none; padding: 0; margin: 1rem 0 1.3rem; counter-reset: none; }
.wiz-steps li { flex: 1; }
.wiz-steps li a { display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem 0.6rem; border-radius: 10px; color: var(--muted); border: 1px solid var(--border); background: var(--card); font-size: 0.84rem; font-weight: 500; }
.wiz-steps li a:hover { border-color: var(--faint); }
.wiz-num { display: inline-flex; align-items: center; justify-content: center; width: 1.55rem; height: 1.55rem; flex-shrink: 0; border-radius: 999px; background: var(--grey-bg); color: var(--muted); font-weight: 700; font-size: 0.82rem; }
.wiz-steps li.current a { border-color: var(--accent); color: var(--text); background: var(--accent-bg); }
.wiz-steps li.current .wiz-num { background: var(--btn-primary); color: #fff; }
.wiz-steps li.done .wiz-num { background: var(--green, #28c76f); color: #fff; }
.wiz-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wiz-card h2 { margin-top: 0; }
.wiz-card h3 { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); margin: 1.2rem 0 0.5rem; }
.wiz-card .opt { font-weight: 400; text-transform: none; letter-spacing: 0; color: var(--muted); }
.wiz-narrow { max-width: 22rem; }
.wiz-actions { display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap; margin-top: 1.1rem; }
.wiz-actions form { margin: 0; }
.wiz-done { text-align: center; }
.wiz-check { width: 3.2rem; height: 3.2rem; margin: 0.2rem auto 0.6rem; border-radius: 999px; background: var(--green-bg); color: var(--green); display: flex; align-items: center; justify-content: center; font-size: 1.6rem; font-weight: 700; }
.wiz-summary { display: flex; gap: 0.6rem; justify-content: center; list-style: none; padding: 0; margin: 1rem 0; flex-wrap: wrap; }
.wiz-summary li { background: var(--grey-bg); border-radius: 10px; padding: 0.7rem 1.1rem; color: var(--muted); font-size: 0.82rem; }
.wiz-summary strong { display: block; font-size: 1.3rem; color: var(--text); }
.wiz-done .wiz-actions { justify-content: center; }
.wiz-assigned { background: var(--green-bg); color: var(--green); border-radius: 8px; padding: 0.6rem 0.85rem; font-size: 0.85rem; font-weight: 500; margin: 0 0 1rem; }

/* Public enquiry page */
.public-page { max-width: 560px; margin: 1.5rem auto; }
.enquiry-head { text-align: center; margin-bottom: 0.4rem; }
.enquiry-head h1 { margin-bottom: 0.2rem; }
.enquiry-intro { text-align: center; color: var(--text); margin: 0 0 1rem; }
.enquiry-vac h2 { margin-top: 0; }
.vac-list { list-style: none; padding: 0; margin: 0.6rem 0 0; display: grid; gap: 0.45rem; }
.vac-list li { display: flex; align-items: center; gap: 0.6rem; }
.vac-count { display: inline-flex; align-items: center; justify-content: center; min-width: 2rem; height: 2rem; border-radius: 8px; background: var(--accent-bg); color: var(--accent); font-weight: 700; }
.enquiry-done { text-align: center; }

/* Settings page groups + jump nav */
.settings-nav { display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 0.9rem 0 0.2rem; position: sticky; top: 0; z-index: 5; background: var(--bg); padding: 0.5rem 0; }
.settings-nav a { font-size: 0.82rem; font-weight: 500; padding: 0.32rem 0.75rem; border-radius: 999px; background: var(--card); border: 1px solid var(--border); color: var(--muted); }
.settings-nav a:hover { color: var(--text); border-color: var(--faint); text-decoration: none; }
h2.settings-group { margin: 1.8rem 0 0.7rem; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.07em; color: var(--muted); font-weight: 700; scroll-margin-top: 3.5rem; border-bottom: 1px solid var(--border); padding-bottom: 0.4rem; }

/* Customer portal */
.text-amber { color: var(--amber); font-weight: 600; }
.portal .portal-head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin-bottom: 1.1rem; padding-bottom: 0.8rem; border-bottom: 1px solid var(--border); }
.portal-brand { font-weight: 650; font-size: 1.05rem; }
.portal-logout { margin: 0; }
.portal h1 { margin: 0 0 0.9rem; }
.portal .card { margin-bottom: 1rem; }
.portal-note { text-align: center; }
.portal-note .wiz-check { margin-inline: auto; }
.portal-dd.ok { border-left: 4px solid var(--green, #28c76f); }
.portal-dd { border-left: 4px solid var(--accent); }
.portal-vehicle { padding: 0.7rem 0; border-top: 1px solid var(--border); }
.portal-vehicle:first-of-type { border-top: none; padding-top: 0.3rem; }
.portal-vehicle details { margin-top: 0.5rem; }
.portal-vehicle summary { cursor: pointer; color: var(--accent); font-size: 0.88rem; font-weight: 500; }
.portal-vehicle details[open] summary { margin-bottom: 0.6rem; }
.portal-vehicle .form { margin-top: 0.4rem; }
@media (max-width: 560px) {
  .wiz-label { display: none; }
  .wiz-steps li a { justify-content: center; }
}

/* Badges & pills */
.badge {
  display: inline-block; min-width: 1.25rem; padding: 0.05rem 0.34rem;
  border-radius: 999px; font-size: 0.68rem; font-weight: 700;
  text-align: center; color: #fff; margin-left: 0.35rem; line-height: 1.5;
}
.badge.red { background: #ea5455; }
.badge.amber { background: #ff9f43; }
/* "There's something new" beside Help. A count would be a lie — it isn't a
   number of anything — so this is a plain dot, sized to read at a glance without
   competing with the overdue badge, which is the one that costs money. */
.badge.dot {
  min-width: 0; width: 0.5rem; height: 0.5rem; padding: 0;
  font-size: 0; line-height: 0; background: #ff9f43;
}
.pill {
  display: inline-block; padding: 0.16rem 0.62rem; border-radius: 6px;
  font-size: 0.74rem; font-weight: 600; white-space: nowrap; letter-spacing: 0.01em;
}
.pill.red, .pill.overdue { background: var(--red-bg); color: var(--red); }
.pill.amber, .pill.due_soon { background: var(--amber-bg); color: var(--amber); }
.pill.green, .pill.paid { background: var(--green-bg); color: var(--green); }
.pill.vacant { background: var(--grey-bg); color: var(--muted); }
.pill.reserved { background: var(--accent-bg); color: var(--accent); }
.pill.out_of_service { background: repeating-linear-gradient(45deg, var(--hatch-a), var(--hatch-a) 4px, var(--hatch-b) 4px, var(--hatch-b) 8px); color: var(--muted); }

/* Forms */
.form label { display: block; margin: 0 0 0.95rem; font-size: 0.84rem; color: var(--muted); font-weight: 500; }
.form input, .form select, .form textarea,
.inline-form input, .inline-form select, .search-row input, .filter-form input, .filter-form select {
  display: block; width: 100%;
  margin-top: 0.3rem;
  padding: 0.55rem 0.8rem;
  font: inherit;
  border: 1px solid var(--field-border);
  border-radius: 8px;
  background: var(--card);
  color: var(--text);
  min-height: 44px;
  transition: border-color 0.13s ease, box-shadow 0.13s ease;
}
.form input:focus, .form select:focus, .form textarea:focus,
.inline-form input:focus, .inline-form select:focus, .search-row input:focus,
.filter-form input:focus, .filter-form select:focus {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-bg);
}
/* Dark only: the browser's default placeholder grey lands at 2.7:1 on the dark
   field. Light mode's default already passes, so it is left alone. */
html.dark input::placeholder, html.dark textarea::placeholder { color: var(--field-placeholder); opacity: 1; }
.form textarea { min-height: auto; }
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.7rem; }
.field-group { border: 1px solid var(--border); border-radius: 10px; padding: 0.9rem 1rem 0.2rem; margin: 0 0 0.95rem; }
.field-group legend { padding: 0 0.4rem; font-size: 0.78rem; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.06em; }
.field-group .muted { margin-bottom: 0.8rem; }
.check-row { display: flex; gap: 1.2rem; margin-bottom: 0.9rem; }
label.check { display: flex; align-items: center; gap: 0.5rem; font-weight: 450; color: var(--text); margin-bottom: 0.9rem; }
label.check input { width: 1.25rem; height: 1.25rem; min-height: auto; margin: 0; accent-color: var(--btn-primary); }
.mode-row { display: flex; gap: 1.4rem; margin-bottom: 0.9rem; }
.mode-row label.check { margin-bottom: 0; }
.inline-form { display: flex; gap: 0.45rem; align-items: center; flex-wrap: wrap; margin: 0.45rem 0; }
.inline-form input, .inline-form select { display: inline-block; width: auto; flex: 1; min-width: 8rem; margin-top: 0; }
.inline-form .btn { flex-shrink: 0; }
/* The move form is a lone dropdown, so left to itself it would stretch the whole
   width of a desktop card and read as far bigger a decision than it is. */
.move-form .inline-form { max-width: 34rem; }
/* One row per occupant: tick, date, Save. The tick and the date read as a single
   statement, so they sit on one line and the date does not stretch. */
.leaving-block { margin-top: 0.9rem; padding-top: 0.7rem; border-top: 1px solid var(--border); }
.leaving-form { margin: 0 0 0.35rem; }
.leaving-form label.check { margin-bottom: 0; flex-shrink: 0; }
/* .inline-form stretches every input to fill the row, which is right for a
   dropdown and wrong for a tick box: Chrome centres the glyph inside the
   oversized control, so the tick drifts away from its own label. */
.leaving-form label.check input { flex: none; width: 1.25rem; min-width: 0; }
.leaving-form input[type="date"] { flex: 0 0 auto; width: auto; min-width: 0; }
.leaving-who { font-weight: 600; color: var(--heading); flex-shrink: 0; }
.leaving-block .muted { margin: 0.5rem 0 0; font-size: 0.82rem; }
.search-row { display: flex; gap: 0.5rem; margin: 0.9rem 0 0.5rem; }
.search-row input { margin: 0; flex: 1; }
.filter-form { display: flex; gap: 0.7rem; flex-wrap: wrap; align-items: end; }
.filter-form label { font-size: 0.82rem; font-weight: 500; color: var(--muted); flex: 1; min-width: 8rem; }
.hidden { display: none; }

/* Filter chips */
/* The yard map's status strip. Four facts, four links, one line — kept
   deliberately quiet (text weight, no boxes) because it sits above the map and
   the map is what you came to look at. A count of zero is dimmed rather than
   hidden: "0 overdue" is worth seeing, and a strip that changes shape as the
   numbers change is harder to read at a glance than one that never moves. */
.estate-strip {
  display: flex; flex-wrap: wrap; gap: 0.25rem 1.1rem;
  margin: 0.15rem 0 0.1rem; font-size: 0.88rem;
}
.estate-strip a { color: var(--muted); }
.estate-strip a strong { color: var(--heading); font-weight: 650; }
.estate-strip a:hover { text-decoration: none; color: var(--accent); }
.estate-strip a:hover strong { color: var(--accent); }
.estate-strip a.none, .estate-strip a.none strong { color: var(--faint); font-weight: 550; }

/* Group tabs — which VIEW of a place you are looking at (Map / List /
   Maintenance). Deliberately a different shape from .chip below: chips say what
   is being filtered, tabs say where you are, and two identical-looking rows on
   one screen is how people stop reading either of them. Underline rather than
   pill, and above the heading rather than under it, so the heading still reads as
   the name of what you are looking at. */
.tabs {
  display: flex; gap: 0.2rem; flex-wrap: wrap; align-items: flex-end;
  margin: 0 0 1rem; border-bottom: 1px solid var(--border);
}
.tabs a {
  padding: 0.5rem 0.85rem; color: var(--muted); font-size: 0.9rem; font-weight: 550;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.tabs a:hover { text-decoration: none; color: var(--heading); }
.tabs a.on { color: var(--accent); border-bottom-color: var(--accent); }
.tabs .tab-count {
  display: inline-block; margin-left: 0.15rem; padding: 0 0.34rem;
  border-radius: 8px; background: var(--grey-bg); color: var(--muted);
  font-size: 0.75rem; font-weight: 600;
}
.tabs a.on .tab-count { background: var(--accent-bg); color: var(--accent); }

.filter-row { display: flex; gap: 0.4rem; flex-wrap: wrap; margin: 0.8rem 0; }
.chip {
  padding: 0.34rem 0.9rem; border-radius: 6px; border: none;
  background: var(--grey-bg); color: var(--muted); font-size: 0.83rem; font-weight: 550;
  transition: background 0.13s ease, color 0.13s ease;
}
.chip:hover { text-decoration: none; color: var(--heading); background: var(--border); }
.chip.on { background: var(--accent); color: #fff; box-shadow: var(--shadow-accent); }
/* Below the desktop breakpoint every chip and the log-out button grow to a 38px
   box. Measured at 28px and 29px, which is tappable at a desk and not tappable
   one-handed in a yard in January — and the filter chips are the most-tapped
   thing on the busiest screens. The text stays the same size; only the hit area
   changes, so nothing reflows. */
@media (max-width: 799px) {
  .chip, .linklike {
    display: inline-flex; align-items: center; min-height: 38px;
  }
  .chip { padding-top: 0; padding-bottom: 0; }
  /* Same for a tick box and its words. The box itself stays 20px — that is what a
     checkbox looks like — but the label around it is what you actually tap, and it
     was the height of one line of text. Settings alone has nineteen of them. */
  label.check { min-height: 38px; }
  /* Except inside a dense inline form or a table row, where a 38px box would
     stretch the row it sits in. The light/dark pair is a row of its own, so it
     gets the bigger target like everything else. */
  .leaving-form label.check, .review-table label.check { min-height: 0; }
  /* The bulk-select boxes in a table have no label to tap — they carry an
     aria-label instead — so the box itself is the target, and 18px of it is not
     enough on a phone. 24px is the WCAG 2.5.8 minimum, and the same size is given
     to the tick boxes in the price-review table, which are in a row too tight to
     grow. */
  .check-col input[type="checkbox"], label.check input { width: 24px; height: 24px; }
  /* A bare dropdown is 19px tall left to itself. Every other control on a phone
     is at least 38px; this makes the odd one out match. */
  select { min-height: 38px; }
}

/* Admin — password-reset link panel */
.reset-panel { border-left: 3px solid var(--accent); }
.reset-panel h2 { margin-top: 0; }
.reset-link-row { display: flex; gap: 0.5rem; align-items: center; margin-top: 0.6rem; }
.reset-link-row input {
  flex: 1; min-width: 0; padding: 0.55rem 0.8rem; font: inherit; min-height: 44px;
  border: 1px solid var(--border); border-radius: 8px; background: var(--bg); color: var(--text);
}
.reset-link-row .btn { flex-shrink: 0; }

/* Admin — signups trend + sparkline */
.trend-card { margin-top: 0.9rem; }
.trend-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.trend-head h2 { margin: 0; }
.trend-figures { display: flex; gap: 1.1rem; flex-wrap: wrap; color: var(--muted); font-size: 0.82rem; }
.trend-figures .tnum { font-weight: 650; font-size: 1rem; color: var(--heading); margin-right: 0.15rem; }
.sparkline { display: flex; align-items: flex-end; gap: 4px; height: 52px; margin: 0.9rem 0 0.3rem; }
.spark-bar { flex: 1; background: var(--accent-bg); border-radius: 3px 3px 0 0; min-height: 3px; }
.spark-caption { font-size: 0.8rem; margin: 0; }
.pill.grey { background: var(--grey-bg); color: var(--muted); }

/* Bulk select/delete on the spaces list */
.check-col { width: 2.4rem; text-align: center; }
.check-col input { width: 1.15rem; height: 1.15rem; min-height: auto; margin: 0; accent-color: var(--btn-primary); cursor: pointer; }
.bulk-bar { display: flex; align-items: center; gap: 0.8rem; flex-wrap: wrap; margin: 0.8rem 0; }
.bulk-bar .muted { font-size: 0.85rem; }

/* Tables */
.table-card { padding: 0; overflow-x: auto; }
table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
th, td { text-align: left; padding: 0.66rem 1rem; border-bottom: 1px solid var(--border); }
th { background: var(--thead-bg); color: var(--muted); font-size: 0.71rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; border-bottom: none; }
thead tr th:first-child { border-top-left-radius: 12px; }
thead tr th:last-child { border-top-right-radius: 12px; }
tbody tr { transition: background 0.1s ease; }
tbody tr:hover { background: var(--row-hover); }
tbody tr:last-child td { border-bottom: none; }
.num-col { text-align: right; font-variant-numeric: tabular-nums; }

/* Lists */
.rows { list-style: none; margin: 0; padding: 0; }
.rows li {
  display: flex; justify-content: space-between; align-items: center; gap: 0.6rem;
  padding: 0.62rem 0; border-bottom: 1px solid var(--border); flex-wrap: wrap;
}
.rows li:last-child { border-bottom: none; padding-bottom: 0.1rem; }
.rows .right { margin-left: auto; text-align: right; }
.move-list .btn-row { margin: 0; justify-content: flex-end; }

/* Vehicle-movement direction pill + customer-list reg tag */
.move-pill {
  display: inline-block; font-size: 0.72rem; font-weight: 650; padding: 0.08rem 0.44rem;
  border-radius: 999px; vertical-align: middle; margin-right: 0.15rem;
}
.move-pill.out { background: var(--amber-bg); color: var(--amber); }
.move-pill.in { background: var(--green-bg); color: var(--green); }
.reg-tag { font-size: 0.78rem; letter-spacing: 0.02em; text-transform: uppercase; }
.row-actions { display: flex; gap: 0.4rem; justify-content: flex-end; flex-wrap: wrap; }
.row-actions form { margin: 0; }
/* A table card scrolls sideways when it will not fit, and the column you must
   never lose to that is the one with the buttons — so it stays pinned to the
   right edge while the rest slides under it. The hover tint is translucent, so
   it is layered OVER an opaque card colour rather than replacing it; otherwise
   the scrolled-under row would show through the pinned cell. */
.table-card td.row-actions {
  position: sticky; right: 0; z-index: 1;
  background: var(--card);
  box-shadow: -10px 0 10px -10px rgba(0, 0, 0, 0.3);
}
.table-card tbody tr:hover td.row-actions {
  background: linear-gradient(var(--row-hover), var(--row-hover)), var(--card);
}
/* Waiting list: pick the plot and convert in one action, so the select has to
   sit against its own button rather than stretching the actions cell. */
.convert-form { display: flex; gap: 0.35rem; align-items: center; }
.convert-form select {
  font: inherit; font-size: 0.85rem; padding: 0.3rem 0.4rem; max-width: 11rem;
  border: 1px solid var(--field-border); border-radius: 7px;
  background: var(--card); color: var(--text);
}
/* "Coming free" splits into empty-now and given-notice; the sub-headings sit
   inside a card, so they need less air above than a standalone h3. */
.card h3:first-of-type { margin-top: 0.2rem; }

/* Direct Debit mandate status pill */
.dd-pill {
  display: inline-block; font-size: 0.74rem; font-weight: 650; padding: 0.12rem 0.55rem;
  border-radius: 999px; vertical-align: middle;
}
.dd-pill.in { background: var(--green-bg); color: var(--green); }
.dd-pill.pending { background: var(--amber-bg); color: var(--amber); }
.dd-pill.out { background: var(--red-bg); color: var(--red); }
.dd-pill.off { background: var(--grey-bg); color: var(--muted); }

/* Key-value blocks */
.kv div { display: flex; gap: 0.8rem; padding: 0.32rem 0; }
.kv .k { color: var(--muted); min-width: 6.5rem; font-size: 0.82rem; padding-top: 0.12rem; }
.kv .total { border-top: 1px solid var(--border); margin-top: 0.4rem; padding-top: 0.6rem; }

/* Yard map */
.legend { display: flex; flex-wrap: wrap; gap: 0.9rem; font-size: 0.78rem; color: var(--muted); margin: 0.8rem 0; }
.legend .sq { display: inline-block; width: 0.85rem; height: 0.85rem; border-radius: 4px; vertical-align: -2px; margin-right: 0.3rem; border: 1px solid rgba(128, 128, 128, 0.2); }
.zone h2 { margin: 1.4rem 0 0.7rem; }
.unit-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(4.6rem, 1fr));
  gap: 0.55rem;
}
.unit-tile {
  /* A positioning context for the "leaving soon" dot. Map-canvas tiles override
     this with `absolute` further down, which is also a context — so the dot
     lands correctly whether the tile is on the grid or free-positioned. */
  position: relative;
  border-radius: 10px;
  padding: 0.5rem 0.3rem;
  text-align: center;
  color: var(--text);
  border: 1px solid rgba(128, 128, 128, 0.16);
  min-height: 3.7rem;
  display: flex; flex-direction: column; justify-content: center;
  transition: box-shadow 0.13s ease, transform 0.13s ease;
}
.unit-tile:hover {
  text-decoration: none; transform: translateY(-2px);
  box-shadow: 0 0 0 2px var(--accent), var(--shadow-md);
}
.unit-tile .tile-label { font-weight: 650; letter-spacing: -0.01em; color: var(--heading); }
.unit-tile .tile-sub { font-size: 0.67rem; opacity: 0.75; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Rent status is carried by the ring as well as the fill: no ring when paid, a
   thin one when due soon, a thick one when overdue.
   Colour alone did not survive contact with real eyes. Simulating deuteranopia
   (the common red-green deficiency, ~8% of men — and this audience is mostly
   men in their 50s and 60s) against these exact tints collapses paid vs overdue
   from a ΔE of 18.6 to 1.5. Below about 2.3 two colours are indistinguishable,
   so on the one screen whose whole job is "which plots need chasing", roughly
   one operator in twelve was reading noise. `reserved` and `out_of_service`
   already leaned on a dashed border and a hatch rather than hue; these three
   now follow suit.
   The rings are drawn with `outline`, not an inset box-shadow: .unit-tile:hover
   replaces box-shadow outright, which would make a ring vanish exactly when
   someone is pointing at the tile. */
.unit-tile.paid, .sq.paid { background: var(--tile-paid); }
.unit-tile.due_soon, .sq.due_soon { background: var(--tile-due); outline: 2px solid var(--amber); outline-offset: -2px; }
.unit-tile.overdue, .sq.overdue { background: var(--tile-overdue); outline: 3px solid var(--red); outline-offset: -3px; }
.unit-tile.vacant, .sq.vacant { background: var(--tile-vacant); border-color: var(--tile-vacant-edge); }
.unit-tile.reserved, .sq.reserved { background: var(--tile-reserved); border-style: dashed; border-color: var(--tile-line); }
.unit-tile.out_of_service, .sq.out_of_service {
  background: repeating-linear-gradient(45deg, var(--tile-oos-a), var(--tile-oos-a) 5px, var(--tile-oos-b) 5px, var(--tile-oos-b) 10px);
}
/* Vacant and out-of-service are light fills in BOTH themes, so their ink is set
   once here rather than per theme — the dark theme's own text colour would be
   unreadable on them. */
/* "Leaving soon" — a plot whose customer has confirmed a date. It is not a
   seventh status: a leaver is still paid, due soon or overdue, so this rides on
   top of whatever fill the tile already has. Hence a MARK, not a colour — every
   other channel (fill, ring, border-style, hatch) is taken, and the presence of
   the dot is the cue, so it survives colour blindness with nothing to tune.
   Leaving room for a second dot later (maintenance due, insurance expiring) is
   why this is a corner dot and not a folded corner. */
.unit-tile.leaving::after, .sq.leaving::after {
  content: ''; position: absolute; border-radius: 50%; background: var(--heading);
}
.unit-tile.leaving::after { top: 6px; right: 6px; width: 9px; height: 9px; }
.legend .sq.leaving { position: relative; }
.legend .sq.leaving::after { top: 2px; right: 2px; width: 5px; height: 5px; }
.unit-tile.vacant, .unit-tile.out_of_service { color: #464d54; }
.unit-tile.vacant .tile-label, .unit-tile.out_of_service .tile-label { color: #1f2529; }

/* Yard map canvas — free-positioned tiles on a snap grid (cell 94x68, tile 86x60;
   keep in sync with MAP_CW / MAP_CH in ui.js) */
.map-scroll { overflow-x: auto; padding-bottom: 0.3rem; }
/* width: max-content keeps wide maps scrollable before (or without) the zoom
   script, which overrides with an explicit scaled size. */
.map-zoom { overflow: hidden; width: max-content; }
.map-canvas { position: relative; border: 1.5px dashed var(--border); border-radius: 12px; transform-origin: 0 0; }
.map-canvas .unit-tile {
  position: absolute; width: 86px; height: 60px; min-height: 0;
  padding: 0.3rem 0.25rem; margin: 0; box-sizing: border-box;
}
.map-canvas .unit-tile:hover { transform: none; }
.map-canvas.editing {
  background-image: radial-gradient(circle, var(--border) 1.5px, transparent 1.5px);
  background-size: 94px 68px; background-position: 43px 30px;
  touch-action: none;
}
.map-canvas.editing .unit-tile { cursor: grab; user-select: none; -webkit-user-select: none; touch-action: none; }
.unit-tile.dragging { cursor: grabbing; z-index: 6; opacity: 0.92; box-shadow: 0 0 0 2px var(--accent), var(--shadow-md); }
.map-editbar { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; margin: 0.5rem 0 0.2rem; }
/* Move mode. The bar sticks to the top because on a big yard the tile you are
   aiming at is often scrolled well away from where you started. */
.map-movebar {
  position: sticky; top: 0; z-index: 8;
  display: flex; align-items: center; gap: 0.7rem; flex-wrap: wrap;
  margin: 0.6rem 0 0.2rem; padding: 0.6rem 0.9rem;
  border-radius: 8px; font-size: 0.9rem; font-weight: 500;
  background: var(--card); color: var(--text); box-shadow: var(--shadow-sm);
}
.map-movebar.picking { background: var(--accent-bg); color: var(--accent); }
.map-movebar.warn { background: var(--amber-bg); color: var(--amber); }
.unit-tile.movable { cursor: pointer; }
/* The picked tile keeps its status colour — you still need to read who is overdue
   while choosing where to put someone, so the other tiles are not dimmed. The
   marker is shape, not hue: overdue rings sit *inside* the tile edge, this one
   sits outside it with a visible gap, and the tile lifts off the page. That
   survives the red-green deficiency the status colours were already tuned for. */
.unit-tile.picked, .map-canvas .unit-tile.picked {
  outline: 3px solid var(--accent); outline-offset: 2px;
  box-shadow: var(--shadow-md); transform: translateY(-2px); z-index: 7;
}
.map-unplaced { margin: 0.8rem 0 0.4rem; }
.zone .section-head { margin: 1.4rem 0 0.7rem; }
.zone .section-head h2 { margin: 0; }
.block-tools { display: inline-flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }
.block-tools form { margin: 0; }
.grid-ctl { display: inline-flex; align-items: center; gap: 0.35rem; font-size: 0.85rem; color: var(--muted); white-space: nowrap; }
.grid-ctl input { width: 2.8rem; text-align: center; font-variant-numeric: tabular-nums; }
.grid-ctl .grid-x { color: var(--muted); }
.zoom-ctl { display: inline-flex; align-items: center; gap: 0.35rem; }
.zoom-ctl .zoom-pct {
  font-size: 0.85rem; color: var(--muted); min-width: 3.2rem; text-align: center;
  font-variant-numeric: tabular-nums;
}
.bulk-zone { display: inline-flex; align-items: center; gap: 0.4rem; }

/* Page furniture */
.page-head { display: flex; justify-content: space-between; align-items: center; gap: 0.5rem; flex-wrap: wrap; margin-top: 0.6rem; }
.page-head > div { display: flex; gap: 0.4rem; flex-wrap: wrap; }
.section-head { display: flex; justify-content: space-between; align-items: center; }
.muted { color: var(--muted); font-size: 0.85rem; }
.text-red { color: var(--red); font-weight: 600; }
.alert { border-radius: 8px; border: none; padding: 0.75rem 1rem; margin: 0.9rem 0; font-size: 0.9rem; font-weight: 500; }
.page-head > div form { display: inline; margin: 0; }
.alert .restore-form { margin-top: 0.6rem; }
th .th-sort { color: inherit; text-decoration: none; white-space: nowrap; }
th .th-sort:hover { text-decoration: underline; }
th .th-sort.on { color: var(--accent); }
th .th-alt { font-weight: 400; font-size: 0.78rem; color: var(--muted); }
.alert.error { background: var(--red-bg); color: var(--red); }
.alert.ok { background: var(--green-bg); color: var(--green); }
.flash { background: var(--green-bg); color: var(--green); border-radius: 8px; padding: 0.75rem 1rem; margin: 0.9rem 0; font-size: 0.9rem; font-weight: 500; }

/* Overdue cards */
.overdue-card .overdue-head { display: flex; justify-content: space-between; align-items: center; gap: 0.5rem; }
.overdue-card .name { font-size: 1.02rem; font-weight: 650; letter-spacing: -0.01em; }
.overdue-meta { margin: 0.45rem 0; font-size: 0.9rem; }

/* Customer detail bits */
.lease-row { display: flex; justify-content: space-between; gap: 0.6rem; padding: 0.65rem 0; border-bottom: 1px solid var(--border); flex-wrap: wrap; }
.lease-row:last-child { border-bottom: none; }
.lease-row.ended { opacity: 0.55; }
.thumb { width: 44px; height: 44px; object-fit: cover; border-radius: 8px; vertical-align: middle; margin-right: 0.4rem; }
.preview { max-width: 100%; max-height: 240px; border-radius: 10px; }
.photo-row { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-bottom: 0.9rem; }
.photo-row .thumb { width: 72px; height: 72px; }
/* The Help card that leads to the release notes, while they are still unread.
   A left edge rather than a filled panel: it should catch the eye on the way
   past without shouting over the "message the team" card, which is what
   somebody in trouble actually came for. */
.card-new { border-left: 3px solid var(--amber); }

/* The version line at the foot of Help. Quiet — it is reference, not content —
   but reachable, because it is what a customer reads back to us when something
   has gone wrong. */
.version-line { margin-top: 1.5rem; text-align: center; font-size: 0.85rem; }

/* "Preparing the photo…" under a file input while it is being shrunk. Its own
   line, because it sits inside the <label> next to the input and would
   otherwise wrap awkwardly against the file-picker button on a phone. */
.upload-note { display: block; margin-top: 0.35rem; font-size: 0.85rem; }
.policy-row > span:first-child { display: flex; align-items: center; gap: 0.3rem; flex-wrap: wrap; }

/* Documents & photos list */
.doc-list { list-style: none; margin: 0; padding: 0; }
.doc-row { display: flex; align-items: center; gap: 0.75rem; padding: 0.55rem 0; border-bottom: 1px solid var(--border); }
.doc-row:last-child { border-bottom: none; }
.doc-thumb { flex: 0 0 auto; width: 52px; height: 52px; border-radius: 8px; overflow: hidden; background: var(--grey-bg); border: 1px solid var(--border); display: flex; align-items: center; justify-content: center; }
.doc-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.doc-icon { display: flex; align-items: center; justify-content: center; color: var(--muted); }
.doc-icon svg { width: 24px; height: 24px; }
.doc-info { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 0.1rem; }
.doc-name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.doc-row form { flex: 0 0 auto; margin: 0; }

/* Login / signup */
.login-wrap { max-width: 23rem; margin: 10vh auto 0; text-align: center; }
.login-wrap .card { text-align: left; box-shadow: var(--shadow-md); padding: 1.5rem; }
.login-icon { width: 54px; height: 54px; border-radius: 14px; box-shadow: var(--shadow-md); margin-bottom: 0.2rem; }
.login-wrap h1 { font-size: 1.3rem; margin: 0.5rem 0 1rem; }

/* Link box */
.linkbox { word-break: break-all; background: var(--grey-bg); padding: 0.7rem 0.8rem; border-radius: 8px; font-size: 0.88rem; }

/* Reports */
.report-head { margin: 0.7rem 0; }
.report-head h1 { margin-bottom: 0.15rem; }
.totals-row td { font-weight: 700; border-top: 2px solid var(--border); }
.invoice-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 1rem; flex-wrap: wrap; }
.invoice-head h1 { margin: 0; }
.invoice-meta { text-align: right; }
.invoice-parties { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin: 1rem 0; }
.invoice-parties .k { font-size: 0.72rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); }
.invoice-totals { margin-left: auto; max-width: 18rem; }
.invoice-totals div { display: flex; justify-content: space-between; padding: 0.2rem 0; }
.invoice-totals .grand { border-top: 2px solid var(--text); font-weight: 700; font-size: 1.1rem; margin-top: 0.3rem; padding-top: 0.4rem; }
.disclaimer { font-size: 0.78rem; color: var(--muted); border-top: 1px solid var(--border); margin-top: 1.2rem; padding-top: 0.6rem; }

/* Printing (receipts + reports) */
/* Invoice document */
.invoice-head { display: flex; justify-content: space-between; gap: 1rem; flex-wrap: wrap; }
.invoice-from { text-align: right; }
.invoice .paid { color: var(--green); }
.invoice-lines { margin: 1rem 0; }
.invoice-lines .num-lbl { text-align: right; }

.small { font-size: 0.82rem; }
.print-only { display: none; }
.msg-preview { border-left: 3px solid var(--accent); }

/* Digital agreements / e-sign */
.sign-page { max-width: 720px; }
.sign-doc h1 { margin-top: 0; }
.sign-body { white-space: normal; line-height: 1.6; margin: 1rem 0; }
.sign-body p { margin: 0 0 0.85rem; }
.sign-form { margin-top: 1.25rem; border-top: 1px solid var(--border); padding-top: 1.25rem; }
.sign-signed {
  margin-top: 1.25rem; padding: 0.85rem 1rem; border-radius: 8px;
  background: var(--green-bg); color: var(--green); border: 1px solid var(--green);
}
.sign-signed .muted { color: var(--green); opacity: 0.85; }
.sign-linkbox { display: flex; gap: 0.5rem; align-items: flex-end; flex-wrap: wrap; margin: 0.75rem 0; }
.sign-linkbox label { flex: 1 1 260px; }
.sign-linkinput { width: 100%; font-family: ui-monospace, monospace; font-size: 0.8rem; }

/* ---- Diary ----------------------------------------------------------------
   The yard's written record. Entries are read far more often than they are
   written, so the words get the weight and everything else — kind, author,
   buttons — sits quietly around them. */
.btn.tiny { padding: 0.16rem 0.55rem; min-height: 30px; font-size: 0.76rem; line-height: 1.6; }

.diary-day { margin: 1.4rem 0 0; }
.diary-date {
  margin: 0 0 0.5rem; font-size: 0.95rem; font-weight: 650; color: var(--heading);
  padding-bottom: 0.3rem; border-bottom: 1px solid var(--border);
}
.diary-date .muted { font-weight: 400; font-size: 0.85rem; }
.diary-day-line { margin: 0.9rem 0 0.2rem; font-size: 0.82rem; }

.diary-entry {
  background: var(--card); border-radius: 10px; box-shadow: var(--shadow-sm);
  padding: 0.8rem 0.95rem; margin: 0.55rem 0;
  border-left: 3px solid var(--border);
}
/* Inside a card the entry is already on card-coloured ground, so the shadow
   would read as a box within a box. */
.card .diary-entry { box-shadow: none; border: 1px solid var(--border); border-left-width: 3px; }
.diary-entry.pinned { border-left-color: var(--accent); }
.diary-head { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; margin-bottom: 0.45rem; }
.diary-by { color: var(--faint); font-size: 0.78rem; margin-left: auto; }
.diary-link {
  font-size: 0.78rem; padding: 0.12rem 0.5rem; border-radius: 999px;
  background: var(--grey-bg); color: var(--muted); text-decoration: none;
}
.diary-link:hover { background: var(--border); color: var(--heading); text-decoration: none; }
/* pre-wrap so a note typed as three lines stays three lines — people use blank
   lines to separate one thing from the next, and flattening that loses it. */
.diary-body { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
.diary-actions { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-top: 0.6rem; }
.diary-actions form { margin: 0; }
.diary-pinned { border-left: 3px solid var(--accent); }
/* The bare search box and category select are direct children of the filter row,
   so they pick up .filter-form's width:100% and each take a line to themselves.
   Inside a label they wouldn't — these are the two that aren't. */
.diary-filters { margin: 1.2rem 0 0.4rem; }
.diary-filters > input, .diary-filters > select { width: auto; margin-top: 0; }
.diary-filters > input[type="search"] { flex: 3 1 13rem; }
.diary-filters > select { flex: 1 1 9rem; }

.diary-quick { margin-top: 1rem; padding-top: 0.9rem; border-top: 1px solid var(--border); }
/* Two date fields side by side are indistinguishable without a word each —
   "28/07/2026" and an empty one say nothing about which is which. */
.diary-quick-row label.mini {
  display: flex; align-items: center; gap: 0.4rem; flex: 1 1 11rem;
  font-size: 0.82rem; color: var(--muted); margin: 0;
}
.diary-quick-row label.mini input { margin-top: 0; }

/* One colour per kind, reusing the palette the rest of the app already speaks:
   red is trouble, green is money, amber is the site itself. */
.pill.cat-general { background: var(--grey-bg); color: var(--muted); }
.pill.cat-site { background: var(--amber-bg); color: var(--amber); }
.pill.cat-customer { background: var(--cyan-bg); color: var(--cyan); }
.pill.cat-money { background: var(--green-bg); color: var(--green); }
.pill.cat-staff { background: var(--accent-bg); color: var(--accent); }
.pill.cat-incident { background: var(--red-bg); color: var(--red); }

/* ---- Help ------------------------------------------------------------------- */

/* The ? beside a page's heading. It is floated and emitted BEFORE the page body
   so it settles onto the first heading's line without every page template having
   to make room for it — the heading text simply flows around it. */
.page-help {
  float: right;
  margin: 1.15rem 0 0.4rem 0.75rem;
  width: 1.65rem; height: 1.65rem;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); border-radius: 50%;
  background: var(--card); color: var(--muted);
  font-size: 0.92rem; font-weight: 700; line-height: 1;
}
.page-help:hover { color: var(--accent); border-color: var(--accent); text-decoration: none; }

/* Visually hidden, still read aloud — so the ? has a real label without a
   tooltip being the only thing that explains it. */
.vh {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

.help-jobs { list-style: none; margin: 0 0 0.9rem; padding: 0; }
.help-jobs li {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 0.5rem;
  padding: 0.55rem 0; border-bottom: 1px solid var(--border);
}
.help-jobs li:last-child { border-bottom: none; }
.help-jobs a { font-weight: 600; }
.help-jobs .muted { font-size: 0.85rem; }

.manual-head { display: flex; gap: 0.5rem; margin: 1.1rem 0; }

/* The manual is a document, not an app screen: a narrower measure than the
   dashboard's full width, because long prose set across 84rem is unreadable. */
.manual {
  background: var(--card); border-radius: 12px; box-shadow: var(--shadow-sm);
  padding: 1.4rem 1.3rem; max-width: 46rem;
}
@media (min-width: 800px) { .manual { padding: 2rem 2.4rem; } }
/* The sticky top bar on phones would otherwise cover a heading jumped to by
   anchor — every ? link and every Contents entry lands via a fragment.
   --topbar-h is measured by the shell script in layout(); the fallback covers
   the phone case if that hasn't run. */
.manual h1, .manual h2, .manual h3 { scroll-margin-top: calc(var(--topbar-h, 10.5rem) + 0.75rem); }
@media (min-width: 800px) { .manual h1, .manual h2, .manual h3 { scroll-margin-top: 1rem; } }
.manual h1 { font-size: 1.25rem; margin: 2.2rem 0 0.9rem; }
.manual h1:first-child { margin-top: 0; }
.manual h2 { font-size: 1.02rem; margin: 1.7rem 0 0.6rem; }
.manual h3 { font-size: 0.95rem; font-weight: 650; color: var(--heading); margin: 1.3rem 0 0.5rem; }
.manual p, .manual li { line-height: 1.62; overflow-wrap: break-word; }
.manual p { margin: 0 0 0.95rem; }
.manual ul, .manual ol { margin: 0 0 1rem; padding-left: 1.4rem; }
.manual li { margin-bottom: 0.4rem; }
.manual li > ul, .manual li > ol { margin: 0.4rem 0 0.2rem; }
.manual hr { border: none; border-top: 1px solid var(--border); margin: 2rem 0; }
/* Addresses like yourname.storageyardmanager.co.uk are one unbreakable token and
   run straight off a phone screen without this. */
.manual code {
  background: var(--accent-bg); color: var(--text);
  padding: 0.1rem 0.32rem; border-radius: 5px; font-size: 0.88em;
  overflow-wrap: anywhere;
}
.manual pre {
  background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
  padding: 0.8rem 1rem; overflow-x: auto; margin: 0 0 1rem;
}
.manual pre code { background: none; padding: 0; }
/* Tables in the manual carry their own scroller so a wide one can never make
   the whole page scroll sideways on a phone. */
.manual .table-wrap {
  overflow-x: auto; margin: 0 0 1.2rem;
  border: 1px solid var(--border); border-radius: 8px;
}
.manual th { background: var(--bg); }
.manual td, .manual th { padding: 0.5rem 0.8rem; }
.manual tr:last-child td { border-bottom: none; }

/* ---- arranging a screen's cards (?edit=1) -----------------------------------
   Per-login card order, same panel on every screen that has one. Arrows rather
   than drag-and-drop on purpose: this gets used one-handed on a phone out in the
   yard, and HTML5 drag has no touch support at all. app.js makes the arrows
   instant where it can; without it they are ordinary submit buttons that still
   work. */
.arrange-panel { border: 1px solid var(--accent); }
.arrange-list { list-style: none; margin: 0.6rem 0 0.9rem; padding: 0; }
.arrange-list li {
  display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap;
  padding: 0.55rem 0; border-bottom: 1px solid var(--border);
}
.arrange-list li:last-child { border-bottom: none; }
.arrange-name { font-weight: 600; flex: 1 1 10rem; }
.arrange-tools { display: flex; align-items: center; gap: 0.5rem; margin-left: auto; }
.arrange-tools .check { margin: 0; font-weight: normal; white-space: nowrap; }
.arrange-tools .btn { min-width: 2.4rem; }
/* Unticked, in the panel and on the page itself: dimmed rather than removed,
   because you need to see what you turned off in order to turn it back on. */
.arrange-list li.is-off .arrange-name { text-decoration: line-through; color: var(--muted); }
.card.is-off { opacity: 0.45; }

/* ---- the 29/07 growth batch ------------------------------------------------ */

/* Wide tables scroll inside their own box rather than pushing the page sideways.
   The manual already had this; the new reports need it too, so it is now a
   standalone class. */
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.table-wrap table { min-width: 34rem; }

/* A .section-head whose right-hand side is a sentence rather than a button.
   The shared one is nowrap with no gap, which is right for "Vehicles  [+ Add]"
   and cramped when the right side is "+£864.00 a year (£72.00 a month)". */
.section-head.wrap-head { flex-wrap: wrap; gap: 0.15rem 0.9rem; align-items: baseline; }

/* Why somebody is leaving, next to the date. Allowed to grow, unlike the date
   beside it, because a reason is a sentence.
   Two classes deliberately: `.inline-form input` is class-plus-type and so
   outspecifies a lone `.leaving-why`, which silently lost this argument and left
   the box at its 8rem minimum showing "Price — goin". */
.leaving-form .leaving-why { flex: 1 1 14rem; }

/* Text replies. A work queue: each message is a block you read and clear, and a
   cleared one stays visible but recedes. */
.reply-list { list-style: none; margin: 0; padding: 0; }
.reply-row { padding: 0.8rem 0; border-bottom: 1px solid var(--border); }
.reply-row:last-child { border-bottom: 0; }
.reply-head { display: flex; gap: 0.6rem; align-items: baseline; flex-wrap: wrap; }
.reply-head .right { margin-left: auto; white-space: nowrap; }
.reply-body {
  margin: 0.4rem 0 0.6rem; padding: 0.55rem 0.7rem;
  background: var(--grey-bg); border-radius: 8px;
  border-left: 3px solid var(--accent);
  overflow-wrap: anywhere;
}
.reply-row.is-done { opacity: 0.55; }
.reply-row.is-done .reply-body { border-left-color: var(--border); }
.reply-form { margin-top: 0.6rem; }

/* Waiting-list matches: a plot, then the people who suit it. */
.match-row { padding: 0.7rem 0; border-bottom: 1px solid var(--border); }
.match-row:last-child { border-bottom: 0; }
.match-row > div:first-child { display: flex; gap: 0.5rem; align-items: baseline; flex-wrap: wrap; margin-bottom: 0.3rem; }
.match-hint { font-size: 0.8rem; color: var(--accent); }

/* Deposits. One block per contract, so a customer with two plots reads clearly. */
.deposit-block + .deposit-block { margin-top: 1.1rem; padding-top: 0.9rem; border-top: 1px solid var(--border); }
.deposit-block .section-head h3 { margin: 0; }
.deposit-form { margin-top: 0.7rem; padding-top: 0.7rem; border-top: 1px dashed var(--border); }

/* Price review. The fee box is edited in place, so it needs to look editable
   without being as wide as the column. */
.review-table td { vertical-align: top; }
.fee-input { max-width: 6.5rem; margin: 0; }
.review-table .check { margin: 0; }

/* Month-by-month cash bars — a chart without a charting library. */
.bar-list { list-style: none; margin: 0.6rem 0 0; padding: 0; display: grid; gap: 0.35rem; }
.bar-list li { display: flex; align-items: center; gap: 0.6rem; font-size: 0.85rem; }
.bar-label { flex: 0 0 4.6rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.bar-track { flex: 1 1 auto; height: 0.85rem; background: var(--grey-bg); border-radius: 4px; overflow: hidden; }
.bar-fill { display: block; height: 100%; background: var(--accent); border-radius: 4px; }
.bar-value { flex: 0 0 5.5rem; text-align: right; font-variant-numeric: tabular-nums; }

/* Reset the yard. Outlined in red because everything inside it deletes rows. */
.danger-card { border: 1px solid color-mix(in srgb, var(--red) 45%, transparent); }
.reset-list { list-style: none; margin: 0.6rem 0; padding: 0; display: grid; gap: 0.7rem; }
.reset-list li { display: grid; gap: 0.15rem; }
.reset-list .check { margin: 0; }
.reset-list .muted { font-size: 0.82rem; }
.reset-count { font-size: 0.8rem; color: var(--accent); font-variant-numeric: tabular-nums; }

@media (max-width: 600px) {
  .bar-label { flex-basis: 3.8rem; }
  .bar-value { flex-basis: 4.6rem; font-size: 0.8rem; }
  /* On a phone the arrange panel and these lists must not push the page wide. */
  .reply-head .right { margin-left: 0; }
  /* Sharing a row with the date leaves about 130px for the reason, which shows
     "Price — goin" and cuts the rest off. A sentence gets its own line. */
  .leaving-form .leaving-why { flex-basis: 100%; }
}

@media print {
  .topbar, .bottomnav, .sidebar, .no-print, .flash, .filter-form,
  .page-help, .manual-head,
  .diary-actions, .diary-quick { display: none !important; }
  /* The bar chart is the one visual on the yard-value report, so it has to
     survive being printed — a background-coloured span would not. */
  .bar-track { border: 1px solid #999; }
  .bar-fill { background: #666 !important; }
  .table-wrap { overflow: visible; }
  .print-only { display: block !important; }
  html { font-size: 15px; }
  body { background: #fff; padding: 0; color: #000; }
  .container { margin-left: 0; }
  .card, .table-card, .stat { break-inside: avoid; box-shadow: none; }
  .card { border: 1px solid #ccc; }
  /* Printing the manual should give a plain document, not a boxed card. */
  .manual { max-width: none; padding: 0; box-shadow: none; background: #fff; }
  .manual h1, .manual h2 { break-after: avoid; }
  .container { max-width: none; padding: 0; }
  th { background: #fff; }
  a { color: inherit; text-decoration: none; }
}

/* ---------------------------------------------------------------------------
   The industrial screens' own classes. These were written into the markup
   during the port and never given rules, so they rendered as bare browser
   defaults on the screens that sell this product — the service charge year end
   and the compliance register. Cosmetic, but they were cosmetic on the
   flagship.
   --------------------------------------------------------------------------- */

/* Service charge year end: budget / actual / variance / voids in one row.
   Same shape as .stat-grid but always four across on a wide screen, because the
   four figures are read against each other and wrapping breaks the comparison. */
.stat-row {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.8rem;
  margin: 1.1rem 0;
}
@media (min-width: 700px) { .stat-row { grid-template-columns: repeat(4, 1fr); } }
.stat-row .stat { padding: 0.85rem 0.95rem; }
.stat-n {
  display: block;
  font-size: 1.35rem;
  font-weight: 650;
  letter-spacing: -0.01em;
  line-height: 1.25;
  color: var(--heading);
}
.stat-l { display: block; font-size: 0.82rem; color: var(--faint); margin-top: 0.15rem; }

/* "Why this exists" explainers — the lease/licence note and the compliance
   intervals. Set apart from the surrounding prose without shouting: this is
   reference material somebody reads once. */
.why {
  border-left: 3px solid var(--border);
  padding: 0.1rem 0 0.1rem 0.9rem;
  margin: 0.9rem 0;
}
.why dt { font-weight: 600; margin-top: 0.55rem; }
.why dt:first-child { margin-top: 0; }
.why dd { margin: 0.1rem 0 0; }
.why p:last-child { margin-bottom: 0; }
.why .mini { margin: 0.3rem 0; }

/* The heads-of-charge breakdown, folded away by default. */
.split-heads { margin: 0.9rem 0; }
.split-heads > summary {
  cursor: pointer;
  font-weight: 600;
  padding: 0.45rem 0;
}
.split-heads[open] > summary { margin-bottom: 0.4rem; }
