:root {
  /* C&H Cleaning Services: navy from the website, pink from the logo */
  --brand: #363969;
  --brand-600: #2a2d54;
  --brand-100: #e3e4f3;
  --brand-50: #f2f2f9;
  --accent: #e80088;
  --accent-100: #fde0ef;
  --ink: #0f172a;
  --ink-2: #334155;
  --muted: #64748b;
  --line: #e2e8f0;
  --bg: #f4f6f8;
  --card: #ffffff;
  /* The rota squares, and the stripe on a day somebody does not work. They follow the background
     colour so the rota reads as part of the same page rather than a grey window onto another one. */
  --cell: #eef1f5;
  --cell-2: #e3e8ee;
  --today: #fff4e0;
  --today-2: #ffe6bd;
  /* A day somebody is off sick. Red whatever the background colour is set to: it is the one thing
     on the rota that has to be seen from across the office. */
  --sick: #fee2e2;
  --sick-2: #fbc4c4;
  --ok: #15803d;
  --ok-bg: #dcfce7;
  --warn: #b45309;
  --warn-bg: #fef3c7;
  --bad: #b91c1c;
  --bad-bg: #fee2e2;
  --info: #1d4ed8;
  --info-bg: #dbeafe;
  --radius: 14px;
  --shadow: 0 1px 2px rgba(15, 23, 42, .06), 0 4px 14px rgba(15, 23, 42, .06);
  --nav-h: 64px;
  --header-h: 56px;
  --safe-b: env(safe-area-inset-bottom, 0px);
  color-scheme: light;
}
/* ---------- Background colour ----------
   Chosen in Settings and shared by everybody, so the office machine and the phone in a bag look the
   same. Only the paper changes: cards stay white and the type stays the same colour, so nothing
   written on the screen becomes harder to read whichever of these is picked. */
