:root {
  /* Resolve green chrome over a SmartSuite grid.
     Direction B (chrome) + Direction C row height — approved 2026-09-07. */
  --green: #1A472A;
  --green-2: #2F7A4B;
  --green-dk: #12331E;
  --green-soft: #E4EDE6;

  --fg: #1F2329;
  --mut: #6B7280;
  --mut-2: #949BA3;

  --page: #FFFFFF;
  --soft: #F4F5F2;
  --hover: #FAFBF9;
  --bd: #E3E5E0;
  --bd-2: #D3D6D0;
  --bd-hair: #F0F1EC;
  --chip: #EDEEE9; /* keep in sync with CHIP_BG in colour.js */

  --warn: #C2410C;
  --warn-soft: #FDF0E8;
  --warn-bd: #F2D6C4;
  --warn-disabled: #E9C3B0;
  --warn-dark: #9A3309;

  --rowh: 44px;
  --font-sans: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;

  /* Grid track list — shared by .grid-head and .grid-row so columns line up
     across every project group inside the one scrolling grid. */
  --cols: 44px 280px minmax(300px, 1fr) 158px 156px 120px 64px 110px 132px 40px 36px;
  --grid-min: 1440px;
  /* Width of the three right-frozen columns: status 132 + comments 40 + delete 36 */
  --freeze-right: 208px;
}
* { box-sizing: border-box; }
body {
  margin: 0;
  font: 13.5px/1.5 var(--font-sans);
  color: var(--fg);
  background: var(--page);
}
button { font: inherit; cursor: pointer; }
[hidden] { display: none !important; }
.muted { color: var(--mut); font-size: 12.5px; }

/* App shell — fixed-height column so the grid gets its own scroll viewport.
   That is what lets the column header stay sticky while the body scrolls
   sideways: `position: sticky; top` cannot reach the page viewport from
   inside an overflow-x container. */
