/* L&R HUB — Option 2A (Minimal Video Hero). Plain CSS, mobile-first, breakpoint 768px. */

:root{
  --accent:#e0362b;
  --accent-hover:#c22c22;
  --accent-ink:#c22c22;  /* the accent when it has to be READ: small action text
                            on white or on the console's tints. Same pigment as
                            --accent-hover/--bad-ink on purpose: two reds in the
                            system, not three. */
  --ink:#0a0a0b;
  --muted:rgba(0,0,0,.55);
  --pill-gray:#F4F4F6;
  --hair:rgba(0,0,0,.12);
  --hair-soft:rgba(0,0,0,.08);
  --ghost:rgba(0,0,0,.35);
  --surface:#ffffff;
  --ok:#34d399;
  --warn:#f59e0b;
  --bad:#f87171;        /* dots, bars, rules */
  --bad-ink:#c22c22;    /* the ONE red for status text; --accent stays for actions */
  --ease:cubic-bezier(.16,1,.3,1);
}

*{ box-sizing:border-box; -webkit-tap-highlight-color:transparent; }
html{ -webkit-text-size-adjust:100%; }
body{
  margin:0; background:var(--surface); color:var(--ink);
  font-family:'Inter',sans-serif; line-height:1.5;
  -webkit-font-smoothing:antialiased; -moz-osx-font-smoothing:grayscale;
}
input,button,select,textarea{ font-family:inherit; }
a{ color:var(--accent); }

input:focus,select:focus,textarea:focus{
  border-color:var(--accent); box-shadow:0 0 0 3px rgba(224,54,43,.15); outline:none;
}
:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; }

/* ---------- Motion ---------- */
@keyframes riseIn{ from{ opacity:0; transform:translateY(18px); } to{ opacity:1; transform:translateY(0); } }
@keyframes riseNav{ from{ opacity:0; transform:translateY(-16px); } to{ opacity:1; transform:translateY(0); } }
@keyframes videoIn{ from{ opacity:0; transform:scale(1.05); } to{ opacity:1; transform:scale(1); } }

.anim-nav{ animation:riseNav .8s var(--ease) both; }
.anim-video{ animation:videoIn 1.8s var(--ease) both; }
.anim-sub{ animation:riseIn .8s var(--ease) .6s both; }
.anim-head{ animation:riseIn .8s var(--ease) .8s both; }
.anim-cta{ animation:riseIn .8s var(--ease) 1s both; }
.anim-page{ animation:riseIn .5s var(--ease) both; }

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

/* ---------- Logo + brand ---------- */
.brand{ display:flex; align-items:center; gap:10px; text-decoration:none; color:var(--ink); }
.brand-text{ display:none; font-size:17px; font-weight:600; letter-spacing:-.02em; }

/* ---------- Navbar ---------- */
.nav{
  position:fixed; top:0; left:0; right:0; z-index:50;
  display:flex; align-items:center; justify-content:space-between;
  padding:16px; pointer-events:none;
}
.nav > *, .nav-left > *, .nav-right > *{ pointer-events:auto; }
.nav-left{ display:flex; align-items:center; gap:10px; }
.nav-right{ display:flex; align-items:center; gap:10px; }

