/* YAP Scheduler application styles */

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  font-family: var(--font);
  font-size: var(--t15);
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}
button, input, select, textarea { font-family: inherit; font-size: inherit; color: inherit; }
a { color: var(--accent); text-decoration: none; }
.hidden { display: none !important; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }

/* Focus visibility (a11y) */
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; }

/* Buttons */
.btn {
  appearance: none; border: 1px solid transparent; border-radius: var(--r-pill);
  background: var(--accent); color: var(--accent-ink);
  font-size: var(--t15); font-weight: var(--w510); padding: 8px 16px;
  cursor: pointer; transition: filter var(--t-fast), background var(--t-fast);
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
}
.btn:hover { filter: brightness(1.05); }
.btn:active { filter: brightness(.95); }
.btn:disabled { opacity: .5; cursor: default; filter: none; }
.btn.secondary { background: var(--surface); color: var(--ink); border-color: var(--line-strong); }
.btn.secondary:hover { background: var(--surface-2); filter: none; }
.btn.ghost { background: transparent; color: var(--accent); border-color: transparent; padding: 8px 10px; }
.btn.ghost:hover { background: var(--accent-soft); filter: none; }
.btn.danger { background: var(--danger); }
.btn.small { padding: 5px 12px; font-size: var(--t13); }
.btn.icon { padding: 7px; border-radius: 50%; }
.icon-btn {
  appearance: none; border: 0; background: transparent; color: var(--muted);
  padding: 7px; border-radius: 8px; cursor: pointer; display: inline-flex;
  align-items: center; justify-content: center; transition: background var(--t-fast), color var(--t-fast);
}
.icon-btn:hover { background: var(--surface-2); color: var(--ink); }
.icon-btn svg { width: 20px; height: 20px; }

/* Inputs : one shared height so text fields and selects always align */
:root { --field-h: 38px; }
input[type=text], input[type=email], input[type=password], input[type=date],
input[type=time], input[type=number], input[type=search], select, textarea {
  -webkit-appearance: none; appearance: none; box-shadow: none;
  width: 100%; padding: 9px 11px; border: 1px solid var(--line-strong);
  border-radius: var(--r); background: var(--surface); outline: none;
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
input[type=text], input[type=email], input[type=password], input[type=date],
input[type=time], input[type=number], input[type=search], select {
  height: var(--field-h); padding-top: 0; padding-bottom: 0; box-sizing: border-box;
}
input[type=search]::-webkit-search-decoration,
input[type=search]::-webkit-search-cancel-button { -webkit-appearance: none; }
/* Clear (x) of a search field (round 68). The wrapper is added by core.js around
   every input[type=search] ; the button is a flex sibling pulled back over the end
   of the field, so it lands on the field itself whatever width the field was given
   (210px topbar, 140px filter pill, 200px journal, 100% of a panel row). */
.in-clear-wrap { display: flex; align-items: center; min-width: 0; }
.in-clear-wrap > input[type=search] { padding-right: 28px; }
.in-clear {
  appearance: none; border: 0; background: transparent; color: var(--muted);
  margin-left: -26px; padding: 0; width: 22px; height: 22px; border-radius: 50%;
  display: none; align-items: center; justify-content: center; cursor: pointer; flex: none;
}
.in-clear-wrap.has-text > .in-clear { display: inline-flex; }
.in-clear:hover { background: var(--surface-2); color: var(--ink); }
.in-clear svg { width: 13px; height: 13px; }
/* Selects : fully custom styling, no native chrome, same height as text inputs */
select {
  -webkit-appearance: none; -moz-appearance: none; appearance: none;
  padding-right: 30px; cursor: pointer; line-height: calc(var(--field-h) - 2px);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'><path d='M2.5 4.5 L6 8 L9.5 4.5' fill='none' stroke='%236E6E73' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat: no-repeat; background-position: right 10px center; background-size: 12px 12px;
}
select::-ms-expand { display: none; }
select:disabled { opacity: .5; cursor: default; }
input:focus, select:focus, textarea:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
label.field { display: block; font-size: var(--t13); color: var(--muted); margin: 0 0 5px; }
.field-row { margin-bottom: 14px; }

/* ============ Login overlay ============ */
#login {
  position: fixed; inset: 0; z-index: 200; display: flex; align-items: center; justify-content: center;
  background: var(--bg); padding: 20px;
}
.login-card {
  width: 100%; max-width: 380px; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: 32px 28px; box-shadow: var(--shadow-1);
}
.login-card h1 { font-size: var(--t28); font-weight: var(--w700); letter-spacing: -.02em; margin: 0 0 4px; }
.login-card .sub { color: var(--muted); font-size: var(--t15); margin: 0 0 24px; }
.err { color: var(--danger); font-size: var(--t13); min-height: 18px; margin: 4px 0 10px; }

/* ============ App shell ============ */
#app { display: grid; grid-template-columns: var(--sidebar-w) 1fr; grid-template-rows: 100vh; }

.sidebar {
  background: var(--surface); border-right: 1px solid var(--line);
  display: flex; flex-direction: column; padding: 14px 12px; gap: 2px; overflow-y: auto;
}
.sidebar .brand { display: flex; align-items: center; gap: 9px; padding: 6px 10px 16px; }
.sidebar .brand .logo { width: 26px; height: 26px; border-radius: 7px; background: var(--accent); display: grid; place-items: center; color: #fff; font-weight: var(--w700); font-size: 14px; }
.sidebar .brand .name { font-weight: var(--w590); font-size: var(--t15); }
.nav-item {
  display: flex; align-items: center; gap: 11px; padding: 9px 11px; border-radius: var(--r);
  color: var(--ink); cursor: pointer; font-size: var(--t15); font-weight: var(--w510);
  transition: background var(--t-fast); position: relative; border: 0; background: transparent; width: 100%; text-align: left;
}
.nav-item:hover { background: var(--surface-2); }
.nav-item.active { background: var(--accent-soft); color: var(--accent); }
.nav-item svg { width: 20px; height: 20px; flex: 0 0 20px; }
.nav-item .count-badge {
  margin-left: auto; background: var(--danger); color: #fff; font-size: 11px; font-weight: var(--w700);
  min-width: 18px; height: 18px; padding: 0 5px; border-radius: 9px; display: inline-flex; align-items: center; justify-content: center;
}
.nav-item.active .count-badge { background: var(--danger); }
.sidebar .spacer { flex: 1; }
/* Sidebar footer (round 71) : build stamp + signature. flex-shrink 0 so a short
   viewport scrolls the nav instead of squashing the footer out of shape. */
.sidebar .side-foot {
  flex: 0 0 auto; display: flex; flex-direction: column; gap: 1px;
  padding: 10px 11px 2px; font-size: 11px; line-height: 1.35; color: var(--muted);
}

.main-col { display: flex; flex-direction: column; min-width: 0; overflow: hidden; }

/* Topbar */
.topbar {
  height: var(--topbar-h); flex: 0 0 var(--topbar-h); display: flex; align-items: center; gap: 10px;
  padding: 0 16px; border-bottom: 1px solid var(--line); background: var(--surface);
}
.topbar .event-switch { display: flex; flex-direction: column; line-height: 1.1; cursor: default; }
.topbar .event-switch .en { font-weight: var(--w590); font-size: var(--t15); }
.topbar .event-switch .em { font-size: 11px; color: var(--muted); }
.date-nav { display: flex; align-items: center; gap: 4px; margin-left: 8px; }
.date-nav .cur-date { font-weight: var(--w510); min-width: 128px; text-align: center; font-size: var(--t15); }
.date-nav input[type=date] { width: auto; padding: 0 8px; height: 32px; }
.seg {
  display: inline-flex; background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--r); padding: 2px;
}
.seg button {
  border: 0; background: transparent; padding: 5px 14px; border-radius: 7px; cursor: pointer;
  font-size: var(--t13); font-weight: var(--w510); color: var(--muted);
}
.seg button.on { background: var(--surface); color: var(--ink); box-shadow: var(--shadow-1); }
.topbar .grow { flex: 1; }
.search-box { position: relative; width: 210px; }
.search-box svg { position: absolute; left: 9px; top: 50%; transform: translateY(-50%); width: 16px; height: 16px; color: var(--muted); }
.search-box input { padding-left: 30px; padding-top: 7px; padding-bottom: 7px; background: var(--surface-2); border-color: var(--line); }
.user-menu { position: relative; }
.avatar { width: 32px; height: 32px; border-radius: 50%; background: var(--accent); color: #fff; display: grid; place-items: center; font-weight: var(--w590); font-size: var(--t13); cursor: pointer; border: 0; }
.menu-pop {
  position: absolute; right: 0; top: 40px; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r); box-shadow: var(--shadow-pop); padding: 6px; min-width: 190px; z-index: 60;
}
.menu-pop .who { padding: 8px 10px; font-size: var(--t13); color: var(--muted); border-bottom: 1px solid var(--line); margin-bottom: 4px; }
.menu-pop button { display: block; width: 100%; text-align: left; border: 0; background: transparent; padding: 8px 10px; border-radius: 7px; cursor: pointer; font-size: var(--t15); }
.menu-pop button:hover { background: var(--surface-2); }