body.shell {
  height: 100dvh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
body.shell #app {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* ---------- SmartSuite top bar ---------- */

.ssbar {
  display: flex;
  align-items: center;
  gap: 14px;
  flex: 0 0 auto;
  height: 46px;
  padding: 0 14px;
  background: var(--green);
  color: #fff;
}
/* The status bar is translucent in standalone mode, so the app paints behind
   it. Without these the top bar sits under the clock and the Dynamic Island.
   Longhands after the padding shorthand above so they win the cascade. */
.ssbar {
  /* The height has to grow by the inset, not just the padding. box-sizing is
     border-box, so a fixed 46px box with a ~54px Dynamic Island inset as top
     padding pushes its own contents out of itself — which sliced the badge and
     title in half on a real iPhone. */
  height: calc(46px + env(safe-area-inset-top));
  padding-top: env(safe-area-inset-top);
  padding-left: max(16px, env(safe-area-inset-left));
  padding-right: max(16px, env(safe-area-inset-right));
}
/* The Resolve badge, top left. Until /icons/rw-badge.png exists the <img>
   removes itself and the olive disc (the badge's own ring colour) stands in. */
.ss-badge {
  width: 30px; height: 30px; border-radius: 50%;
  background: #605F55;
  box-shadow: 0 0 0 1.5px rgba(255,255,255,0.55);
  overflow: hidden;
  flex: 0 0 auto;
  display: block;
}
.ss-badge img { display: block; width: 100%; height: 100%; object-fit: cover; }
.ss-word { font-size: 14px; font-weight: 600; letter-spacing: -0.1px; white-space: nowrap; }
.ss-spacer { flex: 1; }
.ss-who { display: flex; align-items: center; gap: 9px; flex: 0 0 auto; }
.ss-name { text-align: right; line-height: 1.15; max-width: 160px; }
.ss-name b {
  display: block; font-size: 12px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ss-name i { display: block; font-style: normal; font-size: 10px; opacity: 0.8; }
.user-avatar {
  width: 28px; height: 28px; border-radius: 50%;
  background: #B8763A; color: #fff;
  display: grid; place-items: center;
  font-size: 11px; font-weight: 600; text-transform: uppercase;
  border: 1.5px solid rgba(255,255,255,0.5);
  flex: 0 0 auto;
}

/* ---------- View row ---------- */

.viewrow {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 0 0 auto;
  padding: 10px 16px;
  background: var(--page);
  border-bottom: 1px solid var(--bd);
}
.page-title { display: flex; flex-direction: column; line-height: 1.1; min-width: 0; }
.page-eyebrow {
  font-size: 10.5px; font-weight: 600; letter-spacing: 0.9px;
  text-transform: uppercase; color: var(--mut);
}
.page-title h1 { margin: 2px 0 0; font-size: 17px; font-weight: 600; letter-spacing: -0.2px; }
.view-spacer { flex: 1; }
.sync { font-size: 11.5px; color: var(--mut); white-space: nowrap; }
/* "New version" strip. Fixed to the viewport, not in .sync, because .sync is
   hidden on phones and phones are where a stale build lives longest. */
.update-banner {
  position: fixed; left: 50%; bottom: 16px; transform: translateX(-50%);
  z-index: 60; background: var(--fg); color: var(--page);
  border: none; border-radius: 999px; padding: 9px 16px;
  font: inherit; font-size: 13px; box-shadow: 0 6px 20px rgba(0,0,0,0.25);
}
.btn-new {
  background: var(--green);
  color: #fff;
  border: none;
  border-radius: 6px;
  padding: 8px 14px;
  font-size: 12.5px;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}
.btn-new:hover { background: var(--green-dk); }
.btn-icon {
  background: transparent;
  border: 1px solid var(--bd);
  border-radius: 6px;
  width: 30px; height: 30px;
  color: var(--mut);
  font-size: 13px;
  display: grid; place-items: center;
  flex: 0 0 auto;
}
.btn-icon:hover { border-color: var(--bd-2); color: var(--fg); background: var(--soft); }
.btn-ghost {
  background: transparent;
  border: 1px solid var(--bd);
  border-radius: 6px;
  padding: 7px 11px;
  font-size: 12.5px;
  color: var(--fg);
  white-space: nowrap;
}
.btn-ghost:hover { background: var(--soft); border-color: var(--bd-2); }
:where(button, a, input, select, textarea):focus-visible {
  outline: 2px solid var(--green-2);
  outline-offset: 1px;
}

/* ---------- KPI band ---------- */

.welcome-band {
  display: flex;
  flex: 0 0 auto;
  background: var(--page);
  border-bottom: 1px solid var(--bd);
}
.welcome-slot {
  flex: 1 1 0;
  min-width: 0;
  padding: 14px 16px;
  border-right: 1px solid var(--bd);
}
.welcome-slot:last-child { border-right: none; }
.welcome-label {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.9px;
  text-transform: uppercase;
  color: var(--mut);
}
.welcome-value {
  font-size: 26px;
  font-weight: 600;
  letter-spacing: -0.7px;
  line-height: 1.15;
  margin-top: 3px;
  font-variant-numeric: tabular-nums;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.welcome-value.warn { color: var(--warn); }
.welcome-italic { color: var(--mut); }

/* ---------- Filter bar ---------- */

.filter-bar {
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
  flex: 0 0 auto;
  padding: 12px 16px;
  background: var(--soft);
  border-bottom: 1px solid var(--bd);
}
.filter-search {
  /* Capped so search and pills read as one cluster on the left, rather than
     a rope across a wide screen with the pills stranded at the far end. */
  flex: 0 1 360px;
  min-width: 200px;
  /* Option B (2026-09-09): the filter bar was 47px, the thinnest band on a page
     whose other rows run 78 / 56 / 44, and the field inside it was 28px - shorter
     than a single grid row. 40px rather than the phone's 44: a mouse does not need
     a finger's target. 13.5px text matches the view row and the group bands, so
     the chrome finally runs at one size instead of three.
     The phone overrides height, radius, padding and the icon's placement through
     ".filter-bar .filter-search" (0,2,0), which outranks this (0,1,0) rule. */
  height: 40px;
  padding: 0 14px 0 38px;
  border: 1px solid var(--bd-2);
  border-radius: 9px;
  background: var(--page);
  /* Must stay AFTER the background shorthand above, which would otherwise reset
     background-image to none. */
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236B7280' stroke-width='2.2' stroke-linecap='round'><circle cx='11' cy='11' r='7'/><path d='M20 20l-3.6-3.6'/></svg>");
  background-repeat: no-repeat;
  background-position: 12px center;
  background-size: 16px 16px;
  box-shadow: 0 1px 2px rgba(20, 30, 20, 0.06);
  font: inherit;
  font-size: 13.5px;
  color: var(--fg);
}
.filter-search::placeholder { color: var(--mut-2); }
.filter-search:focus { outline: 2px solid var(--green-2); outline-offset: -1px; border-color: var(--green-2); }
.filter-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 34px;
  padding: 0 12px;
  border-radius: 7px;
  border: 1px solid var(--bd);
  background: var(--page);
  color: var(--fg);
  font-size: 13px;
  white-space: nowrap;
}
.filter-pill:hover { border-color: var(--bd-2); }
.filter-pill.active { border-color: var(--green-2); background: var(--green-soft); }
.filter-pill-label { font-weight: 500; }
.filter-pill-badge {
  display: inline-grid; place-items: center;
  min-width: 16px; height: 16px; padding: 0 4px;
  border-radius: 8px;
  background: var(--green-2);
  color: #fff;
  font-size: 10.5px; font-weight: 600;
}
.filter-pill-value {
  font-weight: 500;
  max-width: 140px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.filter-pill-caret { color: var(--mut); font-size: 10px; }
.filter-pill-swatches { display: inline-flex; }
.filter-pill-dot {
  width: 9px; height: 9px; border-radius: 50%;
  border: 1.5px solid var(--page);
  display: inline-block;
}
.filter-pill-swatches .filter-pill-dot + .filter-pill-dot { margin-left: -3px; }
.filter-reset {
  height: 28px;
  padding: 0 11px;
  border-radius: 6px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--mut);
  font-size: 12.5px;
}
.filter-reset:hover { background: var(--page); border-color: var(--bd); color: var(--fg); }
/* Sits to the right of the pills, away from the filters it is not part of. */
.filter-foldall {
  margin-left: auto;
  height: 34px;
  padding: 0 12px;
  border-radius: 7px;
  border: 1px solid var(--bd);
  background: var(--page);
  color: var(--green-2);
  font-size: 13px;
  font-weight: 500;
  white-space: nowrap;
}
.filter-foldall:hover { background: var(--green-soft); border-color: var(--green-2); }
.filter-empty { margin: 32px auto; text-align: center; color: var(--mut); }

/* ---------- Grid viewport ---------- */

.gridwrap {
  flex: 1;
  min-height: 0;
  overflow: auto;
  background: var(--page);
}
.gridwrap {
  padding-bottom: env(safe-area-inset-bottom);
}
.grid { min-width: 0; }
.grid.has-rows { min-width: var(--grid-min); }

/* One header per OPEN project, rendered under its band and above its own rows,
   so the column names are never more than one row from the values. It is not
   sticky any more: several can be on screen at once, and a sticky one would
   pile the open projects' headers on top of each other at the top edge.
   Quieter than the old page-wide header, because it no longer has to read as
   the page's masthead. */
.grid-head {
  display: grid;
  grid-template-columns: var(--cols);
  background: var(--hover);
  border-bottom: 1px solid var(--bd);
}
.grid-head-cell {
  padding: 8px 10px;
  font-size: 12px;
  font-weight: 600;
  color: #4B5563;
  border-right: 1px solid var(--bd);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  user-select: none;
}
.grid-head-cell:last-child { border-right: none; }
.grid-head-cell.num {
  position: sticky;
  left: 0;
  z-index: 6;
  background: var(--soft);
  text-align: center;
  color: var(--mut);
  box-shadow: 2px 0 4px rgba(20, 30, 24, 0.05);
}
.grid-head-cell.right { text-align: right; }
.grid-head-cell.status-col,
.grid-head-cell.comments-col,
.grid-head-cell.delete-col {
  position: sticky;
  z-index: 6;
  background: var(--soft);
}
.grid-head-cell.status-col { right: 76px; box-shadow: -2px 0 4px rgba(20, 30, 24, 0.05); }
.grid-head-cell.comments-col { right: 36px; }
.grid-head-cell.delete-col { right: 0; }
.grid-head-cell.sortable {
  cursor: pointer;
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  color: #4B5563;
  border: none;
  border-right: 1px solid var(--bd);
  text-align: left;
  display: block;
}
.grid-head-cell.sortable.right { text-align: right; }
.grid-head-cell.sortable:hover { color: var(--green-2); }
.grid-head-arrow { color: var(--green-2); font-size: 9px; margin-left: 4px; }

/* Project group band */
.gband {
  padding: 9px 14px;
  background: var(--page);
  border-top: 1px solid var(--bd);
  border-bottom: 1px solid var(--bd);
}
/* Labelled facts in fixed columns, the same on every band, so the eye can run
   down the page and compare projects. Stops growing past 1500px so the facts
   stay near the code, where reading starts. */
.gband-inner {
  display: grid;
  grid-template-columns: minmax(240px, 1.2fr) minmax(160px, 1fr) 100px 190px 250px auto;
  align-items: center;
  gap: 18px;
  position: sticky;
  left: 14px;
  width: calc(100vw - 28px);
  max-width: 1500px;
}
.gband-ident { display: flex; align-items: center; gap: 9px; min-width: 0; }
.gband-fact { display: flex; flex-direction: column; line-height: 1.15; min-width: 0; }
.gband-fact-label {
  font-size: 9.5px; font-weight: 600; letter-spacing: 0.8px;
  text-transform: uppercase; color: var(--mut-2);
}
.gband-fact-value {
  font-size: 12.5px; color: var(--fg); margin-top: 3px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  font-variant-numeric: tabular-nums;
}
.gband-fact-value.n { font-weight: 600; }
.gband-toggle {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  background: transparent;
  border: none;
  padding: 0;
  color: inherit;
}
.gband-chev { color: var(--mut); font-size: 9px; width: 9px; }
.gband-code {
  background: var(--green-soft);
  color: var(--green);
  border-radius: 5px;
  padding: 3px 9px;
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
}
.gband-rest { font-size: 12.5px; color: var(--fg); white-space: nowrap; }
/* Shown on a project where this person is a team member, not the PM. */
.gband-role {
  font-size: 10.5px; font-weight: 500; color: var(--mut);
  border: 1px solid var(--bd-2); border-radius: 999px; padding: 2px 8px;
  white-space: nowrap;
}
.cell.static { cursor: default; }

/* Phone-only caption under an open project (client + period). Declared here,
   ahead of the 720px block that shows it, so the default cannot lose a
   same-specificity tie to the rule that turns it on. */
.gband-open { display: none; }

/* Project Brief — a caption, not a section. No label and no rule: it is quiet
   context under the project code, shown only while the group is expanded. */
.gband-brief {
  display: flex;
  gap: 8px;
  align-items: baseline;
  margin-top: 6px;
  padding-left: 18px; /* lines up with the project code chip */
  position: sticky;
  left: 14px;
  width: max-content;
  max-width: calc(100vw - 44px);
}
.gband-brief-text {
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--mut);
  max-width: 92ch;
  min-width: 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.gband-brief.open .gband-brief-text {
  display: block;
  -webkit-line-clamp: none;
  line-clamp: none;
  white-space: pre-wrap;
}
.gband-brief-more {
  background: transparent;
  border: none;
  padding: 0;
  font-size: 11.5px;
  color: var(--mut-2);
  flex: 0 0 auto;
  align-self: flex-end;
}
.gband-brief-more:hover { color: var(--green-2); text-decoration: underline; }
.gband-add {
  justify-self: end;
  background: transparent;
  border: 1px solid var(--bd);
  border-radius: 6px;
  color: var(--green-2);
  font-size: 11.5px;
  font-weight: 500;
  padding: 4px 10px;
  white-space: nowrap;
}
.gband-add:hover { background: var(--green-soft); border-color: var(--green-2); }
/* A member's band renders the same button, hidden, so the band's last (auto)
   grid track measures identically on every project. Without it the member's
   slot collapsed and the freed width flowed into the fr tracks, knocking that
   band's facts out of line with the rest. */
.gband-add-ghost { visibility: hidden; }

.progress { display: flex; align-items: center; gap: 8px; white-space: nowrap; }
.progress-text { font-size: 12.5px; color: var(--fg); font-variant-numeric: tabular-nums; }
.progress-track { width: 90px; height: 4px; border-radius: 2px; background: var(--bd); overflow: hidden; }
.progress-fill { height: 100%; background: var(--green-2); transition: width 0.35s ease; }
.progress-overdue { font-size: 11.5px; color: var(--warn); font-weight: 500; }

/* ---------- Rows ---------- */

.grid-row {
  display: grid;
  grid-template-columns: var(--cols);
  border-bottom: 1px solid var(--bd);
  background: var(--page);
  min-height: var(--rowh);
}
.grid-row:hover { background: var(--hover); }
.grid-row.overdue { background: #FDF7F3; }
.grid-row.overdue:hover { background: #FBF0E9; }

.cell {
  padding: 0 12px;
  border-right: 1px solid var(--bd);
  display: flex;
  align-items: center;
  min-width: 0;
  overflow: hidden;
  cursor: pointer;
  font-size: 13px;
  color: var(--fg);
}
.cell:last-child { border-right: none; }
.cell-text {
  display: block;
  width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.row-num {
  position: sticky;
  left: 0;
  z-index: 4;
  background: var(--page);
  justify-content: center;
  color: var(--mut);
  font-size: 11.5px;
  font-variant-numeric: tabular-nums;
  cursor: default;
  box-shadow: 2px 0 4px rgba(20, 30, 24, 0.05);
}
.grid-row:hover .row-num { background: var(--hover); }
.grid-row.overdue .row-num { background: #FDF7F3; }
.grid-row.overdue:hover .row-num { background: #FBF0E9; }

/* Status is what PMs scan for, so it stays on screen when the grid scrolls
   sideways — at 1024px it was 421px off the right edge. Comments and delete
   ride along so the frozen group is contiguous. */
.cell.row-status,
.cell.row-comments,
.cell.row-delete {
  position: sticky;
  z-index: 3;
  background: var(--page);
}
.cell.row-status { right: 76px; box-shadow: -2px 0 4px rgba(20, 30, 24, 0.05); }
.cell.row-comments { right: 36px; }
.cell.row-delete { right: 0; }
.grid-row:hover .row-status,
.grid-row:hover .row-comments,
.grid-row:hover .row-delete { background: var(--hover); }
.grid-row.overdue .row-status,
.grid-row.overdue .row-comments,
.grid-row.overdue .row-delete { background: #FDF7F3; }
.grid-row.overdue:hover .row-status,
.grid-row.overdue:hover .row-comments,
.grid-row.overdue:hover .row-delete { background: #FBF0E9; }
.row-title { font-weight: 500; }
.row-objective { color: var(--mut); }
.row-objective.empty { color: var(--mut-2); }
.row-units { justify-content: flex-end; font-variant-numeric: tabular-nums; }
.row-location { color: var(--fg); }
.row-comments, .row-delete {
  justify-content: center;
  color: var(--mut-2);
  font-size: 12.5px;
}
.row-comments:hover { color: var(--green-2); }
.row-delete-icon { opacity: 0.4; transition: opacity 0.12s ease; }
.grid-row:hover .row-delete-icon { opacity: 0.7; }
.row-delete:hover .row-delete-icon { opacity: 1; }
.row-delete:hover { color: var(--warn); }

/* Due date + status badge */
.due {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  min-width: 0;
}
.due-badge {
  width: 17px; height: 17px; border-radius: 50%;
  display: grid; place-items: center;
  font-size: 9.5px; font-weight: 700; color: #fff;
  flex: 0 0 auto;
}
/* Contrast-checked against their own fill: white on the dark ones, dark ink on
   the light ones. All four failed AA as white-on-mid-tone before this. */
.due-badge.ok { background: #2E7D48; color: #fff; }
.due-badge.late { background: #C62828; color: #fff; }
.due-badge.soon { background: #E8A33D; color: #422C05; }
.due-badge.far { background: #B7BEC5; color: #2B3138; }

/* Type / location chips */
.dot-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--chip);
  border-radius: 999px;
  padding: 3px 10px 3px 8px;
  font-size: 12px;
  max-width: 100%;
}
.dot-chip-dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 auto; }
.dot-chip-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Status — fills the whole cell, SmartSuite style */
.cell.row-status { padding: 0; }
.status-block {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  align-self: stretch;
  min-height: var(--rowh);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.1px;
  padding: 0 8px;
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Team avatars */
.team-avatars { display: inline-flex; align-items: center; }
.team-avatar {
  width: 21px; height: 21px; border-radius: 50%;
  color: #fff;
  display: inline-grid; place-items: center;
  font-size: 9px; font-weight: 600;
  border: 1.5px solid var(--page);
  text-transform: uppercase;
}
.team-avatars .team-avatar + .team-avatar { margin-left: -6px; }
.team-overflow { margin-left: 5px; font-size: 11px; color: var(--mut); }
.team-empty { color: var(--mut-2); font-size: 12px; }

.add-row {
  padding: 9px 14px 9px 56px;
  font-size: 12.5px;
  color: var(--mut);
  background: var(--page);
  border-bottom: 1px solid var(--bd);
  cursor: pointer;
  position: sticky;
  left: 0;
  max-width: 100vw;
}
.add-row:hover { background: var(--soft); color: var(--green-2); }
/* An expanded project with nothing in it yet. Same shape as .add-row above. */
.empty-group {
  padding: 9px 14px 9px 56px;
  font-size: 12.5px;
  color: var(--mut);
  background: var(--page);
  border-bottom: 1px solid var(--bd);
  position: sticky;
  left: 0;
  max-width: 100vw;
}

/* ---------- Objective panel ---------- */

.objective-panel {
  background: var(--soft);
  border-bottom: 1px solid var(--bd);
  border-left: 3px solid var(--green-2);
  padding: 12px 16px;
  position: sticky;
  left: 0;
  max-width: 100vw;
}
.objective-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
  max-width: 1100px;
}
@media (max-width: 900px) {
  .objective-grid { grid-template-columns: 1fr; gap: 14px; }
}
.objective-col { min-width: 0; }
.objective-hint-row {
  margin-top: 9px;
  font-size: 11.5px;
  color: var(--mut-2);
}
.objective-panel { position: relative; }
.objective-header { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.objective-spacer { flex: 1; }
.objective-btn {
  background: var(--page);
  border: 1px solid var(--bd);
  border-radius: 6px;
  padding: 4px 10px;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--fg);
}
.objective-btn:hover { background: var(--soft); border-color: var(--bd-2); }
.objective-btn.primary { background: var(--green); color: #fff; border-color: var(--green); }
.objective-btn.primary:hover { background: var(--green-dk); border-color: var(--green-dk); }
.objective-close {
  position: absolute;
  top: 8px;
  right: 12px;
  background: transparent;
  border: none;
  font-size: 18px;
  line-height: 1;
  color: var(--mut);
  padding: 0 4px;
}
.objective-close:hover { color: var(--fg); }
.objective-actions { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.objective-hint { flex: 1; font-size: 11px; color: var(--mut-2); }
.objective-label {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.9px;
  text-transform: uppercase;
  color: var(--mut);
}
.objective-body {
  font-size: 13.5px;
  color: var(--fg);
  line-height: 1.6;
  cursor: text;
  white-space: pre-wrap;
  max-width: 78ch;
}
.objective-body.empty { color: var(--mut-2); }
.objective-body[data-editing="1"] { padding: 0; }
.objective-edit {
  width: 100%;
  max-width: 78ch;
  padding: 7px 9px;
  border: 1px solid var(--bd);
  border-radius: 6px;
  font: inherit;
  resize: none;
  background: var(--page);
  color: var(--fg);
}
.objective-edit:focus { outline: 2px solid var(--green-2); outline-offset: -1px; border-color: var(--green-2); }

/* ---------- Inline cell edit ---------- */

.inline-edit {
  width: 100%;
  padding: 0;
  margin: 0;
  border: 1px solid transparent;
  border-radius: 2px;
  font: inherit;
  color: inherit;
  background: transparent;
  line-height: inherit;
}
.inline-edit:focus {
  outline: 2px solid var(--green-2);
  outline-offset: -1px;
  background: var(--page);
}
textarea.inline-edit { resize: none; overflow-y: hidden; display: block; }
.cell[data-editing="1"] { padding: 0 12px; }

/* ---------- Popovers ---------- */

.editor-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(24, 30, 26, 0.18);
  z-index: 30;
  backdrop-filter: blur(1px);
  -webkit-backdrop-filter: blur(1px);
}
.editor-popover {
  position: absolute;
  background: var(--page);
  border: 1px solid var(--bd);
  border-radius: 8px;
  box-shadow: 0 12px 32px rgba(20, 30, 24, 0.14);
  padding: 12px;
  z-index: 31;
  min-width: 240px;
  max-width: 360px;
}
/* Popovers with no anchor (delete, create) sit centred near the top. */
.editor-popover.centred {
  position: fixed;
  top: 80px;
  left: 50%;
  transform: translateX(-50%);
}
.editor-popover.delete-confirm.centred { top: 20vh; }
.editor-popover label {
  display: block;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.9px;
  text-transform: uppercase;
  color: var(--mut);
  margin-bottom: 4px;
}
.editor-popover input,
.editor-popover select,
.editor-popover textarea {
  width: 100%;
  padding: 7px 9px;
  border: 1px solid var(--bd);
  border-radius: 6px;
  font: inherit;
  margin-bottom: 8px;
  background: var(--page);
  color: var(--fg);
}
.editor-popover input:focus,
.editor-popover select:focus,
.editor-popover textarea:focus {
  outline: 2px solid var(--green-2);
  outline-offset: -1px;
  border-color: var(--green-2);
}
.editor-popover .editor-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 4px; }
.editor-popover button { font: inherit; font-size: 12.5px; padding: 7px 13px; border-radius: 6px; }
.editor-popover button:not(.primary):not(.danger) {
  background: transparent;
  border: 1px solid var(--bd);
  color: var(--fg);
}
.editor-popover button:not(.primary):not(.danger):hover { background: var(--soft); }
.editor-popover button.primary {
  background: var(--green);
  color: #fff;
  border: 1px solid var(--green);
  font-weight: 600;
}
.editor-popover button.primary:hover { background: var(--green-dk); border-color: var(--green-dk); }
.team-picker {
  max-height: 180px;
  overflow-y: auto;
  border: 1px solid var(--bd);
  border-radius: 6px;
  padding: 6px;
  margin-bottom: 8px;
  background: var(--soft);
}
.team-picker label {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 0;
  margin: 0;
  font-size: 13px;
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
  color: var(--fg);
  cursor: pointer;
}
.team-picker input[type="checkbox"] { width: auto; margin: 0; accent-color: var(--green-2); }
.editor-date-range { display: flex; gap: 6px; }
.editor-date-range input { width: 100%; margin-bottom: 0; }

/* Filter popover — .editor-popover is defined above and shares the chrome;
   these override only what differs. */
.editor-popover.filter-popover { min-width: 240px; max-width: 320px; padding: 8px; }
.filter-popover-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 2px 4px 8px;
  border-bottom: 1px solid var(--bd);
  margin-bottom: 6px;
}
.filter-popover-title {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.9px;
  text-transform: uppercase;
  color: var(--mut);
}
.filter-popover-clear {
  background: transparent;
  border: none;
  color: var(--green-2);
  font-size: 11.5px;
  font-weight: 500;
  padding: 2px 4px;
}
.filter-popover-clear:hover { text-decoration: underline; }
.filter-popover-search { width: 100%; padding: 6px 8px; border: 1px solid var(--bd); border-radius: 6px; font: inherit; margin-bottom: 6px; }
.filter-popover-list { max-height: 260px; overflow-y: auto; display: flex; flex-direction: column; gap: 1px; }
.filter-popover label.filter-popover-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 8px;
  margin: 0;
  border-radius: 6px;
  cursor: pointer;
  font-size: 12.5px;
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
  color: var(--fg);
}
.filter-popover-row:hover { background: var(--soft); }
.filter-popover-row.checked,
.filter-popover-row.checked:hover { background: var(--green-soft); }
.filter-popover-row input[type="checkbox"],
.filter-popover-row input[type="radio"] {
  margin: 0; flex: 0 0 auto; width: 14px; height: 14px; padding: 0;
  cursor: pointer; accent-color: var(--green-2);
}
.filter-popover-dot { display: inline-block; flex: 0 0 10px; width: 10px; height: 10px; border-radius: 50%; border: 1px solid var(--bd); }
.filter-popover-text { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.filter-popover-empty { padding: 12px; text-align: center; color: var(--mut); font-size: 12px; }

/* ---------- Delete confirmation ---------- */

.delete-confirm { min-width: 320px; max-width: 380px; padding: 18px 18px 14px; }
.delete-confirm-title { font-size: 16px; font-weight: 600; color: var(--fg); margin-bottom: 8px; }
.delete-confirm-name {
  font-size: 13.5px;
  font-weight: 500;
  color: var(--warn);
  background: var(--warn-soft);
  border: 1px solid var(--warn-bd);
  border-radius: 6px;
  padding: 8px 12px;
  margin-bottom: 10px;
  word-break: break-word;
}
.delete-confirm-note { font-size: 12.5px; color: var(--mut); margin-bottom: 14px; line-height: 1.55; }
.delete-confirm-prompt {
  display: block;
  font-size: 12.5px;
  font-weight: 400;
  color: var(--fg);
  margin-bottom: 6px;
  letter-spacing: normal;
  text-transform: none;
}
.delete-confirm-prompt strong {
  background: var(--chip);
  color: var(--warn);
  padding: 1px 6px;
  border-radius: 4px;
  font-size: 12px;
  font-weight: 600;
}
.delete-confirm-input {
  width: 100%;
  padding: 8px 10px;
  border: 1px solid var(--bd);
  border-radius: 6px;
  font: inherit;
  font-size: 13px;
  margin-bottom: 10px;
  background: var(--page);
}
.delete-confirm-input:focus { outline: 2px solid var(--warn); outline-offset: -1px; border-color: var(--warn); }
.editor-popover button.danger {
  background: var(--warn);
  color: #fff;
  border: 1px solid var(--warn);
  font-weight: 600;
}
.editor-popover button.danger:disabled {
  background: var(--warn-disabled);
  border-color: var(--warn-disabled);
  cursor: not-allowed;
}
.editor-popover button.danger:not(:disabled):hover {
  background: var(--warn-dark);
  border-color: var(--warn-dark);
}

/* ---------- Comments panel ---------- */

#comments-panel {
  position: fixed;
  top: 0; right: 0;
  width: 360px;
  height: 100dvh;
  background: var(--page);
  border-left: 1px solid var(--bd);
  display: flex;
  flex-direction: column;
  z-index: 20;
  box-shadow: -4px 0 16px rgba(20, 30, 24, 0.07);
}
/* The panel is fixed at top:0 and the app paints behind the status bar in
   standalone mode, so without the inset this header — and the only way out of
   a full-width panel — sits under the Dynamic Island, where iOS takes the
   touch. Same failure the top bar had; this was the one overlay that never got
   the treatment. The side insets are for landscape. */
#comments-panel header {
  padding: max(14px, env(safe-area-inset-top)) max(16px, env(safe-area-inset-right))
           14px max(16px, env(safe-area-inset-left));
  border-bottom: 1px solid var(--bd);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 12px;
  background: var(--soft);
}
#comments-title { font-size: 15px; font-weight: 600; color: var(--fg); }
/* 44px is Apple's minimum touch target; the glyph alone was about 22px, so
   even once it cleared the status bar it was a poor thing to aim at. Negative
   margin keeps the visual padding of the header unchanged. */
#comments-close {
  background: transparent;
  border: none;
  font-size: 22px;
  color: var(--mut);
  line-height: 1;
  flex: 0 0 auto;
  width: 44px;
  height: 44px;
  /* Only the horizontal pull-in: a negative TOP margin would drag the button
     back up through the safe-area padding and under the Dynamic Island, which
     is the whole bug this rule exists to fix. */
  margin-right: -12px;
  display: grid;
  place-items: center;
}
#comments-close:hover { color: var(--fg); }
#comments-list {
  flex: 1;
  overflow: auto;
  padding: 12px max(16px, env(safe-area-inset-right)) 12px max(16px, env(safe-area-inset-left));
}
.comment { margin-bottom: 14px; padding-left: 10px; border-left: 2px solid var(--green-soft); }
.comment .muted {
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.9px;
  text-transform: uppercase;
  color: var(--mut);
  margin-bottom: 4px;
}
.comment > div:not(.muted) { font-size: 13.5px; color: var(--fg); line-height: 1.55; }
#comments-form {
  border-top: 1px solid var(--bd);
  /* Clears the home indicator; the panel is 100dvh so the form sits on it. */
  padding: 12px max(16px, env(safe-area-inset-right))
           calc(12px + env(safe-area-inset-bottom)) max(16px, env(safe-area-inset-left));
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: var(--soft);
}
#comments-form textarea {
  padding: 8px 10px;
  border: 1px solid var(--bd);
  border-radius: 6px;
  resize: vertical;
  font: inherit;
  background: var(--page);
  color: var(--fg);
}
#comments-form textarea:focus { outline: 2px solid var(--green-2); outline-offset: -1px; border-color: var(--green-2); }
#comments-form button {
  align-self: flex-end;
  background: var(--green);
  color: #fff;
  border: 1px solid var(--green);
  padding: 7px 14px;
  border-radius: 6px;
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
}
#comments-form button:hover { background: var(--green-dk); }
.comment-btn { cursor: pointer; }

/* ---------- Login / error pages ---------- */

body.login {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100dvh;
  background: var(--soft);
  padding: 20px;
  height: auto;
  overflow: auto;
}
.login-card {
  background: var(--page);
  padding: 32px;
  border: 1px solid var(--bd);
  border-radius: 10px;
  max-width: 380px;
  width: 100%;
  box-shadow: 0 4px 24px rgba(20, 30, 24, 0.06);
  text-align: center;
}
.login-mark {
  width: 40px; height: 40px;
  margin: 0 auto 14px;
  border-radius: 8px;
  background: var(--green);
  color: #fff;
  display: grid; place-items: center;
  font-size: 17px; font-weight: 700;
}
.login-card h1 { margin: 0 0 6px; font-size: 20px; font-weight: 600; letter-spacing: -0.3px; color: var(--fg); }
.login-card p, .login-card .muted { font-size: 12.5px; color: var(--mut); margin: 4px 0; }
.login-name { font-size: 19px; font-weight: 600; margin: 6px 0 18px; color: var(--fg); }
.login-card label {
  display: block;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.9px;
  text-transform: uppercase;
  color: var(--mut);
  margin-bottom: 6px;
  text-align: left;
}
.login-card input {
  width: 100%;
  padding: 12px;
  border: 1px solid var(--bd);
  border-radius: 6px;
  margin: 4px 0 8px;
  font: inherit;
  font-size: 22px;
  letter-spacing: 6px;
  text-align: center;
  background: var(--page);
  color: var(--fg);
}
.login-card input:focus { outline: 2px solid var(--green-2); outline-offset: -1px; border-color: var(--green-2); }
.login-card button {
  width: 100%;
  padding: 12px;
  background: var(--green);
  color: #fff;
  border: 1px solid var(--green);
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  margin-top: 12px;
  border-radius: 6px;
}
.login-card button:hover:not(:disabled) { background: var(--green-dk); }
.login-card button:disabled { opacity: 0.5; cursor: not-allowed; }
.login-error { color: var(--warn); font-size: 12.5px; min-height: 18px; margin-top: 6px; text-align: center; }

.load-error {
  text-align: center;
  margin: 40px auto;
  max-width: 420px;
  background: var(--page);
  border: 1px solid var(--bd);
  border-radius: 10px;
  padding: 24px;
}
.load-error button.primary {
  background: var(--green);
  color: #fff;
  border: 1px solid var(--green);
  padding: 9px 16px;
  border-radius: 6px;
  margin-top: 12px;
  font-weight: 600;
}
.inline-link {
  background: none;
  border: none;
  color: var(--green-2);
  text-decoration: underline;
  padding: 0;
  font: inherit;
}

/* Mobile-only elements — shown inside the media query below */
.row-objective-mobile { display: none; }
.row-remarks-mobile { display: none; }

/* ---------- Mobile: stacked cards ---------- */

@media (max-width: 1100px) {
  /* Location and Team come out of the grid rather than off the right-hand
     edge. Both are one tap away in the expanded row, and losing them is what
     lets a 1024px window show every remaining column without sideways scroll. */
  :root {
    --cols: 44px 220px minmax(240px, 1fr) 158px 156px 64px 132px 40px 36px;
    --grid-min: 1090px;
  }
  .grid-head-cell.location-col.location-col,
  .grid-head-cell.team-col,
  .cell.row-location,
  .cell.row-team { display: none; }
  .gband-inner { display: flex; flex-wrap: wrap; gap: 8px 18px; width: auto; max-width: none; }
  .gband-ident { flex: 1 1 100%; }
  .gband-add { margin-left: auto; }
}

/* ---------- Phone create button (FAB) ---------- */
/* Declared here, ahead of the 720px block below, on purpose. Both this rule
   and the media query's ".fab { display: block; }" are bare ".fab" —
   identical (0,1,0) specificity — so whichever is later in the file wins the
   tie regardless of the media query's own condition. Case in point: this
   ruleset first lived after the record-sheet section near the end of the
   file, past the 720px block, and its "display: none" silently beat the
   media query's "display: block" at every phone width — caught only by
   checking computed style in the browser at 375px, not by reading the CSS.
   Keeping the full ruleset ahead of the block that turns it on is what makes
   the win order-independent instead of a second fragile tie.
   The button is unconditionally rendered by render() (guarded there by the
   D-005 PM check, not by CSS), so a real display:none default is required —
   relying on the media query being the only place .fab is styled would show
   it on desktop the moment that assumption broke.
   z-index 15 is deliberate, not the 40 a first draft would reach for:
   .editor-backdrop/.editor-popover sit at 30/31 (see above) and
   #comments-panel at 20 — both render as full-viewport-or-full-width
   opaque/semi-opaque overlays at the phone widths where the FAB is visible.
   At 40 the FAB would float on top of an open create form, the record
   sheet, or the comments panel and stay tappable through them. 15 sits
   below both so either overlay naturally covers and blocks it, and still
   above the grid's own stacking (max 6) so it isn't buried under card
   content. */
.fab {
  display: none;
  position: fixed;
  right: max(16px, env(safe-area-inset-right));
  bottom: calc(16px + env(safe-area-inset-bottom));
  width: 56px;
  height: 56px;
  border-radius: 50%;
  border: none;
  background: var(--green);
  color: #fff;
  font-size: 26px;
  line-height: 1;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.28);
  z-index: 15;
}
.fab:active { background: var(--green-dk); }

@media (max-width: 720px) {
  body.shell { height: auto; overflow: visible; display: block; }
  body.shell #app { display: block; }
  .gridwrap { overflow: visible; min-height: 0; }
  /* .grid.has-rows outranks .grid, so name it here or the phone grid stays 1440px wide. */
  .grid, .grid.has-rows { min-width: 0; }

  .ssbar { padding: 0 12px; gap: 10px; }
  /* The shorthand above would silently swallow the safe-area longhands
     set on the base .ssbar rule (same specificity, later in the cascade) —
     restate them here so iPhone-width viewports still clear the notch. */
  .ssbar {
    padding-top: env(safe-area-inset-top);
    padding-left: max(12px, env(safe-area-inset-left));
    padding-right: max(12px, env(safe-area-inset-right));
  }
  .ss-name { display: none; }

  .viewrow { padding: 9px 12px; gap: 8px; }
  .sync { display: none; }
  #new-btn { display: none; }

  /* Three counts on one line. The tiles used to scroll sideways here, which
     hid Overdue — the one number that means "act now" — off the right edge.
     Signed-in and Completed are dropped (.aside): the name is already in the
     top bar, and a year total is context, not an action. */
  .welcome-band {
    flex-wrap: nowrap;
    overflow: visible;
    gap: 14px;
    align-items: baseline;
    padding: 7px 12px;
    background: var(--soft);
  }
  .welcome-slot.aside { display: none; }
  .welcome-slot {
    flex: 0 0 auto;
    display: flex;
    align-items: baseline;
    flex-direction: row-reverse;
    gap: 4px;
    border-right: none;
    padding: 0;
  }
  .welcome-label {
    font-size: 12px;
    font-weight: 400;
    letter-spacing: 0;
    text-transform: none;
    color: var(--mut);
  }
  .welcome-value { font-size: 13px; font-weight: 600; margin-top: 0; }

  .filter-bar { padding: 11px 12px; gap: 8px; }
  .filter-search { flex: 1 1 100%; }

  /* Option C (2026-09-09): the field was 28px tall holding 16px text, so the
     letters sat 6px from each edge and it read as a slot rather than a field.
     28px is also well under the 44px both Apple and Android ask for, on the
     control people tap most often. 44px leaves 14px of breathing room.

     The magnifier is a background-image rather than an element because the
     input is built in app.js as a bare <input> with no wrapper; drawing it in
     CSS avoids touching the markup. The site CSP only sets frame-ancestors,
     so the data: URI is not blocked (checked in src/public/_headers).

     Specificity: the base .filter-search (0,1,0) sets height: 28px earlier in
     the file, so this must be (0,2,0) to win - source order alone would not
     do it if the base rule ever moves. Font size stays 16px: anything under
     that makes iOS zoom the whole page on focus, which breaks the standalone
     PWA out of full screen. */
  .filter-bar .filter-search {
    height: 44px;
    border-radius: 10px;
    padding: 0 14px 0 40px;
    border-color: var(--bd-2);
    box-shadow: 0 1px 2px rgba(20, 30, 20, 0.06);
    background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236B7280' stroke-width='2.2' stroke-linecap='round'><circle cx='11' cy='11' r='7'/><path d='M20 20l-3.6-3.6'/></svg>");
    background-repeat: no-repeat;
    background-position: 13px center;
    background-size: 17px 17px;
  }
  /* The desktop base is 34px (Option B, 2026-09-09), which is also the better
     touch target here, so the phone no longer overrides it. Kept as an explicit
     restatement rather than deleted: it documents that 34px is deliberate on
     the phone too, and stops a future desktop change silently shrinking it. */
  .filter-pill,
  .filter-foldall { height: 34px; }

  .grid-head { display: none; }
  /* One line per project. Four labelled facts wrapped into two columns cost
     ~200px each, so seven projects was ~1400px of scrolling before the first
     deliverable. Client and period move to .gband-open below, shown only once
     the project is expanded — you get them when you are looking at that
     project rather than on all seven at once. */
  /* Option A (2026-09-09): the project leads. It was the same height as one
     deliverable and typographically quieter than its own children, so the page
     read as a flat alternating strip. The band now sits on a tinted strip with
     a heavier rule above it, and the horizontal padding moves from .gband to
     .gband-inner so the tint runs edge to edge. */
  .gband { padding: 0; background: var(--soft); border-top: 1px solid var(--bd-2); }
  .gband-inner {
    padding: 0 12px;
    position: static;
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    gap: 9px;
    width: auto;
    max-width: none;
    min-height: 56px;
  }
  .gband-ident { flex: 0 1 auto; min-width: 0; }
  .gband-fact.f-client,
  .gband-fact.f-count,
  .gband-fact.f-period { display: none; }
  .gband-fact.f-progress { flex: 0 0 auto; margin-left: auto; }
  .gband-fact.f-progress .gband-fact-label { display: none; }
  .gband-fact.f-progress .gband-fact-value { margin-top: 0; }
  .progress { gap: 6px; }
  .progress-text { font-size: 12px; color: var(--mut); }
  .progress-word { display: none; }
  .progress-track { width: 40px; }
  .progress-overdue { font-size: 11.5px; }
  .gband-toggle { flex-wrap: nowrap; align-items: center; text-align: left; min-width: 0; }
  .gband-rest { display: none; }
  .gband-code {
    white-space: nowrap;
    font-size: 15px;
    font-weight: 650;
    color: var(--fg);
    letter-spacing: -0.1px;
  }
  /* "Your deliverables" collided with the progress figure at 375px (-9px, i.e.
     overlapping) - JP hit this on a 375pt phone; at 393px it cleared by 11px,
     which is how it got past the first check. Short chip here, 41px clear. The
     full wording stays in the element's text for screen readers and in its
     title attribute for a hover. */
  .gband-role { font-size: 0; padding: 1px 7px; }
  .gband-role::after { content: "Yours"; font-size: 9.5px; }
  .gband-add { display: none; }
  .add-row { display: none; }

  /* A card, not a grid row wearing labels. Name on top, then one dense meta
     line. Field names are dropped: a status chip and a due badge say what
     they are by shape, and the record sheet labels everything properly. */
  .grid-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 10px;
    padding: 11px 12px 11px 22px;
    min-height: 0;
    cursor: pointer;
    /* The rail is what says "this belongs to the band above". Indent alone
       reads as a margin; a rail reads as containment. */
    margin-left: 9px;
    border-left: 3px solid var(--green-soft);
  }
  /* A green rail beside a warm-pink overdue card fights it. */
  .grid-row.overdue { border-left-color: var(--warn-bd); }
  .cell {
    padding: 0;
    border-right: none;
    display: block;
    width: auto;
  }
  .cell::before { content: none; }

  /* P1 (2026-09-09): the four card facts need 457px worst case and the card is
     329px wide at 375px, so they cannot share the meta line. The title is the
     only element with slack (it already clamps at two lines), so it gives up
     132px and the type chip sits beside the name - "what is this" next to
     "what is it called" - leaving status, due and the team on one comfortable
     line. Measured at 375px: cards a uniform 77px, 94px when a title wraps to
     two lines, and the chip keeps 10px clear of the title in every case. */
  .cell.row-title { flex: 1 1 calc(100% - 138px); min-width: 0; font-size: 14px; font-weight: 550; }
  .cell.row-title .cell-text {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    white-space: normal;
    overflow-wrap: anywhere;
  }
  .cell.row-status { order: 1; }
  .cell.row-due { order: 2; }
  .cell.row-type {
    order: 0;              /* same as the title's implicit 0: DOM order puts it second */
    margin-left: auto;     /* hard right, so the chips line up down the page */
    flex: 0 0 auto;
    /* Pinned to the title's FIRST line. Without this the row's align-items:
       center floats the chip to the middle of a two-line title. */
    align-self: flex-start;
    margin-top: 1px;
    /* The reservation above is 138px: the widest type in the workspace is
       "Accommodation" at 124px, plus the 10px gap. At 132px that one label
       wrapped below the title on its own line. The cap keeps a future longer
       type name truncating (.dot-chip-label already ellipses) rather than
       silently breaking the layout again. */
    max-width: 128px;
  }
  .cell.row-units { order: 4; justify-content: flex-start; }
  /* Task 5 hid .cell.row-team at widths under 1100px (a 1100px-block rule
     that also fires here); restore it locally so team avatars appear on
     the card without giving the 1100px query a second bound to track.
     margin-left: auto used to push the avatars to the right edge, which made
     a tidy column on the rows that fit and a stranded orphan on the rows that
     did not. Measured on live data at 393px: 5 of 22 cards overflow the meta
     line, because status + due + type + team runs to 457px worst case inside
     a 347px box (a 4-avatar cluster is ~90px, and "15 Sept -> 16 Sept" with a
     day badge is 135px). Flowing left means an overflowing cluster wraps to
     the start of the next line and reads as a continuation. */
  .cell.row-team { order: 5; margin-left: auto; display: block; }

  /* Everything else lives in the record sheet. */
  .cell.row-num,
  .cell.row-objective,
  .cell.row-location,
  .cell.row-units,
  .cell.row-comments,
  .cell.row-delete { display: none; }
  /* "Nobody assigned" does not need an em dash on a phone card. Dropping it
     also buys back the last of the width the indent costs. */
  .team-empty { display: none; }
  .row-objective-mobile,
  .row-remarks-mobile { display: none; }
  .row-num { box-shadow: none; }
  .cell.row-status,
  .cell.row-comments,
  .cell.row-delete { position: static; box-shadow: none; background: transparent; }
  .row-delete { display: none; }
  .cell.row-status { padding: 0; }
  /* A full-cell colour block makes no sense in a card — fall back to a pill. */
  .status-block {
    display: inline-flex;
    width: auto;
    min-height: 0;
    border-radius: 999px;
    padding: 3px 10px;
    font-size: 11.5px;
  }
  .row-units { justify-content: flex-start; }

  /* No per-cell quick edits on the phone card at all: every tap routes to
     the record sheet (see the row click handler in rowEl), which lists
     every field properly labelled. pointer-events: none here is what makes
     that true — without it, a tap on Status/Due/Type/Team would still hit
     that cell's own onclick (from cellEditable) before the row's click
     handler ever sees the event, opening a per-cell editor. Location and
     the others are already display: none above, so they don't need this. */
  .cell.row-title,
  .cell.row-units,
  .cell.row-objective,
  .cell.row-status,
  .cell.row-due,
  .cell.row-type,
  .cell.row-team { pointer-events: none; cursor: default; }
  .cell.row-objective { display: none; }
  .objective-panel { display: none; }

  /* Card no longer inlines objective/remarks (record sheet task shows them
     properly); display: none above already hides both, so this block only
     carries leftover text styling that stays dormant while hidden. */
  .row-objective-mobile {
    font-size: 13px;
    line-height: 1.5;
    color: var(--fg);
    padding: 6px 0 0;
    border-top: 1px dashed var(--bd);
    margin-top: 4px;
    white-space: pre-wrap;
  }
  .row-objective-mobile.muted { color: var(--mut); }

  .row-remarks-mobile {
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--mut);
    padding: 4px 0 0;
    white-space: pre-wrap;
  }
  .row-remarks-label {
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.9px;
    text-transform: uppercase;
    color: var(--mut-2);
  }

  .gband-open {
    display: flex;
    flex-wrap: nowrap;
    gap: 0 8px;
    padding: 0 12px 10px 30px;
    font-size: 12px;
    color: var(--mut);
    background: var(--soft);
    border-bottom: none;
    overflow: hidden;
  }
  /* This used to wrap on the projects with a long client name and not on the
     others, which is why bands measured anywhere from 78px to 99px and no two
     lined up. Forced to one line: the client name is the half that truncates,
     because half a date range is worse than no date range. */
  .gband-open span { white-space: nowrap; }
  .gband-open span:first-child { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
  .gband-open span + span { flex: 0 0 auto; }
  .gband-open span + span::before {
    content: "\00b7";
    margin-right: 8px;
    color: var(--mut-2);
  }
  .gband-brief { position: static; width: auto; max-width: none; }

  #comments-panel { width: 100%; }

  .fab { display: block; }

  /* The create-form popover (Task 8 covered the record-sheet field editors,
     not this one) inherits font-size: 13.5px from ".editor-popover input,
     select, textarea" at line ~793 via its "font: inherit" shorthand — under
     16px, iOS zooms the whole page on focus. Same selector list, same
     (0,1,1) specificity per selector as that base rule, so this wins purely
     by appearing later in the file; nothing between the two re-declares
     font-size on ".editor-popover input/select/textarea" at equal or higher
     specificity (checked by grep), so there is no ninth cascade trap hiding
     here. Scoped to this media query only, matching where the phone's
     primary create path (the "+" button) actually renders. */
  /* Every remaining focusable field on a phone. iOS zooms the whole page when
     a focused input's font is under 16px, and in a standalone PWA that zoom
     does not cleanly reset — it breaks the layout and drops the app out of
     full screen. JP hit this on the search bar.
     Each selector is written to outrank its own base rule rather than to win
     on source order:
       .filter-search           (0,1,0) at 12.5px  -> .filter-bar .filter-search   (0,2,0)
       .filter-popover-search   (0,1,0) at 13.5px  -> .filter-popover .…-search    (0,2,0)
       #comments-form textarea  (1,0,1) at 13.5px  -> same selector, later in file
     .delete-confirm-input (13px) needs nothing: it sits inside .editor-popover,
     so the (0,1,1) rule below already outranks its (0,1,0) base.
     ANY NEW INPUT NEEDS A LINE HERE. There is no test that catches this. */
  .filter-bar .filter-search,
  .filter-popover .filter-popover-search,
  #comments-form textarea {
    font-size: 16px;
  }

  .editor-popover input,
  .editor-popover select,
  .editor-popover textarea {
    font-size: 16px;
  }
}

