/* Spectrum Tasks — Spectrum Energy Systems house style.
   Navy #1F233C is the structure, cyan #3AC3E9 is the accent, charcoal #494949 is the copy.
   Cyan is DELIBERATELY never used for small text on white — it only clears 2.1:1 there.
   It earns its keep as a fill (with navy sitting on top, 7.5:1), a border, or an active state. */
:root {
  --navy: #1F233C;          /* headers, primary buttons, headings */
  --navy-deep: #2A3050;     /* hover, links, dark accents */
  --cyan: #3AC3E9;          /* accents, active states, badges ON navy */
  --cyan-soft: #7fd9f1;     /* cyan that has to sit on a dark surface */
  --cyan-wash: #eaf7fd;     /* barely-there cyan tint for unread/active surfaces */
  --charcoal: #494949;      /* body copy */
  --ink: #1F233C;           /* headings, task titles, dark chrome */
  --muted: #5c6b7a;         /* secondary copy — 5.5:1 on white */
  --bg: #f4f6fa;            /* matches manifest background_color */
  --card: #ffffff;
  --line: #dde3ed;
  /* -ink variants are the readable pair: use them for text, and for any fill that
     carries white text. The plain hue stays for borders, rails and soft washes. */
  --red: #e23a3a; --red-ink: #c62828;
  --amber: #e8920c; --amber-ink: #8a5a00;
  --green: #158b52;
  --slate: #5f7385; --slate-soft: #9fb4c2;
  --band: linear-gradient(135deg, #1F233C, #2A3050);
  --radius: 12px; --shadow: 0 1px 3px rgba(31, 35, 60, .08);
}
* { box-sizing: border-box; }
body {
  margin: 0; background: var(--bg); color: var(--charcoal);
  font-family: 'Roboto', 'Lato', 'Helvetica Neue', Arial, sans-serif;
  font-size: 15px; line-height: 1.45;
}
/* Body links are navy-deep (12.8:1), never cyan. Components that are really buttons
   (nav pills, task rows, menu items) opt out of the underline via their own rules. */
a { color: var(--navy-deep); text-decoration: none; }
h1, h2, h3 { color: var(--ink); }
h1 { font-size: 26px; margin: 0 0 4px; font-weight: 700; letter-spacing: -.2px; }
h2 { font-size: 17px; margin: 0 0 10px; font-weight: 500; }
h2 small, h1 small { color: var(--muted); font-weight: 400; font-size: 12px; }
code { background: #eef2f7; padding: 1px 5px; border-radius: 5px; font-size: 13px; color: var(--navy-deep); }
/* 1300px, not 1040: the nav carries eight items once Jobs, Site Log, Training and
   Processes are all on, and at 1040 "Site Log" broke onto a second line.
   Kept as a VARIABLE because #backzone sizes itself from the leftover gutter — when this
   was a bare 1040 in two places, widening one and not the other put the back button on
   top of the page content. Anything that measures the gutter must read --wrapw. */
:root { --wrapw: 1300px; }
.wrap { max-width: var(--wrapw); margin: 0 auto; padding: 0 18px; }
.muted { color: var(--muted); } .small { font-size: 12.5px; }

/* ---- top bar: the Spectrum band — navy gradient + 4px cyan stripe ---- */
.top {
  background: var(--band);
  border-bottom: 4px solid var(--cyan);
  position: sticky; top: 0; z-index: 50;
}
.bar { display: flex; align-items: center; gap: 18px; height: 56px; }
.brand { font-size: 20px; color: var(--ink); letter-spacing: .2px; }
.brand span { color: var(--navy); }
.brand.big { font-size: 30px; }
.top .brand { color: #fff; }
/* The header band is navy, so the wordmark has to be the white-text lockup.
   Set here as well as in the markup so the band is never left with a navy-on-navy logo. */
.top .brandlogo { content: url("brand/logo-white.png"); }
.top .brandtasks { color: var(--cyan); }

/* The nine nav items need ~764px of label. Below roughly 1200px the bar cannot hold them
   all, and with overflow visible the tail ("Processes", "To review") printed straight over
   the bell, the avatar and + New task — and past the viewport, panning the whole page
   sideways on an iPad (768/834). overflow-x:auto keeps every item reachable by swiping the
   bar instead, and does nothing at all once the row fits (1200px+). */
.mainnav { display: flex; gap: 4px; flex: 1 1 0; min-width: 0; overflow-x: auto; scrollbar-width: none; }
.mainnav::-webkit-scrollbar { display: none; }
/* the bell + avatar + New task never give up their space to the scrolling nav */
.rightnav { flex: none; }
/* A nav label is a label: it never splits across lines. Widening .wrap gives it the room,
   and nowrap makes sure a future item can only ever scroll, never wrap mid-word. */
.navlink { padding: 6px 12px; border-radius: 8px; color: var(--ink); white-space: nowrap; flex: none; }
.navlink:hover { background: #eef6fb; }
.navlink.on { background: var(--navy); color: #fff; }
.top .navlink { color: rgba(255, 255, 255, .82); }
.top .navlink:hover { background: rgba(255, 255, 255, .13); color: #fff; }
/* cyan pill, navy label — 7.5:1, and the one place cyan gets to shout */
.top .navlink.on { background: var(--cyan); color: var(--navy); font-weight: 500; }
.top .navlink.rq { color: #cbb9ff; }
.top .navlink.rq.on { color: var(--navy); }

.rightnav { display: flex; align-items: center; gap: 10px; }
.bell { position: relative; font-size: 19px; text-decoration: none; }
.badge {
  position: absolute; top: -6px; right: -10px; background: var(--cyan); color: var(--navy);
  font-size: 10.5px; font-weight: 700; border-radius: 999px; padding: 1px 5px;
  min-width: 17px; text-align: center;
}
.avatar, .mini-avatar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border-radius: 50%; background: var(--navy); color: #fff;
  font-size: 12.5px; flex: none;
}
.mini-avatar { width: 26px; height: 26px; font-size: 11px; }
.mini-avatar.none { background: #c3d2dc; color: var(--navy); }
/* the avatar carries an inline user colour — a hairline ring keeps it off the navy band */
.rightnav .avatar { box-shadow: 0 0 0 2px rgba(255, 255, 255, .38); }
.top .userdd > summary:focus-visible, .top .belldd > summary:focus-visible { outline: 2px solid var(--cyan); outline-offset: 3px; }

/* ---- buttons / forms ---- */
.btn {
  display: inline-block; border: 1px solid var(--line); background: #fff; color: var(--ink);
  padding: 7px 14px; border-radius: 9px; cursor: pointer; font: inherit; font-size: 14px;
}
.btn:hover { border-color: var(--navy); color: var(--navy-deep); }
.btn.primary { background: var(--navy); border-color: var(--navy); color: #fff; }
.btn.primary:hover { background: var(--navy-deep); border-color: var(--navy-deep); color: #fff; }
/* cyan fill + navy label (7.5:1). White on cyan is 2.1:1 and was unreadable. */
.btn.new { background: var(--cyan); border-color: var(--cyan); color: var(--navy); font-weight: 500; }
.btn.new:hover { background: var(--cyan-soft); border-color: var(--cyan-soft); color: var(--navy); }
.btn.small { padding: 4px 10px; font-size: 13px; }
.btn.ghost { background: transparent; }
.btn.danger { color: var(--red-ink); border-color: #f2c9c9; }
.btn.danger:hover { border-color: var(--red-ink); color: var(--red-ink); }
.btn.wide { width: 100%; }
.btn:focus-visible { outline: 2px solid var(--cyan); outline-offset: 2px; }
.linkish { background: none; border: 0; color: var(--navy-deep); cursor: pointer; font: inherit; font-size: 12.5px; text-decoration: underline; padding: 0; }
input, select, textarea {
  font: inherit; color: var(--ink); background: #fff;
  border: 1px solid var(--line); border-radius: 9px; padding: 7px 10px; max-width: 100%;
}
input:focus, select:focus, textarea:focus { outline: 2px solid rgba(58, 195, 233, .55); border-color: var(--navy); }
label { display: flex; flex-direction: column; gap: 4px; font-size: 13.5px; }
label small { color: var(--muted); }
/* A caption and its qualifier on ONE line. `label` is a column flex container, so a bare
   <small> next to the caption text became a second flex item and dropped to its own line —
   that is why "Value" sat above "(GBP)". .lcap makes the pair a single flex item, and the
   <small> is ordinary inline content inside it again. */
.lcap { display: block; }
.lcap small { margin-left: 4px; font-weight: 400; }

/* ---- Customers: one-click pipeline-stage chips ----
   Built from the live pipeline mirror, so a stage renamed in SomsConnect just appears. */
.qfbar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  margin: 0 0 12px; padding: 10px 12px;
  background: #fff; border: 1px solid var(--line); border-radius: 12px;
}
.qflab {
  font-size: 11px; letter-spacing: 1.4px; text-transform: uppercase;
  color: var(--muted); font-weight: 700; margin-right: 4px;
}
.qfchip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 10px; border-radius: 999px; white-space: nowrap;
  border: 1px solid var(--line); background: #f7f9fc;
  color: var(--navy); font-size: 12.5px; line-height: 1.2;
}
.qfchip:hover { border-color: var(--cyan); background: #eef9fd; }
/* cyan fill, navy label — the same "one place cyan gets to shout" as the active nav pill */
.qfchip.on { background: var(--cyan); border-color: var(--cyan); color: var(--navy); font-weight: 600; }
.qfn {
  min-width: 18px; padding: 0 5px; border-radius: 999px; text-align: center;
  background: rgba(31, 35, 60, .08); font-size: 11px; font-weight: 600;
}
.qfchip.on .qfn { background: rgba(31, 35, 60, .18); }
/* Dead ends (Job Lost, Abandoned…) stay reachable but stop competing for attention.
   Quietened with COLOUR, never `opacity` — opacity fades the label and its count together
   and dragged them to 3.9:1 / 3.7:1, i.e. unreadable in daylight. Muted navy on the same
   wash is 5.2:1 (label) and 6.2:1 (count): still visibly the back of the queue, still legible. */
.qfchip.quiet { color: var(--muted); border-color: #e7ebf2; background: #fafbfd; }
.qfchip.quiet .qfn { background: rgba(31, 35, 60, .07); color: #4a5563; }
.qfchip.quiet:hover, .qfchip.quiet.on { color: var(--navy); }
.qfchip.quiet:hover .qfn, .qfchip.quiet.on .qfn { color: var(--navy); }
@media (max-width: 720px) {
  .qfbar { gap: 5px; padding: 9px 10px; }
  .qfchip { font-size: 12px; padding: 6px 10px; min-height: 32px; }
}
/* Thin phones: a stage name straight out of SomsConnect can be longer than the screen
   ("3. Final Follow Up For Details Needed For Design"). nowrap + `body{overflow-x:hidden}`
   meant the tail and the count badge were sliced off with no way to scroll to them, so the
   pill wraps here instead. Nothing is ever cut off; it just gets taller. */
@media (max-width: 430px) {
  .qfchip { white-space: normal; max-width: 100%; border-radius: 14px; text-align: left; }
  .qfchip .qfn { flex: none; }
}

/* ---- cards / layout ---- */
.card { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); padding: 18px; margin: 14px 0; }
.card.slim { padding: 10px 18px; }
.pagehead { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin: 22px 0 10px; justify-content: space-between; }
.filters { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.filters select, .filters input { padding: 5px 8px; font-size: 13px; }
.inline { display: inline; }
.flash { margin: 14px 0 0; padding: 10px 14px; border-radius: 9px; background: #e3f6ec; border: 1px solid #bce6d0; color: #14683e; }
.flash.err { background: #fdeaea; border-color: #f2c9c9; color: #8f1f1f; }
.empty { text-align: center; color: var(--muted); padding: 40px 0; }
.empty.small { padding: 12px 0; font-size: 13px; }

/* ---- quadrant groups ---- */
.qgroup { margin: 18px 0 22px; }
.qgroup h2 { display: flex; align-items: baseline; gap: 8px; padding-left: 10px; border-left: 4px solid var(--muted); }
.qgroup.q1 h2 { border-color: var(--red); }
.qgroup.q2 h2 { border-color: var(--navy); }
.qgroup.q3 h2 { border-color: var(--amber); }
.qgroup.q4 h2 { border-color: var(--slate-soft); }

/* ---- task rows ---- */
.task {
  display: flex; align-items: center; gap: 10px; background: #fff;
  border: 1px solid var(--line); border-radius: 10px; padding: 8px 12px; margin: 6px 0;
  box-shadow: var(--shadow);
}
.task.isdone .tasktitle { text-decoration: line-through; color: var(--muted); }
.tasklink { flex: 1; display: flex; flex-wrap: wrap; align-items: center; gap: 6px; color: var(--ink); min-width: 0; }
.tasktitle { font-size: 14.5px; margin-right: 4px; }
.taskmeta { display: flex; align-items: center; gap: 8px; flex: none; }
.tick { flex: none; display: flex; }
.tickbox {
  width: 21px; height: 21px; border-radius: 6px; border: 2px solid #b8ccd9; background: #fff;
  cursor: pointer; color: #fff; font-size: 13px; line-height: 1; padding: 0;
}
.tickbox:hover { border-color: var(--green); }
.tickbox.on { background: var(--green); border-color: var(--green); }
.chip {
  font-size: 11px; padding: 2px 8px; border-radius: 999px; background: #eef4f8; color: var(--muted);
  white-space: nowrap;
}
/* A chip is nowrap, so a long customer name ("The Old Rectory Care Home (Ashbourne)
   Limited") ran past the task card and off a 320px screen, where it was sliced mid-word.
   Inside a task row the chips are flex items, so a max-width + ellipsis cuts it cleanly
   instead — the full name is still on the task page. */
.tasklink .chip { max-width: 100%; overflow: hidden; text-overflow: ellipsis; }
/* .customer is the Spectrum name; .client stays so the rename can land in the markup
   independently without the chip losing its styling mid-flight. */
.chip.client, .chip.customer { background: var(--cyan-wash); color: var(--navy-deep); }
.chip.tag { background: #eef2f7; color: var(--navy-deep); }
.chip.blocked { background: #fdeaea; color: var(--red-ink); }
.chip.progress { background: #fff3e0; color: var(--amber-ink); }
.chip.quad.q1 { background: var(--red-ink); color: #fff; }
.chip.quad.q2 { background: var(--navy); color: #fff; }
.chip.quad.q3 { background: var(--amber-ink); color: #fff; }
.chip.quad.q4 { background: var(--slate); color: #fff; }
.due { font-size: 12px; color: var(--muted); white-space: nowrap; }
.due.overdue { color: #fff; background: var(--red-ink); padding: 2px 8px; border-radius: 999px; }
.due.today { color: #fff; background: var(--amber-ink); padding: 2px 8px; border-radius: 999px; }
.due.soon { color: var(--amber-ink); }

/* ---- matrix ---- */
.axis-top { display: grid; grid-template-columns: 24px 1fr 1fr; gap: 12px; text-align: center; color: var(--muted); font-size: 12.5px; margin-top: 8px; }
.matrix-wrap { display: grid; grid-template-columns: 24px 1fr; gap: 0 12px; }
.axis-left { display: grid; grid-template-rows: 1fr 1fr; }
.axis-left span { writing-mode: vertical-rl; transform: rotate(180deg); text-align: center; color: var(--muted); font-size: 12.5px; }
.matrix { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.cell { border-radius: var(--radius); padding: 12px; border: 1px solid var(--line); background: #fff; min-height: 130px; }
.cell h3 { margin: 0 0 8px; font-size: 14px; }
.cell.q1 { border-top: 4px solid var(--red); }
.cell.q2 { border-top: 4px solid var(--navy); }
.cell.q3 { border-top: 4px solid var(--amber); }
.cell.q4 { border-top: 4px solid var(--slate-soft); }
.cell .task { padding: 6px 9px; }

/* ---- task page ---- */
.taskhead { display: flex; gap: 16px; justify-content: space-between; align-items: flex-start; margin-top: 22px; flex-wrap: wrap; }
.taskbtns { display: flex; gap: 8px; flex-wrap: wrap; }
.chiprow { display: flex; gap: 6px; flex-wrap: wrap; margin: 6px 0; }
.banner.blocked { background: #fdeaea; border: 1px solid #f2c9c9; color: #8f1f1f; padding: 10px 14px; border-radius: 9px; margin: 12px 0; }
.desc { margin: 0; white-space: pre-wrap; }
.comment { display: flex; gap: 10px; padding: 10px 0; border-top: 1px solid var(--line); }
.comment:first-of-type { border-top: 0; }
.comment p { margin: 3px 0 0; }
/* @mentions sit in body copy — cyan would be 2.1:1, so navy carries it and cyan underlines it */
.mention { color: var(--navy-deep); font-weight: 600; box-shadow: inset 0 -2px 0 var(--cyan); }
.commentform { display: flex; gap: 8px; margin-top: 10px; align-items: flex-start; }
.commentform textarea { flex: 1; }
.actline { padding: 3px 0; }
.dayhead { margin: 20px 0 6px; font-size: 14px; color: var(--muted); }

/* ---- forms ---- */
.form-card { max-width: 720px; margin: 24px auto; }
.taskform { display: flex; flex-direction: column; gap: 12px; }
/* minmax(0, 1fr), never a bare 1fr: a plain 1fr track has an automatic minimum of the
   content's min-content width, so one <select> with a long option ("Ready To Book For
   Installation") grew the whole track to 320px inside a 246px card and pushed the Value
   input and Status select off the right of a 320px phone, where body{overflow-x:hidden}
   then clipped them. minmax(0, …) lets the track be as narrow as the card. */
.grid2 { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 12px; }
.prio { border: 1px solid var(--line); border-radius: 9px; padding: 10px 14px; display: flex; gap: 26px; }
.prio legend { font-size: 13px; color: var(--muted); padding: 0 6px; }
.check { flex-direction: row; align-items: center; gap: 8px; font-size: 14px; }
.formfoot { display: flex; gap: 10px; }
.inlineform { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }

/* ---- tables / admin ---- */
.table { width: 100%; border-collapse: collapse; font-size: 14px; }
.table th { text-align: left; color: var(--muted); font-size: 12px; font-weight: 400; padding: 4px 8px; border-bottom: 1px solid var(--line); }
.table td { padding: 7px 8px; border-bottom: 1px solid var(--line); }
.rowbtns { display: flex; gap: 6px; flex-wrap: wrap; }
/* .customergrid/.customerrow are the Spectrum names; the .client* pair is kept so the
   markup rename can land on its own without the layout collapsing in between. */
.clientgrid, .customergrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 4px 16px; margin-top: 8px; }
.clientrow, .customerrow { display: flex; justify-content: space-between; gap: 8px; padding: 3px 0; font-size: 13.5px; border-bottom: 1px dashed var(--line); }
.clientrow.arch span, .customerrow.arch span { color: var(--muted); text-decoration: line-through; }

/* ---- notifications ---- */
.notif { display: block; background: #fff; border: 1px solid var(--line); border-radius: 9px; padding: 9px 13px; margin: 6px 0; color: var(--ink); }
.notif.unread { border-left: 4px solid var(--cyan); background: var(--cyan-wash); }

/* ---- login: the second Spectrum band ---- */
.login-bg {
  min-height: 100vh; display: flex; align-items: center; justify-content: center;
  padding: 24px 16px; background: var(--band); position: relative;
}
/* one soft cyan bloom so the band is Spectrum rather than just "a dark blue page" */
.login-bg::before {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(60% 55% at 82% 14%, rgba(58, 195, 233, .30), transparent 68%);
}
.login-card {
  background: #fff; border-radius: 16px; padding: 34px 36px; width: 340px; max-width: 100%;
  box-shadow: 0 10px 40px rgba(0, 0, 0, .25); border-top: 4px solid var(--cyan);
  position: relative; z-index: 1;
}
.login-card form { display: flex; flex-direction: column; gap: 12px; margin-top: 16px; }
.login-card .muted { margin: 4px 0 0; }

@media (max-width: 720px) {
  .grid2 { grid-template-columns: minmax(0, 1fr); }
  .matrix { grid-template-columns: minmax(0, 1fr); }
  .axis-top, .axis-left { display: none; }
  .matrix-wrap { grid-template-columns: minmax(0, 1fr); }
  .bar { gap: 8px; }
}

/* Tablets get the desktop table, so a wide table has to scroll inside itself here too —
   the Customers table needs 826px and was stretching the whole page at 768 and 834.
   1023 rather than 720: above that every table we ship fits the card unaided. */
@media (min-width: 721px) and (max-width: 1023px) {
  .table { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }
}

/* ---- quadrant dropdown ---- */
.quadselect { font-weight: 600; border-width: 2px; }
.quadselect.q1 { border-color: var(--red); color: var(--red-ink); background: #fdeaea; }
.quadselect.q2 { border-color: var(--navy); color: var(--navy-deep); background: var(--cyan-wash); }
.quadselect.q3 { border-color: var(--amber); color: var(--amber-ink); background: #fff9ef; }
.quadselect.q4 { border-color: var(--slate-soft); color: var(--muted); background: #f4f7f9; }

/* ---- stat tiles (admin dashboard, SOMS sync counts) ---- */
.table.slim td, .table.slim th { padding: 4px 8px; }
.statrow { display: flex; gap: 12px; flex-wrap: wrap; }
.stat { background: var(--cyan-wash); border: 1px solid var(--line); border-radius: 10px; padding: 10px 16px; display: flex; flex-direction: column; min-width: 110px; }
.stat strong { font-size: 20px; color: var(--navy); }
.stat span { font-size: 12.5px; color: var(--muted); }

/* ---- inline row priority ---- */
.rowquad { font-size: 11.5px; padding: 2px 4px; border-radius: 999px; border: 1px solid var(--line); background: #fff; color: var(--muted); max-width: 96px; cursor: pointer; }
.rowquad.q1 { border-color: var(--red); color: var(--red-ink); background: #fdeaea; font-weight: 600; }
.rowquad.q2 { border-color: var(--navy); color: var(--navy-deep); background: var(--cyan-wash); }
.rowquad.q3 { border-color: var(--amber); color: var(--amber-ink); background: #fff9ef; }
.rowquad.q4 { color: var(--muted); }

/* ---- sticky save bar on the task form ---- */
.formfoot { position: sticky; bottom: 0; background: var(--card); padding: 12px 0; margin: 0 -4px; border-top: 1px solid var(--line); z-index: 5; }
.formfoot .btn.primary { flex: 1; text-align: center; font-size: 16px; padding: 12px; }
/* hover stays navy — the white label needs it (cyan would drop this to 2.1:1) */
.formfoot .btn.primary:hover { background: var(--navy-deep); border-color: var(--navy-deep); box-shadow: inset 0 -3px 0 var(--cyan); }
@media (max-width: 720px) { .taskmeta .rowquad { max-width: 70px; } }

/* ---- dependency controls ---- */
.depbox { border: 1px solid var(--line); border-radius: 9px; padding: 10px 14px; }
.depbox legend { font-size: 13px; color: var(--muted); padding: 0 6px; }
.deplist { max-height: 190px; overflow-y: auto; display: flex; flex-direction: column; gap: 2px; }
.depitem { flex-direction: row; align-items: center; gap: 8px; font-size: 13.5px; padding: 4px 2px; border-radius: 6px; cursor: pointer; }
.depitem:hover { background: #f0f7fb; }
.depitem em { color: var(--muted); font-style: normal; font-size: 12px; }
.deprow { display: flex; align-items: center; gap: 8px; }
.deprow .task { flex: 1; }

/* ---- review + attachments ---- */
.chip.review { background: #f1e6ff; color: #7a2ee8; }
.chip.review.big { font-size: 13px; padding: 6px 12px; align-self: center; }
.btn.review { background: #7a2ee8; border-color: #7a2ee8; color: #fff; }
.btn.review:hover { filter: brightness(.92); color: #fff; }
.attgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px; margin-bottom: 12px; }
.att { margin: 0; }
.att img, .att video { width: 100%; max-height: 180px; object-fit: cover; border-radius: 9px; border: 1px solid var(--line); background: #000; }
.att img { background: #f4f7f9; }
.attdoc { display: block; padding: 24px 12px; text-align: center; border: 1px dashed var(--line); border-radius: 9px; }
.att figcaption { margin-top: 3px; }

/* ---- snooze ---- */
.snz { font-size: 12px; padding: 2px 4px; border-radius: 999px; border: 1px solid var(--line); color: var(--muted); background: #fff; max-width: 52px; cursor: pointer; }
.snz.wide { max-width: 140px; padding: 6px 8px; font-size: 13px; }
.snoozeform { display: flex; gap: 6px; align-items: center; }

/* ---- bell dropdown ---- */
.belldd { position: relative; }
.belldd summary { list-style: none; cursor: pointer; }
.belldd summary::-webkit-details-marker { display: none; }
.ddpanel {
  position: absolute; right: -40px; top: 34px; width: 340px; max-width: 90vw; z-index: 100;
  background: #fff; border: 1px solid var(--line); border-radius: 12px; box-shadow: 0 8px 30px rgba(23,39,51,.18);
  padding: 6px; display: flex; flex-direction: column; gap: 2px;
}
.ddnotif { display: block; padding: 8px 10px; border-radius: 8px; color: var(--ink); font-size: 13px; }
.ddnotif:hover { background: #f0f7fb; }
.ddnotif.unread { border-left: 3px solid var(--cyan); background: var(--cyan-wash); }
.ddfoot { display: flex; justify-content: space-between; padding: 8px 10px 4px; border-top: 1px solid var(--line); margin-top: 4px; }

/* ---- Spectrum Energy Systems brand logo ---- */
.brand { display: flex; align-items: center; gap: 8px; }
.brandlogo { height: 34px; width: auto; display: block; }
.brandlogo.login { height: 52px; margin: 0 auto; }
.brandtasks { font-size: 15px; color: var(--navy); letter-spacing: 2.5px; text-transform: uppercase; margin-top: 6px; }
.brandtasks.big { text-align: center; font-size: 18px; margin-top: 8px; }
.login-card .brand.big { flex-direction: column; }

/* ---- hero band: the reusable Spectrum band for section headers (Customers, Jobs, …) ---- */
.heroband {
  background: var(--band); border-bottom: 4px solid var(--cyan); color: #fff;
  border-radius: var(--radius) var(--radius) 0 0; padding: 18px 20px 16px; margin: 22px 0 0;
}
.heroband h1, .heroband h2 { color: #fff; margin: 0; }
.heroband p, .heroband .muted { color: rgba(255, 255, 255, .78); margin: 4px 0 0; }
.heroband + .card { border-radius: 0 0 var(--radius) var(--radius); margin-top: 0; border-top: 0; }

/* ==================== V2: quick add, menu, repeats ==================== */
.quickadd { display: flex; flex-direction: column; gap: 8px; background: #fff; border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); padding: 12px 14px; margin: 4px 0 14px; }
.qarow1 { display: flex; gap: 8px; align-items: stretch; }
.fieldwrap { position: relative; display: flex; }
.fieldwrap.grow { flex: 1; }
.fieldwrap > input, .fieldwrap > textarea { width: 100%; padding-right: 44px; }
.fieldwrap .micbtn { position: absolute; right: 6px; top: 50%; transform: translateY(-50%); margin: 0; padding: 4px 7px; }
.fieldwrap:has(textarea) .micbtn { top: 10px; transform: none; }
.fieldwrap .micbtn span { display: none; }
  /* Dictation is the whole point of the Site Log's long fields — an installer in
     gloves talks rather than types — but icon-only left it a 31x27 target. The field
     already reserves a 44px gutter on the right, so fill it. Scoped to .slform so
     the denser task forms are untouched. */
  .slform .fieldwrap .micbtn { min-width: 44px; min-height: 44px; padding: 0; right: 2px; justify-content: center; }
  .slform .fieldwrap:has(textarea) .micbtn { top: 4px; }
.qatitle { font-size: 15px; }
.qaadd { flex: none; font-size: 15px; padding: 9px 18px; }
.qahint code { font-size: 11.5px; }
.qaopts { display: flex; gap: 12px; flex-wrap: wrap; align-items: flex-end; border-top: 1px dashed var(--line); padding-top: 10px; }
.qaopts select { font-size: 12.5px; padding: 5px 6px; max-width: 170px; }
.qfield { display: flex; flex-direction: column; gap: 3px; }
/* 11.5px, not 10.5px: these are the captions that tell you what each Add-a-task control is
   ("Assign to", "Customer", "Due"). Uppercase grey at 10.5px is the smallest type in the app
   and the first thing to disappear in daylight. */
.qfield > span { font-size: 11.5px; color: var(--muted); text-transform: uppercase; letter-spacing: .6px; }
.menucard { display: flex; flex-direction: column; padding: 6px; }
.menuitem { display: flex; flex-direction: column; gap: 1px; padding: 14px 14px; border-bottom: 1px solid var(--line); color: var(--ink); font-size: 16px; border-radius: 8px; }
.menuitem:last-child { border-bottom: 0; }
.menuitem:hover { background: #f0f7fb; }
.linkish.big { font-size: 16px; text-decoration: none; color: var(--red-ink); text-align: left; }
.chip.warn { background: #fff3e0; color: var(--amber-ink); }
.chip.ok { background: #e3f6ec; color: #14683e; }
.chip.paused { background: #eef4f8; color: var(--muted); }
.repeatrow .tasklink { flex-direction: column; align-items: flex-start; gap: 2px; }
.backlink { display: none; }
#toast[hidden] { display: none; }
#toast { position: fixed; left: 50%; transform: translateX(-50%); bottom: 24px; background: var(--navy); color: #fff; border-radius: 999px; padding: 10px 18px; font-size: 14px; z-index: 300; display: flex; gap: 14px; align-items: center; box-shadow: 0 6px 24px rgba(31, 35, 60, .38); }
/* cyan finally gets to be text — on navy it is 7.5:1 */
#toast button { background: none; border: 0; color: var(--cyan); font: inherit; cursor: pointer; text-decoration: underline; }
.filterwrap > summary { display: none; }

/* ==================== V2: mobile app shell ==================== */
.tabbar { display: none; }
@media (max-width: 720px) {
  :root { --tabbar-h: calc(58px + env(safe-area-inset-bottom)); }
  body.has-tabbar { padding-bottom: var(--tabbar-h); }
  .tabbar {
    display: flex; position: fixed; bottom: 0; left: 0; right: 0; z-index: 200;
    background: #fff; border-top: 1px solid var(--line);
    padding-bottom: env(safe-area-inset-bottom); height: var(--tabbar-h);
  }
  /* 11.5px: this is the whole navigation on a phone. 10.5px grey was the smallest text on
     the busiest control in the app; the five labels still fit comfortably at 320px. */
  .tabitem { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; color: var(--muted); font-size: 11.5px; position: relative; padding-top: 6px; min-height: 44px; }
  .tabitem svg { width: 24px; height: 24px; }
  .tabitem.on { color: var(--navy); font-weight: 500; }
  /* the active tab gets a cyan tick above it — the label stays navy so it stays readable */
  .tabitem.on::before { content: ''; position: absolute; top: 0; left: 50%; transform: translateX(-50%); width: 26px; height: 3px; border-radius: 0 0 3px 3px; background: var(--cyan); }
  .tab-new .plus { display: flex; align-items: center; justify-content: center; width: 46px; height: 46px; border-radius: 50%; background: var(--cyan); color: var(--navy); margin-top: -22px; box-shadow: 0 4px 12px rgba(31, 35, 60, .28); }
  .tab-new .plus svg { width: 24px; height: 24px; }
  .tab-new.tabitem.on::before { display: none; }
  .tbadge { position: absolute; top: 2px; right: 50%; margin-right: -20px; background: var(--cyan); color: var(--navy); font-weight: 700; font-size: 9.5px; border-radius: 999px; padding: 1px 5px; min-width: 15px; text-align: center; }
  body:has(.qatitle:focus) .tabbar { display: none; }

  /* hide desktop chrome */
  .mainnav, .rightnav .btn.new, .rightnav .belldd { display: none; }
  .bar { justify-content: space-between; }

  /* back link on detail/form pages (standalone PWAs lose browser chrome) */
  .backlink { display: inline-block; margin: 10px 0 -6px; font-size: 15px; color: var(--navy-deep); }

  /* inputs: 16px kills iOS focus zoom; bigger touch targets */
  input, select, textarea, .qatitle { font-size: 16px !important; }
  .btn { padding: 10px 16px; }
  .btn.small { padding: 8px 12px; font-size: 14px; }
  .tickbox { width: 26px; height: 26px; }
  .navlink, .menuitem { min-height: 44px; }

  /* task rows restack: tick | title / meta */
  .task { display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto; gap: 4px 10px; padding: 10px 12px; }
  .task .tick { grid-row: 1 / span 2; align-self: center; }
  .tasklink { grid-column: 2; }
  .tasktitle { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
  .taskmeta { grid-column: 2; justify-content: flex-start; flex-wrap: wrap; }
  .taskmeta .mini-avatar { display: inline-flex; width: 22px; height: 22px; }
  .chip.tag { display: none; }

  /* collapsible filters */
  .filterwrap > summary { display: flex; align-items: center; gap: 6px; cursor: pointer; list-style: none; font-size: 14px; color: var(--navy-deep); padding: 6px 0; }
  .filterwrap > summary::-webkit-details-marker { display: none; }
  .filterwrap:not([open]) .filters { display: none; }
  .filters { flex-direction: column; align-items: stretch; }
  .filters select, .filters input { width: 100%; }

  /* quick add opts wrap; sticky save clears tab bar */
  .qaopts select { flex: 1 1 40%; max-width: none; }
  .formfoot { bottom: var(--tabbar-h); }
  .pagehead { margin-top: 14px; }
  h1 { font-size: 22px; }
  .ddpanel { right: -80px; }
  .statrow { gap: 8px; }
  .attgrid { grid-template-columns: 1fr 1fr; }
  .snoozeform input[type="date"] { max-width: 130px; }
  #toast { bottom: calc(var(--tabbar-h) + 12px); }
}

/* ---- super admin badge ---- */
.chip.super { background: var(--navy); color: #ffd76d; font-size: 10.5px; vertical-align: middle; }

/* ---- searchable customer combobox ---- */
.combo { position: relative; width: 100%; min-width: 170px; }
.combo-input { width: 100%; padding-right: 40px; }
/* Clear (x) — sits in the input's own right-hand gutter, so it costs no layout width.
   Full 40x44 hit area on touch (the field is 44px tall there anyway) with the glyph
   staying small, so it never becomes the loudest thing in the row. */
.comboclear {
  position: absolute; top: 50%; right: 2px; transform: translateY(-50%);
  width: 36px; height: 30px; display: flex; align-items: center; justify-content: center;
  border: 0; background: none; color: var(--muted); font-size: 20px; line-height: 1;
  cursor: pointer; border-radius: 8px; padding: 0;
}
@media (max-width: 1024px) { .comboclear { width: 40px; height: 44px; } }
.comboclear:hover, .comboclear:focus-visible { background: var(--cyan-wash); color: var(--navy-deep); }
.comboclear[hidden] { display: none; }
.combo-list {
  position: absolute; top: calc(100% + 4px); left: 0; right: 0; z-index: 150;
  background: #fff; border: 1px solid var(--line); border-radius: 10px;
  box-shadow: 0 8px 30px rgba(23,39,51,.18); max-height: 240px; overflow-y: auto; padding: 4px;
}
.combo-list[hidden] { display: none; }
.combo-item { padding: 8px 10px; border-radius: 7px; cursor: pointer; font-size: 14px; }
.combo-item:hover, .combo-item.hl { background: var(--cyan-wash); }
.combo-group { padding: 6px 10px 2px; font-size: 11px; text-transform: uppercase; letter-spacing: .6px; color: var(--muted); }
.taskform .combo { max-width: none; }

/* ---- quick add details ---- */
.qamore { display: flex; flex-direction: column; gap: 8px; border-top: 1px dashed var(--line); padding-top: 8px; }
.qamore[hidden] { display: none; }
.qamore textarea { width: 100%; }
.qafile { font-size: 12.5px; color: var(--muted); flex-direction: row; align-items: center; gap: 8px; flex-wrap: wrap; }
@media (max-width: 720px) { .combo { max-width: none; } }

/* ---- named reviewer ---- */
.reviewform { display: flex; gap: 6px; align-items: center; }
.reviewform select { font-size: 13px; padding: 6px 8px; }

/* ---- team tasks ---- */
.chip.team { background: #e8e9ff; color: #4249c4; }
.banner.team { background: #eef0ff; border: 1px solid #ccd0f5; color: #333a8f; padding: 10px 14px; border-radius: 9px; margin: 12px 0; }
.teambox { border: 1px dashed #ccd0f5; border-radius: 9px; padding: 10px 14px; background: #fbfbff; }
.teambox legend { font-size: 13px; color: #4249c4; padding: 0 6px; }
.teamrow { display: flex; gap: 8px; margin: 6px 0; }
.teamrow select { min-width: 130px; }
.teamrow input { flex: 1; }
select.qaprio { max-width: none !important; width: auto; }
#qateamhint { padding: 2px 4px 0; color: #4249c4; }

/* ---- quick due presets ---- */
.duequick { display: flex; gap: 3px; align-items: center; flex-wrap: wrap; }
.duebtn { font-size: 11.5px; padding: 4px 8px; border-radius: 999px; border: 1px solid var(--line); background: #fff; color: var(--muted); cursor: pointer; font-family: inherit; }
.duebtn:hover { border-color: var(--navy); color: var(--navy-deep); }
.duebtn.on { background: var(--navy); border-color: var(--navy); color: #fff; }
.duequick input[type="date"] { padding: 4px 6px; font-size: 12.5px; max-width: 150px; }
@media (max-width: 720px) { .duequick { flex-basis: 100%; } }

/* ---- voice dictation ---- */
.micbtn {
  align-self: flex-start; margin-top: 3px; font: inherit; font-size: 12px;
  border: 1px solid var(--line); background: #fff; color: var(--muted);
  border-radius: 999px; padding: 3px 10px; cursor: pointer; display: inline-flex; gap: 5px; align-items: center;
}
.micbtn:hover { border-color: var(--navy); color: var(--navy-deep); }
.micbtn.rec { background: var(--red); border-color: var(--red); color: #fff; animation: micpulse 1.2s infinite; }
@keyframes micpulse { 50% { box-shadow: 0 0 0 6px rgba(226, 58, 58, .18); } }
.quickadd .micbtn { align-self: flex-start; }

/* combo inside a column qfield: flex-basis would become HEIGHT — neutralise it */
.qfield .combo { width: 210px; max-width: 100%; }

/* ---- single/team mode tabs ---- */
.modetabs { display: flex; gap: 8px; margin: -4px 0 14px; }
.modetab { flex: 1; padding: 11px; font: inherit; font-size: 15px; border: 2px solid var(--line); background: #fff; border-radius: 10px; cursor: pointer; color: var(--muted); }
.modetab.on { border-color: var(--navy); color: var(--navy-deep); background: #f0faff; font-weight: 600; }
.team-only { display: none; }
.teamrow .check { min-width: 110px; }

/* ---- tidy + rich descriptions ---- */
.tidybtn { color: #7a2ee8; border-color: #d9c6f7; }
.fieldwrap .tidybtn { right: 40px; }
.fieldwrap .tidybtn span { display: none; }
.dhead { font-weight: 600; font-size: 16px; display: inline-block; margin-top: 6px; }
.dbullet { display: inline-block; padding-left: 10px; }

/* ==================== form v3: chips, prio buttons, dep modal ==================== */
.memchips { display: flex; gap: 8px; flex-wrap: wrap; }
.memchip { font: inherit; font-size: 14.5px; padding: 10px 18px; border-radius: 999px; border: 2px solid var(--line); background: #fff; color: var(--ink); cursor: pointer; }
.memchip:hover { border-color: var(--mc); }
.memchip.on { background: var(--mc); border-color: var(--mc); color: var(--mcfg, #fff); font-weight: 600; }
.teamkind { display: flex; gap: 8px; }
.kindbtn { flex: 1; font: inherit; font-size: 13.5px; padding: 9px 12px; border-radius: 9px; border: 2px solid var(--line); background: #fff; color: var(--muted); cursor: pointer; }
.kindbtn.on { border-color: #4249c4; color: #4249c4; background: #eef0ff; font-weight: 600; }
.teambox { display: flex; flex-direction: column; gap: 12px; }
/* .team-only (display:none, further up) and .teambox have the SAME specificity, so the
   later .teambox rule was winning and the team picker rendered on every fresh ?v=new load
   even though the form opens in Single-task mode — pushing "1 Task title" 215px (375px
   phone) / 287px (320px phone) down the page, below the fold. This more specific rule
   restores the intended default; the mode tabs set an INLINE display, which still wins. */
.teambox.team-only { display: none; }
.priogroup { display: flex; gap: 6px; flex-wrap: wrap; }
.priobtn { font: inherit; font-size: 13.5px; padding: 9px 16px; border-radius: 999px; border: 2px solid var(--line); background: #fff; color: var(--muted); cursor: pointer; }
.priobtn.q1.on { background: var(--red-ink); border-color: var(--red-ink); color: #fff; font-weight: 600; }
.priobtn.q2.on { background: var(--navy); border-color: var(--navy); color: #fff; font-weight: 600; }
.priobtn.q3.on { background: var(--amber-ink); border-color: var(--amber-ink); color: #fff; font-weight: 600; }
.priobtn.q4.on { background: var(--slate); border-color: var(--slate); color: #fff; font-weight: 600; }
.priobtn.q1:hover { border-color: var(--red); } .priobtn.q2:hover { border-color: var(--navy); }
.priobtn.q3:hover { border-color: var(--amber); } .priobtn.q4:hover { border-color: var(--slate-soft); }
.qflabel { display: flex; flex-direction: column; gap: 6px; font-size: 13.5px; }
.biginput { font-size: 16px; padding: 11px 44px 11px 12px; }
.depqueue { display: flex; gap: 6px; flex-wrap: wrap; }
.depqueue:empty { display: none; }
.depchip { cursor: pointer; }
.modal { position: fixed; inset: 0; background: rgba(23, 39, 51, .55); z-index: 400; display: flex; align-items: center; justify-content: center; padding: 16px; }
.modal[hidden] { display: none; }
.modal-card { background: #fff; border-radius: 14px; padding: 22px; width: 540px; max-width: 100%; max-height: 90vh; overflow-y: auto; display: flex; flex-direction: column; gap: 12px; box-shadow: 0 20px 60px rgba(0, 0, 0, .35); }
.modal-card h2 { margin: 0; }
.modalfoot { display: flex; gap: 10px; }
.modalfoot .btn.primary { flex: 1; }
.schedrow { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.schedrow select, .schedrow input { max-width: 190px; }
.schedrow input[type="number"] { width: 80px; }
.teamrow[hidden] { display: none; }

/* ---- feedback capture ---- */
.commentside { display: flex; flex-direction: column; gap: 6px; }
.fbcheck { font-size: 12px; color: var(--muted); max-width: 160px; }

/* ---- multi-part team rows ---- */
.partlist { flex: 1; display: flex; flex-direction: column; gap: 6px; }
.addpart { width: 34px; height: 34px; border-radius: 50%; border: 2px solid var(--line); background: #fff; color: var(--navy-deep); font-size: 17px; cursor: pointer; align-self: flex-start; line-height: 1; }
.addpart:hover { border-color: var(--navy); background: #f0faff; }
.teamrow { align-items: flex-start; }
.teamrow .mini-avatar { margin-top: 5px; }
.duetime { display: flex; gap: 6px; }
.duetime input[type="time"], .duequick input[type="time"] { max-width: 110px; padding: 4px 6px; font-size: 12.5px; }
.cfile { cursor: pointer; font-size: 18px; text-align: center; }
.cattrow { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 6px; }
.cattthumb { max-height: 90px; border-radius: 8px; border: 1px solid var(--line); }

/* ---- clearer quick-add option rows: top-aligned captions, uniform control heights ----
   The caption rule MUST stay scoped to :first-child — .duequick/.cbquick are also direct
   span children, and forcing them to 15px squashed the pill rows so they spilled out of
   the card over the content below (the mobile bug Mitch screenshotted, 30 Jul). */
.qaopts { align-items: flex-start !important; gap: 12px 18px; }
.qaopts .qfield > span:first-child { display: flex; align-items: center; min-height: 18px; line-height: 18px; }
.qaopts .qfield > .duequick, .qaopts .qfield > .cbquick { height: auto; line-height: normal; }
.qaopts select, .qaopts input, .qaopts .combo-input, .qaopts .duebtn { min-height: 34px; }
.qaopts .duebtn { min-height: 34px; padding-top: 0; padding-bottom: 0; }
.tsel { display: inline-flex; align-items: center; gap: 2px; }
.tsel select { min-height: 34px; padding: 4px 4px; font-size: 12.5px; }
.tcolon { color: var(--muted); }
.duetime { align-items: center; }

/* ---- bin + branded confirm ---- */
/* .35 left the bin all but invisible — a control you cannot see is a control you cannot use.
   .62 still reads as "secondary, not the main action" but is actually findable on a phone. */
.binbtn { background: none; border: 0; cursor: pointer; font-size: 15px; opacity: .62; padding: 2px 4px; }
.binbtn:hover, .binbtn:focus-visible { opacity: 1; }
.modal-card.slim { width: 420px; gap: 8px; }
#confirmmsg { margin: 0; color: var(--muted); }
.cbquick { display: flex; gap: 3px; flex-wrap: wrap; align-items: center; }
.cbquick input[type="date"] { padding: 4px 6px; font-size: 12.5px; max-width: 150px; min-height: 34px; }

/* ---- task rows: title on top (max 2 lines), controls underneath — all screen sizes ---- */
.task { display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto; gap: 5px 12px; align-items: start; }
.task .tick { grid-row: 1 / span 2; align-self: center; }
.tasklink { grid-column: 2; }
.tasktitle { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; font-size: 15px; }
.taskmeta { grid-column: 2; justify-content: flex-start; flex-wrap: wrap; row-gap: 6px; }
.cell .task { padding: 8px 10px; }
.remindrow { display: flex; gap: 10px; align-items: center; padding: 4px 0; font-size: 13.5px; }
hr.soft { border: 0; border-top: 1px solid var(--line); margin: 14px 0 10px; }

/* ---- numbered form steps (Mitch, 30 Jul: "number each thing so it's clear where to go first") ---- */
.stepn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; border-radius: 50%; background: var(--navy); color: #fff;
  font-size: 11px; font-style: normal; flex: none; margin-right: 6px;
}
.stepcap { display: flex; align-items: center; font-size: 12.5px; color: var(--muted); margin-bottom: -2px; }
.stepcap em { font-style: normal; margin-left: 4px; }
.lcap { display: flex; align-items: center; }
.qaopts .qfield > span:first-child .stepn, .qafile .stepn { width: 16px; height: 16px; font-size: 10px; }
.qafile .stepn { margin-right: 4px; }
.qflabel > span .stepn { vertical-align: -4px; }

/* iOS zooms the whole page in when you focus a field whose text is under 16px, and
   then the installer has to pinch back out to carry on. The mobile block below fixes
   that at <=720px — but every iPad is wider than 720 (768 portrait, 834 Air, 1024
   landscape) and is still a touch screen, so on a tablet every field still zoomed.
   Pin the size on any coarse-pointer device, whatever the width. Mice are unaffected,
   so desktop is untouched. */
@media (pointer: coarse) {
  input, textarea, select { font-size: 16px; }
}

/* ---- mobile clarity pass (thin 320px phones up to wide 430px phones) ---- */
@media (max-width: 720px) {
  /* Safety net: one overflowing element must never let the whole page pan sideways.
     `hidden` on html is what actually does the clamping — it propagates to the
     viewport. body deliberately uses `clip`, NOT `hidden`: `hidden` makes body a
     scroll container, and a scroll container between a sticky element and the
     viewport silently kills `position: sticky` on everything inside it. That is
     what stopped the Site Log's "Submit report" bar sticking on every phone, so an
     installer had to scroll the whole 4,000px form to find it. Browsers without
     `clip` just drop this line and html's rule still prevents the sideways pan. */
  html { overflow-x: hidden; }
  body { overflow-x: clip; }

  /* option fields stack full-width: one clear row per control, easy to read + tap.
     Scoped to DIRECT children of .qfield — the little hour/minute selects nested
     inside the Due row must keep their natural width or "Time" gets clipped. */
  .qaopts { flex-direction: column; align-items: stretch !important; gap: 14px; }
  .qaopts .qfield { width: 100%; }
  .qaopts .qfield > select, select.qaprio { width: 100% !important; max-width: none; }
  .tsel select { width: auto !important; min-width: max-content; padding: 4px 8px; }
  .duetime { flex-wrap: wrap; }
  .duetime input[type="date"] { flex: 1 1 100%; max-width: none; min-height: 44px; }
  .qfield .combo { width: 100%; }

  /* pills: bigger tap targets, roomier spacing */
  .duequick, .cbquick { gap: 6px; }
  .duequick .duebtn, .cbquick .duebtn { min-height: 38px; padding: 0 12px; }
  .duequick input[type="date"], .cbquick input[type="date"] { min-height: 38px; max-width: 210px; flex: 1 1 auto; }
  .tsel select { min-height: 38px; }

  /* row controls: stop "Do First" truncating to "Do Firs…" */
  .taskmeta .rowquad { max-width: 142px; }
  .snz { min-height: 30px; }
  .binbtn { padding: 6px 8px; }
}

/* ---- quick-add submit placement: top-right on desktop, BOTH on mobile ----
   The top button used to be hidden on phones, which left the bottom duplicate as the
   only way to submit — and the form is ~1,090px tall at 320px, so that button sat 692px
   below the fold (274px at 375). Typing a title and pressing Add, the whole point of a
   quick-add bar, meant scrolling past seven controls first. Both buttons now show on a
   phone: the compact one beside the title for "just add it", the full-width one at the
   end for when you have actually filled the options in. .qarow1 wraps so that at 320px
   the button drops onto its own line under the title rather than crushing the field. */
.qaadd-bottom { display: none; }
@media (max-width: 720px) {
  .qarow1 { flex-wrap: wrap; }
  .qarow1 .fieldwrap.grow { flex: 1 1 190px; }
  .qarow1 .qaadd { flex: 0 0 auto; padding: 9px 12px; font-size: 14.5px; }
  .qaadd-bottom { display: block; width: 100%; min-height: 48px; font-size: 16px; margin-top: 2px; }
}

/* ---- row avatar reassign button ---- */
.rowassign { background: none; border: 0; padding: 0; cursor: pointer; display: inline-flex; border-radius: 50%; }
.rowassign:hover .mini-avatar, .rowassign:focus-visible .mini-avatar { outline: 2px solid var(--navy); outline-offset: 1px; }

/* ---- thin-device sweep (Mitch, 30 Jul: "full check on each page, everything readable fully") ---- */
@media (max-width: 720px) {
  /* task page snooze row: "⏰ Come back…" was clipped by the 140px cap at the 16px mobile font */
  .snoozeform { flex-wrap: wrap; }
  .snoozeform .snz.wide { max-width: none; flex: 1 1 100%; min-height: 44px; }
  .snoozeform input[type="date"] { flex: 1 1 auto; max-width: none; min-height: 44px; }
  .snoozeform .btn { min-height: 44px; }

  /* comment box: textarea and button stack instead of squeezing side by side */
  .commentform { flex-direction: column; }
  .commentform textarea { width: 100%; }
  .commentside { width: 100%; }
  .fbcheck { max-width: none; }

  /* reviewer picker and other control rows wrap instead of clipping */
  .reviewform { flex-wrap: wrap; }
  .taskbtns .btn { min-height: 44px; }

  /* admin/time tables scroll inside themselves rather than stretching the page */
  .table { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }

  /* repeat-form schedule controls: full-ish width, nothing clipped */
  .schedrow select, .schedrow input { max-width: none; flex: 1 1 45%; }
  .schedrow input[type="number"] { flex: 0 1 90px; }

  /* row snooze ⏰ needs a touch more room at the 16px font */
  .snz { max-width: 64px; min-height: 30px; }
}

/* ---- notifications page: actions can never be missed on a phone ---- */
@media (max-width: 720px) {
  .pageactions { flex-direction: row !important; width: 100%; }
  .pageactions form { flex: 1; }
  .pageactions .btn { width: 100%; min-height: 44px; }
}

/* ---- training ---- */
.traincat { margin: 22px 0 6px; padding-left: 10px; border-left: 4px solid var(--cyan); }
.trainsub { margin: 14px 0 4px; font-size: 14px; color: var(--muted); font-weight: 400; }
.trainrow { color: var(--ink); }
.trainicon { font-size: 20px; align-self: center; }
.trainframe { border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; background: #fff; margin: 14px 0; box-shadow: var(--shadow); }
/* 78vh is only the pre-JS fallback — the module reports its height and the iframe grows
   to fit, so the page scrolls as one document (no nested scroll, which is what broke mobile). */
.trainframe iframe { width: 100%; height: 78vh; border: 0; display: block; }
@media (max-width: 720px) {
  .trainframe { margin: 10px -6px; border-radius: 10px; }
  .trainframe iframe { height: 70vh; }
}

/* ---- today / bands / waiting / completion chips ---- */
.band { margin: 16px 0; }
.band > h2 { display: flex; align-items: baseline; gap: 8px; padding-left: 10px; border-left: 4px solid var(--line); }
.band.b-over > h2 { border-color: var(--red); }
.band.b-today > h2 { border-color: var(--amber); }
.band.b-doing > h2 { border-color: var(--navy); }
.band.b-review > h2 { border-color: #7a2ee8; }
details.band > summary { cursor: pointer; color: var(--muted); font-size: 14px; padding: 8px 0; }
.chip.waiting { background: #fff3e0; color: var(--amber-ink); }
.navlink.rq { color: #6a24cf; }
.badge.inline { position: static; margin-left: 4px; background: #6a24cf; color: #fff; }
.overdueline { margin: 4px 0 10px; font-size: 13.5px; color: var(--red-ink); }
.clearbar { display: flex; gap: 10px; align-items: center; margin: 6px 0 10px; }
#clearword { font-family: ui-monospace, monospace; letter-spacing: 1px; }

/* ---- user menu under the avatar ---- */
.userdd { position: relative; }
.userdd > summary { list-style: none; cursor: pointer; }
.userdd > summary::-webkit-details-marker { display: none; }
.usermenu { right: 0; width: 250px; }
.ddwho { display: flex; flex-direction: column; padding: 8px 10px 10px; border-bottom: 1px solid var(--line); margin-bottom: 4px; }
.usermenu .ddnotif { display: flex; align-items: center; gap: 8px; }
.usermenu .ddfoot { justify-content: flex-start; }

/* ==================== My Day ==================== */
/* min-height, NOT height: at 320px "…finishing around 13:00" wraps to a second line, and a
   fixed 30px box with overflow:hidden swallowed the finish time completely. The bar now grows
   to fit its own label, so the one number worth reading is never the bit that gets cut. */
.capbar { position: relative; min-height: 30px; background: #e6eef4; border-radius: 999px; overflow: hidden; margin: 6px 0 16px; }
.capfill { position: absolute; inset: 0 auto 0 0; background: linear-gradient(90deg, var(--navy), var(--cyan)); transition: width .4s ease; }
.capfill.over { background: linear-gradient(90deg, var(--amber-ink), var(--red-ink)); }
.capnote { position: relative; z-index: 1; display: flex; align-items: center; min-height: 30px; line-height: 1.25; padding: 4px 14px; font-size: 12.5px; color: var(--ink); }
.capnote .red { color: var(--red-ink); }
.dayplan { display: flex; flex-direction: column; gap: 6px; }
.dayblock { display: flex; gap: 12px; align-items: stretch; background: #fff; border: 1px solid var(--line);
  border-radius: 12px; padding: 10px 14px; box-shadow: var(--shadow); position: relative; }
.dayblock .daytime { flex: none; width: 46px; font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; padding-top: 2px; }
.daybody { flex: 1; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; min-width: 0; }
.daytitle { font-size: 15px; color: var(--ink); flex: 1 1 220px; min-width: 0; }
.daymeta { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.dayblock.task { border-left: 4px solid var(--line); cursor: grab; }
.dayblock.task.q1 { border-left-color: var(--red); }
.dayblock.task.q2 { border-left-color: var(--navy); }
.dayblock.task.q3 { border-left-color: var(--amber); }
.dayblock.task.q4 { border-left-color: var(--slate-soft); }
.dayblock.task.dragging { opacity: .4; }
.dayblock.task.dragover { border-top: 3px solid var(--cyan); }
.dayblock.lunch { background: #fff8e9; border-color: #f2e0b8; border-left: 4px solid var(--amber); }
.dayblock.lunch .daybody { flex-direction: column; align-items: flex-start; gap: 2px; }
.dayblock.gap { background: transparent; border-style: dashed; box-shadow: none; }
.draghandle { color: #c3d2dc; cursor: grab; font-size: 15px; flex: none; }
.estsel { font-size: 12px; padding: 3px 6px; border-radius: 999px; max-width: 108px; }
.dayfoot { margin-top: 14px; }
@media (max-width: 720px) {
  .dayblock { padding: 10px; gap: 8px; }
  .dayblock .daytime { width: 40px; font-size: 11.5px; }
  .daytitle { flex-basis: 100%; }
  .draghandle { display: none; }
}
.daynav { gap: 6px; }
.dayblock.gap { min-height: 0 !important; padding: 4px 14px; }

/* ==================== My Day — six looks ==================== */
.stylepick { display: flex; gap: 4px; flex-wrap: wrap; align-items: center; margin: 0 0 12px; }
.stylebtn { font-size: 12px; padding: 5px 11px; border-radius: 999px; border: 1px solid var(--line); background: #fff; color: var(--muted); }
.stylebtn:hover { border-color: var(--navy); color: var(--navy-deep); }
.stylebtn.on { background: var(--ink); border-color: var(--ink); color: #fff; }
.dayplan .axis { display: none; }

/* — 1. Timeline bars: a Gantt row per task on a shared time axis — */
.dayplan.s1 { position: relative; padding-top: 24px; gap: 4px; }
.dayplan.s1 .axis { display: block; position: relative; height: 20px; margin-bottom: 4px; border-bottom: 1px solid var(--line); }
.dayplan.s1 .axis .tick { position: absolute; transform: translateX(-50%); font-size: 10.5px; color: var(--muted); }
.dayplan.s1 .axis .tick::after { content: ''; position: absolute; left: 50%; top: 15px; width: 1px; height: 6px; background: var(--line); }
.dayplan.s1 .dayblock { position: relative; min-height: 0 !important; height: 42px; padding: 0; border: 0; background: transparent; box-shadow: none; overflow: visible; }
.dayplan.s1 .dayblock .daytime { display: none; }
.dayplan.s1 .daybody { position: absolute; left: var(--l); width: var(--w); min-width: 74px; height: 38px;
  background: #fff; border: 1px solid var(--line); border-left: 4px solid var(--navy); border-radius: 8px;
  padding: 0 10px; gap: 6px; overflow: hidden; box-shadow: var(--shadow); flex-wrap: nowrap; }
.dayplan.s1 .dayblock.q1 .daybody { border-left-color: var(--red); }
.dayplan.s1 .dayblock.q3 .daybody { border-left-color: var(--amber); }
.dayplan.s1 .dayblock.q4 .daybody { border-left-color: var(--slate-soft); }
.dayplan.s1 .daytitle { font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 1 1 auto; }
.dayplan.s1 .daymeta { flex: none; }
.dayplan.s1 .daymeta .chip, .dayplan.s1 .daymeta .due { display: none; }
.dayplan.s1 .estsel { max-width: 78px; font-size: 11px; }
.dayplan.s1 .dayblock.lunch .daybody { background: #fff8e9; border-left-color: var(--amber); }
.dayplan.s1 .dayblock.gap { display: none; }

/* — 6. Centre rail: alternating cards down a spine — */
.dayplan.s6 { position: relative; padding: 8px 0; }
.dayplan.s6::before { content: ''; position: absolute; left: 50%; top: 0; bottom: 0; width: 2px; background: var(--line); }
.dayplan.s6 .axis { display: none; }
.dayplan.s6 .dayblock { width: calc(50% - 26px); position: relative; }
.dayplan.s6 .dayblock:nth-child(odd) { margin-right: auto; }
.dayplan.s6 .dayblock:nth-child(even) { margin-left: auto; }
.dayplan.s6 .dayblock::after { content: ''; position: absolute; top: 18px; width: 11px; height: 11px; border-radius: 50%;
  background: var(--navy); border: 2px solid #fff; box-shadow: 0 0 0 2px var(--line); }
.dayplan.s6 .dayblock:nth-child(odd)::after { right: -32px; }
.dayplan.s6 .dayblock:nth-child(even)::after { left: -32px; }
.dayplan.s6 .dayblock.q1::after { background: var(--red); }
.dayplan.s6 .dayblock.gap { display: none; }
@media (max-width: 720px) {
  .dayplan.s6::before { left: 14px; }
  .dayplan.s6 .dayblock { width: calc(100% - 30px); margin-left: auto !important; }
  .dayplan.s6 .dayblock::after { left: -22px !important; right: auto !important; }
  .dayplan.s1 .daybody { min-width: 56px; }
  .dayplan.s4 .daytitle { font-size: 10px; }
}

/* Horizontal looks need width — on phones they fall back to the agenda layout. */
@media (max-width: 760px) {
  .dayplan.s1, .dayplan.s4 { display: flex; flex-direction: column; gap: 6px; padding-top: 0; position: static; }
  .dayplan.s1 .axis, .dayplan.s4 .axis { display: none; }
  .dayplan.s1 .dayblock, .dayplan.s4 .dayblock {
    position: static; width: auto; height: auto; min-height: 46px !important;
    background: #fff; border: 1px solid var(--line); border-radius: 12px; padding: 10px 14px; box-shadow: var(--shadow); }
  .dayplan.s1 .dayblock .daytime, .dayplan.s4 .dayblock .daytime { display: block; }
  .dayplan.s1 .daybody, .dayplan.s4 .daybody {
    position: static; width: auto; height: auto; border: 0; box-shadow: none; padding: 0; flex-wrap: wrap; background: transparent; }
  .dayplan.s1 .daytitle, .dayplan.s4 .daytitle { white-space: normal; font-size: 14.5px; color: var(--ink); -webkit-line-clamp: 2; }
  .dayplan.s1 .daymeta { display: flex; }
  .dayplan.s1 .daymeta .chip, .dayplan.s1 .daymeta .due, .dayplan.s4 .daymeta { display: inline-flex; }
  .dayplan.s4 .dayblock.q1, .dayplan.s4 .dayblock.q2, .dayplan.s4 .dayblock.q3, .dayplan.s4 .dayblock.q4 { background: #fff; }
  .dayplan.s4 .dayblock { border-left: 4px solid var(--navy); }
  .dayplan.s4 .dayblock.q1 { border-left-color: var(--red); }
  .dayplan.s4 .dayblock.q3 { border-left-color: var(--amber); }
  .dayplan.s4 .dayblock.q4 { border-left-color: var(--slate-soft); }
  .dayplan.s4::after { display: none; }
  .dayplan.s4 .dayblock.lunch { background: #fff8e9; }
  .dayplan.s4 .dayblock.lunch .muted { display: block; }
  .stylepick { gap: 5px; }
  .stylebtn { flex: 1 1 auto; text-align: center; }
}

/* Horizontal bars: the title is the point — hide the controls, let it breathe. */
.dayplan.s1 .estsel, .dayplan.s1 .draghandle { display: none; }
.dayplan.s1 .dayblock { height: 34px; }
.dayplan.s1 .daybody { height: 30px; min-width: 118px; padding: 0 10px; cursor: grab; }
.dayplan.s1 .daytitle { font-size: 12px; }
.dayplan.s1 .dayblock.lunch .daybody { min-width: 90px; }
.dayplan.s1 .dayblock.lunch .daybody strong { font-size: 12px; }
.dayplan.s1 .dayblock.lunch .muted { display: none; }
.dayplan.s4 .dayblock { min-width: 30px; }
.dayplan.s4 .dayblock .daytitle { font-size: 10.5px; }

/* ---- notification dropdown: scrolling list, fixed actions ---- */
.ddpanel { padding: 0; }
.ddscroll { max-height: 340px; overflow-y: auto; overscroll-behavior: contain; padding: 6px; display: flex; flex-direction: column; gap: 2px; }
.ddfootwrap { border-top: 1px solid var(--line); background: #fff; border-radius: 0 0 12px 12px; position: sticky; bottom: 0; }
.ddfootwrap .ddfoot { border-top: 0; margin: 0; padding: 10px 12px; }
.ddloading { padding: 10px; text-align: center; font-size: 12px; color: var(--muted); }

/* ---- the big left-hand back zone on detail pages ---- */
#backzone {
  position: fixed; left: 0; top: 57px; bottom: 0;
  /* Sized from the REAL gutter beside .wrap — read the variable, never a hardcoded width,
     or widening .wrap silently parks this on top of the page content. Capped so it stays a
     slim edge affordance on very wide screens instead of a great slab of nothing. */
  width: calc((100vw - var(--wrapw)) / 2 - 14px);
  max-width: 92px;
  display: none; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  border: 0; cursor: pointer; z-index: 40; padding: 0;
  background: linear-gradient(90deg, rgba(31, 35, 60,.13), rgba(31, 35, 60,.05) 55%, rgba(31, 35, 60,0));
  box-shadow: inset 1px 0 0 rgba(31, 35, 60,.16);
  transition: background .25s ease, box-shadow .25s ease;
  font-family: inherit;
}
#backzone::after {
  content: ''; position: absolute; right: 0; top: 12%; bottom: 12%; width: 2px;
  background: linear-gradient(180deg, transparent, rgba(31, 35, 60,.5), transparent);
  filter: blur(.5px); opacity: .8;
}
#backzone .bzarrow {
  font-size: 26px; line-height: 1; color: var(--navy-deep);
  width: 44px; height: 44px; border-radius: 50%; background: rgba(255,255,255,.92);
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 4px 18px rgba(31, 35, 60,.28); transition: transform .25s ease, box-shadow .25s ease;
}
#backzone .bztext { font-size: 10.5px; letter-spacing: 1.6px; text-transform: uppercase; color: var(--navy-deep); opacity: .75; }
#backzone:hover {
  background: linear-gradient(90deg, rgba(31, 35, 60,.26), rgba(31, 35, 60,.10) 55%, rgba(31, 35, 60,0));
  box-shadow: inset 1px 0 0 rgba(31, 35, 60,.3);
}
#backzone:hover .bzarrow { transform: translateX(-5px); box-shadow: 0 6px 26px rgba(31, 35, 60,.45); }
#backzone:active .bzarrow { transform: translateX(-9px) scale(.96); }
#backzone:focus-visible { outline: 2px solid var(--navy); outline-offset: -4px; }
/* only when there is genuinely a gutter to use */
/* 1300 wrap + 2x18 padding + 2x~92 zone => needs ~1520px before there is real room.
   Below that the zone would either overlap the content or be a useless sliver. */
@media (min-width: 1520px) { #backzone { display: flex; } }
@media (prefers-reduced-motion: reduce) { #backzone, #backzone .bzarrow { transition: none; } }

/* ==================== Timeline bars v2: full titles, descriptions, time-drag ==================== */
.stylenote { margin-left: 6px; }
.dayplan.s1 { padding-top: 26px; gap: 6px; }
.dayplan.s1 .dayblock { height: auto; min-height: 62px !important; }
.dayplan.s1 .daybody {
  height: auto; min-height: 58px; min-width: 230px; padding: 9px 14px;
  flex-direction: column; align-items: flex-start; justify-content: center; gap: 3px;
  cursor: grab; user-select: none; touch-action: none;
}

/* The bar is absolutely positioned at `left: var(--l)` (a % of the day) and then forced
   to 230px wide. Nothing stopped left + 230px running past the end of the day, so a
   late-afternoon task hung over the right edge and panned the WHOLE PAGE sideways —
   measured as a 9px horizontal scroll on ?v=day at 768px, and it does the same at any
   width once a task sits late enough (left > container - 230px). Clamping the minimum
   as well as the maximum is the point: a plain `max-width` loses to `min-width` in the
   cascade, so the min has to know about the space available too.

   Scoped to >760px ON PURPOSE. Below that the block above turns the bar into a static
   agenda row, `--l` keeps its value, and this clamp would squeeze each row to whatever
   was left of the working day — the later the task, the narrower the row. Because both
   selectors are (0,2,0), a reset inside the 760px block would LOSE to this rule further
   down the file, so the media query is the fix, not a counter-declaration. */
@media (min-width: 761px) {
  .dayplan.s1 .daybody {
    min-width: min(230px, calc(100% - var(--l)));
    max-width: calc(100% - var(--l));
  }
}
.dayplan.s1 .daybody:active { cursor: grabbing; }
.dayplan.s1 .daytext { display: flex; flex-direction: column; gap: 1px; width: 100%; min-width: 0; }
.dayplan.s1 .daytitle {
  font-size: 12.5px; line-height: 1.25; white-space: normal;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.dayplan.s1 .daydesc {
  font-size: 11px; line-height: 1.3; color: var(--muted);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.dayplan.s1 .daymeta { display: flex; }
.dayplan.s1 .daymeta .chip { display: none; }
.dayplan.s1 .daymeta .chip.pin { display: inline-flex; font-size: 10px; padding: 1px 6px; }
.dayplan.s1 .dayblock.pinned .daybody { box-shadow: 0 0 0 2px rgba(58, 195, 233,.35), var(--shadow); }
.chip.pin { background: var(--cyan-wash); color: var(--navy-deep); box-shadow: inset 0 0 0 1px rgba(58, 195, 233, .5); }

/* drag feedback — the bar you are holding must be the brightest thing on screen */
.dayplan.s1.dragging-now .daybody { transition: none; }
.dayplan.s1.dragging-now .dayblock:not(.timedrag) .daybody { opacity: .38; filter: saturate(.5); }
.dayplan.s1 .dayblock.timedrag { z-index: 40; }
.dayblock.timedrag .daybody {
  opacity: 1; transform: scale(1.02); cursor: grabbing;
  box-shadow: 0 0 0 3px var(--cyan), 0 16px 34px rgba(23,39,51,.28);
}
/* the lane the bar will drop into */
#dropzone {
  position: absolute; border-radius: 12px; background: rgba(58, 195, 233,.1);
  border: 2px dashed rgba(58, 195, 233,.75); z-index: 28; pointer-events: none; display: none;
}
#dropguide {
  position: absolute; top: 22px; bottom: 0; width: 2px; background: var(--cyan);
  box-shadow: 0 0 10px rgba(58, 195, 233,.7); z-index: 30; pointer-events: none; display: none;
}
/* the time readout rides directly above the bar you are dragging */
#droplabel {
  position: absolute; transform: translateY(-100%); background: var(--cyan); color: var(--navy);
  font-size: 13px; font-weight: 700; letter-spacing: .01em; padding: 5px 12px; border-radius: 999px;
  white-space: nowrap; z-index: 41; pointer-events: none; display: none;
  box-shadow: 0 4px 14px rgba(31, 35, 60, .35);
}
#droplabel .dur { font-weight: 500; opacity: .85; margin-left: 6px; }
#droplabel::after {
  content: ''; position: absolute; left: 16px; top: 100%; width: 0; height: 0;
  border: 5px solid transparent; border-top-color: var(--cyan);
}

/* ==================== Training library: category + course cards ==================== */
.crumb { display: inline-block; margin: 14px 0 -4px; color: var(--navy-deep); font-size: 14px; }
.catgrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; margin: 12px 0 32px; }
.coursegrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; margin: 16px 0 30px; }
.catcard, .ccard {
  display: flex; flex-direction: column; background: #fff; border: 1px solid var(--line);
  border-radius: 16px; overflow: hidden; color: var(--ink); position: relative;
  box-shadow: 0 2px 10px rgba(23,39,51,.07);
  transition: transform .28s cubic-bezier(.2,.7,.3,1), box-shadow .28s ease, border-color .28s ease;
}
.catcard:hover, .ccard:hover {
  transform: translateY(-6px); border-color: rgba(31, 35, 60,.45);
  box-shadow: 0 16px 34px rgba(23,39,51,.16),
              0 12px 46px -8px rgba(31, 35, 60,.55),
              0 12px 46px -8px rgba(58, 195, 233,.4);
}
.ccimg {
  display: block; position: relative; aspect-ratio: 3 / 2; background-size: cover; background-position: center;
  background-color: var(--navy); transition: transform .5s cubic-bezier(.2,.7,.3,1);
}
.catcard:hover .ccimg, .ccard:hover .ccimg { transform: scale(1.045); }
.ccglow { position: absolute; inset: 0; background: linear-gradient(180deg, rgba(31, 35, 60,0) 45%, rgba(14,28,46,.55)); transition: opacity .3s ease; }
.catcard:hover .ccglow, .ccard:hover .ccglow { opacity: .45; }
.catbody { padding: 14px 16px 16px; display: flex; flex-direction: column; gap: 3px; }
.cattitle { font-size: 16px; font-weight: 600; }
.catcount { font-size: 12.5px; color: var(--muted); }
.catbody::after { content: 'View courses ›'; margin-top: 6px; font-size: 12.5px; font-weight: 600; color: var(--navy-deep);
  opacity: 0; transform: translateX(-6px); transition: opacity .25s ease, transform .25s ease; }
.catcard:hover .catbody::after { opacity: 1; transform: none; }
.ccbody { padding: 14px 16px 18px; display: flex; flex-direction: column; gap: 7px; }
.ccmeta { display: flex; gap: 5px; flex-wrap: wrap; }
.cctitle { font-size: 15.5px; font-weight: 600; line-height: 1.3; }
.ccsum { font-size: 12.5px; color: var(--muted); line-height: 1.45;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.ccfor { font-size: 11.5px; color: var(--navy-deep); }
@media (max-width: 980px) { .coursegrid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .coursegrid, .catgrid { grid-template-columns: 1fr; gap: 16px; } }

/* ==================== Access grid + security log ==================== */
.accessgrid td, .accessgrid th { vertical-align: middle; }
.accessgrid td:first-child { max-width: 420px; }
.accesswho { text-align: center; white-space: nowrap; font-weight: 600; font-size: 13px; }
/* the avatar's centring comes from its own flex rules — display:block would clip the initials */
.accesswho .mini-avatar { display: inline-flex; align-items: center; justify-content: center; margin: 0 auto 4px; }
.accesswho { display: table-cell; }
.accesswho span.mini-avatar { margin-bottom: 5px; }
.accesscell { text-align: center; }
.switch { display: inline-block; position: relative; cursor: pointer; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch .track {
  display: block; width: 42px; height: 24px; border-radius: 999px; background: #d6e2ea;
  position: relative; transition: background .2s ease;
}
.switch .track::after {
  content: ''; position: absolute; top: 3px; left: 3px; width: 18px; height: 18px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 3px rgba(23,39,51,.3); transition: transform .2s ease;
}
.switch input:checked + .track { background: var(--navy); }
.switch input:checked + .track::after { transform: translateX(18px); }
.switch input:focus-visible + .track { outline: 2px solid var(--cyan); outline-offset: 2px; }

.secstats { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin: 4px 0 16px; }
.secstat { background: var(--bg); border: 1px solid var(--line); border-radius: 12px; padding: 12px 14px; }
.secstat strong { display: block; font-size: 24px; line-height: 1.1; }
.secstat span { font-size: 12px; color: var(--muted); }
.secstat.ok strong { color: var(--green); }
.secstat.bad { border-color: #f3c3cd; background: #fff2f5; }
.secstat.bad strong { color: var(--red-ink); }
.secstat.warn { border-color: #f2e0b8; background: #fff8e9; }
.secstat.warn strong { color: var(--amber-ink); }
.secbadge { display: inline-block; font-size: 11.5px; font-weight: 600; padding: 2px 9px; border-radius: 999px; white-space: nowrap; }
/* These are 11.5px badges, so they need the full 4.5:1 — the old greens and ambers were
   3.3:1 and 3.5:1 against their own tints, which is a pale smudge on a phone in daylight. */
.secbadge.ok { background: #e5f7f0; color: #0d6b4a; }      /* was #12996b, 3.26:1 → 5.87:1 */
.secbadge.bad { background: #ffe9ed; color: #b4213a; }     /* was #c9304a, 4.53:1 → 5.6:1  */
.secbadge.warn { background: #fff3dc; color: #8a5a00; }    /* was #b87400, 3.45:1 → 5.39:1 */
.secbadge.muted { background: var(--bg); color: var(--muted); }
.warncard { border-color: #f2e0b8; background: #fffdf7; }
.tablewrap { overflow-x: auto; }
.sectable { min-width: 620px; }
.nowrap { white-space: nowrap; }
.filtertabs { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; align-items: center; }
.filtertab { padding: 5px 13px; border-radius: 999px; border: 1px solid var(--line); font-size: 13px; background: #fff; }
.filtertab.on { background: var(--ink); color: #fff; border-color: var(--ink); }
@media (max-width: 700px) {
  .secstats { grid-template-columns: repeat(2, 1fr); }
  .accessgrid td:first-child { max-width: none; }
}

/* =====================================================================
   Installer Site Log (?v=sitelog / ?v=sitereport)
   Added by app/lib/sitelog.php. The same rules are inlined by
   sitelog_styles() so the form stays readable on a roof with one bar of
   signal and a half-loaded stylesheet; these are the cached copy.
   Mobile-first throughout: 44px touch targets, 16px inputs (anything
   smaller makes iOS zoom on focus), and nothing that scrolls sideways
   at 320px.
   ===================================================================== */
.slgreet { margin: 18px 0 14px; }
.slgreet h1 { font-size: 25px; margin: 0 0 2px; }
.slformhead { margin-bottom: 8px; }
.slhero { display: block; background: var(--band); color: #fff; border-radius: 16px;
  padding: 20px 18px; margin: 0 0 22px; box-shadow: 0 4px 14px rgba(31,35,60,.18); }
.slhero:active { transform: scale(.995); }
.slherotitle { display: block; font-size: 20px; font-weight: 700; color: #fff; }
.slherosub { display: block; font-size: 14px; color: rgba(255,255,255,.82); margin-top: 2px; }
.slsection { margin: 0 0 26px; }
.slsechead { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin-bottom: 8px; }
.slsechead h2 { margin: 0; font-size: 13px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.sllink { color: var(--navy-deep); font-size: 13px; text-decoration: underline; }

.sllist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.slrow { background: var(--card); border: 1px solid var(--line); border-radius: 12px; overflow: hidden; }
.slrowlink { display: flex; align-items: center; gap: 10px; padding: 12px 14px; min-height: 44px; color: var(--ink); }
.slrowlink:hover, .slrowlink:focus-visible { background: var(--cyan-wash); }
.slrowmain { flex: 1 1 auto; min-width: 0; }
.slrowtitle { display: block; font-weight: 500; color: var(--ink); overflow-wrap: anywhere; }
.slrowmeta { display: block; font-size: 12.5px; color: var(--muted); overflow-wrap: anywhere; }
.slrowtags { display: flex; flex-wrap: wrap; gap: 5px; justify-content: flex-end; }
.slchev { color: var(--muted); font-size: 18px; flex: none; }
.sljob { display: flex; flex-direction: column; }
.sljob .slrowlink { border-bottom: 1px solid var(--line); }
.sljoblog { display: block; padding: 10px 14px; min-height: 44px; font-size: 13px; font-weight: 500;
  color: var(--navy-deep); background: #f8fbfe; }
.sljoblog:hover, .sljoblog:focus-visible { background: var(--cyan-wash); }

.slpill { display: inline-block; font-size: 11.5px; font-weight: 700; border-radius: 999px; padding: 2px 9px; white-space: nowrap; }
.slpill.draft { background: #fff3e0; color: var(--amber-ink); }
/* --green on this wash is only 3.81:1, under the 4.5 small bold text needs — too thin
   to read on a roof in bright sun. Darkened to 4.9:1 here only; --green stays as it is
   for the tickbox, where it is a background rather than text. */
.slpill.done { background: #e5f4ec; color: #11774a; }
.slpill.issue { background: #fdeaea; color: var(--red-ink); }
.slpill.issue.minor { background: #eef2f7; color: var(--navy-deep); }
.slpill.issue.attention { background: #fff3e0; color: var(--amber-ink); }

/* Stage badge — cyan through the install window, navy once it is complete.
   Cyan only ever carries navy text (7.5:1); it is never used for small text on white. */
.slstage { display: inline-block; font-size: 11.5px; font-weight: 700; border-radius: 999px;
  padding: 3px 10px; flex: none; max-width: 44%; text-align: center; line-height: 1.3;
  /* Long stage names ("DNO Commissioning & Export MPAN") wrap onto a second line
     rather than being cut with an ellipsis — an installer needs the whole stage. */
  white-space: normal; overflow-wrap: anywhere; }
.slstage.cyan { background: var(--cyan); color: var(--navy); }
.slstage.navy { background: var(--navy); color: #fff; }
.slstage.none { background: #eef2f7; color: var(--navy-deep); }

.slfind { display: flex; gap: 8px; margin-bottom: 10px; }
.slfilterinput { flex: 1 1 auto; min-width: 0; font-size: 16px; padding: 11px 13px; min-height: 44px;
  border: 1px solid var(--line); border-radius: 10px; background: #fff; color: var(--charcoal); }
.slfilterinput:focus-visible { outline: 2px solid var(--cyan); outline-offset: 1px; }
.slfindgo { flex: none; min-height: 44px; }
.slnomatch { margin: 10px 0 0; }

.slform { margin: 0 0 90px; }
.slsec { background: var(--card); border: 1px solid var(--line); border-radius: 14px;
  padding: 16px 15px; margin: 0 0 14px; box-shadow: var(--shadow); }
.slsec > h2 { display: flex; align-items: center; gap: 9px; font-size: 16px; font-weight: 700; margin: 0 0 12px; }
.slnum { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px;
  border-radius: 50%; background: var(--navy); color: #fff; font-size: 13px; font-weight: 700; flex: none; }
.slreq { color: var(--red-ink); font-weight: 700; }
.slsec input[type="text"], .slsec input[type="date"], .slsec input[type="number"], .slsec textarea {
  width: 100%; font-size: 16px; font-family: inherit; padding: 11px 12px; min-height: 44px;
  border: 1px solid var(--line); border-radius: 10px; background: #fff; color: var(--charcoal); }
.slsec textarea { line-height: 1.5; resize: vertical; }
.slsec input:focus-visible, .slsec textarea:focus-visible { outline: 2px solid var(--cyan); outline-offset: 1px; border-color: var(--cyan); }
.slprev { background: var(--cyan-wash); border: 1px solid #bfe7f6; border-radius: 10px;
  padding: 9px 12px; font-size: 13.5px; color: var(--navy-deep); margin: 10px 0 0; }
.slsavebad { color: var(--red-ink); }

.slsitecard { border: 1px solid var(--line); border-radius: 12px; padding: 12px 13px; background: #f8fbfe; }
.slsitetop { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.slsitewho { min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.slsitewho strong { color: var(--ink); overflow-wrap: anywhere; }
.sltel { display: inline-block; margin-top: 4px; min-height: 44px; line-height: 44px; font-weight: 500; text-decoration: underline; }
.slsitecard .sllink { display: inline-block; margin-top: 6px; min-height: 44px; line-height: 44px; }

.slchklist { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; }
.slchk { border: 1px solid var(--line); border-radius: 12px; padding: 11px 12px; }
.slchktop { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 9px; }
/* break-word, NOT anywhere: `anywhere` collapses a flex child's min-content
   width, which squeezed "Panels installed" to one letter per line next to the
   number field. break-word still breaks a genuinely over-long word. */
.slchkname { flex: 1 1 auto; min-width: 0; font-weight: 500; color: var(--ink); overflow-wrap: break-word; }
/* Element + class, to out-specify the `.slsec input[type=...] { width: 100% }`
   rule above — without it the count box eats the whole row and squashes the
   checklist label to nothing. */
.slsec input.slchkextra { flex: none; width: 78px; text-align: center; }
.slsec input.slchkextra.wide { width: 122px; }

/* Segmented controls are real radios under the skin, so they work with the
   keyboard and without JavaScript. */
.slseg { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
.slyn, .slsev { grid-template-columns: repeat(2, 1fr); }
.slsev { grid-template-columns: repeat(3, 1fr); margin-bottom: 12px; }
.slsegopt { position: relative; display: block; }
.slsegopt input { position: absolute; inset: 0; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.slsegopt span { display: flex; align-items: center; justify-content: center; text-align: center;
  min-height: 44px; padding: 6px 4px; border: 1px solid var(--line); border-radius: 10px;
  font-size: 13px; font-weight: 700; color: var(--muted); background: #fff; line-height: 1.2; }
.slsegopt input:checked + span { border-color: var(--cyan); background: var(--cyan-wash); color: var(--navy); }
.slsegopt input:focus-visible + span { outline: 2px solid var(--cyan); outline-offset: 2px; }
.slsev .sev-blocking input:checked + span { border-color: var(--red-ink); background: #fdeaea; color: var(--red-ink); }
.slsev .sev-attention input:checked + span { border-color: var(--amber); background: #fff3e0; color: var(--amber-ink); }
.slissuebox[data-hide="1"] { display: none; }

.slphotobtns { display: flex; flex-wrap: wrap; gap: 8px; }
.slphotobtn { min-height: 44px; font-size: 15px; }
.slphotos { list-style: none; margin: 10px 0 0; padding: 0; display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.slphotos img { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; border-radius: 10px; border: 1px solid var(--line); display: block; }
.slphotos li { position: relative; }

.slphotoup { min-height: 44px; margin-top: 10px; }
.slphotoform { margin: 0; }
.slsubmit { position: sticky; bottom: 0; background: var(--bg); border-top: 1px solid var(--line);
  padding: 12px 0 14px; margin-top: 16px; z-index: 20; }
.slsubmitbtn { width: 100%; min-height: 52px; font-size: 17px; font-weight: 700; }
.slsubmit p { margin: 7px 0 0; text-align: center; }

.slro { margin: 0; }
.slro dt { font-size: 11.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); margin-top: 12px; }
.slro dt:first-child { margin-top: 0; }
.slro dd { margin: 3px 0 0; overflow-wrap: anywhere; }
.slrochk { list-style: none; margin: 0; padding: 0; }
.slrochk li { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 9px 0; border-bottom: 1px solid var(--line); }
.slrochk li:last-child { border-bottom: 0; }
.slroitem { flex: 1 1 auto; min-width: 0; overflow-wrap: break-word; }
.slrovalue { font-weight: 700; color: var(--navy-deep); white-space: nowrap; }
.slstate { flex: none; font-size: 11.5px; font-weight: 700; border-radius: 999px; padding: 3px 10px; background: #eef2f7; color: var(--muted); }
.slstate.done { background: var(--navy); color: #fff; }
.slstate.in_progress { background: var(--cyan); color: var(--navy); }
.slsec.slissue { border-color: #f2c9c9; background: #fff7f7; }
.slsec.slissue.blocking { border-left: 5px solid var(--red-ink); }
.slfoot { margin: 18px 0 10px; }

@media (min-width: 720px) {
  .slphotos { grid-template-columns: repeat(5, 1fr); }
  .sljob { flex-direction: row; align-items: stretch; }
  .sljob .slrowlink { flex: 1 1 auto; border-bottom: 0; border-right: 1px solid var(--line); }
  .sljoblog { display: flex; align-items: center; flex: none; }
}
@media (max-width: 400px) {
  .slseg { grid-template-columns: repeat(2, 1fr); }
  .slsev { grid-template-columns: 1fr; }
  .slstage { max-width: 100%; }
  /* On a 320px phone the stage pill and the customer name were fighting over one
     line: the pill is `flex: none` so it keeps its full width, while the text column
     is `min-width: 0` so it collapses — "Boulder Developments Ltd" came out at one
     letter per line, and the site card squeezed the address and phone into 58px.
     Give the text the whole first line and let the pill drop underneath it. */
  .slrowlink { flex-wrap: wrap; }
  .slrowmain { flex: 1 1 100%; }
  .slsitetop { flex-direction: column; align-items: flex-start; gap: 6px; }
}
/* Site Log back-links get the same 44px target as everything else on these pages. */
.slback { display: inline-flex; align-items: center; min-height: 44px; }

/* =====================================================================
   TOUCH TARGETS — 44x44 minimum on phones and tablets
   ---------------------------------------------------------------------
   Measured, not guessed: every a / button / input / select / textarea /
   label-wrapping-a-control was rendered inside an exact-width iframe at
   320 · 375 · 430 · 768 · 834 · 1024 · 1280 · 1440 and its box read
   back, then hit-tested with elementFromPoint so the overlays below
   count too. The Site Log daily report already passed everywhere (44px
   throughout, 16px fields) — this block brings the task and office
   views up to the same standard.

   The band is <= 1024px, not <= 720px. 720 is the app's phone
   breakpoint, but iPad portrait (768), iPad Air (834) and iPad
   landscape (1024) all sit above it, so every tablet was being handed
   the desktop chrome: a 21px complete tick, a 22px snooze, a 22px
   notification bell. Those are touch-only devices. Nothing in here
   applies at 1280 or 1440, so the desktop is untouched.

   Two techniques, deliberately:
     - REAL size (min-height / min-width) where the control can grow
       without the row looking wrong;
     - an invisible ::after overlay where it cannot — the tick and the
       row avatar have to stay visually small, so the finger gets a
       44px box while the eye keeps the 22–26px one.
   ===================================================================== */

@media (max-width: 1024px) {

  /* ---- task row controls: the four the brief called out ---- */

  /* Complete tick: 26x26 on a phone, 21x21 on an iPad — the most-tapped
     control in the app at a quarter of the minimum area. It has to stay
     a small tick visually, so the hit area is an overlay centred on it. */
  .tickbox { position: relative; }

  /* Row avatar reassign: 22x22, the smallest target measured anywhere. */
  .rowassign { position: relative; }

  /* The user menu, in the 56px header band on every page for every role. */
  .userdd > summary.avatar { position: relative; }

  .tickbox::after,
  .rowassign::after,
  .userdd > summary.avatar::after {
    content: ''; position: absolute; top: 50%; left: 50%;
    width: 44px; height: 44px; transform: translate(-50%, -50%);
  }

  /* Snooze (64x30), priority (101x27) and the bin (35x37) can simply
     grow — once all three are 44 the meta line reads as one band. */
  .taskmeta .snz,
  .taskmeta .rowquad { min-height: 44px; }
  .binbtn { min-width: 44px; min-height: 44px; padding: 6px 8px; }

  /* 8px is the floor for adjacent targets; the snooze/bin pair measured
     6px. Widen the gutters rather than shrink the controls — and 12px,
     not 10px, because the avatar's 44px overlay reaches 11px past its
     22px disc and would otherwise be clipped by the bin next to it. */
  .taskmeta { gap: 12px; row-gap: 8px; }

  /* The row's own link. It was 43.5px — half a pixel short. */
  .tasklink { min-height: 44px; }

  /* ---- buttons, pills and chips ---- */
  .btn { min-height: 44px; }
  .btn.small { min-width: 44px; min-height: 44px; }
  .duequick .duebtn, .cbquick .duebtn { min-width: 44px; min-height: 44px; }
  .duequick input[type="date"], .cbquick input[type="date"],
  .tsel select { min-height: 44px; }
  .priobtn, .memchip, .kindbtn, .modetab, .stylebtn, .filtertab, .qfchip, .addpart {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 44px; min-height: 44px;
  }

  /* Dictation. The Site Log's copy is already 44x44; there is no reason
     the office forms should be harder to hit than the roof ones. Both
     shapes are covered: inside a .fieldwrap gutter, and the bare button
     app.js appends after a comment box. */
  .micbtn { min-height: 44px; }
  .fieldwrap .micbtn { min-width: 44px; padding: 0; justify-content: center; }

  /* ---- form fields ----
     16px is already handled below; this is about height. Everything
     from the quick-add selects (33px) to the Admin email inputs (39px)
     was short of a comfortable tap. */
  input:not([type="hidden"]):not([type="checkbox"]):not([type="radio"]),
  select, textarea { min-height: 44px; }
  /* The quick-add option row sets its own 34px floor at (0,2,0); restate it
     here at matching specificity so these selects come up with the rest. */
  .qaopts select, .qaopts input, .qaopts .combo-input, .qaopts .duebtn { min-height: 44px; }

  /* ---- links that are really buttons ---- */

  /* "‹ Back" is the ONLY back affordance once the PWA is installed and
     the browser chrome is gone. It measured 41x22. */
  .backlink { display: inline-flex; align-items: center; min-width: 44px; min-height: 44px; }

  /* Text-buttons: "Save as my default", "Archive", "Edit customer
     details" — 18 to 22px tall. */
  .linkish { display: inline-flex; align-items: center; min-width: 44px; min-height: 44px; }
  .linkish.big { min-height: 48px; }
  /* …but a <summary class="linkish"> ("Edit customer details", "Activity")
     is a full-width disclosure row, not an inline button. inline-flex would
     shrink-wrap it from 301px to 117px — taller, but a smaller target
     overall. Plain flex is block-level, so it keeps the whole row. */
  summary.linkish { display: flex; }

  /* Disclosure rows: "Filters ▾" (32px), "Activity (1)" (22px),
     "Add customer". The whole row is the control, so grow it. */
  .filterwrap > summary,
  details.card.slim > summary { min-height: 44px; align-items: center; }

  /* My Day block titles (31px) and the brand lockup, which is the
     home link on every page (34px). */
  .daytitle { display: flex; align-items: center; min-height: 44px; }
  .brand { min-height: 44px; }

  /* Table cell links. The worst was the Customers open-task count — a
     bare digit at 7.9x16, the smallest target in the whole app. */
  .table td a { display: inline-flex; align-items: center; justify-content: flex-start;
                min-width: 44px; min-height: 44px; }

  /* Site Log admin list: report title (16px), the Job card / Open
     report chips (20px), the date tabs (32px), "All reports ›" (19px). */
  .slrep .slwho,
  .slrep .slfoot .chip,
  .sltabs a,
  .slsechead .sllink { display: inline-flex; align-items: center; min-height: 44px; }

  /* Admin: the access-grid switch (42x24) and the inline checkbox
     labels (20px). The switch keeps its 42x24 track; the label grows. */
  .switch { padding: 10px 1px; }
  .check, .depitem { min-height: 44px; }

  /* Notification / account dropdown rows measured 34.8px on every phone and
     tablet — the one menu you reach for one-handed, and the shortest target
     left in the app. These are full-width rows, so height is all they need. */
  .ddnotif, .ddfoot a, .ddfoot button { display: flex; align-items: center; min-height: 44px; }
  .usermenu .ddnotif { display: flex; }

  /* Alerts page rows came out 41.8px — 2.2px short, and this is the list an
     installer taps to find out what needs doing. */
  .notif { display: flex; align-items: center; min-height: 44px; }
}

/* ---- tablets only: 721–1024px ----
   Phones already get 16px fields and the bottom tab bar from the 720px
   block. Tablets get neither, and sub-16px fields are what make iPadOS
   Safari zoom the page on every focus. */
@media (min-width: 721px) and (max-width: 1024px) {

  /* Scoped to the fields you actually type into: a <select> opens a
     picker wheel and never triggers the zoom, and leaving the inline
     row selects alone keeps "Do First" from outgrowing its pill.
     !important for the same reason the 720px block uses it: .filters input
     and .qaopts .combo-input set 13px at (0,2,0) and would otherwise win.
     input:not([type]) is in the list on purpose: the filter box and several
     admin fields carry no type attribute at all, and were the ones left at
     13px after the first pass. */
  input:not([type]),
  input[type="text"], input[type="search"], input[type="email"],
  input[type="tel"], input[type="url"], input[type="password"],
  input[type="number"], input[type="date"], input[type="time"],
  textarea, .qatitle, .combo-input { font-size: 16px !important; }

  /* Top nav and header: mouse-sized at 34px, thumb-sized now. The bell
     gets an overlay rather than a wider box so its unread badge, which
     is positioned against it, does not move. */
  .navlink { display: inline-flex; align-items: center; min-height: 44px; }
  /* The bell and the avatar sit 10px apart, so their 44px overlays were
     landing on top of each other and the bell came out 28px wide. Give
     the header row enough gutter for both to be whole. */
  .rightnav { gap: 20px; }
  .belldd > summary { position: relative; }
  .belldd > summary::after {
    content: ''; position: absolute; top: 50%; left: 50%;
    width: 44px; height: 44px; transform: translate(-50%, -50%);
  }
}

/* The nav-overflow fix this audit found (nine items do not fit below
   ~1190px, so Jobs / Site Log / Training / Processes / "To review" were
   clipped and untappable on an iPad) now lives on .mainnav and .navlink
   in the base rules above — overflow-x:auto plus flex:none. Verified
   here at 768 / 834 / 1024: all nine reachable, 44px tall. */

/* ==================== Spectrum Training additions ==================== */
/* Ported from the Fresh training portal, recoloured to the Spectrum palette. */

/* Training progress bar + checklist on the course page */
.trainprog { padding: 14px 18px; }
.tp-row { display: flex; align-items: center; gap: 14px; }
.tp-bar { flex: 1; height: 10px; border-radius: 999px; background: var(--bg); border: 1px solid var(--line); overflow: hidden; }
.tp-fill { display: block; height: 100%; border-radius: 999px; background: linear-gradient(90deg, var(--cyan), #158b52); transition: width .6s ease; }
.tp-pct { flex: none; font-size: 13.5px; font-weight: 700; color: var(--ink); min-width: 44px; text-align: right; }
.tp-actions { display: flex; align-items: center; justify-content: space-between; gap: 14px; margin-top: 10px; flex-wrap: wrap; }
.tp-actions p { margin: 0; }
.tp-actions button[disabled] { opacity: .45; cursor: not-allowed; }
.tp-btns { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.tp-checklist { margin: 10px 0 2px; font-size: 14px; }
.tp-checklist ul { list-style: none; margin: 6px 0 0; padding: 0; }
.tp-checklist li { padding: 3px 0; }
.chip.ok { background: #e5f7f0; color: #12996b; }
#confirmmsg { white-space: pre-line; text-align: left; }

/* exercise comments / quiz insight on the module admin card */
.exnotes { display: inline-block; }
.exnotes summary { cursor: pointer; color: var(--navy-deep); font-size: 13px; }
.exnotelist { margin: 8px 0 4px; display: grid; gap: 8px; }
.exnote { background: var(--bg); border: 1px solid var(--line); border-radius: 10px; padding: 9px 12px; font-size: 13.5px; }

/* Training progress view (super admins) */
.tphead { display: flex; gap: 14px; align-items: center; }
.tpimg { width: 92px; height: 58px; border-radius: 8px; background-size: cover; background-position: center; flex: 0 0 auto; box-shadow: 0 1px 4px rgba(31, 35, 60, .18); }
html { scroll-behavior: smooth; }