/* Filter pills */
.filters {
  height: var(--filters-h); flex: 0 0 var(--filters-h); display: flex; align-items: center; gap: 8px;
  padding: 0 16px; border-bottom: 1px solid var(--line); background: var(--surface); overflow-x: auto;
}
.pill {
  display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--line-strong); background: var(--surface);
  border-radius: var(--r-pill); padding: 5px 12px; font-size: var(--t13); font-weight: var(--w510); cursor: pointer;
  white-space: nowrap; color: var(--ink); transition: all var(--t-fast);
}
.pill:hover { background: var(--surface-2); }
.pill.on { background: var(--accent); color: #fff; border-color: var(--accent); }
.pill.on.grp-b { background: var(--grp-b); border-color: var(--grp-b); }
.pill select {
  border: 0; padding: 0 16px 0 0; width: auto; height: auto; line-height: inherit;
  color: inherit; font-weight: var(--w510); background-color: transparent;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 12 12'><path d='M2.5 4.5 L6 8 L9.5 4.5' fill='none' stroke='%236E6E73' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-position: right 2px center; background-size: 10px 10px;
}
.pill select:focus { box-shadow: none; }
.pill input[type="search"] { border: 0; padding: 0; width: 140px; height: auto; line-height: inherit; font: inherit; color: inherit; background: transparent; }
.pill input[type="search"]:focus { box-shadow: none; outline: none; }
/* The pill input carries no padding of its own : make room for its clear button. */
.pill .in-clear-wrap > input[type="search"] { padding-right: 20px; }
.pill .in-clear { margin-left: -20px; width: 18px; height: 18px; }

/* Main scroll area */
.view { flex: 1; overflow: auto; padding: 20px; min-height: 0; }
.view-head { display: flex; align-items: center; gap: 12px; margin-bottom: 16px; }
.view-head h2 { font-size: var(--t22); font-weight: var(--w700); letter-spacing: -.01em; margin: 0; }
.view-head .grow { flex: 1; }
/* Schedule date navigation + Day|Week toggle, sitting under the Schedule title.
   Round 68 : it STAYS at the top while the grid scrolls. The bar wraps on a narrow
   screen, so its height is measured after each mount (mountScheduleBar) and pushed
   into --sched-bar-h : the sticky table headers below offset themselves by it
   instead of trusting a hard-coded number that a second line would break.
   The background is opaque and the flat shadow extends it over the 20px top padding
   of .view, where the bar parks : without it the rows scroll visibly through that
   strip. A shadow rather than a negative margin, so no layout arithmetic and the
   measured height stays the height of the bar itself.

   Round 88, the bug that shadow caused. It is 22px tall and opaque, and it is
   painted whether the bar is stuck or at rest. At rest the only thing separating
   the bar from the page title above it was .view-head's 16px bottom margin, so the
   last 6px of the shadow landed INSIDE the h2's line box and repainted it in --bg :
   the baseline row of every letter was erased, and a descender with it. The client
   saw "Dashboard" with its feet cut off, on the three screens that carry this bar
   (Dashboard, Schedule, Lunches) and on those only.
   INVARIANT : margin-top equals the shadow, so the shadow always falls on empty
   space and stops exactly at the title's box instead of eating into it. It is a
   whole 22px and not the 6px difference on purpose : this margin and .view-head's
   bottom margin are adjacent, so they COLLAPSE to the larger of the two. 6px would
   have collapsed into the existing 16px and changed nothing at all. Change the
   shadow and change this number with it, they are the same measurement. */
.sched-bar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  position: sticky; top: 0; z-index: 20; background: var(--bg);
  box-shadow: 0 -22px 0 var(--bg);
  margin: 22px 0 14px; padding: 8px 0 10px;
}
.sched-bar .date-nav { margin-left: 0; }
.sched-bar .grow { flex: 1; }

/* Round 104 : the Activities head, stuck the same way as the date bar above.
   That list is around 15 000px tall and New activity used to sit at the very top
   of it, so creating a second activity meant scrolling all the way back up.

   SAME MECHANISM as .sched-bar, deliberately, rather than a second invention :
   sticky at top 0, opaque --bg background, and a flat opaque shadow extending it
   UPWARDS over the top padding of .view, where rows would otherwise be seen
   scrolling through the gap. .view is the scroll container, so the shadow is
   clipped by it and cannot leak above the page.

   ROUND 88'S TRAP CANNOT FIRE HERE, and that is by construction, not by luck.
   There, the shadow belonged to a bar sitting UNDER a page title : it was painted
   over the h2 above and erased the baseline row of every letter ("Dashboard" with
   its feet cut off). Here the sticky element IS the .view-head, so the title
   travels inside it and the shadow falls on .view's own padding, which is empty by
   definition. No text can ever be under it, so .sched-bar's compensating margin-top
   is NOT copied : adding one would only push the title down for nothing.

   The 16px gap .view-head had below it is kept, moved from margin to padding so
   that the opaque background covers it : content must not show through the gap
   while it scrolls under the bar.

   ponytail: effective on the DESKTOP layout only, and that is a property of the
   phone layout, not of this rule. Under 760px `#app` drops its `100vh` row, so
   `.main-col` and `.view` grow to their content and the DOCUMENT scrolls instead.
   `.view` still declares `overflow: auto`, so it is still the scrollport this
   sticky resolves against -- and that scrollport never scrolls, which makes every
   sticky bar of this app inert on a phone. Measured at 390px on 27/08/2026 : the
   round 68 `.sched-bar` of the Dashboard travels from y=223 to y=-977 on a
   1200px scroll, exactly like this one. Nothing here made that worse, and the two
   bars behave identically, which is the point.
   The upgrade path, if a coordinator ever asks for it on a phone, is to give
   `#app` a `100dvh` row under 760px so `.view` scrolls internally again. That
   changes the scroll model of EVERY back office screen on mobile, so it belongs
   to a round of its own with all ten screens re-checked, not to this one. */
.act-head {
  position: sticky; top: 0; z-index: 20; background: var(--bg);
  box-shadow: 0 -22px 0 var(--bg);
  margin-bottom: 0; padding-bottom: 16px;
}

/* Cards */
.card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg); box-shadow: var(--shadow-1); }
.card.pad { padding: 18px; }
.grid { display: grid; gap: 14px; }
.cards-auto { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }

/* Empty state */
.empty {
  text-align: center; padding: 48px 20px; color: var(--muted); background: var(--surface);
  border: 1px dashed var(--line-strong); border-radius: var(--r-lg);
}
.empty svg { width: 40px; height: 40px; margin-bottom: 12px; color: var(--line-strong); }
.empty h3 { margin: 0 0 6px; color: var(--ink); font-size: var(--t17); font-weight: var(--w590); }
.empty p { margin: 0 0 16px; font-size: var(--t15); }
.loading { text-align: center; padding: 48px; color: var(--muted); }