/* ---------- Record sheet (mobile) ---------- */
/* Outside any media query: the sheet is opened by JS only on phone widths
   (see the click handler in rowEl), so it never needs a breakpoint of its
   own — it just needs to exist wherever openRecordSheet() runs.
   openPopover() adds "centred" whenever there is no anchor (true here), and
   .editor-popover.centred sets `transform: translateX(-50%)` for its
   near-the-top, half-width popovers. That transform is never touched by
   .centred's own selector losing the cascade — .record-sheet wins the tied
   specificity by appearing later for every property they both set (position,
   inset vs. top/left) — but `transform` is only declared on .centred, so
   without an explicit override here it survives untouched. Once inset: 0
   makes this element span the full viewport width, translateX(-50%) would
   shove half of it off-screen to the left. `transform: none` cancels that. */
.editor-popover.record-sheet {
  position: fixed;
  inset: 0;
  width: auto;
  max-width: none;
  max-height: none;
  border-radius: 0;
  transform: none;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding: 0;
}
.sheet-head {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: max(12px, env(safe-area-inset-top)) 14px 10px;
  background: var(--green);
  color: #fff;
}
.sheet-title { flex: 1; font-size: 16px; font-weight: 600; line-height: 1.35; }
/* Found sweeping every .sheet-* rule against .editor-popover's descendant
   rules (same trap as .sheet-option/.sheet-btn further down): the close "×"
   is a plain button (neither .primary nor .danger), so it's also matched by
   the pre-existing ".editor-popover button:not(.primary):not(.danger)"
   (0,3,1), one level above ".editor-popover button" (0,1,1) alone. Its only
   real DOM ancestors are .record-sheet and .sheet-head, so
   ".record-sheet .sheet-head .sheet-close" tops out at (0,3,0) -- still
   loses to (0,3,1) on the element component. Repeating the class
   (".sheet-close.sheet-close") adds a fourth class selector with no element,
   landing at (0,4,0), which beats (0,3,1) on b alone regardless of source
   order. Confirmed broken before this fix -- computed style measured
   border-radius: 6px, padding: 7px 13px, font-size: 12.5px (the × glyph at
   half its coded size), a 1px solid border that should not exist, and
   color: var(--fg) (near-invisible dark text on the green header) instead
   of white. */