:root[data-theme="pink"] { --bg: #fdeff6; --cell: #fbe2ef; --cell-2: #f6d2e6; }
:root[data-theme="sky"] { --bg: #eef4fc; --cell: #e3ecf9; --cell-2: #d5e2f4; }
:root[data-theme="mint"] { --bg: #eef7f1; --cell: #e1f0e8; --cell-2: #d2e7dc; }
:root[data-theme="sand"] { --bg: #faf4ea; --cell: #f4ebd9; --cell-2: #ebdfc6; --today: #ffe0b5; --today-2: #f7c98a; }
:root[data-theme="lilac"] { --bg: #f4f1fc; --cell: #e9e4f8; --cell-2: #ddd5f2; }
:root[data-theme="slate"] { --bg: #eceff3; --cell: #e1e6ec; --cell-2: #d3dae2; }
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; height: 100%; }
body {
  font-family: "Helvetica Neue", Helvetica, Arial, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, sans-serif;
  font-size: 15px; color: var(--ink); background: var(--bg);
  -webkit-font-smoothing: antialiased; -webkit-tap-highlight-color: transparent;
  overscroll-behavior-y: none;
}
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; border: 0; background: none; padding: 0; }
a { color: var(--brand); text-decoration: none; }
h1, h2, h3, h4 { margin: 0; font-weight: 700; letter-spacing: -.01em; }
h1 { font-size: 22px; } h2 { font-size: 18px; } h3 { font-size: 15px; }
p { margin: 0; }
.muted { color: var(--muted); }
.small { font-size: 13px; }
.tiny { font-size: 12px; }
.bold { font-weight: 600; }
.row { display: flex; align-items: center; gap: 10px; }
.row.between { justify-content: space-between; }
.row.wrap { flex-wrap: wrap; }
.col { display: flex; flex-direction: column; gap: 8px; }
.grow { flex: 1; min-width: 0; }
.ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.hidden { display: none !important; }
.mt8 { margin-top: 8px; } .mt12 { margin-top: 12px; } .mt16 { margin-top: 16px; } .mt24 { margin-top: 24px; }
.mb8 { margin-bottom: 8px; } .mb12 { margin-bottom: 12px; } .mb16 { margin-bottom: 16px; }
svg.icon { width: 20px; height: 20px; flex: none; stroke: currentColor; fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }

/* ---------- App shell ---------- */
#app { min-height: 100%; }
.shell { display: grid; grid-template-rows: var(--header-h) 1fr; min-height: 100vh; min-height: 100dvh; }
.topbar {
  height: var(--header-h); background: var(--card); border-bottom: 1px solid var(--line);
  display: flex; align-items: center; gap: 12px; padding: 0 14px; position: sticky; top: 0; z-index: 30;
}
.brand { display: flex; align-items: center; gap: 10px; font-weight: 800; letter-spacing: -.02em; font-size: 17px; white-space: nowrap; }
.brand-mark { width: 36px; height: 36px; border-radius: 50%; background: #fff; border: 1px solid var(--line); display: grid; place-items: center; overflow: hidden; }
.brand-mark img { width: 34px; height: 34px; object-fit: contain; display: block; }
.brand .app-name { color: var(--brand); }
.brand .app-name b { color: var(--accent); }
.topbar .spacer { flex: 1; }
.pill { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: 999px; font-size: 12px; font-weight: 600; line-height: 1.2; white-space: nowrap; }
.pill svg { width: 14px; height: 14px; }
.pill.ok { background: var(--ok-bg); color: var(--ok); }
.pill.warn { background: var(--warn-bg); color: var(--warn); }
.pill.bad { background: var(--bad-bg); color: var(--bad); }
.pill.info { background: var(--info-bg); color: var(--info); }
.pill.neutral { background: #e2e8f0; color: var(--ink-2); }
.pill.brand { background: var(--brand-100); color: var(--brand-600); }
.persona-btn { display: flex; align-items: center; gap: 8px; padding: 4px 8px 4px 4px; border-radius: 999px; border: 1px solid var(--line); background: var(--card); }
.persona-btn:hover { background: var(--bg); }

.layout { display: grid; grid-template-columns: minmax(0, 1fr); }
.main { min-width: 0; }
.sidenav { display: none; }
.main { padding: 16px 14px calc(var(--nav-h) + var(--safe-b) + 16px); max-width: 1400px; width: 100%; margin: 0 auto; }
.main.full { max-width: none; padding: 0 0 calc(var(--nav-h) + var(--safe-b)); }
/* The rota uses the whole width of the window. Everything else stays capped at a width that is
   comfortable to read. */
.main.wide { max-width: none; }

.bottomnav {
  position: fixed; left: 0; right: 0; bottom: 0; height: calc(var(--nav-h) + var(--safe-b)); padding-bottom: var(--safe-b);
  background: var(--card); border-top: 1px solid var(--line); display: flex; z-index: 30;
}
/* Eight items across a small phone were wider than the screen, which pushed every page sideways.
   They share whatever width there is instead, and a long label ellipsises rather than spreading. */
.bottomnav a { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; font-size: 11px; font-weight: 600; color: var(--muted); position: relative; padding: 0 2px; }
.bottomnav a span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bottomnav a.active { color: var(--brand); }
.bottomnav a svg { width: 22px; height: 22px; }
.navbadge { position: absolute; top: 6px; left: calc(50% + 6px); background: var(--bad); color: #fff; font-size: 10px; min-width: 16px; height: 16px; border-radius: 8px; display: grid; place-items: center; padding: 0 4px; }

@media (min-width: 900px) {
  .layout { grid-template-columns: 224px minmax(0, 1fr); }
  .sidenav { display: flex; flex-direction: column; gap: 4px; padding: 14px 10px; border-right: 1px solid var(--line); background: var(--card); position: sticky; top: var(--header-h); height: calc(100vh - var(--header-h)); height: calc(100dvh - var(--header-h)); }
  .sidenav a { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-radius: 10px; color: var(--ink-2); font-weight: 600; font-size: 14px; position: relative; }
  .sidenav a:hover { background: var(--bg); }
  .sidenav a.active { background: var(--brand-50); color: var(--brand-600); }
  .sidenav .navbadge { position: static; margin-left: auto; }
  .sidenav .foot { margin-top: auto; padding: 10px 12px; font-size: 12px; color: var(--muted); }
  /* Sits above the menu it collapses, so it is the first thing under the top bar rather than the
     last thing at the foot of a tall column. */
  .sidenav .navmin { display: flex; align-items: center; gap: 10px; padding: 8px 12px; border-radius: 10px; color: var(--muted); font-size: 13px; font-weight: 600; border-bottom: 1px solid var(--line); margin: 0 0 6px; }
  .sidenav .navmin:hover { background: var(--bg); color: var(--ink-2); }
  .bottomnav { display: none; }
  .main { padding: 22px 24px 32px; }
  .main.full { padding: 0; }

  /* The menu down to icons, which gives the rota back about 160px. */
  .layout.nav-min { grid-template-columns: 64px minmax(0, 1fr); }
  .layout.nav-min .sidenav { padding: 14px 8px; }
  .layout.nav-min .sidenav a { justify-content: center; padding: 10px 0; }
  /* The labels go, the counts stay. The rule that hides the label was hiding the badge with it,
     because both are a span inside the link, so a minimised menu showed nothing waiting at all. */
  .layout.nav-min .sidenav a > span:not(.navbadge), .layout.nav-min .sidenav .foot, .layout.nav-min .sidenav .navmin span { display: none; }
  .layout.nav-min .sidenav .navmin { justify-content: center; padding: 8px 0; margin: 0 0 6px; }
  .layout.nav-min .sidenav .navbadge { display: grid; position: absolute; top: 2px; right: 6px; left: auto; margin: 0; min-width: 18px; height: 18px; padding: 0 4px; font-size: 10px; border: 2px solid var(--card); }
  .layout.nav-min .main { padding: 22px 16px 32px; }
}

/* ---------- Components ---------- */
.card { background: var(--card); border-radius: var(--radius); box-shadow: var(--shadow); padding: 14px; }
.card.pad0 { padding: 0; }
.card.click { cursor: pointer; transition: transform .08s, box-shadow .08s; }
.card.click:active { transform: scale(.995); }
.section-title { display: flex; align-items: center; justify-content: space-between; margin: 18px 0 10px; }
.section-title h2 { font-size: 16px; }
.grid { display: grid; gap: 12px; }
.grid.tiles { grid-template-columns: repeat(2, 1fr); }
.grid.cards { grid-template-columns: 1fr; }
@media (min-width: 700px) { .grid.tiles { grid-template-columns: repeat(3, 1fr); } .grid.cards { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1100px) { .grid.tiles { grid-template-columns: repeat(6, 1fr); } .grid.cards { grid-template-columns: repeat(3, 1fr); } }
.tile { background: var(--card); border-radius: var(--radius); box-shadow: var(--shadow); padding: 12px 14px; display: flex; flex-direction: column; gap: 2px; cursor: pointer; }
.tile .n { font-size: 26px; font-weight: 800; letter-spacing: -.02em; }
.tile .l { font-size: 12px; color: var(--muted); font-weight: 600; }
.tile.warn .n { color: var(--warn); } .tile.bad .n { color: var(--bad); } .tile.ok .n { color: var(--ok); } .tile.info .n { color: var(--info); }

.btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 10px 16px; border-radius: 11px; font-weight: 600; font-size: 14px; border: 1px solid var(--line); background: var(--card); color: var(--ink); min-height: 42px; white-space: nowrap; }
.btn:hover { background: var(--bg); }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn.primary { background: var(--brand); color: #fff; border-color: var(--brand); }
.btn.primary:hover { background: var(--brand-600); }
.btn.danger { background: var(--bad); color: #fff; border-color: var(--bad); }
.btn.ghost { border-color: transparent; background: transparent; }
.btn.ghost:hover { background: var(--bg); }
.btn.sm { padding: 6px 10px; min-height: 32px; font-size: 13px; border-radius: 9px; }
.btn.lg { padding: 14px 20px; min-height: 54px; font-size: 16px; border-radius: 14px; }
.btn.block { width: 100%; }
.btn.icon { padding: 8px; min-height: 36px; min-width: 36px; }
.btn svg { width: 18px; height: 18px; }

.input, select.input, textarea.input { width: 100%; padding: 10px 12px; border: 1px solid var(--line); border-radius: 10px; background: var(--card); min-height: 42px; }
textarea.input { min-height: 80px; resize: vertical; }
.input:focus { outline: 2px solid var(--brand-100); border-color: var(--brand); }
label.field { display: flex; flex-direction: column; gap: 6px; font-size: 13px; font-weight: 600; color: var(--ink-2); }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }

.avatar { width: 34px; height: 34px; border-radius: 50%; display: grid; place-items: center; color: #fff; font-weight: 700; font-size: 13px; flex: none; letter-spacing: .02em; }
.avatar.sm { width: 26px; height: 26px; font-size: 11px; }
.avatar.lg { width: 52px; height: 52px; font-size: 18px; }

.chip { display: inline-flex; align-items: center; gap: 5px; padding: 3px 8px; border-radius: 6px; font-size: 11px; font-weight: 700; letter-spacing: .02em; text-transform: uppercase; color: #fff; }
.dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; flex: none; }
.dot.scheduled { background: #94a3b8; } .dot.in_progress { background: #f59e0b; box-shadow: 0 0 0 3px rgba(245,158,11,.25); } .dot.done { background: var(--ok); }

.tabs { display: flex; gap: 4px; background: #e9eef3; padding: 4px; border-radius: 12px; }
.tabs button { flex: 1; padding: 8px 10px; border-radius: 9px; font-weight: 600; font-size: 13px; color: var(--muted); display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
.tabs button.active { background: var(--card); color: var(--ink); box-shadow: var(--shadow); }
.tabs .navbadge { position: static; }

.list { display: flex; flex-direction: column; }
.list > .item { display: flex; align-items: center; gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--line); }
.list > .item:last-child { border-bottom: 0; }
.list > .item.click { cursor: pointer; }
.list > .item.click:hover { background: var(--bg); margin: 0 -14px; padding-left: 14px; padding-right: 14px; }

.empty { text-align: center; color: var(--muted); padding: 28px 12px; font-size: 14px; }
.empty svg { width: 36px; height: 36px; opacity: .4; margin-bottom: 8px; }

.alert { border-radius: 12px; padding: 12px 14px; display: flex; gap: 12px; align-items: flex-start; }
.alert svg { width: 20px; height: 20px; flex: none; margin-top: 1px; }
.alert.warn { background: var(--warn-bg); color: #78350f; }
.alert.bad { background: var(--bad-bg); color: #7f1d1d; }
.alert.info { background: var(--info-bg); color: #1e3a8a; }
.alert.ok { background: var(--ok-bg); color: #14532d; }
.alert .actions { display: flex; gap: 8px; margin-top: 8px; flex-wrap: wrap; }

/* ---------- Sheet / modal ---------- */
.sheet-backdrop { position: fixed; inset: 0; background: rgba(15, 23, 42, .45); z-index: 100; display: flex; align-items: flex-end; justify-content: center; animation: fade .15s; }
.sheet { background: var(--card); width: 100%; max-height: 92vh; max-height: 92dvh; border-radius: 20px 20px 0 0; display: flex; flex-direction: column; animation: up .2s; }
.sheet-head { display: flex; align-items: center; gap: 10px; padding: 14px 16px 10px; border-bottom: 1px solid var(--line); }
.sheet-head h2 { flex: 1; font-size: 17px; }
.sheet-body { overflow: auto; padding: 14px 16px calc(16px + var(--safe-b)); -webkit-overflow-scrolling: touch; }
.sheet-foot { padding: 10px 16px calc(12px + var(--safe-b)); border-top: 1px solid var(--line); display: flex; gap: 10px; }
.sheet-foot .btn { flex: 1; }
@media (min-width: 700px) {
  .sheet-backdrop { align-items: center; padding: 20px; }
  .sheet { max-width: 640px; border-radius: 18px; max-height: 88vh; }
  .sheet.wide { max-width: 860px; }
}
@keyframes fade { from { opacity: 0 } to { opacity: 1 } }
@keyframes up { from { transform: translateY(30px); opacity: 0 } to { transform: none; opacity: 1 } }

.toast-wrap { position: fixed; left: 0; right: 0; bottom: calc(var(--nav-h) + var(--safe-b) + 12px); display: flex; flex-direction: column; align-items: center; gap: 8px; z-index: 200; pointer-events: none; }
@media (min-width: 900px) { .toast-wrap { bottom: 24px; } }
.toast { background: var(--ink); color: #fff; padding: 10px 16px; border-radius: 12px; font-size: 14px; font-weight: 500; box-shadow: var(--shadow); animation: up .2s; max-width: 92vw; display: flex; gap: 8px; align-items: center; }
.toast.ok { background: var(--ok); } .toast.warn { background: var(--warn); } .toast.bad { background: var(--bad); }
.toast svg { width: 16px; height: 16px; }

/* ---------- Job cards (board) ---------- */
.jobcard { background: var(--card); border-radius: 10px; border: 1px solid var(--line); border-left: 4px solid var(--type, var(--brand)); padding: 8px 10px; margin-bottom: 6px; cursor: grab; box-shadow: 0 1px 2px rgba(15,23,42,.05); user-select: none; -webkit-user-select: none; touch-action: pan-y; }
.jobcard:active { cursor: grabbing; }
.jobcard .t { font-weight: 700; font-size: 13px; line-height: 1.25; }
.jobcard .s { font-size: 12px; color: var(--muted); display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin-top: 3px; }
.jobcard .meta { display: flex; gap: 8px; align-items: center; margin-top: 6px; font-size: 11px; color: var(--muted); }
.jobcard .meta svg { width: 13px; height: 13px; }
.jobcard.done { opacity: .72; }
.jobcard.done .t { text-decoration: none; }
.jobcard.needs-cover { border-color: var(--bad); background: #fff7f7; }
.sortable-ghost { opacity: .35; }
.sortable-chosen { box-shadow: 0 8px 22px rgba(15,23,42,.18); }
.sortable-drag { opacity: .95; }

/* The rota fills what is left of the window and scrolls inside itself, so the filters and the day
   headings stay put. The max height is measured in JS, because it depends on how tall the toolbar
   has wrapped to. */
.board-wrap { overflow: auto; -webkit-overflow-scrolling: touch; border: 1px solid var(--line); border-radius: var(--radius); background: var(--card); overscroll-behavior: contain; }
/* Everything on the rota is sized from --rz, so zooming keeps the text sharp and drag and drop
   working. Scaling the whole grid with transform would do neither. */
.board { font-size: calc(13px * var(--rz, 1)); }
.board .jobcard { padding: calc(6px * var(--rz, 1)) calc(7px * var(--rz, 1)); border-radius: calc(8px * var(--rz, 1)); }
.board .jobcard .t { font-size: calc(12px * var(--rz, 1)); }
.board .jobcard .s, .board .jobcard .meta { font-size: calc(10.5px * var(--rz, 1)); }
/* A long property name must not set a floor under how narrow a column can go, or zooming out stops
   working once the widest card is reached. Names wrap and then clip instead. */
.board .cell, .board .cell .drop, .board .jobcard { min-width: 0; }
.board .jobcard .t { overflow-wrap: anywhere; }
.board .jobcard .s, .board .jobcard .meta { overflow: hidden; }
.board .rh .nm { font-size: calc(13px * var(--rz, 1)); }
.board .rh .hrs { font-size: calc(11px * var(--rz, 1)); }
.board .rh { padding: calc(8px * var(--rz, 1)); gap: calc(7px * var(--rz, 1)); }
/* The name column was setting the height of every row: a fixed 34px avatar and a name wrapping onto
   three lines. It scales with everything else now, and each line stays a line. */
.board .rh .avatar { width: calc(30px * var(--rz, 1)); height: calc(30px * var(--rz, 1)); font-size: calc(11px * var(--rz, 1)); flex: none; }
.board .rh > div { min-width: 0; }
/* Narrower, with the name wrapping onto a second line instead of being cut off. A column of
   "Em…" tells nobody anything, and the width it was taking is a card across the whole week. */
.board .rh .nm { white-space: normal; overflow-wrap: anywhere; line-height: 1.15; }
.board .rh .hrs { white-space: normal; overflow-wrap: anywhere; line-height: 1.15; }
/* A mini card is about 30px, so a band square only has to hold one comfortably. */
.board .cell { min-height: calc(38px * var(--rz, 1)); padding: calc(4px * var(--rz, 1)); }
.board .cell.band { min-height: calc(34px * var(--rz, 1)); }
.board .cell.band .drop { min-height: calc(26px * var(--rz, 1)); }
.zoombar { gap: 4px; }
.zoombar .btn { min-height: 34px; padding: 0 8px; }
.board { display: grid; grid-template-columns: 170px repeat(7, minmax(168px, 1fr)); min-width: 1300px; }
.board .hd { position: sticky; top: 0; z-index: 3; background: var(--card); border-bottom: 1px solid var(--line); padding: 10px; font-size: 13px; font-weight: 700; }

.board .hd .cnt { font-weight: 500; color: var(--muted); font-size: 12px; }
.board .rh { position: sticky; left: 0; z-index: 2; background: var(--card); border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); padding: 10px; display: flex; align-items: center; gap: 8px; }
.board .rh.corner { z-index: 4; top: 0; }
.board .rh .nm { font-weight: 700; font-size: 13px; line-height: 1.2; }
.board .rh .hrs { font-size: 11px; color: var(--muted); }
/* The squares are tinted and the cards are white, so a card reads as an object sitting in a day
   rather than as more of the background. */
.board .cell { border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); padding: 6px; min-height: 64px; background: var(--cell); position: relative; }
/* Today is the column the office works from, so it is a colour rather than a shade: a warm band
   down the whole day, headings included, next to the plain grey of the rest of the week. */
.board .cell.today { background: var(--today); }
.board .cell.today.off { background: repeating-linear-gradient(135deg, var(--today-2) 0 8px, var(--today) 8px 16px); }
.board .cell.off { background: repeating-linear-gradient(135deg, var(--cell) 0 8px, var(--cell-2) 8px 16px); }
/* A day off and a day off sick both say so plainly. It was 10px grey and easy to miss. */
.board .cell.off::before { content: attr(data-off); position: absolute; top: 5px; right: 7px; font-size: 11px; font-weight: 800; color: var(--ink-2); text-transform: uppercase; letter-spacing: .04em; }
.board .cell.over { color: var(--bad); }
.board .cell .cap { font-size: 10px; color: var(--muted); text-align: right; padding: 2px 2px 0; }
.board .cell .cap.over { color: var(--bad); font-weight: 700; }
.board .cell { display: flex; flex-direction: column; }
/* A square is as tall as what is in it. The strip left underneath is only what a card needs to be
   dropped into an empty day, not a reserved block of nothing. */
.board .cell .drop { flex: 1; min-height: 20px; padding-bottom: 10px; }
.board .row-unassigned .rh, .board .row-unassigned .cell { background: #fffbeb; }
.board .row-unassigned .cell { border-bottom: 2px solid #fcd34d; }
.board .row-unassigned .rh { border-bottom: 2px solid #fcd34d; }

.dayview .daycol { background: var(--card); border-radius: var(--radius); box-shadow: var(--shadow); padding: 12px; margin-bottom: 12px; }
.dayview .daycol .drop { min-height: 56px; border-radius: 10px; padding-bottom: 20px; }
.dayview .daycol.off { background: repeating-linear-gradient(135deg, #f8fafc 0 8px, #f1f5f9 8px 16px); }
.dayview .daycol.unassigned { background: #fffbeb; border: 1px dashed #fcd34d; }
/* The customer's list of cleans, as a table: dates and scores line up down the page. */
.vtable { background: var(--card); border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden; }
.vrow { display: grid; grid-template-columns: 104px minmax(0, 1.5fr) minmax(0, .9fr) 76px minmax(0, 1.7fr) 22px; gap: 12px; align-items: center; padding: 12px 14px; border-top: 1px solid var(--line); width: 100%; text-align: left; background: none; }
.vrow:first-child { border-top: 0; }
.vrow.click { cursor: pointer; }
.vrow.click:hover { background: var(--brand-50); }
.vhead { padding: 10px 14px; background: var(--bg); font-size: 10px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.vrow .vc { min-width: 0; }
.vrow .vdate { font-weight: 700; font-size: 13px; line-height: 1.25; }
.vrow .vnote { font-size: 12px; font-style: italic; color: var(--ink-2); margin-top: 2px; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.vrow .vgo { color: var(--muted); display: flex; justify-content: flex-end; }
.vrow .vshot { position: relative; width: 64px; height: 48px; }
.vrow .vshot img { width: 100%; height: 100%; object-fit: cover; border-radius: 8px; display: block; background: var(--bg); }
.vrow .vshot .n { position: absolute; right: -5px; bottom: -5px; background: var(--ink); color: #fff; font-size: 10px; font-weight: 700; border-radius: 999px; padding: 1px 5px; border: 1.5px solid var(--card); }
.vtable:not(.wide) .vrow { grid-template-columns: 104px minmax(0, 1.2fr) minmax(0, .9fr) 76px minmax(0, 1.7fr) 22px; }

/* On a phone each row folds into a block and carries its own labels. */
@media (max-width: 760px) {
  .vhead { display: none; }
  .vrow { grid-template-columns: 1fr auto; gap: 6px 10px; padding: 12px 14px; }
  .vrow .vc { grid-column: 1 / -1; display: grid; grid-template-columns: 88px minmax(0, 1fr); gap: 2px 10px; align-items: start; }
  /* Every part of the value goes in the second column, or a second line drops back under the label. */
  .vrow .vc > * { grid-column: 2; }
  .vrow .vc::before { content: attr(data-label); grid-column: 1; grid-row: 1; font-size: 10px; font-weight: 800; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); padding-top: 2px; }
  .vrow .vc:first-child::before { content: none; }
  .vrow .vc:first-child { grid-template-columns: 1fr; }
  .vrow .vc:first-child > * { grid-column: 1; }
  .vrow .vc .pill { justify-self: start; }
  .vrow .vc:first-child .vdate { font-size: 15px; }
  .vrow .vgo { display: none; }
}

/* Feedback on a single clean, in the customer's portal. */
.stars { display: flex; gap: 4px; }
.stars button, .stars span { font-size: 26px; line-height: 1; color: var(--line); background: none; border: 0; padding: 0; }
.stars button:hover { color: #fbbf24; }
.stars .on, .stars button.on { color: #f59e0b; }
.stars.read span { font-size: 20px; }
.stars.read.sm span { font-size: 15px; letter-spacing: 1px; }
.list .item.picked { background: var(--brand-50); border-radius: 10px; }

.confirm-preview { font: 13px/1.6 "Helvetica Neue", Helvetica, Arial, sans-serif; white-space: pre-wrap; margin: 0; max-height: 46vh; overflow: auto; }

/* What is drawn on the map, and where each person travels out from. */
.maptoggles { display: grid; gap: 4px; }
.maptoggle { display: flex; align-items: flex-start; gap: 9px; padding: 6px 8px; border-radius: 9px; cursor: pointer; font-size: 13px; }
.maptoggle:hover { background: var(--bg); }
.maptoggle input { margin: 2px 0 0; accent-color: var(--brand); }
.st-marker { width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center; color: #fff; font-size: 10px; font-weight: 800; border: 2px dashed rgba(255,255,255,.9); box-shadow: 0 1px 4px rgba(15,23,42,.35); }
.st-marker.on { border-style: solid; transform: scale(1.15); }
.st-marker.off { opacity: .45; }

/* Today's jobs on the map, coloured by whoever is doing them. */
.job-marker { width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center; color: #fff; font-size: 10px; font-weight: 800; border: 2px solid #fff; box-shadow: 0 1px 4px rgba(15,23,42,.35); position: relative; }
.job-marker.unassigned { border-style: dashed; border-color: #fff; }
.job-marker b { position: absolute; right: -5px; bottom: -5px; background: var(--ink); color: #fff; border-radius: 999px; font-size: 9px; padding: 0 4px; border: 1px solid #fff; }

/* Which days somebody works, and which parts of those days. */
.availgrid { display: grid; grid-template-columns: 64px repeat(7, 1fr); gap: 4px; align-items: center; }
.availgrid .availlab { font-size: 11px; font-weight: 700; color: var(--muted); }
.availgrid .availday { height: 28px; border-radius: 8px; border: 1px solid var(--line); background: var(--card); font-size: 11px; font-weight: 700; color: var(--muted); }
.availgrid .availday.on { background: var(--brand); border-color: var(--brand); color: #fff; }
.availgrid .availcell { height: 30px; border-radius: 8px; border: 1px solid var(--line); background: var(--bg); display: grid; place-items: center; color: #fff; }
.availgrid .availcell.on { background: var(--ok); border-color: var(--ok); }
.availgrid .availcell svg { width: 14px; height: 14px; }
.availgrid .availcell:hover { border-color: var(--brand); }
.warn-text { color: var(--warn); }
.bad-text { color: var(--bad); }

/* One cleaner's work, opened from their row on the overview. */
table.tbl tr.open > td { background: var(--brand-50); font-weight: 600; }
table.tbl tr.expand > td { padding: 0; background: var(--bg); white-space: normal; }
.cpanel { padding: 14px; border-left: 3px solid var(--brand); }
.pstats { display: grid; grid-template-columns: repeat(auto-fit, minmax(84px, 1fr)); gap: 8px; }
.pstat { background: var(--card); border: 1px solid var(--line); border-radius: 10px; padding: 8px 10px; }
.pstat .n { font-size: 17px; font-weight: 800; letter-spacing: -.01em; font-variant-numeric: tabular-nums; }
.pstat .l { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin-top: 1px; }
.pstat.ok .n { color: var(--ok); } .pstat.warn .n { color: var(--warn); }
.pstat.bad .n { color: var(--bad); } .pstat.info .n { color: var(--info); }
.bars .b.on { background: var(--brand-50); border-radius: 8px; }
/* The colour of a kind of work, next to its name. */
.swatch { display: inline-block; width: 14px; height: 28px; border-radius: 5px; flex: none; }
/* A month nobody has worked yet is still listed, just quietly. */
table.tbl tr.muted td { color: var(--muted); }

/* Growth over time, with the trend drawn through it. */
.growthwrap { width: 100%; overflow: hidden; }
svg.growth { width: 100%; height: 180px; display: block; }
.growthx { display: flex; justify-content: space-between; gap: 2px; padding: 0 22px; margin-top: 2px; }
.growthx span { font-size: 10px; color: var(--muted); font-weight: 600; white-space: nowrap; }
.growthx span.click { cursor: pointer; text-decoration: underline dotted; text-underline-offset: 3px; }
.growthx span.click:hover { color: var(--brand); }
.growthx span.part { opacity: .5; font-style: italic; }
@media (max-width: 700px) { .growthx span:nth-child(even) { visibility: hidden; } }

/* A scorecard that opens: the headline figure, then the detail underneath when asked for. */
.scorecard { padding: 12px 14px; }
.scorehead { display: flex; align-items: center; justify-content: space-between; gap: 12px; width: 100%; text-align: left; background: none; border: 0; font: inherit; color: inherit; }
.scorehead .n { font-size: 28px; font-weight: 800; letter-spacing: -.02em; line-height: 1; }
.scorehead .l { font-size: 12px; color: var(--muted); font-weight: 600; margin-top: 2px; }
.scorehead.has .n { color: var(--bad); }
.scorehead:hover { opacity: .85; }

/* How often a clean happens: tick boxes, not a menu to hunt through. */
/* How often a clean happens, and on which days. These are labelled options, not the round tick
   buttons used on checklists: they were called .tick as well, which handed them that component's
   30px box and white text and left the words invisible against the card. Their own name now. */
.ticks { display: flex; flex-wrap: wrap; gap: 8px; }
.ticks .topt { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border: 1px solid var(--line); border-radius: 11px; background: var(--card); color: var(--ink); cursor: pointer; font-size: 13px; line-height: 1.25; flex: 1 1 150px; min-width: 0; }
.ticks .topt:hover { background: var(--bg); border-color: var(--brand-100); }
.ticks .topt.on { border-color: var(--brand); background: var(--brand-50); box-shadow: inset 0 0 0 1px var(--brand); }
.ticks .topt input { margin: 0; accent-color: var(--brand); width: 17px; height: 17px; flex: none; }
.ticks .topt .lbl { display: flex; flex-direction: column; min-width: 0; }
.ticks .topt .lbl b { font-weight: 700; }
.ticks .topt .lbl .tiny { display: block; }
/* The seven days sit in one even row, each box saying which day it is. */
.ticks.days { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
.ticks.days .topt { flex: none; padding: 8px 6px; justify-content: center; gap: 6px; text-align: left; }
@media (max-width: 560px) {
  .ticks.days { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .ticks.days .topt .lbl .tiny { display: none; }
}
.pickwrap { border: 1px solid var(--line); border-radius: 12px; padding: 10px; background: var(--bg); }

/* Length of time entered as hours and minutes, never as a box wanting "120". */
.durin .row { gap: 6px; }
.durin input { width: 74px; text-align: right; }
.durin .tiny { display: block; margin-top: 4px; }

/* Scorecard: every figure opens the jobs behind it, so tiles and bars are buttons. */
button.tile { text-align: left; width: 100%; border: 0; font: inherit; color: inherit; }
button.tile:hover { box-shadow: 0 1px 2px rgba(15,23,42,.08), 0 6px 18px rgba(15,23,42,.10); transform: translateY(-1px); }
button.tile:active { transform: none; }
.hb.click, .bars .b.click { cursor: pointer; border-radius: 8px; }
.hb.click:hover, .bars .b.click:hover { background: var(--bg); }
.hb.solo .track .r { top: 0; bottom: 0; border-radius: 6px; }

/* Picking the days of a repeat clean, and the dates of a holiday let that books sporadically. */
.daypick { min-width: 48px; min-height: 40px; padding: 0 10px; border-radius: 10px; border: 1px solid var(--line); background: var(--card); font-size: 13px; font-weight: 700; color: var(--ink-2); }
.daypick:hover { background: var(--bg); }
.daypick.on { background: var(--brand); border-color: var(--brand); color: #fff; }
.pickgrid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
.pickgrid .ph { text-align: center; font-size: 11px; font-weight: 700; color: var(--muted); padding-bottom: 2px; }
.pickday { aspect-ratio: 1; min-height: 38px; border-radius: 9px; border: 1px solid var(--line); background: var(--card); font-size: 13px; font-weight: 600; }
.pickday:hover { background: var(--bg); }
.pickday.past { color: var(--muted); background: var(--bg); }
.pickday.on { background: var(--accent); border-color: var(--accent); color: #fff; }

/* Morning, afternoon and evening bands inside a day column. */
.bandrow { display: grid; grid-template-columns: 96px minmax(0, 1fr); gap: 8px; align-items: start; padding: 6px 0; border-top: 1px dashed var(--line); }
.bandrow:first-of-type { border-top: 0; }
.bandrow .bandlab { display: flex; flex-direction: column; gap: 1px; font-size: 12px; font-weight: 700; padding-top: 4px; }
.bandrow .bandlab .muted { font-weight: 500; font-size: 11px; }
.bandrow.empty .bandlab { opacity: .55; }
.bandrow .drop { min-height: 44px; border-radius: 10px; padding-bottom: 14px; }
.bandrow.empty .drop { border: 1px dashed var(--line); }
@media (max-width: 560px) { .bandrow { grid-template-columns: minmax(0, 1fr); gap: 2px; } .bandrow .bandlab { flex-direction: row; gap: 6px; align-items: baseline; } }

/* The rota splits every day into Morning, Afternoon and Evening, as a row under the date. */
.board .bh { position: sticky; top: 46px; z-index: 3; background: var(--card); border-bottom: 1px solid var(--line); border-right: 1px dashed var(--line); padding: 3px 6px; display: flex; align-items: center; justify-content: space-between; gap: 4px; }
.board .bh .bl { font-size: 10px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.board .bh .bn { font-size: 10px; font-weight: 700; color: var(--ink-2); background: var(--bg); border-radius: 999px; padding: 0 5px; }
.board .bh.today .bl { color: var(--brand); }
.board.banded .hd { height: 46px; }
.board .cell.band { border-right: 1px dashed var(--line); padding: 4px; }
.board .cell.band .drop { min-height: 40px; padding-bottom: 16px; }
/* Not one of their usual parts of the day. Still droppable — the rota warns, it does not refuse. */
.board .cell.band.nb { background: repeating-linear-gradient(135deg, #fafbfc 0 6px, #f1f5f9 6px 12px); }
.board .cell.band.wk, .board .bh.wk { border-left: 2px solid var(--line); }

/* Folded day columns and the four-week rota. */
.board .hd.thin, .board .cell.thin { padding: 4px 2px; text-align: center; }
.board .hd.thin { display: flex; flex-direction: column; align-items: center; gap: 4px; }
.board .hd.thin .vert { writing-mode: vertical-rl; font-size: 11px; font-weight: 600; color: var(--muted); white-space: nowrap; }
.board .cell.thin { background: var(--card); display: flex; align-items: flex-start; justify-content: center; min-height: 40px; }
.board .cell.thin .dot { margin-top: 6px; font-size: 11px; font-weight: 700; color: var(--muted); background: var(--bg); border: 1px solid var(--line); border-radius: 999px; padding: 1px 6px; }
.board .collapse { background: none; border: 0; padding: 2px; color: var(--muted); cursor: pointer; line-height: 0; border-radius: 6px; }
.board .collapse:hover { background: var(--bg); color: var(--ink); }
.board .hd.wk, .board .cell.wk { border-left: 2px solid var(--line); }
/* Today, boxed in so it stands out from the days either side of it. The outline goes round the whole
   day and never between the parts of it: each square owns only the outside edges it actually sits on,
   and every edge is a slot in the same shadow so any combination can be drawn. It is drawn on the
   inside, so it does not push the columns around. */
.board .hd.today, .board .bh.today, .board .cell.today {
  box-shadow: var(--edge-l, 0 0 transparent), var(--edge-r, 0 0 transparent), var(--edge-t, 0 0 transparent), var(--edge-b, 0 0 transparent);
}
.board .today.tfirst { --edge-l: inset 2px 0 0 var(--brand); }
.board .today.tlast { --edge-r: inset -2px 0 0 var(--brand); }
.board .hd.today { --edge-t: inset 0 3px 0 #b45309; background: var(--today-2); color: #7c3c04; }
.board .today.tfirst { --edge-l: inset 2px 0 0 #b45309; }
.board .today.tlast { --edge-r: inset -2px 0 0 #b45309; }
.board .cell.today.last-row { --edge-b: inset 0 -3px 0 #b45309; }
.board .cell.today.last-row { --edge-b: inset 0 -3px 0 var(--brand); }
.board .bh.today { color: #7c3c04; }
.board .bh.today { background: var(--brand-50); }
.board.four .jobcard .s span:last-child, .board.four .jobcard .meta { font-size: 10px; }
.board.four .jobcard { padding: 5px 6px; }

/* The row of days scrolls sideways on its own. Without min-width it reported its full content width
   to the page, and on a phone that pushed every screen 70px wider than the glass. */
.daychips { display: flex; gap: 6px; overflow-x: auto; padding-bottom: 6px; scrollbar-width: none; min-width: 0; max-width: 100%; }
.daychips > * { flex: none; }
.daychips::-webkit-scrollbar { display: none; }
.daychips button { flex: none; padding: 8px 12px; border-radius: 10px; background: var(--card); border: 1px solid var(--line); font-size: 13px; font-weight: 600; display: flex; flex-direction: column; align-items: center; gap: 1px; min-width: 62px; }
.daychips button .d { font-size: 11px; color: var(--muted); font-weight: 600; }
.daychips button .n { font-size: 15px; }
.daychips button.active { background: var(--brand); color: #fff; border-color: var(--brand); }
.daychips button.active .d { color: rgba(255,255,255,.8); }
.daychips button.today:not(.active) { border-color: var(--brand); color: var(--brand); }

/* ---------- Cleaner today ---------- */
.hero { background: linear-gradient(135deg, var(--brand) 0%, #4e5282 70%, #7a4a8f 100%); color: #fff; border-radius: 18px; padding: 18px; box-shadow: var(--shadow); }
.hero h1 { color: #fff; }
.hero .muted { color: rgba(255,255,255,.8); }
.jobrow { background: var(--card); border-radius: var(--radius); box-shadow: var(--shadow); padding: 14px; display: flex; gap: 12px; align-items: flex-start; border-left: 5px solid var(--type, var(--brand)); }
.jobrow .time { font-weight: 800; font-size: 15px; min-width: 52px; line-height: 1.2; }
.jobrow .time .dur { font-size: 11px; color: var(--muted); font-weight: 600; }
.jobrow.done { opacity: .75; }
.jobrow.active { border-left-color: #f59e0b; box-shadow: 0 0 0 2px #fde68a, var(--shadow); }
.progress { height: 8px; background: #e2e8f0; border-radius: 4px; overflow: hidden; }
.progress > i { display: block; height: 100%; background: var(--brand); border-radius: 4px; transition: width .2s; }
.checklist { display: flex; flex-direction: column; }
.checklist label { display: flex; align-items: center; gap: 12px; padding: 11px 0; border-bottom: 1px solid var(--line); cursor: pointer; font-size: 15px; }
.checklist label:last-child { border-bottom: 0; }
.checklist input { width: 22px; height: 22px; accent-color: var(--brand); flex: none; }
.checklist label.checked { color: var(--muted); text-decoration: line-through; }
.photos { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.photos .ph { aspect-ratio: 4/3; border-radius: 10px; overflow: hidden; background: #e2e8f0; position: relative; }
.photos .ph img { width: 100%; height: 100%; object-fit: cover; display: block; }
.photos .ph .cap { position: absolute; left: 0; right: 0; top: 0; background: rgba(0,0,0,.45); color: #fff; font-size: 11px; padding: 3px 6px; }
.photos .add { aspect-ratio: 4/3; border-radius: 10px; border: 2px dashed var(--line); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px; color: var(--muted); font-size: 12px; font-weight: 600; background: var(--bg); }
.photos .add svg { width: 22px; height: 22px; }

/* NFC sheet */
.nfc-anim { position: relative; width: 160px; height: 160px; margin: 10px auto; display: grid; place-items: center; }
.nfc-anim .ring { position: absolute; inset: 0; border-radius: 50%; border: 2px solid var(--brand); opacity: 0; animation: ring 2s infinite; }
.nfc-anim .ring:nth-child(2) { animation-delay: .6s; } .nfc-anim .ring:nth-child(3) { animation-delay: 1.2s; }
.nfc-anim .core { width: 76px; height: 76px; border-radius: 50%; background: var(--brand-100); color: var(--brand); display: grid; place-items: center; }
.nfc-anim .core svg { width: 36px; height: 36px; }
@keyframes ring { 0% { transform: scale(.5); opacity: .7 } 100% { transform: scale(1.1); opacity: 0 } }
.nfc-success { width: 96px; height: 96px; border-radius: 50%; background: var(--ok-bg); color: var(--ok); display: grid; place-items: center; margin: 16px auto; animation: pop .3s; }
.nfc-success svg { width: 48px; height: 48px; stroke-width: 3; }
@keyframes pop { from { transform: scale(.5); opacity: 0 } to { transform: none; opacity: 1 } }

/* ---------- Map ---------- */
.mapwrap { position: relative; height: calc(100vh - var(--header-h) - var(--nav-h) - var(--safe-b)); height: calc(100dvh - var(--header-h) - var(--nav-h) - var(--safe-b)); display: grid; grid-template-rows: 1fr auto; }
@media (min-width: 900px) { .mapwrap { height: calc(100vh - var(--header-h)); height: calc(100dvh - var(--header-h)); grid-template-columns: 1fr 340px; grid-template-rows: 1fr; } }
/* Leaflet stacks its own panes and controls up to z-index 1000, which is above the top bar, the
   bottom bar, the sheets and the toasts. Boxing it into its own stacking context keeps all of that
   inside the map, so nothing of ours ends up behind it. */
#map { width: 100%; height: 100%; min-height: 240px; background: #dbe7ee; position: relative; z-index: 0; isolation: isolate; }
.mappanel { position: relative; z-index: 1; }
.mappanel { background: var(--card); border-top: 1px solid var(--line); max-height: 42vh; overflow: auto; display: flex; flex-direction: column; }
@media (min-width: 900px) { .mappanel { border-top: 0; border-left: 1px solid var(--line); max-height: none; } }
.mappanel .ctl { padding: 10px 14px; border-bottom: 1px solid var(--line); position: sticky; top: 0; background: var(--card); z-index: 2; }
.mappanel .plist { padding: 6px 14px; }
.mappanel .plist .item { padding: 9px 0; }
.mappanel .plist .item.sel { background: var(--brand-50); margin: 0 -14px; padding: 9px 14px; }
input[type=range] { width: 100%; accent-color: var(--brand); }
.cl-marker { width: 30px; height: 30px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 2px 6px rgba(0,0,0,.35); display: grid; place-items: center; color: #fff; font-size: 11px; font-weight: 800; position: relative; }
.cl-marker.onsite::after { content: ''; position: absolute; inset: -6px; border-radius: 50%; border: 2px solid #f59e0b; animation: ring 1.6s infinite; }
.cl-marker.home { opacity: .55; }
.pr-marker { width: 12px; height: 12px; border-radius: 3px; background: #475569; border: 2px solid #fff; box-shadow: 0 1px 4px rgba(0,0,0,.3); transform: rotate(45deg); }
.pr-marker.active { background: var(--brand); width: 14px; height: 14px; }
.leaflet-popup-content { font-size: 13px; margin: 10px 12px; }
.timeline { position: relative; padding-left: 18px; }
.timeline::before { content: ''; position: absolute; left: 5px; top: 6px; bottom: 6px; width: 2px; background: var(--line); }
.timeline .ev { position: relative; padding: 4px 0 10px; font-size: 13px; }
.timeline .ev::before { content: ''; position: absolute; left: -17px; top: 9px; width: 8px; height: 8px; border-radius: 50%; background: var(--brand); border: 2px solid #fff; box-shadow: 0 0 0 1px var(--line); }
.timeline .ev.out::before { background: var(--ok); }
.timeline .ev.future::before { background: #cbd5e1; }

/* ---------- Messages ---------- */
.msgs { display: grid; grid-template-columns: 1fr; height: calc(100vh - var(--header-h) - var(--nav-h) - var(--safe-b)); height: calc(100dvh - var(--header-h) - var(--nav-h) - var(--safe-b)); }
@media (min-width: 900px) { .msgs { grid-template-columns: 300px 1fr; height: calc(100vh - var(--header-h)); height: calc(100dvh - var(--header-h)); } }
.threads { overflow: auto; border-right: 1px solid var(--line); background: var(--card); }
.threads .th { display: flex; align-items: center; gap: 12px; padding: 12px 14px; border-bottom: 1px solid var(--line); cursor: pointer; }
.threads .th:hover, .threads .th.sel { background: var(--brand-50); }
.threads .th .nm { font-weight: 700; font-size: 14px; }
.threads .th .pv { font-size: 12px; color: var(--muted); }
.threads .th.unread .nm { color: var(--ink); }
.threads .th.unread .pv { color: var(--ink); font-weight: 600; }
.convo { display: flex; flex-direction: column; background: #eef2f5; min-height: 0; }
.convo .chead { padding: 10px 14px; background: var(--card); border-bottom: 1px solid var(--line); display: flex; align-items: center; gap: 10px; }
.convo .cbody { flex: 1; overflow: auto; padding: 14px; display: flex; flex-direction: column; gap: 8px; }
.bubble { max-width: 78%; padding: 9px 12px; border-radius: 16px; background: var(--card); font-size: 14px; line-height: 1.35; box-shadow: 0 1px 1px rgba(15,23,42,.06); align-self: flex-start; }
.bubble.me { background: var(--brand); color: #fff; align-self: flex-end; border-bottom-right-radius: 4px; }
.bubble.them { border-bottom-left-radius: 4px; }
.bubble .who { font-size: 11px; font-weight: 700; margin-bottom: 2px; color: var(--brand-600); }
.bubble .when { font-size: 10px; opacity: .7; margin-top: 3px; text-align: right; }
.daysep { align-self: center; font-size: 11px; color: var(--muted); font-weight: 600; margin: 6px 0; }
.composer { padding: 10px 12px calc(10px + var(--safe-b)); background: var(--card); border-top: 1px solid var(--line); display: flex; gap: 8px; align-items: flex-end; }
.composer textarea { flex: 1; resize: none; min-height: 42px; max-height: 120px; }
.quick { display: flex; gap: 6px; overflow-x: auto; padding: 8px 12px 0; background: var(--card); scrollbar-width: none; }
.quick button { flex: none; font-size: 12px; padding: 6px 10px; border-radius: 999px; border: 1px solid var(--line); background: var(--bg); font-weight: 600; }
@media (max-width: 899px) { .msgs.in-thread .threads { display: none; } .msgs:not(.in-thread) .convo { display: none; } }

/* ---------- Reports ---------- */
.bars { display: flex; flex-direction: column; gap: 6px; }
.bars .b { display: grid; grid-template-columns: 120px 1fr 48px; align-items: center; gap: 10px; font-size: 13px; }
.bars .b .bar { height: 14px; background: var(--brand-100); border-radius: 7px; overflow: hidden; }
.bars .b .bar > i { display: block; height: 100%; background: var(--brand); border-radius: 7px; }
.bars .b .v { text-align: right; font-weight: 600; font-variant-numeric: tabular-nums; }
table.tbl { width: 100%; border-collapse: collapse; font-size: 13px; }
table.tbl th, table.tbl td { padding: 9px 8px; text-align: left; border-bottom: 1px solid var(--line); white-space: nowrap; }
table.tbl th { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
table.tbl td.num, table.tbl th.num { text-align: right; font-variant-numeric: tabular-nums; }
/* A wide table scrolls inside its own card. Without min-width it reports the table's full width to
   the page instead, and on a phone the whole screen slides sideways. */
.tblwrap { overflow-x: auto; min-width: 0; max-width: 100%; }

/* ---------- Absence calendar ---------- */
.abscal { display: grid; font-size: 11px; min-width: 900px; }
.abscal .c { border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); height: 30px; display: grid; place-items: center; }
.abscal .c.name { position: sticky; left: 0; background: var(--card); justify-items: start; padding-left: 8px; font-weight: 600; font-size: 12px; z-index: 1; }
.abscal .c.hd { font-weight: 700; color: var(--muted); background: var(--card); position: sticky; top: 0; }
.abscal .c.hd.wkend { background: #f1f5f9; }
.abscal .c.hd.today { color: var(--brand); }
.abscal .c.holiday { background: #bfdbfe; } .abscal .c.sick { background: #fecaca; } .abscal .c.pending { background: repeating-linear-gradient(45deg, #bfdbfe 0 4px, #fff 4px 8px); } .abscal .c.dayoff { background: #f1f5f9; }
.legend { display: flex; gap: 12px; flex-wrap: wrap; font-size: 12px; color: var(--muted); }
.legend i { display: inline-block; width: 14px; height: 14px; border-radius: 3px; vertical-align: -2px; margin-right: 4px; }

/* ---------- Login ---------- */
.login { min-height: 100vh; min-height: 100dvh; display: grid; place-items: center; padding: 20px; background: radial-gradient(1200px 600px at 20% -10%, #d7f0ec, transparent), radial-gradient(900px 500px at 110% 110%, #dbeafe, transparent), var(--bg); }
.login .box { width: 100%; max-width: 440px; }
.login .choice { display: flex; align-items: center; gap: 14px; padding: 16px; border-radius: 14px; border: 1px solid var(--line); background: var(--card); cursor: pointer; width: 100%; text-align: left; }
.login .choice:hover { border-color: var(--brand); background: var(--brand-50); }
.login .choice svg.icon.arrow { margin-left: auto; color: var(--muted); }
.offline-banner { background: var(--warn-bg); color: #78350f; font-size: 13px; font-weight: 600; padding: 8px 14px; text-align: center; }

/* ---------- v2.0: post-meeting update ---------- */
.btn.xl { min-height: 64px; font-size: 19px; font-weight: 800; border-radius: 16px; box-shadow: var(--shadow); }
.btn.xl svg { width: 26px; height: 26px; }
.bigrow { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.bigrow .btn { min-height: 52px; white-space: normal; }
.nowcard { background: var(--card); border-radius: 18px; box-shadow: var(--shadow); padding: 16px; border-top: 6px solid var(--type, var(--brand)); margin-bottom: 12px; }
.steps { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px; }
.steps span { font-size: 11px; font-weight: 700; text-align: center; padding: 5px 2px; border-radius: 8px; background: #e9eef3; color: var(--muted); }
.steps span.done { background: var(--ok-bg); color: var(--ok); }
.steps span.now { background: var(--accent); color: #fff; }
.donebar { display: flex; align-items: center; justify-content: center; gap: 8px; padding: 14px; border-radius: 14px; background: var(--ok-bg); color: var(--ok); font-weight: 700; }
.officenote { display: flex; gap: 8px; align-items: flex-start; background: var(--accent-100); color: #7a0148; padding: 10px 12px; border-radius: 10px; font-size: 14px; }
.officenote svg { flex: none; margin-top: 2px; }
.demo-box { border: 2px dashed var(--line); border-radius: 12px; padding: 12px; background: var(--bg); text-align: left; }
.checkline { display: flex; gap: 10px; align-items: flex-start; font-size: 14px; cursor: pointer; }
.checkline input { width: 20px; height: 20px; accent-color: var(--brand); flex: none; margin-top: 1px; }
.plainlist { margin: 6px 0 0; padding-left: 18px; font-size: 14px; line-height: 1.5; }
.linkbtn { color: var(--muted); font-size: 11px; margin-left: 8px; text-decoration: underline; }
.crewpick { display: grid; grid-template-columns: 1fr; gap: 6px; max-height: 46vh; overflow: auto; }
@media (min-width: 700px) { .crewpick { grid-template-columns: 1fr 1fr; } }
.crewopt { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border: 1px solid var(--line); border-radius: 12px; cursor: pointer; background: var(--card); }
.crewopt input { width: 20px; height: 20px; accent-color: var(--brand); flex: none; }
.crewopt.on { border-color: var(--brand); background: var(--brand-50); }
.crewopt.off { opacity: .6; }
.tasks { display: flex; flex-direction: column; }
.task { display: flex; gap: 12px; align-items: flex-start; padding: 10px 0; border-top: 1px solid var(--line); }
.task:first-child { border-top: 0; }
.task .ttl { font-weight: 700; font-size: 15px; cursor: pointer; }
.task.done .ttl { color: var(--muted); }
.tick { width: 30px; height: 30px; border-radius: 9px; border: 2px solid #cbd5e1; display: grid; place-items: center; flex: none; background: var(--card); color: #fff; }
.tick.on { background: var(--ok); border-color: var(--ok); }
.tick svg { width: 18px; height: 18px; stroke-width: 3; }
.tick:disabled { opacity: .7; }
div.tick { color: var(--muted); } div.tick.on { color: #fff; }
.taskdetails { margin: 4px 0 8px 42px; padding-left: 16px; font-size: 13px; color: var(--ink-2); line-height: 1.5; }
.card .taskdetails { margin-left: 4px; }
.ctxcard { background: var(--card); border: 1px solid var(--line); border-left: 4px solid var(--accent); border-radius: 12px; padding: 10px 12px; cursor: pointer; align-self: stretch; }
.bubble img.attach { display: block; width: 200px; max-width: 100%; border-radius: 10px; margin-bottom: 4px; }
.threads .th .tag, .chatpanel .th .tag { display: inline-block; font-size: 10px; font-weight: 700; background: var(--brand-100); color: var(--brand-600); padding: 0 5px; border-radius: 4px; margin-right: 5px; }

/* Conversations with customers. The whole point is that they never look like the team's: a pink
   edge down the side, the word Customer on the row and in the header, and a line at the top of the
   conversation saying who is reading it. */
.tag.client { background: #fde7f3 !important; color: #a1005e !important; }
.thgroup { display: flex; align-items: center; gap: 6px; padding: 12px 14px 6px; font-size: 11px; font-weight: 800; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); background: var(--bg); border-bottom: 1px solid var(--line); }
.thgroup svg { width: 14px; height: 14px; }
.thgroup .tiny { text-transform: none; letter-spacing: 0; font-weight: 600; font-size: 10px; margin-left: auto; }
.thgroup.client { color: var(--accent); background: #fff5fa; }
.threads .th.client { border-left: 3px solid var(--accent); background: #fffafd; }
.threads .th.client:hover, .threads .th.client.sel { background: #fde7f3; }
.convo .chead.client { background: #fff5fa; border-bottom: 2px solid var(--accent); }
.clientbar { background: #fde7f3; border: 1px solid #f9a8d4; color: #831843; border-radius: 12px; padding: 10px 12px; margin-bottom: 10px; }
.clientbar b { font-weight: 800; }
.clientbar svg { width: 16px; height: 16px; flex: none; }
.composer.client { border-top: 2px solid var(--accent); background: #fff5fa; }
/* The customers' screen is a different place from the team's, and looks like one from the title
   down: a pink edge along the top of the list and a pink heading. */
.msgs.clientscope .threads { border-top: 3px solid var(--accent); }
.msgs.clientscope .clienthead h1 { color: var(--accent); }
@media (min-width: 900px) { .convo .backbtn { display: none; } }
.jobcard.cancelled { opacity: .55; cursor: default; }
.jobcard.cancelled .t { text-decoration: line-through; }

/* On the grid a card is a property and a time. Everything else is one tap away, and the space it
   used to take is another cleaner's row on the screen. */
.jobcard.mini { padding: calc(4px * var(--rz, 1)) calc(6px * var(--rz, 1)); margin-bottom: calc(4px * var(--rz, 1)); border-left-width: 3px; background: #fff; box-shadow: 0 1px 2px rgba(15,23,42,.10); }
.jobcard.mini .t { font-size: calc(11.5px * var(--rz, 1)); line-height: 1.2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.jobcard.mini .s { font-size: calc(10.5px * var(--rz, 1)); margin-top: 0; gap: 4px; flex-wrap: nowrap; }
.jobcard.mini .s .tm { font-variant-numeric: tabular-nums; }
.jobcard.mini .flag { display: inline-flex; color: var(--muted); }
.jobcard.mini .flag.bad { color: var(--bad); }
.jobcard.mini .flag svg { width: calc(11px * var(--rz, 1)); height: calc(11px * var(--rz, 1)); }
/* A reply from the office, said loudly. A customer opens the portal once a fortnight: a quiet line
   in a list is no use to them, so it sits at the top of the page until they have read it. */
.newmsg { display: flex; align-items: center; gap: 14px; width: 100%; text-align: left; padding: 16px 18px; border: 0; border-radius: 16px; cursor: pointer;
  background: linear-gradient(135deg, var(--brand) 0%, #4a4f8f 100%); color: #fff; box-shadow: 0 6px 20px rgba(54,57,105,.28); animation: landed .5s ease both; }
.newmsg:hover { filter: brightness(1.06); }
.newmsg .ico { display: grid; place-items: center; width: 44px; height: 44px; border-radius: 50%; background: rgba(255,255,255,.18); flex: none; }
.newmsg .ico svg { width: 22px; height: 22px; }
.newmsg .txt { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.newmsg .hd { font-size: 18px; font-weight: 800; letter-spacing: -.01em; }
.newmsg .sub { font-size: 12px; opacity: .85; }
.newmsg .quote { font-size: 13px; opacity: .95; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-top: 2px; }
.newmsg .go { display: flex; align-items: center; gap: 4px; font-size: 13px; font-weight: 700; flex: none; }
.newmsg .go svg { width: 16px; height: 16px; }
@keyframes landed { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }
@media (max-width: 560px) { .newmsg .go span { display: none; } .newmsg .hd { font-size: 16px; } }

/* Everything above the rota, kept to two tight lines. */
.rotahead { gap: 6px; }
.rotahead h1 { font-size: 20px; line-height: 1.1; margin: 0; }
.rotahead .input { font-size: 13px; }
.rotahead .tabs button { padding: 6px 10px; }
.tabs.scroll { overflow-x: auto; scrollbar-width: none; }
.tabs.scroll button { flex: none; }
.syncpill { cursor: pointer; border: 0; }
/* On a small phone the top bar's own contents were wider than the screen — the name, the sync pill,
   the cog and the persona button add up — which pushed every page sideways by about 70px. Each part
   gives way instead: the sync pill keeps its dot, the persona button keeps its face. */
@media (max-width: 520px) {
  .topbar { gap: 6px; }
  .brand { font-size: 15px; min-width: 0; overflow: hidden; }
  .brand-mark { width: 30px; height: 30px; } .brand-mark img { width: 28px; height: 28px; }
  .syncpill span, .persona-btn .small, .persona-btn svg { display: none; }
  .persona-btn { padding: 3px; }
}
.rot90 { transform: rotate(90deg); }
tr.click { cursor: pointer; } tr.click:hover td { background: var(--brand-50); }
.bottomnav.n7 a { font-size: 9.5px; } .bottomnav.n7 a svg { width: 20px; height: 20px; }
.bottomnav.n8 a { font-size: 9px; gap: 2px; } .bottomnav.n8 a svg { width: 19px; height: 19px; }

/* Month view */
.month-wrap { overflow-x: auto; }
.month { display: grid; grid-template-columns: repeat(7, minmax(92px, 1fr)); gap: 6px; min-width: 680px; }
.month .mh { font-size: 12px; font-weight: 700; color: var(--muted); text-align: center; padding: 4px; }
.month .mc { text-align: left; background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 8px; min-height: 84px; display: flex; flex-direction: column; gap: 2px; border-top: 4px solid #e2e8f0; }
.month .mc.blank { background: transparent; border: 0; }
.month .mc.past { background: #f8fafc; }
.month .mc.today { outline: 2px solid var(--accent); }
.month .mc.good { border-top-color: var(--ok); } .month .mc.under { border-top-color: #f59e0b; } .month .mc.over { border-top-color: var(--bad); }
.month .mc:hover { border-color: var(--brand); }
.month .mn { font-weight: 800; font-size: 15px; } .month .mhrs { font-size: 12px; font-weight: 700; color: var(--ink-2); }
/* A cleaner's own rota: the same month grid, narrower because it is read on a phone, with a dot per
   job in the colour of the kind of work. */
.month.mine { grid-template-columns: repeat(7, minmax(44px, 1fr)); min-width: 320px; gap: 4px; }
.month.mine .mc { min-height: 62px; padding: 5px; border-radius: 9px; }
.month.mine .mn { font-size: 13px; }
.month.mine .mc.picked { border-color: var(--brand); box-shadow: inset 0 0 0 1px var(--brand); background: var(--brand-50); }
.month .jdot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; }

/* A cleaner's own week. Seven days across on a laptop, one under another on a phone, because a
   column 50px wide is no use to somebody checking what time they start on Thursday. */
.myweek { display: grid; gap: 8px; grid-template-columns: 1fr; }
.myweek .wday { background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 8px; display: flex; flex-direction: column; gap: 6px; min-height: 64px; }
.myweek .wday.today { border-color: var(--brand); box-shadow: inset 0 0 0 1px var(--brand); }
.myweek .wday.past { background: #f8fafc; }
.myweek .wday.off { background: repeating-linear-gradient(135deg, #f8fafc 0 8px, #f1f5f9 8px 16px); }
.myweek .whd { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.myweek .wdn { font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); }
.myweek .wdd { font-size: 18px; font-weight: 800; line-height: 1; }
.myweek .wjob { text-align: left; width: 100%; border: 1px solid var(--line); border-left: 4px solid var(--type, var(--brand)); border-radius: 10px; background: var(--bg); padding: 7px 9px; cursor: pointer; }
.myweek .wjob:hover { background: #fff; border-color: var(--brand); }
.myweek .wjob.done { opacity: .7; }
.myweek .wt { font-size: 13px; font-weight: 800; font-variant-numeric: tabular-nums; }
.myweek .wp { font-size: 13px; font-weight: 700; line-height: 1.2; overflow-wrap: anywhere; }
.myweek .wm { font-size: 11px; color: var(--muted); margin-top: 2px; }
.myweek .wempty { font-size: 11px; color: var(--muted); padding: 2px 2px 4px; }
@media (min-width: 900px) {
  .myweek { grid-template-columns: repeat(7, minmax(0, 1fr)); }
  .myweek .wday { min-height: 150px; }
  .myweek .whd { flex-direction: column; align-items: flex-start; gap: 2px; }
}
span.legend { display: inline-flex; padding: 1px 8px; border-radius: 6px; font-weight: 600; font-size: 11px; }
span.legend.good { background: var(--ok-bg); color: var(--ok); } span.legend.under { background: var(--warn-bg); color: var(--warn); } span.legend.over { background: var(--bad-bg); color: var(--bad); }

/* Planned against recorded bars */
.hbars { display: flex; flex-direction: column; gap: 8px; }
.hb { display: grid; grid-template-columns: 120px 1fr 96px 72px; gap: 8px; align-items: center; font-size: 12px; }
.hb .lbl { font-weight: 600; }
.hb .track { position: relative; height: 18px; background: #f1f5f9; border-radius: 6px; overflow: hidden; }
.hb .track .p { position: absolute; left: 0; top: 0; bottom: 0; background: var(--brand-100); }
.hb .track .r { position: absolute; left: 0; top: 5px; bottom: 5px; background: var(--brand); border-radius: 0 4px 4px 0; }
.hb .track .tgt { position: absolute; top: 0; bottom: 0; width: 2px; background: var(--accent); }
.hb .v { text-align: right; font-variant-numeric: tabular-nums; color: var(--ink-2); }
@media (max-width: 699px) { .hb { grid-template-columns: 84px 1fr 80px; } .hb > :last-child { display: none; } }

/* Floating chat */
.chatdock { position: fixed; right: 14px; bottom: calc(var(--nav-h) + var(--safe-b) + 14px); z-index: 35; display: flex; flex-direction: column; align-items: flex-end; gap: 10px; }
@media (min-width: 900px) { .chatdock { bottom: 18px; right: 18px; } }
.chatfab { width: 54px; height: 54px; border-radius: 50%; background: var(--accent); color: #fff; display: grid; place-items: center; box-shadow: 0 6px 18px rgba(15,23,42,.28); position: relative; }
.chatfab svg { width: 24px; height: 24px; }
.chatfab .navbadge { top: -2px; left: auto; right: -2px; }
.chatpanel { width: min(360px, calc(100vw - 28px)); height: min(520px, calc(100dvh - var(--header-h) - var(--nav-h) - 110px)); background: var(--card); border-radius: 16px; box-shadow: 0 12px 40px rgba(15,23,42,.3); overflow: hidden; display: flex; flex-direction: column; border: 1px solid var(--line); }
.chatpanel .convo { flex: 1; min-height: 0; }
.chatpanel .th { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-bottom: 1px solid var(--line); cursor: pointer; }
.chatpanel .th:hover { background: var(--brand-50); }
.chatpanel .th .nm { font-weight: 700; font-size: 13px; } .chatpanel .th .pv { font-size: 12px; color: var(--muted); }
body:has(.sheet-backdrop) .chatdock { display: none; }
.wizard .item .tick { width: 26px; height: 26px; }

/* App update prompt */
.update-bar { position: fixed; left: 12px; right: 12px; top: calc(var(--header-h) + 8px); z-index: 60; display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 12px 14px; border-radius: 14px; background: var(--card); border: 2px solid var(--accent); box-shadow: 0 12px 40px rgba(15,23,42,.28); font-size: 14px; max-width: 760px; margin: 0 auto; }
.update-bar > svg { color: var(--accent); flex: none; }
.update-bar .grow { min-width: 200px; }
.update-pill { cursor: pointer; border: 0; animation: pop .3s; }

/* Choice rows used in sheets as well as the sign-in screen */
.sheet .choice { display: flex; align-items: center; gap: 14px; padding: 13px; border-radius: 14px; border: 1px solid var(--line); background: var(--card); cursor: pointer; width: 100%; text-align: left; }
.sheet .choice:hover { border-color: var(--brand); background: var(--brand-50); }
.sheet .choice.on { border-color: var(--brand); background: var(--brand-50); }

/* Text and card size, turned up without giving up the week. --rz fits the columns to the screen;
   --tz is how big the writing is inside them and --cz how much room a card takes. A name that no
   longer fits on one line wraps to two and the row grows taller, which is the trade the office
   chose when they pressed the button. */
.board { --tz: 1; --cz: 1; }
.board { font-size: calc(13px * var(--rz, 1) * var(--tz)); }
.board .jobcard .t { font-size: calc(12px * var(--rz, 1) * var(--tz)); }
.board .jobcard .s, .board .jobcard .meta { font-size: calc(10.5px * var(--rz, 1) * var(--tz)); }
.board .rh .nm { font-size: calc(13px * var(--rz, 1) * var(--tz)); }
.board .rh .hrs { font-size: calc(11px * var(--rz, 1) * var(--tz)); }
.board .hd, .board .cell .cap { font-size: calc(11px * var(--rz, 1) * var(--tz)); }
.board .jobcard.mini { padding: calc(4px * var(--rz, 1) * var(--cz)) calc(6px * var(--rz, 1) * var(--cz)); margin-bottom: calc(4px * var(--rz, 1) * var(--cz)); }
.board .jobcard.mini .t { font-size: calc(11.5px * var(--rz, 1) * var(--tz)); }
.board .jobcard.mini .s { font-size: calc(10.5px * var(--rz, 1) * var(--tz)); }
.board .jobcard.mini .flag svg { width: calc(11px * var(--rz, 1) * var(--tz)); height: calc(11px * var(--rz, 1) * var(--tz)); }
.board .cell { min-height: calc(52px * var(--rz, 1) * var(--cz)); }
.board .cell .drop { min-height: calc(26px * var(--rz, 1) * var(--cz)); }
/* Above normal, a property name is allowed two lines rather than being cut off mid-word. */
.board.bigtext .jobcard.mini .t, .board.cards2 .jobcard.mini .t, .board.cards3 .jobcard.mini .t {
  white-space: normal; overflow-wrap: anywhere; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
/* At the largest card size there is room to say where the property is, which is what the office
   asks next after reading the name. */
.board .jobcard.mini .tw { display: none; }
.board .jobcard.mini { min-height: calc(30px * var(--rz, 1) * var(--cz)); display: flex; flex-direction: column; justify-content: center; }
.board.cards3 .jobcard.mini .tw { display: block; font-size: calc(10px * var(--rz, 1) * var(--tz)); color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.azoom { font-weight: 900; font-size: 15px; line-height: 1; }

/* Dragging the edge of a heading sets that column's width. The grip is a hair of a thing until the
   pointer is near it, so it never competes with the fold-away button next to it. */
.board .hd, .board .rh.corner { position: relative; }
.board .colgrip { position: absolute; top: 0; right: -3px; width: 8px; height: 100%; cursor: col-resize; z-index: 5; touch-action: none; }
.board .colgrip::after { content: ''; position: absolute; top: 12%; bottom: 12%; left: 3px; width: 2px; border-radius: 2px; background: transparent; }
.board .colgrip:hover::after, .board.sizing .colgrip::after { background: var(--brand); }
.board.sizing { cursor: col-resize; user-select: none; }
.board .hd.set .cnt::after { content: ' · set'; color: var(--muted); font-weight: 600; }
/* The day view lists a person's jobs straight down, in time order. */
.dayview .daycol .dayjobs { min-height: 56px; border-radius: 10px; padding-bottom: 20px; }

/* ---------- Background colour swatches ---------- */
.bgswatches { display: flex; flex-wrap: wrap; gap: 10px; }
.bgswatch { display: block; width: 104px; height: auto; padding: 8px; border: 2px solid var(--line); border-radius: 12px; background: var(--card); text-align: left; }
.bgswatch .sw { display: block; height: 34px; border-radius: 8px; border: 1px solid rgba(15, 23, 42, .12); position: relative; margin-bottom: 6px; }
/* A little white card on the colour, so the swatch shows what the screen will look like rather than
   just what colour it is. */
.bgswatch .sw::after { content: ''; position: absolute; left: 7px; right: 7px; bottom: 6px; height: 13px; border-radius: 3px; background: #fff; box-shadow: 0 1px 2px rgba(15, 23, 42, .22); }
.bgswatch .nm { font-size: 12px; font-weight: 700; color: var(--ink-2); }
.bgswatch.on { border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-100); }
.bgswatch.on .nm { color: var(--brand); }

/* ---------- Report a problem, at the top of a job ---------- */
.reportbar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding: 10px 12px; border: 1px solid var(--bad-bg); border-left: 4px solid var(--bad); border-radius: 12px; background: #fff7f7; }
.reportbar .why { font-size: 12px; color: var(--muted); flex: 1 1 200px; min-width: 0; }

/* ---------- Row heights on the rota ----------
   The same handle as a column, turned through ninety degrees: the bottom edge of a name sets that
   row, the bottom edge of the corner sets all of them. A row with a height keeps it, and anything
   that will not fit inside scrolls within the square rather than pushing the week taller. */
/* Kept wholly inside the name cell: the row below is sticky too, and an edge that hangs over
   into it is covered by it and cannot be grabbed. */
.board .rowgrip { position: absolute; left: 0; bottom: 0; width: 100%; height: 9px; cursor: row-resize; z-index: 5; touch-action: none; }
.board .rowgrip::after { content: ''; position: absolute; left: 12%; right: 12%; bottom: 2px; height: 2px; border-radius: 2px; background: transparent; }
.board .rowgrip:hover::after, .board.vsizing .rowgrip::after { background: var(--brand); }
.board.vsizing { cursor: row-resize; user-select: none; }
.board.rowfix .rh, .board.rowfix .cell { overflow: hidden; }
.board.rowfix .cell .drop { overflow-y: auto; padding-bottom: 4px; }

/* ---------- Off sick ----------
   A day off and a holiday are grey stripes: nothing to do about them. Sickness is the day's problem,
   so it is red, the word SICK is red, and it stays red on top of today's amber. */
.board .cell.off.sick, .board .cell.today.off.sick { background: repeating-linear-gradient(135deg, var(--sick) 0 8px, var(--sick-2) 8px 16px); }
.board .cell.off.sick { box-shadow: var(--edge-l, 0 0 transparent), var(--edge-r, 0 0 transparent), var(--edge-t, 0 0 transparent), var(--edge-b, 0 0 transparent), inset 0 0 0 1px var(--sick-2); }
.board .cell.off.sick::before { color: var(--bad); }
.board .cell.thin.off.sick { background: var(--sick); }
.daycol.sick { background: var(--sick); border-color: var(--sick-2); }

/* ---------- An urgent problem ----------
   Red where it is listed, not just a red word at the side of it: on Today, in the problems
   scorecard and on the job itself. Marking it sorted takes the red away. */
.list > .item.urgent, .item.urgent { background: var(--bad-bg); box-shadow: inset 3px 0 0 var(--bad); border-radius: 8px; padding-left: 10px; padding-right: 8px; }
.list > .item.urgent + .item { border-top: 0; }
.item.urgent .bold { color: #7f1d1d; }

/* ---------- Somebody on this job is off sick ---------- */
.jobcard.sickcrew { box-shadow: inset 3px 0 0 var(--bad), 0 1px 2px rgba(15, 23, 42, .1); }
.jobcard .sickdot { display: inline-flex; align-items: center; justify-content: center; width: 14px; height: 14px; border-radius: 50%; background: var(--bad); color: #fff; font-size: 9px; font-weight: 800; flex: none; }

/* ---------- A reply the demo wrote itself ---------- */
.bubble .simtag { font-size: 9px; font-weight: 800; letter-spacing: .04em; opacity: .75; margin-bottom: 3px; }
.bubble.them.sim { border: 1px dashed var(--line); }

/* ---------- Notifications ---------- */
.alertrow { align-items: flex-start; gap: 10px; }
.alertrow .alerticon { display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; border-radius: 9px; flex: none; }
.alertrow .alerticon svg { width: 16px; height: 16px; }
.alertrow .alerticon.bad { background: var(--bad-bg); color: var(--bad); }
.alertrow .alerticon.ok { background: var(--ok-bg); color: var(--ok); }
.alertrow .alerticon.info { background: var(--info-bg); color: var(--info); }
/* What arrived since the last look, marked down the edge rather than with another red number. */
.alertrow.fresh { box-shadow: inset 3px 0 0 var(--brand); border-radius: 8px; padding-left: 10px; }

/* ---------- The printed rota ----------
   Built into the page and hidden, rather than opened in a new window: a new window is blocked often
   enough to be unreliable, and it would not be the installed app. On paper everything else is hidden
   and only these sheets are left. */
#printarea { display: none; }
@media print {
  @page { size: A4 portrait; margin: 14mm; }
  body > *:not(#printarea) { display: none !important; }
  #printarea { display: block !important; }
  .prota { page-break-after: always; break-after: page; color: #000; font-family: Calibri, Helvetica, Arial, sans-serif; }
  .prota:last-child { page-break-after: auto; break-after: auto; }
  .prota .phead { border-bottom: 2px solid #000; padding-bottom: 6mm; margin-bottom: 6mm; }
  .prota .pname { font-size: 24pt; font-weight: 800; line-height: 1.1; }
  .prota .pfor { font-size: 12pt; margin-top: 2mm; }
  .prota .pprinted { font-size: 9pt; color: #444; margin-top: 1mm; }
  .prota .ptable { width: 100%; border-collapse: collapse; font-size: 11pt; }
  .prota .ptable th { text-align: left; font-size: 9pt; text-transform: uppercase; letter-spacing: .06em; border-bottom: 1px solid #000; padding: 2mm 2mm 1.5mm; }
  .prota .ptable td { padding: 2mm; border-bottom: 1px solid #ccc; vertical-align: top; }
  /* Date, start and finish are read down the page, so they keep their own width and never wrap. */
  .prota .ptable th:nth-child(1), .prota .ptable td:nth-child(1) { width: 26mm; white-space: nowrap; font-weight: 700; }
  .prota .ptable th:nth-child(2), .prota .ptable td:nth-child(2),
  .prota .ptable th:nth-child(3), .prota .ptable td:nth-child(3) { width: 20mm; white-space: nowrap; }
  .prota .pjob { display: block; font-weight: 600; }
  .prota .paddr { display: block; font-size: 9pt; color: #444; }
  .prota .pfree td { color: #666; font-style: italic; }
  .prota .pfree td:first-child { font-style: normal; }
  .prota .pfoot { margin-top: 6mm; font-size: 9pt; color: #444; border-top: 1px solid #ccc; padding-top: 2mm; }
  /* A row is never split across two pages, and the heading repeats when a fortnight runs over one. */
  .prota tr { page-break-inside: avoid; break-inside: avoid; }
  .prota thead { display: table-header-group; }
}

/* ---------- The pin board ---------- */
.pinpost { padding: 14px; margin-bottom: 12px; position: relative; }
.pinpost.pinned { box-shadow: inset 4px 0 0 var(--accent), var(--shadow); }
.pinpost.fresh { border: 1px solid var(--brand-100); }
.pinpost .pintack { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border-radius: 50%; background: var(--accent-100); color: var(--accent); }
.pinpost .pintack svg { width: 13px; height: 13px; }
.pinpost .pintitle { font-size: 17px; font-weight: 800; line-height: 1.25; }
.pinpost .pinphoto { display: block; margin-top: 10px; max-width: 220px; width: 100%; border-radius: 10px; }
.pinpost .jobdot { width: 10px; height: 10px; border-radius: 50%; flex: none; }
.pinpost .pinreplies { margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--line); }
@media (max-width: 560px) { .pinpost .pintitle { font-size: 16px; } }

/* A customer's own words in their conversation, so it does not read as if the office said everything. */
.mynote { display: flex; gap: 8px; align-items: flex-start; padding: 8px 10px; border-radius: 10px; background: var(--brand-50); border-left: 3px solid var(--brand); }
.mynote svg { width: 15px; height: 15px; flex: none; color: var(--brand); margin-top: 2px; }

/* ---------- Three actions of equal weight ----------
   A two column grid left the third button orphaned on its own line and sitting under the floating
   chat button. Full width one under another on a phone, three across as soon as there is room. */
.actionrow { display: grid; gap: 8px; grid-template-columns: repeat(3, 1fr); }
.actionrow .btn { min-height: 52px; white-space: normal; }
@media (max-width: 560px) {
  .actionrow { grid-template-columns: 1fr; }
}
/* The customer message a notification was pointing at. */
.card.picked { box-shadow: 0 0 0 3px var(--brand-100), inset 4px 0 0 var(--brand), var(--shadow); }

/* ---------- More ---------- */
.morelicon { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: 9px; background: var(--brand-50); color: var(--brand); flex: none; }
.morelicon svg { width: 17px; height: 17px; }
.bottomnav a[data-id="more"] { cursor: pointer; }

/* ---------- a rota week that is not out yet ---------- */
/* Amber, hatched, and said in words on the heading: the office is looking at something nobody else
   can see, and that has to be obvious at a glance rather than something you remember. */
.board .hd.draftday { background: var(--warn-bg); border-bottom-color: var(--warn); }
.board .hd.draftday .cnt { color: var(--warn); }
.board .cell.draftday { background: repeating-linear-gradient(135deg, var(--cell) 0 10px, var(--warn-bg) 10px 20px); }
.board .cell.draftday.off { background: repeating-linear-gradient(135deg, var(--cell-2) 0 8px, var(--warn-bg) 8px 16px); }
.mc.draftday { background: var(--warn-bg); }
.btn.warn { background: var(--warn-bg); color: #78350f; border-color: var(--warn); font-weight: 700; }
.btn.warn:hover { background: #fde8a9; }
.list .item.on { box-shadow: inset 3px 0 0 var(--brand); }
/* Two buttons and a description do not share 375px. On a phone the week reads across the top and its
   buttons sit under it, full width, rather than squeezing the words into a column one letter wide. */
@media (max-width: 560px) {
  .pubrow { flex-wrap: wrap; }
  .pubrow > .grow { flex: 1 0 calc(100% - 26px); }
  .pubrow > .row { flex: 1 0 100%; justify-content: flex-start !important; margin-top: 8px; }
  .pubrow > .row .btn { flex: 1; justify-content: center; }
}

/* The printed timesheet reuses the rota sheet, but its wide column is the job, not the last one. */
@media print {
  .prota.psheet .ptable th:nth-child(2), .prota.psheet .ptable td:nth-child(2) { width: auto; white-space: normal; font-weight: 400; }
  .prota.psheet .ptable th:nth-child(3), .prota.psheet .ptable td:nth-child(3),
  .prota.psheet .ptable th:nth-child(4), .prota.psheet .ptable td:nth-child(4) { width: 22mm; white-space: nowrap; }
  .prota.psheet .ptable th:nth-child(5), .prota.psheet .ptable td:nth-child(5) { width: 20mm; white-space: nowrap; text-align: right; font-weight: 700; }
  .prota.psheet .ptotal td { border-top: 1.5pt solid #000; border-bottom: none; font-weight: 800; font-size: 12pt; padding-top: 3mm; }
  .prota.psheet .ptotal td:nth-child(4) { font-weight: 400; font-size: 9pt; color: #444; text-align: right; }
  /* Room to sign. A timesheet that gets queried is one somebody wants a signature on. */
  .prota .psign { display: flex; gap: 10mm; margin-top: 14mm; }
  .prota .psign div { flex: 1; border-top: 1px solid #000; padding-top: 2mm; font-size: 9pt; color: #444; }
  .prota .psign div:last-child { flex: 0 0 34mm; }
}

/* ---------- sharing a team's hours ---------- */
/* Its own box under the team, so a choice that halves everybody's hours is not a line anybody skims past. */
.splitline { padding: 12px 14px; border-radius: 12px; background: var(--brand-50, #eef1f7); border: 1px solid var(--line); }
.splitline input[type="checkbox"] { width: 20px; height: 20px; margin-top: 2px; flex: none; }

/* ---------- late to site ---------- */
/* Red, fixed under the top bar on every screen, so it cannot be scrolled past or missed on a phone. */
.latebar { position: fixed; left: 0; right: 0; top: var(--header-h); z-index: 55; background: #b91c1c; color: #fff; padding: 10px 14px; box-shadow: 0 8px 24px rgba(127,29,29,.35); display: flex; flex-direction: column; gap: 8px; animation: latepulse 1.6s ease-in-out 3; }
.latebar .latehead { display: flex; align-items: center; gap: 8px; font-weight: 800; text-transform: uppercase; letter-spacing: .04em; font-size: 13px; }
.latebar .latehead svg { width: 18px; height: 18px; }
.latebar .laterow { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; font-size: 15px; }
.latebar .laterow .grow { min-width: 220px; }
.latebar .btn { background: #fff; color: #7f1d1d; border-color: #fff; }
.latebar .btn.ghost { background: transparent; color: #fff; border-color: rgba(255,255,255,.6); }
.latebar a:not(.btn) { color: #fff; text-decoration: underline; }
.shell.haslate .layout { padding-top: var(--latebar-h, 0px); }
@keyframes latepulse { 50% { background: #dc2626; } }
.latefacts { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.latefacts > div { background: var(--bg); border-radius: 10px; padding: 8px 10px; }
.latefacts b { font-size: 18px; }
.card.selected { outline: 2px solid var(--bad); }
.card.flush { padding: 0; }

/* ---------- stock ---------- */
.stepper { display: inline-flex; align-items: center; gap: 12px; margin-top: 6px; }
.stepper .btn { width: 52px; height: 52px; padding: 0; justify-content: center; font-size: 22px; border-radius: 14px; }
.stepper .stepnum, .stepper input { min-width: 56px; width: 80px; text-align: center; font-size: 26px; font-weight: 800; }
.stockrow { gap: 10px; }
.stockrow .stockcount { font-size: 20px; font-weight: 800; min-width: 38px; text-align: right; flex: none; }
.stockrow.hiddenitem .bold { color: var(--ink-2); }
.mt4s { margin-top: 4px; } .mb4s { margin-bottom: 4px; }
@media (max-width: 560px) { .stockrow { flex-wrap: wrap; } .stockrow .grow { flex-basis: 60%; } }
.stockpage .card.flush .item { padding: 11px 14px; }
.kindpick { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.kindopt { text-align: left; border: 2px solid var(--line); border-radius: 12px; padding: 10px 12px; background: var(--card); display: flex; flex-direction: column; gap: 4px; cursor: pointer; }
.kindopt.on { border-color: var(--brand); background: var(--brand-50, #eef1f7); }
@media (max-width: 520px) { .kindpick { grid-template-columns: 1fr; } }

/* ---------- Problems first ----------
   What a cleaner reported, in their words, at the top of the job, the property record and the
   property card. Amber while open, red if any of it is urgent. */
.problemscard.open { border-left: 4px solid var(--warn); }
.problemscard.open:has(.item.urgent) { border-left-color: var(--bad); }
.cardproblem { display: flex; gap: 6px; align-items: flex-start; margin: 6px 0; padding: 6px 8px; border-radius: 8px; background: var(--warn-bg); color: var(--ink); font-size: 13px; line-height: 1.35; }
.cardproblem svg { width: 15px; height: 15px; flex: none; color: var(--warn); margin-top: 2px; }
.cardproblem.urgent { background: var(--bad-bg); }
.cardproblem.urgent svg { color: var(--bad); }
.cardproblem span { overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
/* The practice copy says so in the top bar, on every screen. */
.testsite { margin-left: 8px; padding: 2px 7px; border-radius: 6px; background: #f59e0b; color: #1f2937; font-size: 10px; font-weight: 800; letter-spacing: .06em; white-space: nowrap; }
/* A line of the starter kit being handed over: tick, what it is, how many or which one. */
.kitrow { gap: 10px; align-items: center; }
.kitrow input[type=checkbox] { width: 20px; height: 20px; flex: none; }
.kitrow select.input { max-width: 140px; }