/* Chips (person) */
.chip {
  display: inline-flex; align-items: center; gap: 6px; background: var(--surface); border: 1px solid var(--line);
  border-radius: 8px; padding: 4px 8px; font-size: var(--t13); cursor: grab; max-width: 100%;
  box-shadow: var(--shadow-1); user-select: none;
}
.chip:active { cursor: grabbing; }
.chip.dragging { opacity: .4; }
.chip .ini { width: 20px; height: 20px; border-radius: 50%; display: grid; place-items: center; font-size: 10px; font-weight: var(--w700); color: #fff; flex: 0 0 20px; }
.chip .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* IT / PV badges. Round 68 : the same two badges are drawn on the day chips AND in
   the person column of the week grid, so the selectors hang on the badge, not on
   the chip that used to be their only host (personBadges() in core.js). */
.badges { display: inline-flex; align-items: center; gap: 3px; flex: none; }
.it { font-size: 9px; font-weight: var(--w700); color: var(--type-visit); border: 1px solid currentColor; border-radius: 4px; padding: 0 3px; }
/* PV : private_visit eligible. Same badge shape as IT, discreet blue ink. */
.pv { font-size: 9px; font-weight: var(--w700); color: var(--accent); border: 1px solid currentColor; border-radius: 4px; padding: 0 3px; }
.chip .ven { width: 7px; height: 7px; border-radius: 50%; background: var(--venice); flex: 0 0 7px; }
.chip.forced { border-color: var(--warning); box-shadow: 0 0 0 1px var(--warning); }

/* Round 101 : the activity blocks and the note the coordinator writes on them.
   ONE family of rules for the four screens that show it (activity list, day grid,
   dashboard, agenda tables), because a note that reads differently depending on
   the screen is how a plan stops being one plan. */
/* The block of the Activities screen : the same flex row it has always been, plus
   a chip strip and a note under the subtitle. `min-width: 0` on the growing column
   is what lets a long name ellipse instead of pushing the pencil off a 390px
   screen ; `align-items: flex-start` keeps the type tag and the pencil at the top
   once the block is three lines tall. */
.act-card { display: flex; align-items: flex-start; gap: 14px; cursor: pointer; }
.act-card > .grow { min-width: 0; }
.act-card:hover { border-color: var(--line-strong); }
.act-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.act-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
/* The chips of a LIST are read, not dragged : no grab cursor, and the click goes
   to the block underneath (which opens the staffing panel). */
.act-chips .chip { cursor: inherit; }
/* The note, full text, wrapped. overflow-wrap so a pasted link or one very long
   word cannot widen the block and bring back a horizontal scroll. */
.act-note-full, .act-card .act-note {
  margin-top: 6px; font-size: var(--t13); line-height: 1.4; color: var(--ink);
  white-space: pre-line; overflow-wrap: anywhere;
}
/* Round 106 : a convocation, wherever it is drawn next to real staffing. DASHED,
   and that is the whole message : this block is not solid work, it takes no slot
   and staffs nothing. A coordinator scanning the Activities list or the day grid
   must be able to tell the two apart without reading a word. */
.act-card.conv-card { border-style: dashed; border-color: var(--warning); }
.act-chip.conv-chip { border-style: dashed !important; }

/* Round 102 : the activity block of the day grid stops being a one-line pill. It
   holds EVERYBODY on the activity plus the note, so it becomes a small column :
   a header line (type initial, name, count), the note, then the chip strip. Full
   width of the cell, so the chips wrap INSIDE the block instead of beside it. */
.act-chip {
  display: flex; flex-direction: column; align-items: stretch; gap: 4px;
  width: 100%; max-width: 100%; min-width: 0;
}
.act-chip .act-chip-head { display: flex; align-items: center; gap: 6px; min-width: 0; }
.act-chip .act-chip-people { display: flex; flex-wrap: wrap; gap: 4px; min-width: 0; }
/* Smaller than a room chip on purpose : these are a roll call inside a block, not
   drag handles. They are read, not dragged, and the click goes to the block
   underneath, which opens the staffing panel. */
.act-chip .act-chip-people .chip {
  cursor: inherit; font-size: 11px; padding: 2px 6px; gap: 4px; box-shadow: none;
}
.act-chip .act-chip-people .chip .ini { width: 16px; height: 16px; flex: 0 0 16px; font-size: 9px; }
/* The note stays discreet and BOUNDED : two lines at most, so a 500 character note
   can never push the people out of sight. The full text is in the tooltip and in
   the staffing panel. An activity without a note renders no element at all. */
.act-chip .act-note {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
  min-width: 0; font-size: 10px; line-height: 1.35; color: var(--muted);
  border-left: 2px solid var(--line); padding-left: 6px; overflow-wrap: anywhere;
}
/* Agenda tables of the back office (person record, print) : the note under the
   placement, never struck through with a cancelled slot. */
.ag-tbl .ag-note {
  display: block; margin-top: 2px; font-size: 12px; line-height: 1.35; color: var(--muted);
  white-space: pre-line; overflow-wrap: anywhere; text-decoration: none;
}

/* ============ Allocation journal (round 42) ============ */
.alloc-log { display: flex; flex-direction: column; }
.alloc-row { display: flex; align-items: flex-start; gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--line); }
.alloc-row:last-child { border-bottom: 0; }
.alloc-what { font-size: var(--t14); }
.alloc-act {
  font-size: 10px; font-weight: var(--w700); padding: 3px 8px; border-radius: 6px; white-space: nowrap;
  background: var(--surface-2); color: var(--muted); flex: 0 0 auto; min-width: 74px; text-align: center;
}
.alloc-act.create { background: var(--ok-bg); color: var(--ok-ink); }
.alloc-act.move { background: var(--over-bg); color: var(--over-ink); }
.alloc-act.delete, .alloc-act.cancel { background: var(--under-bg); color: var(--under-ink); }

/* ============ Schedule Day ============ */
.day-grid { display: grid; grid-template-columns: 190px 1fr 1fr 1fr; border: 1px solid var(--line); border-radius: var(--r-lg); overflow: hidden; background: var(--surface); }
/* `position: sticky` here is scoped to .day-grid, which is a scroll container of its
   own (overflow: hidden) and never scrolls : these headers travel with the grid and
   never meet the sticky bar above. Same story for .week-row.head inside .week-grid. */
.day-grid .gh { background: var(--surface-2); font-weight: var(--w590); font-size: var(--t13); color: var(--muted); padding: 10px 12px; border-bottom: 1px solid var(--line); position: sticky; top: 0; z-index: 2; }
.day-grid .rlabel { padding: 10px 12px; border-bottom: 1px solid var(--line); border-right: 1px solid var(--line); display: flex; flex-direction: column; gap: 3px; }
.day-grid .rlabel .rn { font-weight: var(--w590); font-size: var(--t13); }
.day-grid .rlabel .rmeta { font-size: 11px; color: var(--muted); }
.day-grid .rlabel .lang-badge { align-self: flex-start; margin-top: 2px; font-size: 10px; font-weight: var(--w590); letter-spacing: .02em; color: var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); border-radius: 5px; padding: 1px 6px; cursor: default; }
.cell {
  border-bottom: 1px solid var(--line); border-right: 1px solid var(--line); padding: 8px; min-height: 62px;
  display: flex; flex-wrap: wrap; gap: 5px; align-content: flex-start; position: relative; transition: background var(--t-fast); cursor: pointer;
}
.cell:hover { background: var(--surface-2); }
.cell.drop-ok { background: var(--ok-bg); outline: 2px dashed var(--success); outline-offset: -2px; }
/* Person search active and nobody in this cell matches : greyed back, still usable. */
.cell.cell-nomatch { background: var(--surface-2); opacity: .6; }
.cell .cell-count { position: absolute; top: 5px; right: 7px; font-size: 10px; font-weight: var(--w700); padding: 1px 6px; border-radius: 8px; }
/* Round 84 : a shift whose minimum was derogated carries the one-line reason left of
   its counter. Both share one strip pinned top-right so the note can ellipse without
   ever pushing the counter out of the cell. */
.cell .cell-meta { position: absolute; top: 5px; left: 30px; right: 7px; display: flex; align-items: center; justify-content: flex-end; gap: 4px; }
.cell .cell-meta .cell-count { position: static; flex: none; }
/* Both oranges come from the theme warning family : --over-ink is the INK the app
   already uses for warning TEXT (toast.warn, status-strip strong), and it stays
   readable at 10px where the raw --warning is too pale on a light background. */