.record-sheet .sheet-head .sheet-close.sheet-close {
  background: transparent;
  border: none;
  color: #fff;
  font-size: 26px;
  line-height: 1;
  padding: 0 4px;
}
.sheet-body {
  flex: 1;
  overflow-y: auto;
  padding-bottom: max(24px, env(safe-area-inset-bottom));
}
.sheet-row {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 52px;
  padding: 10px 14px;
  border-bottom: 1px solid var(--bd);
}
.sheet-row.stacked { flex-direction: column; align-items: stretch; gap: 6px; }
.sheet-label {
  flex: 0 0 96px;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.9px;
  text-transform: uppercase;
  color: var(--mut);
}
.sheet-row.stacked .sheet-label { flex: none; }
.sheet-control { flex: 1; min-width: 0; }
.sheet-long { font-size: 14px; line-height: 1.55; white-space: pre-wrap; }
/* Scoped under .record-sheet, same reasoning as the .sheet-dates/.sheet-
   number/.sheet-textarea fix below: a bare ".sheet-btn" is (0,1,0), which
   loses to the pre-existing ".editor-popover button" (0,1,1) on font-size/
   padding/border-radius. ".record-sheet .sheet-btn" is (0,2,0), which beats
   that cleanly on b alone (order-independent). Deliberately NOT scoped any
   wider than that: ".editor-popover button.primary" / "button.danger" are
   (0,2,1), one step above this, so Save/Delete's colours still correctly
   defer to those rules (and to .sheet-btn.primary/.sheet-btn.danger below)
   rather than falling back to this rule's plain background/border. Widening
   this selector further (e.g. adding .sheet-row/.sheet-control) would flip
   that and silently repaint the Delete button back to page colours -- see
   the .sheet-actions .sheet-btn rule instead for where that headroom is
   actually needed. Verified: Save is still green, Delete still red. */