.nav--app{
  padding-bottom:22px;
  background:linear-gradient(to bottom,#fff 30%,rgba(255,255,255,.85) 62%,transparent 100%);
}

.tags-pill{
  display:none; align-items:center; gap:12px;
  background:var(--pill-gray); border-radius:999px; padding:9px 16px;
  font-size:12px; color:var(--ink);
}
/* inline-block: as a pure inline element width/height are ignored and the
   separator disappears wherever it is used outside a flex row. */
.dot-sep{
  display:inline-block; vertical-align:middle;
  width:3px; height:3px; border-radius:50%; background:rgba(0,0,0,.3);
}

.right-pill{
  display:flex; align-items:center; gap:10px;
  background:var(--pill-gray); border-radius:999px; padding:5px 16px 5px 5px;
}
.grid-circle{
  width:28px; height:28px; border-radius:50%; background:var(--ink);
  display:flex; align-items:center; justify-content:center; flex:0 0 auto;
}
.right-label{ font-size:12px; font-weight:500; color:var(--ink); letter-spacing:.02em; }

.user-pill{
  display:flex; align-items:center; gap:9px;
  background:var(--pill-gray); border-radius:999px; padding:5px 14px 5px 5px;
  max-width:60vw;
}
.user-avatar{
  width:28px; height:28px; border-radius:50%; background:var(--ink); color:#fff;
  display:flex; align-items:center; justify-content:center;
  font-size:12px; font-weight:600; flex:0 0 auto;
}
.user-name{ font-size:12px; font-weight:500; color:var(--ink); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.user-role{ display:none; font-size:11px; color:var(--muted); text-transform:uppercase; letter-spacing:.06em; }

.nav-logout{
  display:inline-flex; align-items:center; justify-content:center;
  min-height:44px; padding:0 16px; border-radius:999px;
  border:1px solid var(--ghost); background:transparent;
  font-size:12px; font-weight:500; color:var(--ink); text-decoration:none;
  transition:background .2s var(--ease);
}
.nav-logout:hover{ background:rgba(0,0,0,.06); }

/* ---------- Presence widget ---------- */
.presence{
  display:inline-flex; align-items:center; gap:8px;
  background:var(--pill-gray); border:1px solid var(--hair);
  border-radius:999px; padding:4px 12px 4px 6px;
}
.presence[hidden]{ display:none; }
.presence-stack{ display:inline-flex; }
.presence-av{
  width:24px; height:24px; border-radius:50%;
  background:var(--ink); color:#fff;
  display:inline-flex; align-items:center; justify-content:center;
  font-size:10px; font-weight:600; letter-spacing:.02em;
  border:2px solid var(--pill-gray); margin-left:-8px; flex:0 0 auto;
}
.presence-av:first-child{ margin-left:0; }
.presence-label{
  display:inline-flex; align-items:center; gap:6px;
  font-size:12px; font-weight:500; color:var(--ink); white-space:nowrap;
}
.presence-dot{ width:8px; height:8px; border-radius:50%; background:var(--ok); flex:0 0 auto; }

/* ---------- Hero ---------- */
.hero{
  position:relative; min-height:100vh; min-height:100dvh; overflow:hidden;
  background:#fff; color:var(--ink);
  display:flex; flex-direction:column; justify-content:space-between;
}
.hero__video-wrap{
  position:absolute; top:10%; left:10%; width:80%; height:80%;
  z-index:0; border-radius:18px; overflow:hidden; background:var(--pill-gray);
}
.hero__video{ width:100%; height:100%; object-fit:cover; display:block; }

.hero__footer{
  position:relative; z-index:30; margin-top:auto;
  padding:120px 20px 26px;
  background:linear-gradient(to top,#ffffff 0%,rgba(255,255,255,.82) 50%,transparent 100%);
  display:flex; flex-direction:column; gap:24px;
}
.hero__footer-main{ max-width:760px; }

.subtitle{ display:flex; align-items:center; gap:8px; margin-bottom:12px; }
.subtitle-dot{ width:8px; height:8px; border-radius:50%; background:var(--accent); flex:0 0 auto; }
.subtitle span:last-child{ font-size:13px; color:var(--muted); }

.headline{
  margin:0; font-weight:300; letter-spacing:-.03em; line-height:1;
  color:var(--ink); font-size:clamp(2rem,8vw,4.5rem);
}

/* ---------- Auth / message card ---------- */
.authcard{
  background:#fff; border:1px solid var(--hair); border-radius:18px;
  box-shadow:0 12px 40px rgba(0,0,0,.08);
  padding:24px; width:100%;
}
.card-title{ font-size:20px; font-weight:600; letter-spacing:-.01em; margin:0 0 4px; }
.lead{ font-size:13px; color:var(--muted); margin:0 0 18px; line-height:1.55; }

.field{ margin-bottom:14px; }
.field label{ display:block; font-size:12px; font-weight:500; color:var(--muted); margin-bottom:6px; }
.field input,.field select{
  width:100%; background:#fff; border:1px solid var(--hair); border-radius:12px;
  padding:12px 14px; font-size:14px; color:var(--ink);
}
.readonly{
  background:var(--pill-gray); border-radius:12px; padding:11px 14px;
  font-size:14px; color:var(--ink);
}

.btn{
  display:inline-flex; align-items:center; justify-content:center;
  min-height:44px; border-radius:999px; padding:12px 22px;
  font-size:13px; font-weight:500; cursor:pointer; border:none; text-decoration:none;
  transition:background .2s var(--ease), color .2s var(--ease);
}
.btn--primary{ background:var(--accent); color:#fff; }
.btn--primary:hover{ background:var(--accent-hover); }
.btn--ghost{ background:transparent; color:var(--ink); border:1px solid var(--ghost); }
.btn--ghost:hover{ background:rgba(0,0,0,.06); }
.btn--block{ width:100%; margin-top:4px; }
/* The buttons in the console that take something away. Outlined rather than
   filled: --primary is the accent solid, and a solid red button is what "Save"
   looks like everywhere else on the same page.
   ONE red for the action: the link that opens the panel (`.mini.danger`) and the
   button that carries it out are the same action a click apart, and they were
   two different reds — --accent and --bad-ink — inside one card. --accent is the
   one this file reserves for actions; --bad-ink stays on status text. */
.btn--danger{
  background:#fff; color:var(--accent); border:1px solid rgba(224,54,43,.45);
}
.btn--danger:hover{ background:rgba(224,54,43,.06); }

.foot{ margin-top:16px; font-size:12px; color:var(--muted); text-align:center; }
.foot-link{ color:var(--muted); text-decoration:underline; }
.foot-link:hover{ color:var(--ink); }

.alert{
  border-radius:12px; padding:10px 14px; font-size:13px; margin-bottom:14px;
  border:1px solid var(--hair); background:#fff; color:var(--ink);
}
/* --bad-ink, not --accent, for the same reason .hint--err uses it: the accent
   on this tinted background is 4.07:1, and every serious message on this
   console (a refused provision, a rejected save) is read through it. */
.alert.err{ border-color:rgba(224,54,43,.35); color:var(--bad-ink); background:rgba(224,54,43,.06); }
.alert.ok{ border-color:rgba(52,211,153,.45); color:#15803d; background:rgba(52,211,153,.09); }
.alert.warn{ border-color:rgba(245,158,11,.45); color:#b45309; background:rgba(245,158,11,.09); }

/* ---------- Interior pages ---------- */
.wrap{ max-width:1080px; margin:0 auto; padding:104px 20px 80px; }

.eyebrow{ display:flex; align-items:center; gap:8px; font-size:13px; color:var(--muted); margin-bottom:14px; }
.crumb{ margin-bottom:16px; }
.crumb a{ font-size:13px; color:var(--muted); text-decoration:none; }
.crumb a:hover{ color:var(--ink); }
/* The way out of a 10,000px page. It was a 67x16px target — the only real one
   left on the page below the 44px minimum every other control here meets. */
.crumb__l{ display:inline-flex; align-items:center; min-height:44px; padding-right:12px; }

.display{ font-weight:300; letter-spacing:-.03em; line-height:1; font-size:clamp(2rem,6vw,3.4rem); margin:0 0 8px; }
.sub{ font-size:14px; color:var(--muted); margin:0; }

.pagehead{ display:flex; flex-wrap:wrap; align-items:center; gap:14px; }
.datepill{
  display:inline-flex; align-items:center; background:var(--pill-gray);
  border-radius:999px; padding:7px 14px; font-size:12px; font-weight:500; color:var(--ink);
}
.datepill:empty{ display:none; }

.chip{
  display:inline-flex; align-items:center; background:var(--pill-gray);
  border-radius:999px; padding:4px 11px; font-size:12px; font-weight:500; color:var(--ink);
}

.msgwrap{ max-width:440px; margin:120px auto 0; padding:0 20px 80px; }
.authcard--center{ text-align:center; }

/* ---------- Tiles ---------- */
.tiles{ display:grid; grid-template-columns:1fr; gap:16px; margin-top:24px; }
.tile{
  position:relative; display:block; background:#fff; border:1px solid var(--hair);
  border-radius:18px; padding:24px; text-decoration:none; color:var(--ink);
  transition:box-shadow .2s var(--ease), transform .2s var(--ease);
}
.tile:hover{ box-shadow:0 12px 32px rgba(0,0,0,.08); transform:translateY(-2px); }
.tile .tn{ font-size:18px; font-weight:500; letter-spacing:-.01em; }
.tile .code{ margin-top:12px; }

.empty{
  background:#fff; border:1px dashed var(--hair); border-radius:18px;
  padding:40px 24px; text-align:center; color:var(--muted); font-size:14px; margin-top:24px;
}

/* ---------- Cards / panels ---------- */
.card{ background:#fff; border:1px solid var(--hair); border-radius:18px; overflow:hidden; margin-top:20px; }
.card__h{ padding:18px 22px; border-bottom:1px solid var(--hair-soft); }
.card__h--row{ display:flex; align-items:center; justify-content:space-between; gap:14px; }
/* The hint absorbs the slack; without this the title is the one that wraps. */
.card__h--row .t{ flex:0 0 auto; }
/* margin:0 because these titles are <h2> on the two admin pages: ten cards down
   a 9,000px page with a single <h1> gave a screen reader nothing to jump
   between. Everything about how they LOOK is this rule, not the tag. */
.card__h .t{ font-size:15px; font-weight:600; letter-spacing:-.01em; margin:0; }
.card__b{ padding:20px 22px; }
.card__b--scroll{ overflow-x:auto; }

/* Top-aligned, not bottom-aligned: one rejected field grows two lines taller
   than its neighbours and flex-end then staircases every input in the row.
   The button and the bare checkbox carry the label's own height (12px * 1.5 +
   6px) so they still line up with the inputs instead of riding the labels. */
.createform{ display:flex; flex-wrap:wrap; gap:14px; align-items:flex-start; }
.createform .field{ margin-bottom:0; flex:1 1 180px; }
/* Wide enough for the whole 17-character base id: the one field where reading
   the last four characters is the entire point. */
.createform .field--sm{ flex:0 0 210px; }
/* The field that decides where 148 files land. At 210px "/pm0004/MARQUIS HEALTH
   & REHAB" is truncated inside the box with 800px of empty card beside it. */
.createform .field--wide{ flex:1 1 420px; }
/* Shrinkable, and the label wraps inside it: "Provision safety folder for
   MARQUIS HEALTH & REHAB" is 428px of text in a 369px card, and the button
   that writes 26 folders into Dropbox was cut off mid-job-name. */
.createform .btn{ flex:0 1 auto; max-width:100%; white-space:normal; margin-top:24px; }
/* The size has to be repeated here: `.field input{width:100%}` (0-1-1) outranks
   `.chk{width:18px}` (0-1-0), so the Active box was a 210px-wide control with
   its glyph centred ~95px away from the word it belongs to. */
.createform .field .chk{ width:18px; height:18px; margin-top:12px; }
/* The house checkbox pill (`.tabchk`, 44px, label wrapping the box) inside a
   `.field`: `.field label` (0-2-0) outranks `.tabchk` (0-1-0), so the pill came
   out display:block, 12px and muted — the styling for a label that sits BESIDE
   its box, applied to one that wraps it. And the box loses the createform's
   margin-top, which exists to line a loose checkbox up with the text next to it
   and here only pushes it off the centre of the pill. */
.createform .field label.tabchk{
  display:inline-flex; font-size:13px; font-weight:400; color:var(--ink);
  margin-bottom:0;
}
.createform .field .tabchk .chk{ margin:0; flex:0 0 18px; }
/* A block of checkboxes is a row of its own, not the widest field in the row. */
.createform .tabpicker{ flex:1 0 100%; }
/* Same, for the disclosure that holds the optional Airtable ids on the add form:
   a row of its own, and BELOW the button, so the path from the last required
   field to "Add project" is not 689px of boxes nobody knows where to copy
   from. */
.createform .ddetails{ flex:1 0 100%; margin-top:16px; }
/* And for the paragraph that introduces a field whose hint is longer than the
   box is wide. A row of its own, full width: down a 210px column the Airtable
   Workspace's own explanation was 12 lines of 26 characters, which is the same
   200px of prose before the button that sent the four base ids into the
   disclosure.
   No `max-width` here. A max-width caps the flex BASE before the line is
   broken, so `flex:1 0 100%` no longer measured 100% and at 768px and up the
   note came to rest beside the box it introduces instead of above it. Card
   hints run the full width everywhere else on this console. */
.createform .createnote{ flex:1 0 100%; margin:0; }

/* What the Provision button would write, above the button. Ruled off from the
   save above it: it describes the other write on this card, not that one.
   `.cloneplan` is the same piece on the Airtable Bases card — same rule, same
   spacing, same sentence-before-the-button — under a name of its own because
   it is a different write (Airtable, not Dropbox) and because one card must
   never be able to render the other's block. */
.provision, .cloneplan{ margin-top:20px; padding-top:18px; border-top:1px solid var(--hair-soft); }
.provision__plan{ margin:0; font-size:13px; line-height:1.6; color:var(--ink); }
.provision__plan--off{ color:var(--muted); }
/* The path is the point of the sentence and the only thing on the page that can
   catch a typo in the Dropbox Root, so on a phone it wraps rather than push the
   card sideways. `anywhere`, not `break-all`: it takes the spaces in a job name
   first and only splits a word when a word on its own does not fit, so
   "/pm0005/MILINE LUDLAM" stops coming back as "MILINE LUDL / AM". */
.provision__path{ font-weight:600; overflow-wrap:anywhere; }

.linkbox{ margin-top:16px; background:var(--pill-gray); border-radius:14px; padding:14px 16px; }
.linkbox .lbl{ font-size:12px; color:var(--muted); margin-bottom:6px; }
.linkbox code{ font-family:inherit; font-size:13px; font-weight:500; color:var(--accent); word-break:break-all; display:block; }

/* ---------- Tables ---------- */
table.tbl{ width:100%; border-collapse:collapse; font-size:14px; }
table.tbl th{
  text-align:left; font-size:12px; font-weight:500; color:var(--muted);
  padding:10px 12px; border-bottom:1px solid var(--hair-soft); white-space:nowrap;
  /* Only shows when a header carries more than its name — the note under
     "Setup" makes that row 110px tall, and centred labels then float in the
     middle of it. Every other header here is one line, where top and middle are
     the same pixel. */
  vertical-align:top;
}
table.tbl td{ padding:14px 12px; border-bottom:1px solid var(--hair-soft); vertical-align:middle; }
table.tbl tr:last-child td{ border-bottom:0; }
/* Same reason as the `th` above, one table only: a project row is as tall as its
   Setup list (447px measured at 390px, 296px at 1440px) and `middle` left the
   project's own name floating 188px down a cell whose first line of content
   starts at 21px. Team rows are one line tall, where top and middle are the same
   pixel, so this is not global. */
#projects table.tbl td{ vertical-align:top; }
table.tbl tbody tr:hover{ background:rgba(0,0,0,.03); }
.uname{ font-size:14px; font-weight:500; color:var(--ink); }
.uemail{ font-size:12px; color:var(--muted); margin-top:2px; }
.tm{ font-size:13px; color:var(--muted); }

.stat{ display:inline-flex; align-items:center; gap:7px; font-size:12px; color:var(--ink); white-space:nowrap; }
.stat::before{ content:''; width:8px; height:8px; border-radius:50%; background:var(--muted); flex:0 0 auto; }
.stat.active::before{ background:var(--ok); }
.stat.invited::before{ background:var(--warn); }
.stat.disabled::before{ background:var(--bad); }

/* ---------- Logs: status pills + section nav ---------- */
.pill{
  display:inline-flex; align-items:center; font-size:12px; font-weight:500;
  padding:4px 11px; border-radius:999px; border:1px solid var(--hair);
  color:var(--muted); background:#fff; white-space:nowrap;
}
.pill--ok{ color:#15803d; border-color:rgba(52,211,153,.5); background:rgba(52,211,153,.10); }
.pill--warn{ color:#b45309; border-color:rgba(245,158,11,.5); background:rgba(245,158,11,.10); }
.pill--bad{ color:var(--bad-ink); border-color:rgba(224,54,43,.4); background:rgba(224,54,43,.08); }
.pill--locked{ color:#b91c1c; border-color:rgba(248,113,113,.55); background:rgba(248,113,113,.12); }
.pill--neutral{ color:var(--muted); }

.segment{ display:flex; flex-wrap:wrap; gap:8px; margin-top:24px; }
.seg{
  display:inline-flex; align-items:center; gap:8px; text-decoration:none;
  border:1px solid var(--hair); border-radius:999px; padding:9px 16px;
  font-size:13px; font-weight:500; color:var(--ink); background:#fff;
  transition:background .2s var(--ease);
}
.seg:hover{ background:rgba(0,0,0,.04); }
.seg-n{ font-size:12px; color:var(--muted); }
.section-h{ font-size:16px; font-weight:600; letter-spacing:-.01em; margin:28px 0 0; }
.card[id], #safety{ scroll-margin-top:96px; }

/* ---------- Deliveries calendar ---------- */
.t-concrete{ --tc:#0a0a0b; --tb:rgba(10,10,11,.09); }
.t-rebar{ --tc:#b45309; --tb:rgba(180,83,9,.10); }
.t-pt{ --tc:#7c3aed; --tb:rgba(124,58,237,.10); }
.t-materials{ --tc:#0369a1; --tb:rgba(3,105,161,.10); }
.t-equipment{ --tc:#15803d; --tb:rgba(21,128,5,.10); }
.dot{ width:8px; height:8px; border-radius:50%; background:var(--tc,var(--muted)); flex:0 0 auto; }

.vtoggle{ display:inline-flex; gap:4px; margin-top:24px; background:var(--pill-gray); border-radius:999px; padding:4px; }
.vtoggle__btn{
  border:none; background:transparent; border-radius:999px; padding:9px 18px;
  min-height:40px; font-size:13px; font-weight:500; color:var(--muted); cursor:pointer;
}
.vtoggle__btn.is-active{ background:#fff; color:var(--ink); box-shadow:0 1px 3px rgba(0,0,0,.10); }
[data-view-panel][hidden]{ display:none; }

.cal{ margin-top:20px; }
.cal-bar{ display:flex; flex-wrap:wrap; align-items:center; gap:14px; margin-bottom:16px; }
.cal-nav{ display:flex; gap:6px; }
.cal-nav .mini{ min-width:44px; }
.cal-nav .mini[disabled]{ opacity:.4; cursor:not-allowed; }
.cal-title{ font-size:18px; font-weight:600; letter-spacing:-.01em; min-width:150px; }
.legend{ display:flex; flex-wrap:wrap; gap:14px; margin-left:auto; }
.legend-item{ display:inline-flex; align-items:center; gap:6px; font-size:12px; color:var(--muted); }
.cal-note{ margin-bottom:14px; }

.cal-grid{ display:none; grid-template-columns:repeat(7,1fr); gap:6px; }
.cal-weekhead{ font-size:11px; font-weight:500; color:var(--muted); text-align:center; padding:4px 0; }
.cal-cell{
  min-height:104px; display:flex; flex-direction:column; gap:4px;
  border:1px solid var(--hair); border-radius:12px; padding:7px; background:#fff;
  cursor:pointer; overflow:hidden; transition:background .15s var(--ease);
}
.cal-cell:hover{ background:rgba(0,0,0,.03); }
.cal-cell.is-other{ background:var(--pill-gray); opacity:.55; }
.cal-cell.is-past{ opacity:.5; }
.cal-cell.is-today{ border-color:var(--accent); }
.cal-cell.is-selected{ box-shadow:0 0 0 2px var(--ink) inset; }
.cal-date{ font-size:12px; color:var(--muted); }
.cal-cell.is-today .cal-date{ color:var(--accent); font-weight:600; }
.chip-ev{
  display:flex; align-items:center; gap:5px; background:var(--tb);
  border-radius:7px; padding:2px 7px; font-size:11px; color:var(--ink);
  overflow:hidden; white-space:nowrap;
}
.chip-ev-label{ overflow:hidden; text-overflow:ellipsis; }
.chip-more{ font-size:11px; color:var(--muted); padding-left:4px; }

.cal-agenda{ display:block; }
.agenda-day{ border-top:1px solid var(--hair-soft); padding:14px 2px; cursor:pointer; }
.agenda-day:first-child{ border-top:0; }
.agenda-date{ font-size:13px; font-weight:600; margin-bottom:8px; }
.agenda-ev{ display:flex; align-items:center; gap:8px; padding:5px 0; font-size:13px; }
.agenda-ev-label{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

.cal-detail{ margin-top:16px; border:1px solid var(--hair); border-radius:16px; padding:18px 20px; background:#fff; }
.cal-detail-h{ font-size:15px; font-weight:600; letter-spacing:-.01em; margin-bottom:12px; }
.detail-ev{ border-top:1px solid var(--hair-soft); padding:12px 0; }
.detail-ev:first-of-type{ border-top:0; padding-top:0; }
.detail-ev-head{ display:flex; align-items:center; gap:8px; font-weight:500; margin-bottom:8px; }
.detail-meta{ display:grid; grid-template-columns:auto 1fr; gap:3px 16px; margin:0; font-size:13px; }
.detail-meta dt{ color:var(--muted); }
.detail-meta dd{ margin:0; color:var(--ink); }

.roleform{ display:inline-flex; align-items:center; gap:8px; flex-wrap:wrap; }
.roleform select{
  background:#fff; border:1px solid var(--hair); border-radius:999px;
  padding:7px 12px; font-size:12px; font-weight:500; color:var(--ink);
}

.actions{ display:flex; flex-wrap:wrap; gap:8px; }
.actions form{ display:inline; }
.mini{
  display:inline-flex; align-items:center; justify-content:center;
  min-height:38px; padding:0 14px; border-radius:999px;
  border:1px solid var(--ghost); background:transparent; color:var(--ink);
  font-size:12px; font-weight:500; cursor:pointer; text-decoration:none;
  transition:background .2s var(--ease);
}
.mini:hover{ background:rgba(0,0,0,.06); }
.mini.danger{ border-color:rgba(224,54,43,.35); color:var(--accent); }
.mini.danger:hover{ background:rgba(224,54,43,.06); }

/* accent-color is --ink, not --accent: 21 red checkboxes in the role matrix
   read as 21 alarms. Red is the primary action / danger colour, status chrome
   is black. */
.chk{ width:18px; height:18px; accent-color:var(--ink); display:block; margin:10px 0; }

/* ---------- Project dashboard v2 ---------- */
/* For a mark that is otherwise only a colour or a shape: the attention dot on a
   pill, the dot on the project table, the dot on an unfinished card. Same recipe
   as `.matrix thead`. */
.sr-only{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
}
.dash-h1{
  font-weight:300; letter-spacing:-.03em; line-height:1.05;
  font-size:clamp(1.6rem,4.5vw,2.4rem); margin:0 0 6px;
}

/* Setting the day the job started, on the line where it is printed. Closed it
   is one muted word on the end of that line — the page is a dashboard, not a
   form, and a date box permanently open on it would read as something waiting
   to be filled in. Open it becomes a block of its own, because a date input,
   a button and a sentence do not belong on a 14px caption line. */
.startedit{ display:inline-block; margin-left:6px; }
.startedit[open]{ display:block; margin:14px 0 0; }
.startedit__s{
  display:inline-flex; align-items:center; min-height:32px;
  font-size:12px; font-weight:500; color:var(--muted);
  cursor:pointer; list-style:none;
  text-decoration:underline; text-decoration-color:var(--hair);
  text-underline-offset:3px;
}
.startedit__s::-webkit-details-marker{ display:none; }
.startedit__s:hover{ color:var(--ink); }
.startedit__f{
  display:flex; flex-wrap:wrap; align-items:center; gap:8px; margin-top:8px;
}
.startedit__in{
  min-height:38px; padding:0 12px; border-radius:10px;
  border:1px solid var(--hair); background:#fff; color:var(--ink);
  font-size:14px; line-height:1;
}
/* The only red on this control, and only when something was refused: on this
   page the accent belongs to links and to nothing else. --bad-ink is the one
   red that is meant to be READ. */
.startedit__in[aria-invalid="true"]{ border-color:rgba(224,54,43,.55); }
.startedit__e{ margin:8px 0 0; font-size:12px; line-height:1.5; color:var(--bad-ink); max-width:56ch; }
/* Refusing to CLEAR the date is not an error — nothing was typed wrong. Muted,
   like the hint under it. */
.startedit__m{ margin:8px 0 0; font-size:12px; line-height:1.5; color:var(--muted); max-width:56ch; }
.startedit__h{ margin:8px 0 0; font-size:12px; line-height:1.5; color:var(--muted); max-width:56ch; }

/* Section pills: name + attention dot, never a number. One row that scrolls
   sideways on a phone, with a fade so it reads as "more to the right". */
.pills{
  display:flex; gap:8px; margin-top:20px;
  overflow-x:auto; scrollbar-width:none; -webkit-overflow-scrolling:touch;
  -webkit-mask-image:linear-gradient(to right,#000 92%,transparent);
          mask-image:linear-gradient(to right,#000 92%,transparent);
}
.pills::-webkit-scrollbar{ display:none; }
/* position:relative is load-bearing: the dot's visually-hidden label is
   absolutely positioned, and without a positioned ancestor it resolves against
   the viewport, escapes the row's overflow and stretches the DOCUMENT to
   ~800px on a 390px phone — the whole page scrolls sideways. */
.pill-nav{
  position:relative;
  display:inline-flex; align-items:center; gap:8px; flex:0 0 auto;
  min-height:44px; padding:0 16px; border-radius:999px;
  background:var(--pill-gray); color:var(--ink); text-decoration:none;
  font-size:13px; font-weight:500; white-space:nowrap;
  transition:background .2s var(--ease);
}
.pill-nav:hover{ background:rgba(0,0,0,.08); }
.pill-nav--soon{ color:var(--muted); background:transparent; border:1px dashed var(--hair); }
/* Two micro-labels, one recipe: quiet type inside the pill, no colour of its
   own. `.pill-soon` says the app is not built yet; `.pill-note` sits on a LIVE
   pill and says the hub cannot summarize that section (project_dashboard.html).
   Never let either one render to nothing — not display:none, not
   visibility:hidden, not font-size:0: the marker only works because it takes up
   room the eye can see (guarded in tests/test_dispatcher_tile.py). */
.pill-soon,
.pill-note{ font-size:11px; color:var(--muted); }
/* Binary by decision (Jordi, 2026-07-27): the dot is red or it is absent.
   An amber tier trained people to ignore both. Which makes ABSENT a statement
   ("nothing waiting"), so the slot is only printed for a section the hub
   actually reads. Withholding it is not itself a mark, though: `[hidden]` is
   display:none, so it generates no box and the pill without a slot is pixel
   for pixel the pill with an empty one. That is what `.pill-note` is for. */
.pill-dot{ width:8px; height:8px; border-radius:50%; background:var(--accent); flex:0 0 auto; }
.pill-dot[hidden]{ display:none; }

/* KPI band — same shape as the Daily Report dashboard. auto-fit rather than a
   fixed track count on both sides of the breakpoint: with four cells it lays
   out exactly as repeat(2,1fr) did here, and a role that only has one or two
   tabs gets a band that still spans the content width instead of huddling in
   the left half, out of line with the cards under it. */
.kpis{
  display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
  gap:10px; margin-top:20px;
}
.kpi{
  background:#fff; border:1px solid var(--hair); border-radius:16px;
  padding:14px 16px; min-height:104px;
}
.kpi .kk{
  font-size:10px; font-weight:500; letter-spacing:.05em; text-transform:uppercase;
  color:var(--muted);
}
.kpi-b{ min-height:56px; }
.kv{
  font-size:26px; font-weight:600; letter-spacing:-.02em; color:var(--ink);
  font-variant-numeric:tabular-nums; margin-top:6px; line-height:1.1;
}
.kv--acc{ color:var(--accent); }
.kv--bad{ color:var(--bad-ink); }
.kv--warn{ color:#b45309; }
.ks{ font-size:12px; color:var(--muted); margin-top:4px; font-variant-numeric:tabular-nums; }
/* Each fact of the sub is one unbreakable token, so a wrap lands on the
   separator instead of inside a date ("next pour 2026-08-/03"). */
.ks span{ white-space:nowrap; }
.ks--off{ font-style:italic; }
.kd{ font-size:12px; margin-top:3px; font-variant-numeric:tabular-nums; }
.kd--up{ color:#15803d; }
.kd--down{ color:var(--bad-ink); }
.spark{ display:block; width:100%; height:26px; margin-top:8px; }
.spark polyline{ fill:none; stroke:var(--accent); stroke-width:1.5; vector-effect:non-scaling-stroke; }

/* Cards. align-items:start so a short card keeps its height instead of
   stretching to its taller row-mate and growing dead space above the footer. */
.dcards{
  display:grid; grid-template-columns:1fr; gap:16px; margin-top:16px;
  align-items:start;
}
.dcard{
  display:flex; flex-direction:column;
  background:#fff; border:1px solid var(--hair); border-radius:18px;
  overflow:hidden; transition:box-shadow .2s var(--ease);
}
/* Shadow only, no lift: a card that rises on hover promises the whole card is
   clickable, and only the footer link is. */
.dcard:hover{ box-shadow:0 10px 30px rgba(0,0,0,.07); }
.dcard--unavailable{ border-color:rgba(224,54,43,.3); }

.dcard__h{
  display:flex; align-items:flex-start; justify-content:space-between; gap:12px;
  padding:18px 22px 12px;
}
.dcard__title{ display:flex; flex-direction:column; gap:2px; min-width:0; }
.dcard__name{ font-size:16px; font-weight:600; letter-spacing:-.01em; }
.dcard__sub{ font-size:12px; color:var(--muted); }
.dcard__tools{ display:flex; align-items:center; gap:8px; flex:0 0 auto; }
.dcard__tools[hidden]{ display:none; }
.dcard__age{ font-size:11px; color:var(--muted); white-space:nowrap; }
/* 44x44 of TARGET around a 32px circle: the button box is the touch area, the
   ::before is what you see. Sizing the box to the circle put a 32px target on
   a phone, under the system minimum every other control here honours. */
.dcard__refresh{
  position:relative; width:44px; height:44px; padding:0; flex:0 0 auto;
  border:none; background:transparent; color:var(--muted);
  font-size:14px; line-height:1; cursor:pointer;
  display:inline-flex; align-items:center; justify-content:center;
}
.dcard__refresh::before{
  content:''; position:absolute; inset:6px; border-radius:50%;
  border:1px solid var(--hair); background:#fff;
  transition:background .2s var(--ease);
}
.dcard__refresh span{ position:relative; }
.dcard__refresh:hover{ color:var(--ink); }
.dcard__refresh:hover::before{ background:rgba(0,0,0,.05); }
/* Same reason, inside the card body. The footer `.mini` keeps the family's
   existing 38px — it is a link in a row of links, not a lone control. */
.dcard__retry{ min-height:44px; margin-top:10px; }
.dcard__b{ padding:0 22px 18px; flex:1 1 auto; }
/* Up to two links: the card's own screen, and the section that owns its
   objects. Wrapping keeps the second one on a line of its own on a phone
   instead of pushing it out of the card. */
.dcard__f{
  display:flex; flex-wrap:wrap; gap:8px;
  padding:14px 22px; border-top:1px solid var(--hair-soft);
}
/* Secondary by tone, not by size: a cross-reference, not the card's action. */
.dcard__rel{ color:var(--muted); }
.dcard__note{ margin:8px 0 0; font-size:13px; color:var(--muted); }
.dcard__note:first-child{ margin-top:0; }

/* Skeleton: the L&R mark, breathing. One animation, shared by every slot. */
@keyframes skPulse{ 0%,100%{ opacity:.22; } 50%{ opacity:.55; } }
.sk{ display:flex; align-items:center; justify-content:center; min-height:88px; }
.kpi-b .sk{ min-height:52px; }
.sk-mark{ animation:skPulse 1.6s var(--ease) infinite; }
@media (prefers-reduced-motion:reduce){
  .sk-mark{ animation:none; opacity:.25; }
}

.dhead{ margin:6px 0 0; font-size:13px; color:var(--muted); }
.dhead__v{
  font-size:34px; font-weight:300; letter-spacing:-.03em; line-height:1;
  color:var(--ink); display:block; margin-bottom:2px;
  font-variant-numeric:tabular-nums;
}
.dhead__v--accent{ color:var(--accent); }
.dhead__v--bad{ color:var(--bad-ink); }
.dhead__v--warn{ color:#b45309; }
.dhead__l{ display:inline; }
.dhead__d{ display:block; margin-top:4px; font-size:12px; font-variant-numeric:tabular-nums; }
.dhead__d--up{ color:#15803d; }
.dhead__d--down{ color:var(--bad-ink); }

/* Week by day: 7 taps into the daily report of that day.
   The bar is NARROW and the column TALL on purpose. Square blocks the width of
   the cell made 12 men and 18 men look identical — the crew size is the whole
   point of the strip, so height has to carry it. */
.dweek{ display:grid; grid-template-columns:repeat(7,1fr); gap:4px; margin-top:14px; }
.dweek__d{
  display:flex; flex-direction:column; align-items:center; gap:2px;
  min-height:44px; padding:6px 0 4px; border-radius:10px;
  text-decoration:none; color:var(--ink);
  transition:background .15s var(--ease);
}
.dweek__d:hover{ background:rgba(0,0,0,.04); }
.dweek__col{
  display:flex; align-items:flex-end; justify-content:center;
  height:52px; width:100%;
}
.dweek__bar{ width:12px; border-radius:3px 3px 0 0; background:var(--ink); }
.dweek__n{ font-size:13px; font-weight:500; font-variant-numeric:tabular-nums; }
.dweek__mh{ font-size:10px; color:var(--muted); font-variant-numeric:tabular-nums; }
/* A day nobody logged says so ONCE: the dash. No ghost bar under it — two
   marks for one absence read as two different facts. */
.dweek__d.is-empty .dweek__n{ color:var(--muted); font-weight:400; }
.dweek__dow{ font-size:10px; color:var(--muted); letter-spacing:.04em; }

/* Segmented bar: late / at risk / on track. */
.dseg{ margin-top:14px; }
.dseg__bar{ display:flex; height:10px; border-radius:999px; overflow:hidden; background:var(--pill-gray); }
.dseg__p--bad{ background:var(--bad); }
.dseg__p--warn{ background:var(--warn); }
.dseg__p--ok{ background:var(--ok); }
.dseg__p--empty{ width:100%; background:var(--hair); }
.dseg__key{ display:flex; flex-wrap:wrap; gap:12px; margin-top:8px; }
.dseg__k{ display:inline-flex; align-items:center; gap:6px; font-size:12px; color:var(--muted); font-variant-numeric:tabular-nums; }
.dot--bad{ background:var(--bad); }
.dot--warn{ background:var(--warn); }
.dot--ok{ background:var(--ok); }

.dstats{
  list-style:none; margin:14px 0 0; padding:0;
  display:grid; grid-template-columns:1fr 1fr; gap:10px;
}
.dstat{
  border:1px solid var(--hair-soft); border-radius:12px; padding:10px 12px;
  display:flex; flex-direction:column; gap:2px;
}
.dstat__v{ font-size:20px; font-weight:500; letter-spacing:-.02em; font-variant-numeric:tabular-nums; }
.dstat__l{ font-size:11px; color:var(--muted); line-height:1.3; }
.dstat--warn .dstat__v{ color:#b45309; }
.dstat--bad .dstat__v{ color:var(--bad-ink); }

.dmeta{ margin-top:12px; display:flex; flex-direction:column; gap:4px; }
.dmeta__l{ margin:0; font-size:12px; color:var(--muted); display:flex; align-items:center; gap:6px; }
.dmeta__l--bad{ color:var(--bad-ink); }
.dmeta__l--warn{ color:#b45309; }

.ddetails{ margin-top:14px; border-top:1px solid var(--hair-soft); padding-top:12px; }
.ddetails summary{
  display:flex; align-items:center; gap:8px; cursor:pointer;
  font-size:13px; font-weight:500; list-style:none; min-height:32px;
}
.ddetails summary::-webkit-details-marker{ display:none; }
.ddetails summary::after{
  content:'+'; margin-left:auto; color:var(--muted); font-size:16px; line-height:1;
}
.ddetails[open] summary::after{ content:'–'; }
/* The worst item rides in the summary, so collapsing on a phone never hides
   the one line that matters. */
.ddetails__w{
  font-size:12px; color:var(--muted); overflow:hidden; text-overflow:ellipsis;
  white-space:nowrap; min-width:0;
}
.ddetails[open] .ddetails__w{ display:none; }
/* The sentence that says what the list is grouped by, between the summary and
   the items. Its own margins, because a bare <p> here brings the browser's
   1em and lands it halfway to the first row. */
.ddetails > .hint{ margin:8px 0 0; line-height:1.5; }
.ditems{ list-style:none; margin:10px 0 0; padding:0; display:flex; flex-direction:column; gap:8px; }
.ditem{
  border-left:2px solid var(--hair); padding-left:10px;
  display:flex; flex-direction:column; gap:1px;
}
.ditem--warn{ border-left-color:var(--warn); }
.ditem--bad{ border-left-color:var(--bad); }
.ditem__t{ font-size:13px; line-height:1.35; }
.ditem__t a{ color:var(--ink); text-decoration:underline; text-decoration-color:var(--hair); }
.ditem__m{ font-size:11px; color:var(--muted); font-variant-numeric:tabular-nums; }

/* ---------- Admin: role x tab matrix ---------- */
.hint{ font-size:12px; color:var(--muted); font-weight:400; }
.matrix th.matrix-tab{ text-align:center; }
/* 10px, taken from the emptiest cells on the page. `table.tbl` pads every cell
   12px each side, and this table has ELEVEN columns inside a card body that is
   1014px and does not grow — the ninth column ("PM can assign") took it to
   1067px, which is 53px hanging off the right of a scroller whose bar is an
   invisible overlay on a trackpad. Shortening the admin pill gave back 43 of
   those; these two pixels a side, on the eight columns whose entire content is
   an 18px checkbox, give back 32 more and leave the table 22px INSIDE the box
   rather than exactly at its edge. Measured at 1180/1280/1440/1512/1728:
   scrollWidth == clientWidth at every one. */
.matrix td.matrix-cell, .matrix th.matrix-tab{
  padding-left:10px; padding-right:10px;
}
.matrix th.matrix-tab span{ display:inline-block; max-width:88px; white-space:normal; line-height:1.25; }
.matrix td{ vertical-align:middle; }
.matrix td.matrix-cell{ text-align:center; }
.matrix .cellchk{ display:flex; justify-content:center; cursor:pointer; }
.matrix td.matrix-cell .chk{ margin:0; }
.matrix .uname{ white-space:nowrap; }
.matrix .uemail{ white-space:nowrap; }
.matrix .rowactions{ display:flex; gap:6px; align-items:center; flex-wrap:wrap; }
/* The last column of the matrix is not a tab: it is whether a PM may hand this
   role out. One hairline says so — without it the box reads as an eighth
   section, ticked or not, which is the one thing it must not be mistaken for. */
.matrix .col-sep{ border-left:1px solid var(--hair); }

.tabpicker{ margin-top:16px; border-top:1px solid var(--hair-soft); padding-top:14px; }
.tabpicker-lbl{ font-size:12px; color:var(--muted); margin-bottom:10px; }
.tabpicker-row{ display:flex; flex-wrap:wrap; gap:10px; margin-bottom:8px; }
.tabchk{
  display:inline-flex; align-items:center; gap:8px; cursor:pointer;
  border:1px solid var(--hair); border-radius:999px; padding:7px 14px;
  font-size:13px; min-height:44px;   /* the system's touch minimum */
}
/* flex:0 0 16px, not just a width: inside the Clear pills the box is a flex item
   next to two lines of text, and it was being squeezed to 13x16 — under half the
   area of every other checkbox on the page, on the one control that empties a
   column. */
.tabchk .chk{ margin:0; width:16px; height:16px; flex:0 0 16px; }
.tabchk:hover{ background:rgba(0,0,0,.03); }
/* A destructive control has to look like one, not like the list of current
   values it sits next to. */
.tabchk--clear{ background:var(--pill-gray); border-color:transparent; }
.tabchk--clear .clearlbl{ font-weight:500; }
.tabchk--clear:has(.chk:checked){
  background:rgba(224,54,43,.07); border-color:rgba(224,54,43,.45);
}

/* ---------- Admin: per-field validation + project completeness ---------- */
/* The reason a field was rejected sits under that field and the input carries
   aria-invalid, so it is not colour alone that says so. --bad-ink, not
   --accent: 12px error text in the accent lands at 4.44:1 on white, just under
   the 4.5:1 the rest of this file holds itself to. */
.hint--err{ color:var(--bad-ink); }
/* The base a typed id actually points at, in Airtable's own words. */
.hint--base{ color:var(--muted); }
/* The value a Clear checkbox would erase, inside its pill. Darker than --muted:
   that pill has a --pill-gray background, where 12px --muted text measures
   4.65:1 — the one pair on this page under AA. At .62 it is 6.0:1. */
.tabchk .clearval{ color:rgba(0,0,0,.62); font-size:12px; }
.field input[aria-invalid="true"],.field select[aria-invalid="true"]{
  border-color:rgba(224,54,43,.55); background:rgba(224,54,43,.04);
}
/* The bases a project points at: id and the name Airtable answers with. */
.baselist{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:4px; }
.baseitem{ display:flex; flex-wrap:wrap; align-items:baseline; gap:6px; font-size:12px; }
.baseitem__l{ color:var(--muted); min-width:74px; }
.baseitem__id{ font-size:12px; color:var(--ink); font-variant-numeric:tabular-nums; }
.baseitem__n{ color:var(--muted); }
.baseitem__n::before{ content:'— '; }
/* Which question a column answers, in the column head. Capped in ch so it wraps
   into a paragraph instead of setting the column's width from its longest line:
   this table already scrolls sideways on a phone. */
.th-note{
  display:block; max-width:30ch; margin-top:4px;
  font-weight:400; line-height:1.4; white-space:normal;   /* th sets nowrap */
}
/* What each unfinished section still needs, next to its pill. */
.needs{ list-style:none; margin:6px 0 0; padding:0; display:flex; flex-direction:column; gap:6px; }
.need{ display:flex; flex-wrap:wrap; align-items:center; gap:6px; }
.need__m{ font-size:12px; color:var(--muted); }

/* ---------- Admin: the projects table ---------- */
/* A draft is not switched off, so it does not get the red dot `.stat.disabled`
   carries: its setup is unfinished and the team simply cannot see it yet. */
.stat.draft::before{ background:var(--warn); }
.psum{ margin:0 0 12px; }
/* The row's own link. It lives in the first cell because on a 390px phone the
   fifth cell starts at x=481 — the action was off-screen. And it is the target
   this console is really made of, so it meets the 44px minimum: it measured
   71x42px. */
.plink{
  display:inline-flex; align-items:center; gap:8px; min-height:44px;
  text-decoration:none; color:var(--ink);
}
.plink:hover .uname{ text-decoration:underline; }
.pdot{ width:8px; height:8px; border-radius:50%; background:var(--accent); flex:0 0 auto; }

/* ---------- Admin: one project's setup page ---------- */
/* The readiness band. WRAPS — it is the answer to "what is left?", and half a
   pending list off the right edge of a phone is the one thing it cannot do. */
.rband{ display:flex; flex-wrap:wrap; gap:8px; margin:20px 0 4px; }
.rband .pill{ min-height:44px; text-decoration:none; }
.rband .pill:hover{ filter:brightness(.97); }

.cstate{ display:flex; flex-wrap:wrap; align-items:center; gap:8px; margin:0 0 12px; }
/* The counts at the top of the Airtable Bases card carry `cstate--clone`: every
   other `.cstate` answers to a pill in the band at the top of the page, and
   these answer to none — they count six bases, and the six have to add up.
   Which is why THESE pills wrap and the others do not. Every readiness pill is
   two or three words; these carry the reason a base was not created ("1 not
   created — Daily Report already serves this job", 51 characters), and as a
   999px capsule with `white-space:nowrap` that one ran 14px past the card at
   390px and was cut mid-word by `.card{overflow:hidden}` at 320px. Same answer
   as `.tabchk--item`, and for the same reason: a capsule around a sentence is
   not a capsule. The spacing stays the `.cstate` 12px — the 2px this modifier
   used to add was a difference nothing on the page means. */
.cstate--clone .pill{
  max-width:100%; white-space:normal; border-radius:12px; text-align:left;
}
/* The six rows. `.pitem` is the console's list row (the intake proposal uses
   it): a hairline between items, and the measure kept on the text. */
.clonelist{ margin-top:16px; }
.clonelist .preason{ margin-top:6px; }
/* The one link out of both blocks, and in the plan it is the ONLY thing to
   press when a missing JobName blocks the card: measured 296x20px there. */
.clonelist .exlink, .cloneplan .exlink{
  display:inline-flex; align-items:center; min-height:44px;
}
/* The measure, on the two blocks that were never given one: a card body is
   970px on a laptop, and the plan above the button and the workspace refusal
   ran to 160 characters a line — twice what the rest of this page reads at. */
.cloneplan .provision__plan,
.cloneplan .alert,
#airtable > .card__b > .alert.err{ max-width:68ch; }
/* The warning about Daily Report sits INSIDE the plan block, under the
   sentence it qualifies, and it was flush against it. */
.cloneplan .alert{ margin-top:12px; }

/* A card with something outstanding, on a page 10,420px long: same precedent as
   `.dcard--unavailable` on the dashboard. The dot repeats it next to the title,
   where the eye lands, and the border carries it while scrolling.
   NEUTRAL, outside the green-amber-red scale (arbitrated 2026-07-29). The mark
   is binary — one dot means "something is left here" — and in amber it was the
   `--warn` of `pill--warn`, i.e. a third severity tier reading against the pill
   40px under it. Severity is the pill's job; this only says where to look. Red
   is not available either: it is spent on `.pdot`, "the team can already open
   this and it has holes".
   --ghost against the card's own --hair is the same step in weight the amber
   border was, and the dot is --muted — the neutral status dot this file already
   uses for `.stat::before`, and 4.76:1 on white where the amber was 2.15:1. */
.card[data-todo]{ border-color:var(--ghost); }
.cdot{
  display:inline-block; width:8px; height:8px; border-radius:50%;
  background:var(--muted); margin-right:8px; vertical-align:middle;
}

/* What a sibling app answered about this project (evidence) versus what somebody
   ticked (a claim). Never the same mark: one of them the hub can prove. */
.vline{ margin:0 0 10px; font-size:12px; color:var(--muted); }
.vline--ok{ color:#15803d; }
.vline--bad{ color:var(--bad-ink); }

.idlist{ display:grid; grid-template-columns:auto 1fr; gap:4px 16px; margin:0 0 12px; font-size:13px; }
.idlist dt{ color:var(--muted); }
.idlist dd{ margin:0; }
/* The one link out of the hub that is a control and not a word inside a
   sentence: 168x36px measured, on the page that is opened from a phone. */
.idlist .exlink{ display:inline-flex; align-items:center; min-height:44px; }
/* The way to the two answers for a wrong Hub Code, from the row that shows it.
   A line of its own under the chip and 44px tall: the card that holds those two
   answers is the last one on the page, ~6000px down. Muted and underlined —
   this is a door, not the action itself, and the actions are red down there. */
.idfix{
  display:flex; align-items:center; min-height:44px;
  font-size:12px; color:var(--muted); text-decoration:underline;
}
.idfix:hover{ color:var(--ink); }

/* The value that is SAVED in a column, under the box that would replace it. */
.hint--cur{ margin-top:2px; }
.cur{ color:var(--ink); font-weight:500; overflow-wrap:anywhere; }

/* Re-check: created by JS, so nothing dead is rendered without it. */
.checkrow{ display:flex; flex-wrap:wrap; align-items:center; gap:8px; margin-top:8px; }
.checkline:empty{ display:none; }
.checkline--ok{ color:#15803d; }
.checkline--bad{ color:var(--bad-ink); }

/* Straight to the first refused field, from inside the banner. The offset is
   load-bearing: the nav is fixed, so a bare #anchor scrolls the box to y=0 —
   measured at 390px, that put the input and its label underneath the navbar. */
/* And it is a TARGET, not a line of text: this is the control every negative
   answer on the page offers — the refused field, the confirmation box to retype,
   the refusals at the bottom — and it measured 320x20px, the smallest thing to
   hit on a page that is read on a phone with gloves on. */
/* `flex`, not `inline-flex`: a code change offers two of these at once and they
   are two separate destinations, one per line. */
.alert__jump{
  display:flex; align-items:center; width:fit-content;
  min-height:44px; margin-top:2px; font-weight:500; color:inherit;
}
.field input, .field select{ scroll-margin-top:120px; }
/* Same reason, for the other three things this page links to inside itself: the
   audit card the partial-provisioning message sends the reader to, and the two
   confirmation panels — a bare #anchor under a fixed nav lands the panel's own
   title behind it. */
#recent, #identity, #lifecycle, #unlink, #change-code, #publish{ scroll-margin-top:120px; }

code{ font-family:'Inter',sans-serif; font-weight:500; }

/* The panels that ask before they write: publish on the Identity card, and
   change-the-code and unlink at the bottom of the page. Each one says what it
   would do and (for the two that are hard to undo) asks for the code back, so
   what this styling has to carry is "read this before you type" — a rule down
   the left edge and room to breathe, not a red box. The page already spends red
   on things that went wrong, and none of these has. */
.dzone{
  margin-top:18px; padding:16px 0 4px 16px;
  border-top:1px solid var(--hair-soft); border-left:2px solid var(--ghost);
  /* Editorial measure. This is the list that has to be READ before the button
     under it is pressed, and at 1280px it was 970px wide — 150-character lines,
     which is a table row, not a paragraph. */
  max-width:68ch;
}
.dzone__t{ font-size:14px; font-weight:500; margin-bottom:8px; }
.dzone .hint{ margin-bottom:10px; }
/* The consequences, one per line. A paragraph of five of them is a paragraph
   nobody reads before pressing the button under it.
   --ink, like `.provision__plan`, which is the same kind of content on the same
   page: what the write is about to do, above the button that does it. In --muted
   the only text these panels exist to have read was the lightest thing in them,
   at the same weight as the `.hint`s around it. */
.dlist{ margin:0 0 14px; padding-left:18px; font-size:13px; color:var(--ink); }
.dlist li{ margin-bottom:6px; line-height:1.55; }
.dlist .chip{ font-size:12px; }
/* The action goes UNDER the boxes, not beside them: `.createform` wraps its
   children into one row, and a destructive button on the same line as the field
   that authorises it is a button you reach before you have typed. */
.dzone__a{
  flex:1 0 100%; display:flex; flex-wrap:wrap; align-items:center;
  gap:12px; margin-top:4px;
}
.dzone .createform .btn{ margin-top:0; }
/* The two things the code-change panel says do NOT move, as controls and not
   only as words inside the bullets that name them (84x16px and 240x36px). */
.dzone__x{ display:flex; flex-wrap:wrap; gap:10px; margin:0 0 14px; }
/* Every control inside one of these panels is 44px, on any screen: the way out
   of a destructive action measured 38px beside its 44px button, which read as
   two different kinds of control. On a phone `.mini` already gets this from the
   breakpoint block; here it is the panel that asks for it. */
.dzone__a .mini, .dzone__x .mini{ min-height:44px; }
/* Closed, they are two links at the end of the card. */
.dzone__l{
  display:flex; flex-wrap:wrap; gap:10px; margin-top:16px;
  padding-top:14px; border-top:1px solid var(--hair-soft);
}

/* ---------- Admin: the intake proposal ---------- */
/* One proposed record, and one lesson: a LIST ROW, and deliberately NOT `.dzone`.
   That panel's 2px left rule means "read this before you press the button under
   it" — true of the three confirmations on the project page, not of eight rows
   in a row, and its 68ch measure cut the rule off at 686px inside a 1146px card.
   What separates these is the hairline the rest of the console separates list
   items with. The measure stays where it belongs: on the text. */
.pitem{ border-top:1px solid var(--hair-soft); padding:14px 0 2px; }
.pitem:first-of-type{ border-top:0; }
.pitem__h{ display:flex; flex-wrap:wrap; align-items:center; gap:8px; }
/* A folder name is one long unbroken string on a 390px screen. */
.pitem__n{ overflow-wrap:anywhere; }
/* The reason a row is NOT ticked — the only sentence on this screen that asks
   for a decision. Same weight as `.dlist` and for the same reason given there:
   in `.hint` the one thing the row exists to have read was the lightest thing
   in it. */
.preason{ margin:8px 0 0; font-size:13px; line-height:1.55; color:var(--ink); max-width:68ch; }
.pitem .dlist{ margin:10px 0 0; max-width:68ch; }
/* Inside a row the disclosure needs no rule of its own: the row has one. */
.pitem .ddetails{ margin-top:10px; border-top:0; padding-top:0; }
.pitem .dzone{ max-width:68ch; }
/* The house checkbox (`.tabchk`) around a folder name instead of a tab name: the
   same piece — the label WRAPS the box and its text, 44px, hover — with the two
   things a line that wraps needs. The box rides the FIRST line instead of the
   middle of a three-line block, and the corner is 12px because a 999px pill
   around 90 characters of folder name is not a pill. */
.tabchk--item{ align-items:flex-start; border-radius:12px; padding:9px 12px; text-align:left; }
.tabchk--item .chk{ margin-top:2px; width:18px; height:18px; flex:0 0 18px; }
/* A count that is also the way to the section it counts (same as `.rband .pill`,
   which is the other pill in this console that is a link). 44px: it is the one
   control in that band, and on the screen that is read on a phone. */
.cstate a.pill{ text-decoration:none; min-height:44px; }
.cstate a.pill:hover{ filter:brightness(.97); }

/* ---------- Utilities ---------- */
.mt-16{ margin-top:16px; }
.mt-24{ margin-top:24px; }

/* A link that leaves the hub. Underlined, because on this page it sits inside
   grey helper text where the accent alone reads as emphasis — and in
   --accent-ink, because here the accent is body-sized text and not a control:
   --accent is 4.44:1 on white, under the 4.5:1 this file holds itself to, while
   --accent-ink is 5.71:1 on white and stays ≥5.2:1 on the err/warn tints —
   .exlink lives inside `.alert.warn` too. */
.exlink{ color:var(--accent-ink); text-decoration:underline; }

/* Pulido design-review 1.9: nav de mes a 44px (brief móvil) */
.cal-nav .mini{ min-height:44px; }

/* ================= Breakpoints =================
   ONE block per side of the 768px breakpoint, at the end of the file. They
   used to be six blocks scattered through it, and an override written in the
   wrong one silently lost to a later one. Everything above here is the
   mobile-first base. */

/* ---------- Phone ---------- */
@media (max-width:767px){
  /* Phones: keep avatars + online dot, drop the count text so the nav never
     clips Sign out at ~360px with several people online. */
  .presence-count{ display:none; }

  /* A title and its hint side by side squeeze both into narrow columns. */
  .card__h--row{ flex-direction:column; align-items:flex-start; gap:6px; }

  /* 44px is the system's touch minimum (.btn, .nav-logout, .cal-nav .mini). */
  .seg{ min-height:44px; }
  .ddetails summary{ min-height:44px; }
  .startedit__s{ min-height:44px; }
  .startedit__in{ min-height:44px; }
  /* Same rule, and `.mini` had been left out of it: on the admin console it is
     Unlock, Send reset link, Deactivate and Mark done — 38px each. */
  .mini{ min-height:44px; }

  /* One field per row. `.field--sm` is `flex:0 0 210px`, which does not grow, so
     in a ~335px card body it left a 125px gap beside a box holding a 17-character
     base id — the one field where reading the last four characters is the whole
     point. */
  .createform .field, .createform .field--sm{ flex:1 1 100%; }

  /* The role x tab matrix is 841px of intrinsic width inside a ~335px card:
     five of seven columns and the Save button sit off-screen with no scroll
     affordance. Below 768px each row becomes a block — role, one labelled
     44px line per tab, then its actions — so nothing needs a sideways drag. */
  .matrix thead{
    position:absolute; width:1px; height:1px; padding:0; margin:-1px;
    overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
  }
  .matrix, .matrix tbody, .matrix tr, .matrix td{ display:block; width:100%; }
  .matrix tr{
    border:1px solid var(--hair); border-radius:14px;
    padding:4px 14px; margin-bottom:12px;
  }
  /* table.tbl.matrix, not .matrix: `table.tbl td` outranks a single class and
     would keep its 14px/12px padding on every stacked row. */
  table.tbl.matrix tbody tr:hover{ background:transparent; }
  table.tbl.matrix td{ padding:0; border-bottom:1px solid var(--hair-soft); }
  .matrix tr td:first-child{ padding:12px 0 10px; }
  .matrix tr td:last-child{ border-bottom:0; }
  .matrix .uname, .matrix .uemail{ white-space:normal; }
  .matrix td.matrix-cell{
    display:flex; align-items:center; justify-content:space-between; gap:12px;
    min-height:44px; text-align:left; font-size:13px;
  }
  .matrix td.matrix-cell::before{ content:attr(data-label); color:var(--muted); }
  /* Stacked, the vertical hairline has nothing left to separate — it would
     draw down the left edge of one row in the block — so the same class turns
     it horizontal. Without this, "PM can assign" is the ninth line of a list
     of eight tabs, with the same checkbox and the same divider above it: at
     390px it reads as a ninth section of the hub, which is the one thing it is
     not. The gap plus the darker rule is what says "different question". */
  .matrix .col-sep{
    border-left:0; border-top:1px solid var(--hair);
    margin-top:14px; padding-top:14px;
  }
  /* And its label is not a tab name. The eight above it are muted because they
     are a list of the same kind of thing; this one is a question about who may
     hand the role out, and in ink it stops being the ninth item in that list.
     `td.col-sep` to match the specificity of the rule it overrides. */
  .matrix td.col-sep::before{ color:var(--ink); }
  .matrix .cellchk{
    justify-content:flex-end; align-items:center;
    min-width:44px; min-height:44px; flex:0 0 auto;
  }
  .matrix .rowactions{ padding:12px 0; }

  /* Same treatment, same reason, for the projects table: 685px of intrinsic
     width inside a ~348px card body, which put "Setup" at x=369 and "Team" at
     x=584 — the column an admin opens this console to read, and the one the
     line above the table points at ("2 of 3 projects need setup"), off-screen
     behind a sideways drag with no affordance. Each project becomes a block. */
  #projects .tbl thead{
    position:absolute; width:1px; height:1px; padding:0; margin:-1px;
    overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; border:0;
  }
  #projects .tbl, #projects .tbl tbody, #projects .tbl td{ display:block; width:100%; }
  #projects .tbl tr{
    display:flex; flex-direction:column; width:100%;
    border:1px solid var(--hair); border-radius:14px;
    padding:4px 14px; margin-bottom:12px;
  }
  /* table.tbl.., not the single class: `table.tbl td` outranks it and would keep
     its 14px/12px padding on every stacked row. */
  #projects table.tbl tbody tr:hover{ background:transparent; }
  #projects table.tbl td{ padding:10px 0; border-bottom:1px solid var(--hair-soft); }
  #projects .tbl td:first-child{ padding:6px 0 8px; }
  #projects .tbl td[data-label]::before{
    content:attr(data-label); display:block;
    font-size:12px; color:var(--muted); margin-bottom:5px;
  }
  /* Last, whatever the column order is: an id nobody types is not what a phone
     opens this table for, and DOM order has to keep the header it belongs to. */
  #projects .tbl td.pbases{ order:1; border-bottom:0; }
}

/* ---------- Desktop ---------- */
@media (min-width:768px){
  .hero__video-wrap{ top:0; left:0; width:100%; height:100%; border-radius:0; }
  .nav{ padding:24px 32px; }
  .nav--app{ padding-bottom:28px; }
  .nav-left{ gap:14px; }
  .brand-text{ display:inline; }
  .tags-pill{ display:flex; }
  .grid-circle{ width:30px; height:30px; }
  .user-role{ display:inline; }

  .hero__footer{
    padding:150px 34px 40px;
    flex-direction:row; align-items:flex-end; justify-content:space-between; gap:40px;
  }
  .hero__footer-main{ flex:1 1 auto; }
  .headline{ font-size:clamp(2.5rem,5.5vw,4.5rem); }
  .authcard{ flex:0 0 auto; width:380px; }

  .wrap{ padding:120px 32px 90px; }
  .tiles{ grid-template-columns:repeat(2,1fr); }

  .cal-grid{ display:grid; }
  .cal-agenda{ display:none; }

  /* auto-fit, not repeat(4): a role with two tabs gets two cells, and a fixed
     four-column track left them huddled in the left half, out of line with the
     cards underneath. The band always spans the content width. */
  .kpis{ grid-template-columns:repeat(auto-fit,minmax(220px,1fr)); gap:12px; }
  /* This Week and Schedule share the first row: the two things a PM opens
     the page for. */
  .dcards{ grid-template-columns:repeat(2,1fr); }
  .pills{ -webkit-mask-image:none; mask-image:none; flex-wrap:wrap; }
}