.cell .cell-note { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 10px; font-weight: var(--w590); color: var(--over-ink); }
/* The derogation orange NEVER repaints an under-staffed badge : .cc-derog is only put
   on the cell when the shift holds its minimum (see cellHtml), and .cc-under keeps
   winning on specificity order anyway. */
.cc-derog { background: color-mix(in srgb, var(--warning) 22%, transparent); color: var(--over-ink); }
.cc-under { background: var(--under-bg); color: var(--under-ink); }
.cc-ok { background: var(--ok-bg); color: var(--ok-ink); }
.cc-over { background: var(--over-bg); color: var(--over-ink); }
.cc-closed { background: var(--closed-bg); color: var(--closed-ink); }
/* Room outside its opening window on this date : greyed, still droppable (soft ROOM_CLOSED). */
.day-grid .room-closed { background: var(--closed-bg); }
.day-grid .cell.room-closed:hover { background: #E7E7EA; }
.day-grid .rlabel.room-closed .rn { color: var(--closed-ink); }
.day-grid .special .rlabel { background: var(--surface-2); }
/* The Off site row spans the three half-day columns. It used to be written as an
   inline style by schedule-day.js, which no media query can reach : on the single
   column phone grid it created three IMPLICIT columns of its own and dragged the
   whole day sideways. Same declaration, now overridable (see the phone block). */
.day-grid .off-cell { grid-column: 2 / 5; }
.off-cell { background: repeating-linear-gradient(45deg, var(--surface), var(--surface) 6px, #ececef 6px, #ececef 12px); }
.cell .slot-tag { display: none; position: absolute; top: 5px; left: 7px; font-size: 10px; font-weight: var(--w700); color: var(--muted); }
@media (max-width: 760px) { .cell .slot-tag { display: block; } .cell { padding-left: 26px; } }

/* ============ Schedule Week ============ */
.week-grid { border: 1px solid var(--line); border-radius: var(--r-lg); overflow: auto; background: var(--surface); }
.week-row { display: grid; grid-template-columns: 180px repeat(7, minmax(0, 1fr)) 84px; border-bottom: 1px solid var(--line); }
/* Fixed height for exactly three slot lines (M / A / E) : every cell is the same
   size whether the person works one shift, three, or none. */
.week-row:not(.head) { height: 64px; }
/* Sticky INSIDE .week-grid (a scroll container of its own, overflow:auto for the
   horizontal case), not inside .view : these offsets are relative to the grid and
   must stay so. Offsetting them by the sticky bar pushed them down by that much at
   rest, a sticky top constraint applying whether or not the container can scroll. */
.week-row.head { position: sticky; top: 0; z-index: 3; background: var(--surface-2); }
.week-row.head > div { padding: 9px 8px; font-size: var(--t13); font-weight: var(--w590); color: var(--muted); text-align: center; }
.week-row.head > div:first-child { text-align: left; }
.grp-head { position: sticky; top: 37px; z-index: 2; background: var(--accent-soft); color: var(--accent); font-weight: var(--w700); font-size: var(--t13); padding: 6px 12px; border-bottom: 1px solid var(--line); }
.grp-head.b { background: #F6EDFB; color: var(--grp-b); }
.wperson { padding: 8px 10px; border-right: 1px solid var(--line); display: flex; align-items: center; gap: 8px; font-size: var(--t13); font-weight: var(--w510); min-width: 0; }
.wperson .wp-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.wday { border-right: 1px solid var(--line); padding: 3px 5px; display: flex; flex-direction: column; justify-content: flex-start; gap: 2px; min-width: 0; overflow: hidden; }
.seg-seg { display: block; flex: none; height: 18px; max-width: 100%; font-size: 10px; line-height: 14px; font-weight: var(--w590); padding: 2px 5px; border-radius: 5px; color: #fff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.seg-seg .seg-slot { display: inline-block; min-width: 9px; margin-right: 4px; font-weight: var(--w700); opacity: .8; }
.seg-blank { display: block; flex: none; height: 18px; }
.seg-dim { opacity: .32; }
.seg-more { display: block; flex: none; font-size: 10px; line-height: 12px; font-weight: var(--w700); color: var(--muted); padding: 0 5px; }
.wcount { display: flex; align-items: center; justify-content: center; font-size: var(--t13); font-weight: var(--w700); }
.wcount.warn { color: var(--danger); }
.off-label { font-size: 10px; font-weight: var(--w700); color: var(--muted); letter-spacing: .04em; }
.week-legend { display: flex; flex-wrap: wrap; align-items: center; gap: 16px; margin-top: 12px; font-size: var(--t13); color: var(--muted); }
.week-legend > span { display: inline-flex; align-items: center; gap: 6px; }
.lg-sw { width: 12px; height: 12px; border-radius: 3px; display: inline-block; }
.lg-sw.lg-off { background: repeating-linear-gradient(45deg, var(--surface), var(--surface) 3px, #ececef 3px, #ececef 6px); border: 1px solid var(--line); }
/* A slot of the week grid opens its detail modal (round 28). */
.seg-click { cursor: pointer; }
.seg-seg.seg-click:hover { filter: brightness(1.08); }
.off-label.seg-click:hover { color: var(--ink); }
/* Key / value block of the slot detail modal. */
.kv { display: grid; grid-template-columns: 110px 1fr; gap: 8px 12px; margin: 0; }
.kv dt { font-size: var(--t13); color: var(--muted); }
.kv dd { margin: 0; font-size: var(--t15); }

/* ============ Schedule Weeks (round 73, whole period, one square per half-day) ====
   Same shape as .week-grid : the grid is its own scroll container, so the person
   column is sticky on the LEFT and the two header rows are sticky on TOP, both
   relative to the grid and never to the page. Squares carry no attribute and no
   handler ; their colour comes from the per-room rules generated by the view. */
.wk-grid {
  border: 1px solid var(--line); border-radius: var(--r-lg); background: var(--surface);
  /* Fluid width : the grid always fills the page and the day columns share whatever
     is left, so there is never a horizontal scrollbar and never a half-empty box.
     Only the vertical axis scrolls (130 rows). */
  overflow-x: hidden; overflow-y: auto; max-height: calc(100vh - 210px);
  width: 100%;
  --wk-person: 200px;
}
/* Day columns are 1fr each : they stretch on a wide page and shrink on a narrow one.
   minmax(0, 1fr) is what allows them to go under their content width instead of
   forcing the row wider than the box. */
.wk-row { display: grid; grid-template-columns: var(--wk-person) repeat(var(--wk-cols), minmax(0, 1fr)); width: 100%; }
.wk-row:not(.wk-head):not(.wk-months) { border-top: 1px solid var(--line); }
.wk-p {
  position: sticky; left: 0; z-index: 2; background: var(--surface);
  border-right: 1px solid var(--line); padding: 4px 8px; min-width: 0;
  display: flex; align-items: center; gap: 6px; font-size: var(--t13); font-weight: var(--w510); cursor: pointer;
}
.wk-p .wp-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1; }
.wk-n { font-size: 10px; font-weight: var(--w700); color: var(--muted); flex: none; }
/* Two header rows : month bands, then weekday + day number. */
.wk-months { position: sticky; top: 0; z-index: 5; background: var(--surface-2); height: 22px; }
.wk-months .wk-p { background: var(--surface-2); z-index: 6; }
.wk-m { font-size: 11px; font-weight: var(--w700); color: var(--muted); text-align: center; line-height: 22px; border-left: 1px solid var(--line); overflow: hidden; }
.wk-head { position: sticky; top: 22px; z-index: 5; background: var(--surface-2); height: 30px; border-bottom: 1px solid var(--line); }
.wk-head .wk-p { background: var(--surface-2); z-index: 6; color: var(--muted); font-weight: var(--w590); }
.wk-head .wk-d { display: flex; flex-direction: column; align-items: center; justify-content: center; line-height: 1.05; }
.wk-head .wk-d b { font-size: 9px; font-weight: var(--w700); color: var(--muted); }
.wk-head .wk-d i { font-size: 10px; font-style: normal; font-weight: var(--w590); }
.wk-grp {
  /* Full width of the fluid grid, so the band never stops mid-row. */
  position: sticky; top: 52px; z-index: 4; width: 100%;
  background: var(--accent-soft); color: var(--accent); font-weight: var(--w700); font-size: var(--t13);
  border-bottom: 1px solid var(--line); line-height: 22px;
}
.wk-grp.b { background: #F6EDFB; color: var(--grp-b); }
.wk-grp > span { position: sticky; left: 10px; display: inline-block; }
/* One day of one person : three squares, M / A / E top to bottom. */
.wk-d { display: flex; flex-direction: column; align-items: center; gap: 1px; padding: 2px 1px; min-width: 0; }
.wk-d.we { background: var(--surface-2); }
/* The square caps at 10px on a wide page and shrinks with its column on a narrow one.
   aspect-ratio keeps it square instead of turning into a sliver. */
.wk-sq { width: min(10px, 100%); aspect-ratio: 1; border-radius: 2px; flex: none; background: #F2F2F4; cursor: pointer; }
.wk-sq:hover { outline: 1px solid var(--ink); outline-offset: 1px; }
.wk-ro .wk-sq { cursor: default; }
.wk-act { background: #6F7A8A; }
.wk-off { background: #9A9AA0; }
.wk-half { background: #DCDCE0; box-shadow: inset 0 0 0 1px #9A9AA0; }
.wk-abs { background: repeating-linear-gradient(45deg, #fff, #fff 2px, #C9A227 2px, #C9A227 4px); }
.wk-inact { background: #fff; box-shadow: inset 0 0 0 1px var(--line-strong); }
/* Round 82 : the cell holds a room AND an activity. The room keeps the square (that
   is what this grid is read for) and the notched top-right corner says the person is
   also away on an activity that half-day. Works on a 8px square, prints in grey. */
.wk-plus { clip-path: polygon(0 0, 62% 0, 100% 38%, 100% 100%, 0 100%); }
/* Legend : one entry per room, the selected one picked out by the Room filter. */
.wk-legend .lg-sw { border: 1px solid var(--line); }
.wk-legend > span.on { color: var(--ink); font-weight: var(--w700); }
@media (max-width: 760px) { .wk-grid { --wk-person: 130px; --wk-col: 14px; } .wk-sq { width: 8px; height: 8px; } }

/* ============ Schedule Event (whole-period month calendar) ============ */
.event-cal { border: 1px solid var(--line); border-radius: var(--r-lg); overflow: hidden; background: var(--surface); }
.ev-row { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
.ev-row.head > div { padding: 9px 8px; font-size: var(--t13); font-weight: var(--w590); color: var(--muted); text-align: center; background: var(--surface-2); border-bottom: 1px solid var(--line); }
.ev-day { min-height: 84px; padding: 7px 9px; border-right: 1px solid var(--line); border-bottom: 1px solid var(--line); display: flex; flex-direction: column; gap: 6px; cursor: pointer; transition: background var(--t-fast); }
.ev-day:hover { background: var(--surface-2); }
.ev-day.ev-out { background: var(--closed-bg); cursor: default; }
/* Room-filtered Event calendar (round 29) : closed day / under-minimum day. */
.ev-day.ev-closed { background: var(--closed-bg); }
.ev-day.ev-closed .ev-num { color: var(--closed-ink); }
.ev-day.ev-under { box-shadow: inset 0 0 0 1.5px var(--under-ink); }
.ev-row > .ev-day:nth-child(7n) { border-right: 0; }
.ev-num { font-size: var(--t13); font-weight: var(--w700); }
/* Round 68 : two numbers per day, on duty and off, big enough to be read across the
   whole month at once. Room-filtered, only the first one is drawn. */
.ev-counts { display: flex; align-items: flex-end; flex-wrap: wrap; gap: 4px 14px; }
.ev-big { display: inline-flex; align-items: baseline; gap: 4px; }
.ev-big b { font-size: var(--t28); font-weight: var(--w700); letter-spacing: -.02em; line-height: 1; }
/* The label never breaks mid-way : the pair drops to the next line instead. */
.ev-big i { font-style: normal; font-size: 11px; color: var(--muted); white-space: nowrap; }
.ev-big.off b { color: var(--muted); }
.ev-big.zero b { color: var(--line-strong); }
.ev-empty { font-size: 11px; color: var(--muted); }
.week-legend .lg-n { font-size: var(--t17); font-weight: var(--w700); line-height: 1; }
.week-legend .lg-n.off { color: var(--muted); }
.st-dot.st-absent { background: var(--danger); }
@media (max-width: 760px) { .ev-day { min-height: 92px; padding: 6px; } .ev-counts { gap: 10px; } .ev-big b { font-size: var(--t22); } }

/* Pre-opening (27-31 Aug) advisory banner : rules relaxed for the special-activity days. */
.pre-open-banner { display: flex; align-items: center; gap: 8px; margin: 0 0 14px; padding: 9px 14px; border-radius: var(--r-lg); background: var(--accent-soft); color: var(--accent); font-size: var(--t13); font-weight: var(--w510); }
.pre-open-banner svg { width: 16px; height: 16px; flex: none; }

/* ============ Tables ============ */
.tbl { width: 100%; border-collapse: collapse; background: var(--surface); border-radius: var(--r-lg); overflow: hidden; }
.tbl th { text-align: left; font-size: var(--t13); font-weight: var(--w590); color: var(--muted); padding: 11px 14px; border-bottom: 1px solid var(--line); background: var(--surface-2); white-space: nowrap; cursor: pointer; user-select: none; position: sticky; top: 0; }
.tbl th .arrow { font-size: 9px; margin-left: 3px; }
.tbl td { padding: 11px 14px; border-bottom: 1px solid var(--line); font-size: var(--t15); vertical-align: middle; }
.tbl tr:last-child td { border-bottom: 0; }
.tbl tbody tr { cursor: pointer; }
.tbl tbody tr:hover { background: var(--surface-2); }

/* Personal agenda : one row per day, Date / Morning / Afternoon / Evening. */
.ag-tbl { border: 1px solid var(--line); }
.ag-tbl th, .ag-tbl tbody tr { cursor: default; }
.ag-tbl td { padding: 7px 10px; font-size: var(--t13); }
.ag-tbl th { padding: 8px 10px; }
.ag-tbl .ag-date { font-weight: var(--w590); white-space: nowrap; }
.ag-tbl .ag-off { color: var(--muted); }
.ag-tbl .ag-we { background: var(--surface-2); }
.ag-tbl .ag-we:hover { background: var(--surface-2); }
.ag-tbl .ag-time { color: var(--muted); }
.ag-tbl .ag-forced { font-size: 10px; font-weight: var(--w700); color: var(--over-ink); }
/* Round 106, the convocation column of the personal agenda. ONE LINE PER MEETING,
   each with its own hours, in a column of its own : a convocation occupies no
   half-day, so it must not sit in one of the four cells and must not read as a
   shift. Dashed rule on the left = "this is not staffing". */
.ag-tbl .ag-conv { border-left: 1px dashed var(--line-strong); }
.ag-tbl .ag-conv-line {
  display: block; border-left: 3px solid var(--accent); background: var(--accent-soft);
  border-radius: 2px; padding: 3px 6px; overflow-wrap: anywhere;
}
.ag-tbl .ag-conv-line + .ag-conv-line { margin-top: 4px; }
.tag { display: inline-block; font-size: 11px; padding: 2px 8px; border-radius: var(--r-pill); background: var(--surface-2); border: 1px solid var(--line); color: var(--muted); margin: 1px 2px 1px 0; }
.grp-tag { font-size: 11px; font-weight: var(--w700); padding: 2px 8px; border-radius: var(--r-pill); }
.grp-tag.A { background: var(--accent-soft); color: var(--accent); }
.grp-tag.B { background: #F6EDFB; color: var(--grp-b); }
.dot-venice { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--venice); }
.stepper { display: inline-flex; align-items: center; gap: 6px; }
.stepper button { width: 26px; height: 26px; border: 1px solid var(--line-strong); background: var(--surface); border-radius: 7px; cursor: pointer; font-weight: var(--w700); }
.stepper .v { min-width: 22px; text-align: center; font-weight: var(--w590); }

/* Status coverage cards (dashboard) */
.room-card { padding: 14px; border-radius: var(--r-lg); border: 1px solid var(--line); }
.room-card.under { background: var(--under-bg); }
.room-card.ok { background: var(--surface); }
.room-card.over { background: var(--over-bg); }
.room-card.closed { background: var(--closed-bg); opacity: .7; }
.room-card .rc-top { display: flex; justify-content: space-between; align-items: baseline; }
.room-card .rc-name { font-weight: var(--w590); font-size: var(--t15); }
.room-card .rc-stat { font-size: var(--t22); font-weight: var(--w700); }
.room-card .rc-sub { font-size: var(--t13); color: var(--muted); margin-top: 2px; }
.gauge { height: 8px; border-radius: 4px; background: var(--line); overflow: hidden; margin-top: 8px; }
.gauge > span { display: block; height: 100%; background: var(--accent); }

.kpi-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; margin-bottom: 18px; }
.kpi { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r-lg); padding: 16px; box-shadow: var(--shadow-1); }
.kpi .n { font-size: var(--t28); font-weight: var(--w700); letter-spacing: -.02em; }
.kpi .l { font-size: var(--t13); color: var(--muted); margin-top: 2px; }
.section-title { font-size: var(--t17); font-weight: var(--w590); margin: 22px 0 12px; }

/* ============ Modal ============ */
.modal-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,.28); z-index: 100; display: flex; align-items: center; justify-content: center; padding: 20px; }
.modal { background: var(--surface); border-radius: var(--r-lg); box-shadow: var(--shadow-pop); width: 100%; max-width: 520px; max-height: 88vh; overflow: auto; }
.modal.wide { max-width: 720px; }
.modal-head { display: flex; align-items: center; padding: 18px 20px; border-bottom: 1px solid var(--line); }
.modal-head h3 { margin: 0; font-size: var(--t17); font-weight: var(--w590); }
.modal-head .grow { flex: 1; }
.modal-body { padding: 20px; }
.modal-foot { display: flex; gap: 10px; justify-content: flex-end; padding: 16px 20px; border-top: 1px solid var(--line); }
.viol-item { display: flex; gap: 10px; padding: 10px 12px; border-radius: var(--r); background: var(--surface-2); margin-bottom: 8px; }
.viol-item .sev { font-size: 10px; font-weight: var(--w700); padding: 2px 7px; border-radius: 6px; height: fit-content; }
.viol-item .sev.hard { background: var(--under-bg); color: var(--under-ink); }
.viol-item .sev.soft { background: var(--over-bg); color: var(--over-ink); }
.viol-item .vm { font-size: var(--t13); }
.viol-item .vr { font-size: 11px; color: var(--muted); }

/* ============ Side panel ============ */
.side-panel { position: fixed; top: 0; right: 0; bottom: 0; width: 380px; max-width: 92vw; background: var(--surface); box-shadow: var(--shadow-pop); z-index: 90; display: flex; flex-direction: column; transform: translateX(100%); transition: transform 200ms ease; }
.side-panel.open { transform: translateX(0); }
.side-panel .sp-head { display: flex; align-items: center; padding: 16px 18px; border-bottom: 1px solid var(--line); }
.side-panel .sp-head h3 { margin: 0; font-size: var(--t17); font-weight: var(--w590); }
.side-panel .sp-head .grow { flex: 1; }
.side-panel .sp-body { flex: 1; overflow: auto; padding: 14px 18px; }
.candidate { display: flex; align-items: center; gap: 10px; padding: 10px; border: 1px solid var(--line); border-radius: var(--r); margin-bottom: 8px; background: var(--surface); }
.candidate > svg { width: 16px; height: 16px; flex: none; color: var(--muted); }
.candidate .grow { flex: 1; min-width: 0; }
.candidate .cn { font-weight: var(--w510); font-size: var(--t15); }
.candidate .cr { font-size: var(--t13); color: var(--muted); }
.candidate .score { font-weight: var(--w700); color: var(--success); }
/* Move to room / slot (round 68) : green = trained AND already worked in that room
   on this event, orange = trained only. Same two soft tints as the coverage badges,
   no new palette. */
.candidate.mv-worked { background: var(--ok-bg); border-color: var(--ok-ink); }
.candidate.mv-trained { background: var(--over-bg); border-color: var(--over-ink); }
.move-key { display: inline-block; width: 11px; height: 11px; border-radius: 3px; vertical-align: -1px; border: 1px solid; }
.move-key.mv-worked { background: var(--ok-bg); border-color: var(--ok-ink); }
.move-key.mv-trained { background: var(--over-bg); border-color: var(--over-ink); }
.reasons { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 3px; }

/* ============ Toasts ============ */
#toasts { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); z-index: 300; display: flex; flex-direction: column; gap: 8px; align-items: center; }
.toast { display: flex; align-items: center; gap: 12px; background: #1D1D1F; color: #fff; padding: 11px 16px; border-radius: var(--r); box-shadow: var(--shadow-pop); font-size: var(--t15); max-width: 460px; animation: toast-in 180ms ease; }
.toast.warn { background: var(--over-ink); }
.toast.error { background: var(--danger); }
.toast.success { background: var(--ok-ink); }
.toast button { background: rgba(255,255,255,.2); border: 0; color: #fff; padding: 4px 12px; border-radius: var(--r-pill); cursor: pointer; font-weight: var(--w590); font-size: var(--t13); }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

/* ============ Live bar (round 70) ============
   Bottom left, above the tab bar on mobile, clear of the toasts (which sit
   centred). Same z-index as the toasts, so like them it stays readable over an
   open modal : that is exactly the case where the badge shows instead of a
   silent redraw. Non blocking : it takes no click the user did not aim at. */
#live-bar { position: fixed; left: 16px; bottom: 20px; z-index: 300; display: flex; flex-direction: column; gap: 8px; }
.live-item { display: flex; align-items: center; gap: 10px; background: var(--surface); color: var(--ink);
  border: 1px solid var(--line-strong); border-radius: var(--r); box-shadow: var(--shadow-pop);
  padding: 8px 10px 8px 12px; font-size: var(--t13); animation: toast-in 180ms ease; }
.live-item svg { width: 18px; height: 18px; color: var(--muted); flex: none; }
.live-item.app svg { color: var(--accent); }
.live-item .icon-btn { width: 26px; height: 26px; }
.live-item .icon-btn svg { width: 16px; height: 16px; }
@media (max-width: 760px) { #live-bar { left: 12px; right: 12px; bottom: 72px; } }
.live-item.maint { border-color: var(--warning); background: var(--over-bg); color: var(--over-ink); }
.live-item.maint svg { color: var(--over-ink); }
.badge.s-maint { background: var(--over-bg); color: var(--over-ink); border-color: transparent; }

/* ============ Maintenance screen (round 86) ============
   Same visual language as the login overlay (#login) : it IS the same kind of
   object, a full page standing in front of the app. z-index above #login (200)
   and above #live-bar (300) so nothing can peek through. Superadmins never see
   it : they get the .live-item.maint pill above, with its Turn off button. */
#maintenance {
  position: fixed; inset: 0; z-index: 500; display: flex; align-items: center; justify-content: center;
  background: var(--bg); padding: 20px; overflow: auto;
}
.mt-card {
  width: 100%; max-width: 420px; background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--r-lg); padding: 32px 28px; box-shadow: var(--shadow-1); text-align: center;
}
.mt-logo {
  width: 40px; height: 40px; margin: 0 auto 18px; border-radius: 11px; background: var(--accent);
  display: grid; place-items: center; color: #fff; font-weight: var(--w700); font-size: var(--t15);
}
.mt-card h1 { font-size: var(--t22); font-weight: var(--w700); letter-spacing: -.02em; margin: 0 0 10px; }
.mt-card p { color: var(--muted); font-size: var(--t15); margin: 0 0 14px; line-height: 1.45; }
.mt-card .mt-note {
  color: var(--over-ink); background: var(--over-bg); border-radius: var(--r);
  padding: 10px 12px; font-size: var(--t13);
}
.mt-card .mt-wait { color: var(--muted); font-size: var(--t13); margin: 0; }
.mt-dot {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--warning);
  margin-right: 7px; vertical-align: middle; animation: mt-pulse 1.4s ease-in-out infinite;
}
@keyframes mt-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }
/* Phones : the card stops pretending to be a card and takes the width it needs. */
@media (max-width: 480px) {
  #maintenance { padding: 16px; align-items: flex-start; padding-top: 12vh; }
  .mt-card { padding: 26px 20px; border-radius: var(--r); }
  .mt-card h1 { font-size: var(--t17); }
}
@media (prefers-reduced-motion: reduce) { .mt-dot { animation: none; } }

/* Mini month calendar (person) */
.minical { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; }
.minical .mc-h { font-size: 10px; color: var(--muted); text-align: center; }
.minical .mc-d { aspect-ratio: 1; display: grid; place-items: center; font-size: 11px; border-radius: 6px; background: var(--surface-2); }
.minical .mc-d.worked { background: var(--accent-soft); color: var(--accent); font-weight: var(--w590); }
.minical .mc-d.off { background: var(--closed-bg); color: var(--muted); }
.minical .mc-d.empty { background: transparent; }

/* Mobile tab bar (hidden on desktop) */
.tabbar { display: none; }

@media (max-width: 760px) {
  #app { grid-template-columns: 1fr; grid-template-rows: 1fr; }
  .sidebar { display: none; }
  .tabbar {
    display: flex; position: fixed; bottom: 0; left: 0; right: 0; height: 58px; z-index: 80;
    background: var(--surface); border-top: 1px solid var(--line); padding-bottom: env(safe-area-inset-bottom);
  }
  .tabbar button { flex: 1; border: 0; background: transparent; display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 8px 0; color: var(--muted); font-size: 10px; cursor: pointer; position: relative; }
  .tabbar button.active { color: var(--accent); }
  .tabbar button svg { width: 22px; height: 22px; }
  .tabbar button .count-badge { position: absolute; top: 4px; right: 50%; margin-right: -22px; background: var(--danger); color: #fff; font-size: 9px; min-width: 15px; height: 15px; border-radius: 8px; display: grid; place-items: center; }
  /* Round 94 : the tab bar pads itself with the home indicator inset, so the
     content behind it has to clear the same distance or the last row sits under
     the bar on a notched phone (and under the bar itself in standalone mode). */
  .view { padding: 14px 12px calc(78px + env(safe-area-inset-bottom)); }
  .topbar { flex-wrap: wrap; height: auto; padding: 8px 12px; gap: 8px; }
  .search-box { width: 100%; order: 5; }
  .filters { padding: 0 12px; }
  .day-grid { grid-template-columns: 1fr; }
  .day-grid .gh:not(:first-child) { display: none; }
  .day-mobile-room { border: 1px solid var(--line); border-radius: var(--r-lg); margin-bottom: 12px; overflow: hidden; }
  .day-mobile-room .dm-head { padding: 10px 12px; background: var(--surface-2); font-weight: var(--w590); font-size: var(--t15); }
  .day-mobile-slot { display: flex; align-items: flex-start; gap: 8px; padding: 8px 12px; border-top: 1px solid var(--line); }
  .day-mobile-slot .sl { width: 24px; font-size: var(--t13); font-weight: var(--w700); color: var(--muted); }
  .side-panel { width: 100%; }
  .week-row { grid-template-columns: 120px repeat(7, minmax(0, 1fr)) 60px; }
}

/* Weeks view : the name column gives ground first, so the 35 day columns keep as
   much room as possible before the squares have to shrink. */
@media (max-width: 1200px) { .wk-grid { --wk-person: 150px; } }
@media (max-width: 1000px) { .wk-grid { --wk-person: 120px; } }

/* ---------- Round A : roles, admin section, event switcher, 2FA ---------- */

/* Small grey helper line, used outside the room cards too (settings, admin). */
.rc-sub { font-size: var(--t13); color: var(--muted); margin-top: 2px; }

/* Event switcher : a real select as soon as the user may see several events. */
.topbar .event-switch.pick select {
  height: 28px; padding: 0 26px 0 8px; font-weight: var(--w590); font-size: var(--t15);
  border-color: transparent; background-color: transparent; max-width: 260px;
}
.topbar .event-switch.pick select:hover { border-color: var(--line-strong); background-color: var(--surface); }

/* Tabs (Admin : Events / Users) */
.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--line); margin-bottom: 16px; }
.tab {
  appearance: none; background: transparent; border: 0; border-bottom: 2px solid transparent;
  padding: 8px 14px; font-size: var(--t15); font-weight: var(--w510); color: var(--muted); cursor: pointer;
}
.tab:hover { color: var(--ink); }
.tab.on { color: var(--accent); border-bottom-color: var(--accent); }

/* Status badge (event status in the admin list) */
.badge {
  display: inline-block; padding: 2px 10px; border-radius: var(--r-pill);
  font-size: var(--t13); background: var(--surface-2); color: var(--muted); border: 1px solid var(--line);
}
.badge.s-active { background: var(--ok-bg); color: var(--ok-ink); border-color: transparent; }
.badge.s-archived { background: var(--closed-bg); color: var(--closed-ink); border-color: transparent; }

/* 2FA code field */
.code-input { text-align: center; font-size: var(--t28); letter-spacing: 10px; padding-left: 10px; }

/* ============ Allocation status (round B) ============
   Orange while the person has not answered, green once validated, grey and struck
   through once declined or cancelled. Same three tokens everywhere : chips (day),
   segments (week), agenda tables, person record, public page. */
.st-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; vertical-align: baseline; background: var(--closed-ink); }
.st-dot.st-proposed { background: var(--warning); }
.st-dot.st-validated { background: var(--success); }
.st-dot.st-refused, .st-dot.st-cancelled { background: var(--closed-ink); }

.chip.st-proposed { border-left: 3px solid var(--warning); }
.chip.st-validated { border-left: 3px solid var(--success); }
.chip.st-refused, .chip.st-cancelled {
  border-left: 3px solid var(--closed-ink); background: var(--closed-bg); color: var(--closed-ink);
  text-decoration: line-through; box-shadow: none; opacity: .75;
}
.seg-seg.st-refused, .seg-seg.st-cancelled { text-decoration: line-through; opacity: .7; }

.st-pill { display: inline-block; padding: 1px 9px; border-radius: var(--r-pill); font-size: 11px; font-weight: var(--w590); background: var(--closed-bg); color: var(--closed-ink); }
.st-pill.st-proposed { background: var(--over-bg); color: var(--over-ink); }
.st-pill.st-validated { background: var(--ok-bg); color: var(--ok-ink); }
.st-counters { display: flex; gap: 8px; flex-wrap: wrap; margin: 0 0 12px; }

/* Discreet day-level status strip above the grid. */
.status-strip { font-size: var(--t13); color: var(--muted); margin: 0 0 12px; }
.status-strip strong { color: var(--over-ink); font-weight: var(--w590); }

/* Public agenda link block on the person record. */
.pub-link { background: var(--surface-2); border-radius: var(--r); padding: 10px 12px; margin-bottom: 14px; }
.pub-link code { font-size: 12px; color: var(--ink); word-break: break-all; }

/* Agenda cells become clickable on the person record (status actions). */
.ag-tbl td.ag-cell { cursor: pointer; }
.ag-tbl td.ag-cell:hover { background: var(--accent-soft); }

/* Settings : sticky save bar + bottom save (long page, the button must never be out of reach) */
.settings-head { position: sticky; top: 0; z-index: 5; background: var(--bg); padding-top: 4px; padding-bottom: 10px; }
.settings-foot { display: flex; justify-content: flex-end; margin: 4px 0 20px; }

/* ---------- Lunches (round 31 : meals per service) ---------- */
/* One card per lunch hour : the headcount in big, the eaters as chips below. */
.lunch-total.card { font-size: 28px; font-weight: var(--w700); color: var(--ink); text-align: center; margin: 0 0 24px; }
.lunch-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; }
.lunch-meals { font-size: 28px; font-weight: var(--w700); margin: 0 0 10px; }
.lunch-people { display: flex; flex-wrap: wrap; gap: 4px; }
.lunch-chip { display: inline-flex; align-items: center; gap: 5px; max-width: 100%;
  background: var(--surface); border: 1px solid var(--line-strong); border-radius: var(--r-pill);
  padding: 2px 8px 2px 2px; font-size: var(--t13); }
.lunch-chip[role=button] { cursor: pointer; }
.lunch-chip[role=button]:hover { border-color: var(--accent); }
.lunch-chip .ini { width: 20px; height: 20px; border-radius: 50%; display: grid; place-items: center;
  font-size: 10px; font-weight: var(--w700); color: #fff; flex: 0 0 20px; }
.lunch-chip .nm { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lunch-chip select { width: auto; height: 24px; padding: 0 4px; font-size: 12px; }
/* Personal agenda : the Lunch column is informative, never an action. */
.ag-tbl td.ag-lunch { color: var(--muted); white-space: nowrap; }

/* ============ Round 94 : phone layout ============
   EVERY rule below is either inert on a desktop (a wrapper with `overflow:
   visible`) or gated on a max-width media query. The desktop layout is the
   reference and nothing here can reach it.

   The back office is a coordinator tool built for a 1440px screen, and it stays
   one. What follows makes the SAME screens usable on a phone : nothing is hidden,
   nothing is reflowed into a different information design, the page simply stops
   pushing its content off the right edge and its controls stop being smaller than
   a fingertip. */

/* Table that must keep its columns on a phone : the horizontal scroll belongs to
   the table and not to the page, so the title, the buttons and the tabs stay put
   while the columns move. On a desktop this is a plain div (overflow visible), so
   the sticky `th` still resolves against .view exactly as before. */
.tbl-scroll { overflow: visible; }

/* Raw JSON settings editor. Was an inline style on the textarea, which no media
   query can override : 12px is exactly the size that makes iOS zoom the page in. */
.json-editor { font-family: ui-monospace, Menlo, monospace; font-size: 12px; line-height: 1.5; }

@media (max-width: 760px) {
  /* Two column form grids are written as INLINE styles by six view modules
     (person, rooms, dashboard, activities, autoschedule, the people modals), and
     an inline style beats any stylesheet rule. One column is the only sane phone
     layout for all of them, hence the !important : it is aimed at the inline
     attribute, not at another rule of this file. */
  .grid { grid-template-columns: 1fr !important; }

  /* Off site row : one column grid, so it must not span three (see .day-grid
     .off-cell above). Spanning 2/5 here created three implicit columns. */
  .day-grid .off-cell { grid-column: 1; }

  /* Heads that carry a title plus three or four buttons : wrap instead of
     pushing the last buttons out of the viewport. */
  .view-head { flex-wrap: wrap; }
  .date-nav { flex-wrap: wrap; }
  .date-nav .cur-date { min-width: 0; }
  .date-nav input[type=date] { min-width: 0; }

  /* Round 101 hid the note here : the chip was one line and an ellipsed note left
     nothing of the name. Round 102 made the block a column the full width of the
     cell, and on a phone the day grid is ONE column, so the note has a whole 390px
     line to itself. It stays. */

  /* Admin tabs : five of them never fit 390px. Scrolled, not truncated. */
  .tabs { overflow-x: auto; }
  .tab { white-space: nowrap; flex: none; }

  .tbl-scroll { overflow-x: auto; }

  /* iOS zooms the whole page in when a focused field is under 16px, and never
     zooms back out. Fields are 15px everywhere in this app. */
  input, select, textarea { font-size: 16px; }
  .code-input { font-size: var(--t28); }
  /* Three fields carry their own font-size and would keep zooming the page in. */
  .pill input[type="search"] { font-size: 16px; }
  .lunch-chip select { font-size: 16px; }
  .json-editor { font-size: 16px; }
  .date-nav input[type=date] { font-size: 16px; height: 44px; }
  /* One shared field height, finger sized. */
  :root { --field-h: 44px; }
  input[type=checkbox] { width: 22px; height: 22px; }
  .avatar { width: 40px; height: 40px; }
  .chip-x { min-width: 28px; min-height: 28px; }
  /* A room filter offering fifteen room names is as wide as its longest option. */
  .pill select { max-width: 46vw; }

  /* Rows written as an inline flex by a view module : let them wrap rather than
     push their last control past the right edge (alerts cards, activity cards,
     the subgroup editor of the settings). */
  .card.pad { flex-wrap: wrap; }
  .sg-row { flex-wrap: wrap; }
  /* A grid item's default min-width is its content : one wide child (the personal
     agenda table) was making the whole column wider than the screen. */
  .grid > * { min-width: 0; }

  /* Finger sized controls. 44px is the Apple HIG minimum ; these are 26 to 34px
     on a desktop and stay so there. */
  .btn, .btn.small, .icon-btn, .seg button, .pill, .stepper button { min-height: 44px; }
  .icon-btn { min-width: 44px; }
  .stepper button { min-width: 44px; }
  .seg button { padding-left: 14px; padding-right: 14px; }
  /* The filter bar grows with its pills instead of clipping them. */
  .filters { height: auto; min-height: 56px; flex: 0 0 auto; padding: 6px 12px; }
  .lunch-chip select { height: 40px; }

  /* Modal : the validation buttons stay on screen whatever the body does. The
     modal is its own scroll container, so a sticky foot is enough. */
  .modal { max-height: 92dvh; }
  .modal-foot { position: sticky; bottom: 0; background: var(--surface); }

  /* Home indicator / notch : the tab bar already pads itself, the content behind
     it and the panels did not. */
  .side-panel .sp-body { padding-bottom: calc(14px + env(safe-area-inset-bottom)); }

  /* Personal agenda (person record, and the read only agenda modal of the People
     list) : the same five columns the public agenda page already stacks on a
     phone, stacked the same way. Date heading, then one labelled line per
     half-day. The labels come from data-l, written by core.js. */
  .ag-tbl thead { display: none; }
  .ag-tbl, .ag-tbl tbody, .ag-tbl tr, .ag-tbl td { display: block; width: 100%; }
  .ag-tbl tr { border-bottom: 1px solid var(--line); }
  .ag-tbl tr:last-child { border-bottom: 0; }
  .ag-tbl td { border-bottom: 0; padding: 4px 12px 6px; font-size: var(--t15); }
  .ag-tbl td.ag-date { padding: 12px 12px 2px; }
  .ag-tbl td.ag-lunch:empty { display: none; }
  /* Round 106 : no meeting that day, no line at all. The dashed column rule is a
     desktop separator and means nothing once the row is stacked. */
  .ag-tbl td.ag-conv:empty { display: none; }
  .ag-tbl td.ag-conv { border-left: 0; }
  .ag-tbl td.ag-cell { min-height: 44px; }
  .ag-tbl td[data-l]::before {
    content: attr(data-l); display: block; font-size: 11px; letter-spacing: .05em;
    text-transform: uppercase; color: var(--muted); font-weight: var(--w590); margin-bottom: 2px;
  }

  /* Whole period grid : 35 day columns cannot share 390px, they were crushed to
     two pixel squares under an unreadable month band. The grid scrolls sideways
     instead, with the person column pinned, and the squares keep a legible size.
     This view remains a desktop view : see the dev log, round 94. */
  .wk-grid { overflow-x: auto; max-height: calc(100dvh - 300px); }
  .wk-row { grid-template-columns: var(--wk-person) repeat(var(--wk-cols), 15px); width: max-content; min-width: 100%; }
  .wk-sq { width: 9px; }
}

/* Event calendar : seven columns of a month grid on a phone leave about 50px per
   day, which fits the two counters but not the words next to them. The numbers
   stay (bold ink = On site, grey = Off site, as the legend under the calendar
   already states) and the words go. */
@media (max-width: 560px) {
  .ev-big i { display: none; }
  .ev-counts { gap: 2px 8px; }
}