.record-sheet .sheet-btn {
  background: var(--page);
  border: 1px solid var(--bd);
  border-radius: 8px;
  padding: 10px 14px;
  font: inherit;
  font-weight: 500;
  color: var(--fg);
  width: 100%;
}
.sheet-btn.danger { color: var(--warn-dark); border-color: var(--warn-disabled); }
.sheet-danger { border-bottom: none; margin-top: 12px; }

/* ---------- Record sheet in-row editors ---------- */
.sheet-row.editable { cursor: pointer; }
.sheet-row.editable:active { background: var(--hover); }

.sheet-options { display: flex; flex-wrap: wrap; gap: 8px; }
/* Every .sheet-option button is also matched by the pre-existing
   ".editor-popover button:not(.primary):not(.danger)" rule (0,3,1) --
   .sheet-option carries neither class -- which sets background/border/
   color, one level ABOVE ".editor-popover button" (0,1,1) alone. A bare
   ".sheet-option" (0,1,0), or even the ".record-sheet .sheet-option" (0,2,0)
   pattern used for the .sheet-dates/.sheet-number/.sheet-textarea fix below,
   both still lose to (0,3,1). Four classes (record-sheet, sheet-row, sheet-
   options, sheet-option) gives (0,4,0), which beats (0,3,1) on b alone,
   order-independent. For the base (unselected) pill this only fixes border-
   radius/padding/font-size in practice -- the lost "background: transparent"
   happens to render identically to var(--page) here because the enclosing
   .editor-popover is itself white -- but see .sheet-option.on immediately
   below, where the same loss is NOT coincidental. */
.record-sheet .sheet-row .sheet-options .sheet-option {
  background: var(--page);
  border: 1px solid var(--bd);
  border-radius: 999px;
  padding: 9px 14px;
  font: inherit;
  color: var(--fg);
  min-height: 40px; /* a thumb-sized tap target */
}
/* Same rule, +1 class for ".on" (0,5,0) so this stays comfortably ahead of
   (0,3,1) and of its own :hover variant (0,4,1). This one matters: unlike
   the base pill above, ".on"'s green background/white text is the ONLY
   visual signal for which Status/Type/Location/Team option is selected --
   losing this cascade doesn't just cost a pill shape, it makes the selected
   option indistinguishable from every other option. Confirmed by computed
   style before this fix: the "on" pill measured background: transparent
   (rendering white, i.e. showing no selection at all) instead of the coded
   green. */
.record-sheet .sheet-row .sheet-options .sheet-option.on {
  background: var(--green);
  border-color: var(--green);
  color: #fff;
}

/* .record-sheet-prefixed (not a bare ".sheet-dates input" / ".sheet-number" /
   ".sheet-textarea") so these clearly outrank ".editor-popover input,
   .editor-popover select, .editor-popover textarea" above, which sets
   font-size: inherit and would otherwise win or tie on specificity — this
   row lives inside .editor-popover.record-sheet, so that rule matches too.
   A bare class selector (0,1,0) loses outright; even the element-qualified
   ".sheet-dates input" (0,1,1) only wins by ending up later in the file,
   which a future edit could silently undo. Scoping under .record-sheet
   (0,2,1) wins regardless of order. Caught by checking computed font-size
   in the browser, not by reading the rule — .sheet-number and .sheet-textarea
   had the same gap in the brief this was built from. */
.sheet-dates { display: flex; gap: 8px; }
.record-sheet .sheet-dates input {
  flex: 1;
  min-width: 0;
  font: inherit;
  font-size: 16px; /* iOS zooms the page on focus below 16px */
  padding: 10px;
  border: 1px solid var(--bd);
  border-radius: 8px;
}

.record-sheet input.sheet-number {
  width: 100%;
  font: inherit;
  font-size: 16px; /* iOS zooms the page on focus below 16px */
  padding: 10px;
  border: 1px solid var(--bd);
  border-radius: 8px;
}

.record-sheet textarea.sheet-textarea {
  width: 100%;
  font: inherit;
  font-size: 16px; /* iOS zooms the page on focus below 16px */
  line-height: 1.55;
  padding: 10px;
  border: 1px solid var(--bd);
  border-radius: 8px;
  resize: vertical;
}

.sheet-actions { display: flex; gap: 8px; margin-top: 10px; }
/* .sheet-btn (now ".record-sheet .sheet-btn", (0,2,0) — see above) sets
   width: 100% for its usual one-button-per-row use (Open comments, Delete
   deliverable); override back to a flex split so Save and Cancel share the
   row instead of each claiming the full width. Scoping the base .sheet-btn
   under .record-sheet raised it to the same (0,2,0) this rule already had,
   which turned what used to be a clean win (0,2,0 > 0,1,0) into a same-
   specificity tie on the "width" property, resolved only by this rule
   being declared later in the file. Adding .record-sheet here too (0,3,0)
   restores an order-independent win — the exact fragile-tie-by-source-order
   pattern this whole pass exists to close, so left it as a tie would have
   been the same mistake one level down. */
.record-sheet .sheet-actions .sheet-btn { width: auto; flex: 1; }
.sheet-btn.primary { background: var(--green); border-color: var(--green); color: #fff; }

/* ---------- One save contract ----------
   Every editor (grid cell, popover, expanded row, phone sheet) has the same
   three states: editing with Save disabled until the value differs from the
   record, "Saving…", then a brief "Saved". */
.objective-btn:disabled,
.editor-popover button.primary:disabled,
.record-sheet .sheet-btn:disabled { opacity: 0.45; cursor: default; }
.editor-popover button.primary:disabled:hover { background: var(--green); border-color: var(--green); }
.inline-actions { display: flex; align-items: center; gap: 6px; margin: 4px 0 6px; }
.sheet-saving { color: var(--mut); padding: 10px 0; }
.saved-tick {
  margin-left: 8px;
  color: var(--green-2);
  text-transform: none;
  letter-spacing: 0;
  animation: savedTick 1.8s ease-out forwards;
}
.grid-row.saved-flash .cell { animation: savedFlash 1.6s ease-out; }
@keyframes savedTick { 0%, 65% { opacity: 1; } 100% { opacity: 0; } }
@keyframes savedFlash { from { background-color: var(--green-soft); } to { background-color: transparent; } }

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}
