/* ================================================================
   PATCH LAYER — v2 additions (loaded after components.css, so these
   rules win on anything that overlaps)
   ================================================================ */

/* ---------- Uniform control sizing (every input/select/button-field) ---------- */
.field input, .field select, .field textarea,
.qg-scope input:not([type="hidden"]):not([type="file"]), .qg-scope select, .qg-scope textarea{
  height: var(--field-h);
  border-radius: var(--field-radius);
  font-size: 14px;
}
.field textarea{ height: auto; }
.field select{
  appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none'><path d='M5.5 7.5l4.5 4.5 4.5-4.5' stroke='%235B6270' stroke-width='1.8' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat: no-repeat; background-position: right 14px center; background-size: 12px 12px;
  padding-right: 36px;
}

.select-uniform{
  height: var(--field-h); border-radius: var(--field-radius);
  border: 1px solid var(--line-strong); background: var(--paper-raised);
  padding: 0 36px 0 16px; font-size: 14px; font-weight: 600; color: var(--ink);
  appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none'><path d='M5.5 7.5l4.5 4.5 4.5-4.5' stroke='%235B6270' stroke-width='1.8' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat: no-repeat; background-position: right 14px center; background-size: 12px 12px;
}

/* Leading icon on a select field, so its purpose reads at a glance (Filter/Sort, Financial Year, etc)
   Flex so an optional trailing .select-clear button can sit beside the select on the
   same row (the select shrinks to make room). The icon stays absolutely positioned,
   so it still overlays the select's left padding. */
.select-icon-field{ position: relative; display: flex; align-items: center; gap: 8px; }
.select-icon-field > i{
  position: absolute; left: 14px; top: 50%; transform: translateY(-50%);
  color: var(--ink-faint); font-size: 12.5px; pointer-events: none; z-index: 1;
}
.select-icon-field > .select-uniform{ flex: 1 1 auto; min-width: 0; width: 100%; padding-left: 38px; }

/* ---------- Segmented control (iOS style tabs) ---------- */
.segmented{
  position: relative;
  display: inline-flex; background: var(--paper-sunken); border-radius: 11px;
  padding: 3px; gap: 2px; border: none; margin-bottom: 18px;
  max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; -ms-overflow-style: none;
}
.segmented::-webkit-scrollbar{ display: none; width: 0 !important; height: 0 !important; background: transparent; }
/* The gliding pill sits behind the tab labels and slides to the active one with a soft
   spring. JS sizes/positions it; the overshoot in the cubic-bezier gives the spring feel. */
.seg-pill{
  position: absolute; top: 0; left: 0; z-index: 0;
  background: var(--paper-raised); border-radius: 8px; box-shadow: 0 1px 3px rgba(28,34,48,0.14);
  transition: transform 0.44s cubic-bezier(0.34, 1.56, 0.5, 1), width 0.44s cubic-bezier(0.34, 1.56, 0.5, 1), height 0.2s ease, opacity 0.15s ease;
  pointer-events: none;
}
.segmented .seg{
  position: relative; z-index: 1;
  padding: 8px 16px; border-radius: 8px; font-size: 13px; font-weight: 600;
  color: var(--ink-soft); cursor: pointer; flex: 0 0 auto; white-space: nowrap; transition: color 0.18s ease;
}
.segmented .seg.active{ color: var(--ink); background: transparent; box-shadow: none; }

/* ---------- Search with chip suggestions ---------- */
.search-suggest{
  display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; margin-bottom: 14px;
}
.search-suggest .chip{ padding: 6px 12px; font-size: 12px; cursor: pointer; }
.search-suggest .chip .status-dot{ margin-right: 6px; }

/* ---------- Date range + day chips ---------- */
.date-chip-grid{ display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.date-chip{
  padding: 8px 12px; border-radius: 27px; border: 1.5px solid var(--line-strong);
  font-family: var(--font-mono); font-size: 12.5px; font-weight: 600; color: var(--ink-soft);
  background: var(--paper-raised); cursor: pointer; transition: all 0.12s ease; user-select: none;
}
.date-chip.selected{ background: var(--accent); border-color: var(--accent); color: #fff; }
.date-chip:active{ transform: scale(0.95); }

/* ---------- iOS-style switch (for Add-on toggle etc) ---------- */
.switch{ position: relative; display: inline-block; width: 46px; height: 27px; flex-shrink: 0; }
.switch input{ opacity: 0; width: 0; height: 0; }
.switch .track{
  position: absolute; inset: 0; background: var(--line-strong); border-radius: 100px;
  transition: background 0.25s ease; cursor: pointer;
}
.switch .track::before{
  content: ''; position: absolute; width: 23px; height: 23px; left: 2px; top: 2px;
  background: #fff; border-radius: 50%;
  /* Springy knob — a little overshoot as it slides, like a native iOS toggle. */
  transition: transform 0.34s cubic-bezier(0.34, 1.56, 0.64, 1);
  box-shadow: 0 1px 3px rgba(0,0,0,0.25);
}
.switch input:checked + .track{ background: var(--stamp-green); }
.switch input:checked + .track::before{ transform: translateX(19px); }
.switch-row{ display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.switch-label{ font-size: 13px; font-weight: 600; color: var(--ink); }
.switch-sub{ font-size: 11.5px; color: var(--ink-faint); line-height: 1.5; }

/* ---------- Show more / expand toggle ---------- */
.show-more-btn{
  display: flex; align-items: center; justify-content: center; gap: 6px;
  width: 100%; padding: 9px; margin-top: 10px; font-size: 12.5px; font-weight: 600;
  color: var(--accent); background: var(--accent-soft); border-radius: 8px; border: none;
}
.show-more-btn i{ transition: transform 0.18s ease; font-size: 10px; }
.show-more-btn.open i{ transform: rotate(180deg); }

.card-expand{ overflow: hidden; max-height: 0; transition: max-height 0.42s cubic-bezier(0.16, 1, 0.3, 1); }

/* Team member mini-row (Name (Role) Date + paid/unpaid tag) — stacked like a plain list, no divider lines */
.crew-mini-row{ padding: 3px 0; font-size: 12.5px; }
.crew-mini-row .who{ color: var(--ink); font-weight: 600; }
.crew-side-label{ font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.6px; color: var(--ink-faint); margin: 10px 0 3px; }
.crew-side-label:first-child{ margin-top: 2px; }

/* Table expand row */
.table-expand-row td{ padding: 14px 16px; }
.expand-chevron{ transition: transform 0.15s ease; }
.expand-chevron.open{ transform: rotate(180deg); }

/* Row-expand control: a wide, bordered button so it reads as tappable and gives
   a big hit area on long tables (was just a small chevron circle before). */
.row-expand-btn{
  display: flex; align-items: center; justify-content: center;
  width: 100%; min-width: 76px; height: 38px; border-radius: 100px; cursor: pointer;
  border: 1px solid var(--line-strong); background: transparent; color: var(--ink-soft);
  transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease, transform 0.1s ease;
}
.row-expand-btn:hover{ background: var(--paper-sunken); border-color: var(--ink-faint); color: var(--ink); }
.row-expand-btn:active{ transform: scale(0.97); }
.row-expand-btn.open{ background: var(--paper-sunken); border-color: var(--ink-faint); color: var(--ink); }
.row-expand-btn.open .expand-chevron{ transform: rotate(180deg); }
/* Give the button its own column width so it's a comfortable target, not a sliver. */
.data-table td:last-child, .data-table th:last-child{ width: 116px; }

/* ---------- Team member status: colored dot prefix (not a badge) ---------- */
.status-dot{ display: inline-block; width: 9px; height: 9px; border-radius: 50%; margin-right: 7px; flex-shrink: 0; vertical-align: middle; }
.status-dot.paid{ background: var(--stamp-green); }
.status-dot.self{ background: var(--ink-faint, #9a9a92); }
.status-dot.due{ background: var(--stamp-red); }
.status-dot.partial{ background: var(--stamp-amber); }
.status-dot.overpaid{ background: #1976D2; }

/* ---------- Team member type: color applied straight to the name/role/date text ---------- */
.type-dot{ display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 5px; flex-shrink: 0; }

/* ---------- Financials show/hide toggle inside cards ---------- */
.fin-toggle-btn{
  display: flex; align-items: center; justify-content: center; gap: 6px;
  width: 100%; padding: 9px; margin-top: 10px; font-size: 12.5px; font-weight: 700;
  color: var(--accent); background: var(--accent-soft); border-radius: 10px; border: none;
}
.fin-toggle-btn .fin-chevron{ transition: transform 0.18s ease; font-size: 10px; }
.fin-toggle-btn.open .fin-chevron{ transform: rotate(180deg); }
.fin-expand{ overflow: hidden; max-height: 0; transition: max-height 0.42s cubic-bezier(0.16, 1, 0.3, 1); }

/* ---------- Invoice / receipt card (shareable with clients) ---------- */
.invoice-card{
  background: #fff; color: #000; border: 1px solid #e5e5e5; border-radius: 20px;
  padding: 20px; font-family: var(--font-body);
}
.invoice-row{ display: flex; justify-content: space-between; align-items: center; margin: 10px 0; font-size: 14px; }
.invoice-row label{ font-weight: 700; color: #666; margin-right: 12px; }
.invoice-row .val{ font-weight: 700; text-align: right; }
.invoice-row .val.received{ color: #46b450; }
.invoice-row .val.paid{ color: #dc3232; }
.invoice-footer{ margin-top: 20px; padding-top: 16px; border-top: 1px solid #eee; text-align: center; font-weight: 800; font-family: var(--font-display); letter-spacing: 0.4px; color: #111; }

/* ---------- Floating Back / Save nav (event detail — mobile AND desktop) ---------- */
@keyframes floatNavIn{
  from{ transform: translateY(24px) translateX(-50%); opacity: 0; }
  to{ transform: translateY(0) translateX(-50%); opacity: 1; }
}

/* ---------- Home menu bar: same spring entrance, played when returning from a detail page ---------- */
@keyframes menuBarIn{
  from{ transform: translateY(24px); opacity: 0; }
  to{ transform: translateY(0); opacity: 1; }
}
/* No forwards-fill: once the spring entrance finishes, the bar rests at its base
   transform so the keyboard-hide transition below can slide it away smoothly. */
.bottom-bar{ transition: transform 0.3s cubic-bezier(0.4,0,0.2,1), opacity 0.24s ease; }
.bottom-bar.menu-bar-enter{ animation: menuBarIn 0.45s cubic-bezier(0.34, 1.56, 0.64, 1); }
.detail-float-nav{
  display: flex; position: fixed; bottom: max(10px, env(safe-area-inset-bottom)); left: 50%; transform: translateX(-50%); z-index: 50;
  gap: 10px;
  width: calc(100% - 28px); max-width: 420px;
  animation: floatNavIn 0.45s cubic-bezier(0.34, 1.56, 0.64, 1);
  transition: transform 0.3s cubic-bezier(0.4,0,0.2,1), opacity 0.24s ease;
}
.detail-float-btn{
  flex: 1; height: 56px; border-radius: 27px; border: 1px solid var(--glass-border);
  background: var(--glass-bg); -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.6);
  backdrop-filter: blur(var(--glass-blur)) saturate(1.6);
  display: flex; align-items: center; justify-content: center; gap: 8px; cursor: pointer;
  font-size: 13px; font-weight: 700; color: var(--ink); box-shadow: 0 1px 6px rgba(28,34,48,0.03);
  transition: transform 0.2s ease, background 0.2s ease;
}
.detail-float-btn:active{ transform: scale(0.94); }
/* Back was translucent glass over a near-white page, so it washed out next to the
   solid Save and Delete buttons. Give it the brand navy: solid, clearly a button,
   and distinct from green (save) and red (delete) at a glance. */
.detail-float-btn.back{
  background: var(--accent); color: #fff; border-color: var(--accent);
  -webkit-backdrop-filter: none; backdrop-filter: none;
}
.detail-float-btn.save{ background: var(--stamp-green); color: #fff; border-color: var(--stamp-green); }
.detail-float-btn.delete{ background: var(--stamp-red); color: #fff; border-color: var(--stamp-red); flex: 0 0 56px; }
body.detail-mode .bottom-bar{ display: none !important; }
body.detail-mode .page-content{ padding-bottom: 90px; }
.event-detail-header{ margin-bottom: 14px; }
@media (min-width: 901px){
  /* The topbar already shows the event name on desktop — hiding the whole header
     (or making it sticky) either duplicated it again or broke the topbar's layout.
     Just hide the repeated name line here; frame-no/status/meta aren't shown by
     the topbar, so they stay visible in-page. */
  .event-detail-header__name{ display: none; }
}
.insight-card{
  background: var(--glass-bg); border: 1px solid var(--line);
  border-radius: var(--radius-md); padding: 16px;
}
.insight-card h4{ font-size: 13px; margin-bottom: 10px; }
.insight-upcoming-row{ display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid var(--line); font-size: 12.5px; transition: opacity 0.12s ease; }
.insight-upcoming-row:last-child{ border-bottom: none; }
.insight-upcoming-row:hover{ opacity: 0.7; }
.insight-upcoming-row__arrow{ color: var(--ink-faint); font-size: 11px; flex-shrink: 0; margin-left: 8px; }
.insight-clock{ font-family: var(--font-mono); font-size: 13px; color: var(--ink-soft); }
.insight-tz-btn{
  display: inline-flex; align-items: center; gap: 5px; cursor: pointer;
  background: var(--paper-sunken); border: 1px solid var(--line); color: var(--ink-soft);
  font-family: var(--font-body); font-size: 10.5px; font-weight: 600; letter-spacing: 0.2px;
  padding: 3px 8px; border-radius: 100px; transition: color .15s, border-color .15s, background .15s;
}
.insight-tz-btn i.fa-gear{ font-size: 9px; opacity: 0.7; }
.insight-tz-btn:hover{ color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }

/* ---------- Conflict warning ---------- */
.conflict-warning{
  display: flex; gap: 8px; align-items: flex-start; margin-top: 8px; padding: 10px 12px;
  background: var(--stamp-amber-bg); color: var(--stamp-amber); border-radius: 8px; font-size: 12px; font-weight: 600;
}
.conflict-warning i{ margin-top: 1px; }

/* ---------- Calendar polish: 4 nav buttons, event-name-in-cell ---------- */
.cal-nav-group{ display: flex; gap: 6px; }
.cal-day{ cursor: pointer; }
.cal-day .tag{ font-size: 8px; line-height: 1.15; padding: 0 2px; }

/* ---------- Minimal, restrained motion only ---------- */
.card, .entity-card, .stat-card, .btn, .chip, .tab, .seg{ transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease, transform 0.1s ease, color 0.15s ease; }

/* ---------- Word-count indicator under a capped textarea ---------- */
.word-count{ font-size: 11px; color: var(--ink-faint); text-align: right; margin-top: 5px; font-weight: 600; }
.word-count.warn{ color: var(--stamp-amber); }
.word-count.limit{ color: var(--stamp-red); }

/* ---------- Dual/triple stat box: white box with a soft border, columns split by a center divider ---------- */
.dual-stat-box{
  display: flex; background: var(--paper-raised); border: 1.5px solid var(--paper-sunken); border-radius: 12px; padding: 10px 4px; margin: 10px 0;
}
.dual-stat-box .dsb-col{ flex: 1; text-align: center; padding: 0 6px; position: relative; }
.dual-stat-box .dsb-col + .dsb-col::before{
  content: ''; position: absolute; left: 0; top: 4px; bottom: 4px; width: 1px; background: var(--line-strong);
}
.dual-stat-box .dsb-label{ font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--ink-faint); font-weight: 700; margin-bottom: 3px; }
.dual-stat-box .dsb-value{ font-family: var(--font-mono); font-size: 14px; font-weight: 700; }
.dual-stat-box .dsb-value.green{ color: var(--stamp-green); }
.dual-stat-box .dsb-value.red{ color: var(--stamp-red); }
.dual-stat-box .dsb-value.amber{ color: var(--stamp-amber); }

/* ---------- Roster grid: side-by-side on desktop, single column on mobile (no cramped carousel feel) ---------- */
.roster-grid{ display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
.roster-grid > .entity-card{ margin-bottom: 0; }
@media (max-width: 900px){ .roster-grid{ grid-template-columns: 1fr; } }
@media (min-width: 1400px){ .roster-grid{ grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); } }

/* ---------- Team chip with a payment-status dot prefix ---------- */
.team-chip-dot{ display: inline-block; width: 7px; height: 7px; border-radius: 50%; margin-right: 6px; }

/* ---------- Statement sheet: header, per-event group cards, checkboxes ---------- */
.statement-header{ margin: 0 0 16px; }
.statement-header .eyebrow{ color: var(--ink-faint); }
.statement-header .due-now{ text-align: right; }
.statement-header .due-now .amt{ font-family: var(--font-mono); font-size: 22px; font-weight: 700; }
.statement-header .due-now .amt.red{ color: var(--stamp-red); }
.statement-header .due-now .amt.green{ color: var(--stamp-green); }
.statement-header .stat-grid{ display: grid; grid-template-columns: repeat(2,1fr); gap: 14px; margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--line); }
.statement-header .stat-grid .lbl{ color: var(--ink-faint); font-size: 11.5px; }
.statement-header .stat-grid .val{ font-family: var(--font-mono); font-size: 16px; font-weight: 700; }
.statement-header .stat-grid .val.red{ color: var(--stamp-red); }
.statement-header .stat-grid .val.green{ color: var(--stamp-green); }
.statement-header--plain .stat-grid{ margin-top: 0; padding-top: 0; border-top: none; }

.statement-event-card{ background-color: var(--paper-raised); border: 1px solid var(--line-strong); border-radius: var(--radius-md); padding: 14px; margin-bottom: 10px; background-repeat: no-repeat; }
.statement-event-card--upcoming{ opacity: 0.6; }
.statement-checkbox{ width: 22px; height: 22px; margin-top: 2px; border-radius: 8px; border: 2px solid var(--line-strong); background: var(--paper-raised); flex-shrink: 0; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: background 0.15s ease, border-color 0.15s ease, transform 0.1s ease; }
.statement-checkbox:active{ transform: scale(0.92); }
.statement-checkbox.checked{ background: var(--accent); border-color: var(--accent); color: #fff; animation: checkPop 0.32s cubic-bezier(0.34, 1.56, 0.64, 1); }
@keyframes checkPop{ 0%{ transform: scale(0.82); } 55%{ transform: scale(1.12); } 100%{ transform: scale(1); } }
.statement-detail-toggle{ color: var(--accent); font-weight: 700; font-size: 12.5px; text-align: center; margin-top: 8px; cursor: pointer; }
.statement-line-item{ display: flex; justify-content: space-between; align-items: center; padding: 7px 0; border-top: 1px dashed var(--line); font-size: 12.5px; }
.item-tag{ display: inline-block; font-size: 9.5px; font-weight: 700; padding: 2px 7px; border-radius: 100px; margin-left: 6px; text-transform: uppercase; vertical-align: middle; }
.item-tag.role{ background: var(--stamp-blue-bg, #E3EEFB); color: #2A4C7A; }
.item-tag.service{ background: var(--stamp-green-bg); color: var(--stamp-green); }

.multi-pay-bar{
  position: sticky; bottom: 0; left: 0; right: 0; background: var(--paper-raised);
  border-top: 1px solid var(--line-strong); margin: 16px -20px -20px; padding: 14px 20px;
  padding-bottom: calc(14px + env(safe-area-inset-bottom));
}

/* ---------- Standard circular "x" delete/dismiss button — same everywhere ---------- */
.btn-x{
  width: 27px; height: 27px; padding: 0; border-radius: 27px; flex-shrink: 0;
  background: var(--stamp-red); border-color: var(--stamp-red); color: #fff;
  font-size: 11px;
}
.btn-x:hover{ background: #9E2C20; border-color: #9E2C20; color: #fff; filter: none; }

/* ---------- Plain colored status text (replaces the old rubber-stamp badge) ---------- */
.status-text{ font-size: 12.5px; font-weight: 700; }
.status-text.paid{ color: var(--stamp-green); }
.status-text.due{ color: var(--stamp-red); }
.status-text.partial{ color: var(--stamp-amber); }
.status-text.overpaid{ color: #1976D2; }

.item-tag.addon{ background: var(--stamp-amber-bg); color: var(--stamp-amber); }
.item-tag.self{ background: var(--stamp-navy-bg); color: var(--stamp-navy); }
.item-tag.address{ background: var(--stamp-blue-bg, #E3EEFB); color: #2A4C7A; }
.item-tag.venue{ background: var(--stamp-amber-bg); color: var(--stamp-amber); }
.item-tag.address i, .item-tag.venue i{ font-size: 9px; margin-right: 2px; }
.av-icon{ font-size: 11px; vertical-align: middle; margin-left: 2px; }
.av-icon.address{ color: #2A4C7A; }
.av-icon.venue{ color: var(--stamp-amber); }
.item-tag.icon-only{ padding: 3px 7px; margin-left: 4px; }
.item-tag.icon-only i{ margin-right: 0; font-size: 10px; }
.item-tag.upcoming{ background: var(--stamp-amber-bg); color: var(--stamp-amber); }

/* ---------- Desktop "More": inline accordion under the sidebar link, not a flyout ---------- */
.sidebar__more-chevron{ margin-left: auto; font-size: 10px; transition: transform 0.18s ease; }
.sidebar__link--expanded .sidebar__more-chevron{ transform: rotate(180deg); }
.sidebar__more-expand{ overflow: hidden; max-height: 0; transition: max-height 0.40s cubic-bezier(0.16, 1, 0.3, 1); padding-left: 6px; }
.sidebar__more-item{
  display: flex; align-items: center; gap: 10px; padding: 9px 12px; margin: 2px 0 2px 14px;
  border-left: 1px solid rgba(255,255,255,0.14);
  color: #C7CCD3; font-size: 12.5px; font-weight: 500; cursor: pointer; border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  transition: background 0.12s ease, color 0.12s ease;
}
.sidebar__more-item i{ width: 14px; text-align: center; font-size: 12.5px; color: #8B93A0; }
.sidebar__more-item:hover{ background: rgba(255,255,255,0.08); color: #fff; }
.sidebar__more-item:hover i{ color: #fff; }
/* Current sub-page marker — so you can see where you are while the accordion stays open. */
.sidebar__more-item.active{
  background: rgba(255,255,255,0.12); color: #fff;
  border-left-color: var(--accent); border-left-width: 2px; font-weight: 600;
}
.sidebar__more-item.active i{ color: #fff; }
.sidebar__more-item--disabled{ opacity: 0.5; cursor: default; }
.sidebar__more-item--disabled:hover{ background: transparent; color: #C7CCD3; }
.sidebar__more-item--disabled:hover i{ color: #8B93A0; }
.sidebar__more-badge{
  margin-left: auto; font-size: 9px; font-weight: 700; letter-spacing: 0.3px;
  background: rgba(255,255,255,0.12); padding: 2px 7px; border-radius: 100px; flex-shrink: 0;
}

/* ---------- More hub: link cards to Preferences / Tools ---------- */
.hub-link-card{
  display: flex; align-items: center; gap: 14px;
  background: var(--paper-raised); border: 1px solid var(--line-strong); border-radius: var(--radius-md);
  padding: 14px 16px; margin-bottom: 10px; cursor: pointer;
  transition: box-shadow 0.15s ease, transform 0.1s ease, border-color 0.15s ease;
}
.hub-link-card:hover{ box-shadow: var(--shadow-sm); border-color: var(--accent); }
.hub-link-card:active{ transform: scale(0.99); }
.hub-link-card__icon{
  width: 42px; height: 42px; border-radius: 14px; flex-shrink: 0;
  background: var(--accent-soft); color: var(--accent);
  display: flex; align-items: center; justify-content: center; font-size: 17px;
}
.hub-link-card__body{ flex: 1; min-width: 0; }
.hub-link-card__name{ font-weight: 700; font-size: 14.5px; color: var(--ink); }
.hub-link-card__desc{ font-size: 12px; color: var(--ink-faint); margin-top: 2px; }
.hub-link-card__chevron{ color: var(--ink-faint); font-size: 13px; flex-shrink: 0; }
.hub-link-card--disabled{ opacity: 0.55; cursor: default; }
.hub-link-card--disabled:hover{ box-shadow: none; border-color: var(--line-strong); }
.hub-link-card__badge{
  font-size: 10px; font-weight: 700; letter-spacing: 0.3px; color: var(--ink-faint);
  background: var(--paper-sunken); padding: 4px 10px; border-radius: 100px; flex-shrink: 0;
}

/* ---------- Rate Estimator chips ---------- */
.calc-chip{ display: inline-flex; align-items: center; gap: 8px; }
.calc-chip__x{
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; border-radius: 50%; background: rgba(255,255,255,0.3); font-size: 9px;
}
.calc-chip.active .calc-chip__x{ background: rgba(255,255,255,0.35); }

/* Stepper: explicit +1 / -1 buttons on the chip, bigger tap targets than re-tapping the chip body */
.calc-chip__stepper{ display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
.calc-chip__step{
  display: inline-flex; align-items: center; justify-content: center; gap: 4px;
  min-width: 30px; height: 26px; padding: 0 8px; border-radius: 27px;
  background: rgba(255,255,255,0.3); font-size: 10.5px; font-weight: 700; cursor: pointer;
  transition: background 0.12s ease, transform 0.08s ease;
}
.calc-chip__step:hover{ background: rgba(255,255,255,0.45); }
.calc-chip__step:active{ transform: scale(0.92); }
.calc-chip__step i{ font-size: 8.5px; }
.calc-chip:not(.active) .calc-chip__step{ background: var(--accent-soft); color: var(--accent); }
.calc-chip:not(.active) .calc-chip__step:hover{ background: var(--accent); color: #fff; }

/* Stacked variant: one chip per line, full width, label left / remove-x right */
.calc-chip-stack{ display: flex; flex-direction: column; gap: 8px; }
.calc-chip--stacked{ width: 100%; justify-content: space-between; box-sizing: border-box; }
.calc-chip__label{ flex: 1; text-align: left; }

.calc-total-value{ font-size: 19px; color: var(--accent); }
.calc-total-value.red, .mono.red{ color: var(--stamp-red); }
.calc-total-value.green, .mono.green{ color: var(--stamp-green); }

/* ---------- Rate Estimator: paired sections side by side on wide screens ---------- */
.calc-pair{ display: block; }
.calc-pair > * + *{ margin-top: 16px; }
@media (min-width: 1100px){
  .calc-pair{ display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 16px; }
  .calc-pair > * + *{ margin-top: 0; }
}
.calc-tier-grid{ display: grid; grid-template-columns: repeat(3, 1fr) !important; gap: 10px; }
.calc-tier{
  border-radius: var(--radius-md); padding: 12px 10px; text-align: center;
  background: var(--paper-sunken); cursor: pointer; border: 1.5px solid transparent;
  transition: background 0.12s ease, border-color 0.12s ease, transform 0.08s ease;
}
.calc-tier:active{ transform: scale(0.97); }
.calc-tier .lbl{ font-weight: 700; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; }
.calc-tier .val{ font-size: 17px; font-weight: 700; margin-top: 4px; }
.calc-tier.active{ background: var(--accent); border-color: var(--accent); }
.calc-tier.active .lbl, .calc-tier.active .val{ color: #fff; }

/* Reset sits inline after the last card — not a floating FAB — with a red border
   to read as a destructive/clearing action without being a solid danger button. */
.calc-reset-btn{
  border: 1.5px solid var(--stamp-red); color: var(--stamp-red); background: var(--paper-raised);
}
.calc-reset-btn:hover{ background: var(--stamp-red-bg); }
.calc-reset-btn:active{ transform: scale(0.98); }

.app-footer{ text-align: center; padding: 22px 0 90px; }
.app-footer__version{ font-size: 11.5px; font-weight: 700; color: var(--ink-faint); letter-spacing: 0.4px; margin-bottom: 6px; }
.app-footer__credit{ font-size: 12px; color: var(--ink-soft); margin-bottom: 3px; }
.app-footer__credit i{ color: var(--stamp-red); margin: 0 2px; }
.app-footer__copy{ font-size: 11px; color: var(--ink-faint); }
.app-footer__badges{ display: flex; justify-content: center; gap: 6px; margin-top: 8px; }
.app-badge{
  font-size: 9.5px; font-weight: 800; letter-spacing: 0.5px; text-transform: uppercase;
  padding: 2px 8px; border-radius: 999px; line-height: 1.6;
}
.app-badge--beta{ background: var(--accent-soft); color: var(--accent); border: 1px solid var(--accent); }
.app-badge--ksp{ background: var(--stamp-amber-bg); color: var(--stamp-amber); border: 1px solid var(--stamp-amber); }
@media (min-width: 901px){ .app-footer{ padding-bottom: 30px; } }
.card-expand{ margin-top: 0; }
.card-expand.open{ margin-top: 10px; }

/* ---------- Generic page floating action button (Preferences Save, Roster Add, etc) ---------- */
.page-fab{
  position: fixed; z-index: 55; right: 20px; bottom: 28px;
  height: 52px; padding: 0 22px; border-radius: 27px;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  font-size: 14px; font-weight: 700; color: #fff; border: none; cursor: pointer;
  box-shadow: 0 8px 24px rgba(28,34,48,0.22);
  animation: menuBarIn 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
  transition: transform 0.15s ease;
}
.page-fab:active{ transform: scale(0.94); }
.page-fab.save{ background: var(--stamp-green); }
.page-fab.add{ background: var(--accent); }
.page-fab.neutral{ background: var(--ink-soft); }
@media (max-width: 900px){
  .page-fab{ right: 16px; bottom: calc(14px + 62px + 14px + env(safe-area-inset-bottom)); transition: transform 0.2s ease, opacity 0.2s ease; }
  body.keyboard-open .page-fab{ opacity: 0; transform: translateY(24px) scale(0.9); pointer-events: none; }
}
/* When the on-screen keyboard opens (a field is focused) the mobile bars slide
   down out of the way instead of vanishing instantly. */
@media (max-width: 900px){
  body.keyboard-open .bottom-bar{ transform: translateY(160%); opacity: 0; pointer-events: none; }
  body.keyboard-open .detail-float-nav{ transform: translateX(-50%) translateY(160%); opacity: 0; pointer-events: none; }
}

/* ---------- Collapsed run of unused date-picker chips ---------- */
.date-chip--collapsed{
  color: var(--ink-faint); font-style: normal; letter-spacing: 1px;
  background: var(--paper-sunken); border-style: dashed;
}
.date-chip--collapsed .date-chip__count{ font-size: 10px; opacity: 0.75; }

/* ---------- "Did you mean X?" roster near-match hint ---------- */
.name-suggest-hint{
  margin-top: 6px; font-size: 12px; color: var(--ink-soft); background: var(--accent-soft);
  border-radius: 10px; padding: 7px 11px;
}
.name-suggest-hint strong{ color: var(--accent); cursor: pointer; text-decoration: underline; }
.name-suggest-hint [data-dismiss-suggest]{ float: right; cursor: pointer; color: var(--ink-faint); }

/* ---------- In-app reminders panel ---------- */
.reminder-digest{
  font-size: 12.5px; font-weight: 700; color: var(--ink); background: var(--accent-soft);
  border-radius: 8px; padding: 9px 11px; margin-bottom: 8px;
}
/* The reminders toggle reuses .fin-toggle-btn (a filled blue button). Stacked
   above the blue digest callout it read as a doubled header — so here it's a
   plain header row, leaving the digest as the single accent element. */
#reminders-toggle{ background: transparent; color: var(--ink); padding: 4px 4px 10px; font-size: 13px; }
#reminders-toggle .fa-bell{ color: var(--accent); }
#reminders-toggle .fin-chevron{ color: var(--ink-faint); }
.reminder-line{
  display: flex; align-items: flex-start; gap: 8px; padding: 8px 2px; font-size: 12.5px;
  color: var(--ink-soft); cursor: pointer; border-top: 1px dashed var(--line);
}
.reminder-line:first-of-type{ border-top: none; }
/* Match the icon's line box to the text's so it centres on the FIRST line instead of
   floating above it. A fixed margin-top only ever lines up at one font size. */
.reminder-line i{ font-size: 11px; line-height: 1.5; flex-shrink: 0; }
/* Urgency tiers — red (today/tomorrow), orange (this week), plain (further out) */
.reminder-line.level-urgent{ color: var(--stamp-red); font-weight: 600; }
.reminder-line.level-urgent i{ color: var(--stamp-red); }
.reminder-line.level-soon{ color: var(--stamp-amber); }
.reminder-line.level-soon i{ color: var(--stamp-amber); }
.reminder-line.level-normal{ color: var(--ink-soft); }
.reminder-line.level-normal i{ color: var(--ink-faint); }
.reminder-line:hover{ color: var(--accent); }
.reminder-line:hover i{ color: var(--accent); }

/* ---------- Payment method, shown right above the amount so the "how" is as visible as the "how much" ---------- */
.method-tag{ font-size: 10px; font-weight: 700; letter-spacing: 0.3px; text-transform: uppercase; color: var(--ink-faint); margin-bottom: 2px; }

/* ---------- Preferences: paired sections ------------------------------------
   The page is built as a series of two-column pairs (Owner|Appearance,
   Roles|Services, Team Types|Display+Export, Notifications|Session) so a wide screen
   reads as a neat grid instead of one long column of full-width cards. Below the
   breakpoint every pair stacks. */
.pref-pair{ display: block; margin-top: 22px; }
.pref-pair .section-title{ margin-top: 0; }
/* Stacked: the second column needs air above it, or its heading sits flush against
   the card above. Applied generally so every pair behaves the same. */
.pref-pair > div + div{ margin-top: 22px; }
@media (min-width: 1100px){
  /* Natural heights, NOT stretch. Stretching the columns to match made a short card
     (a couple of switches) balloon to the height of a long one and fill with dead
     white space, which reads worse than an uneven bottom edge. Each card should be
     exactly as tall as its contents. */
  .pref-pair{ display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 0 20px; align-items: start; }
  /* Side by side both columns start on the same line, so that stacking margin would
     wrongly push the right-hand column down. Cancel it here. */
  .pref-pair > div + div{ margin-top: 0 !important; }
  /* Keep the last card in each column from butting into the next pair. */
  .pref-pair > div > .card:last-child{ margin-bottom: 0; }
}

/* ---------- Page loading overlay ----------------------------------------
   A centred spinner over a blurred, NON-INTERACTIVE page. Two jobs:
   1. Show the app is working, not broken. The old top bar read as a stray blue
      line, and a bare spinner over empty content still looked like a blank page.
   2. Stop taps landing on stale or half-empty content while data loads. The
      overlay swallows pointer events, and UI.showPageLoading() additionally marks
      #page-root `inert` — the standard way to remove a subtree from clicks, focus
      AND keyboard tabbing (pointer-events alone still leaves elements focusable).
   Layering is deliberate: below the splash (999) and the toast (200). */
#page-loading-bar{
  position: fixed; inset: 0; z-index: 150;
  display: flex; align-items: center; justify-content: center;
  visibility: hidden; opacity: 0;
  background: rgba(239,241,238,0.60);                        /* fallback */
  background: color-mix(in srgb, var(--paper) 60%, transparent);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  transition: opacity 0.2s ease, visibility 0.2s ease;
}
#page-loading-bar.active{ visibility: visible; opacity: 1; }
.page-loading__spinner{
  width: 40px; height: 40px; border-radius: 50%;
  border: 3px solid var(--line);
  border-top-color: var(--accent);
  animation: kspSpin 0.75s linear infinite;
}
@keyframes kspSpin{ to{ transform: rotate(360deg); } }

/* Spinner + action button for in-page states (Still connecting, retry cards). */
.state-spinner{
  width: 38px; height: 38px; border-radius: 50%; margin: 0 auto 14px;
  border: 3px solid var(--line);
  border-top-color: var(--accent);
  animation: kspSpin 0.75s linear infinite;
}
.state-action{ margin-top: 16px; }
/* `.empty-state i` sets a big faint 30px icon — correct for the state's own icon,
   badly wrong for an icon inside a button, which it also matched. Scope it back. */
.empty-state .btn i{ font-size: inherit; color: inherit; margin-bottom: 0; }

@media (prefers-reduced-motion: reduce){
  .page-loading__spinner, .state-spinner{ animation: kspFade 1.4s ease-in-out infinite; }
  @keyframes kspFade{ 0%,100%{ opacity: 0.35; } 50%{ opacity: 1; } }
}

.btn-spinner{
  display: inline-block; width: 15px; height: 15px; border-radius: 50%;
  border: 2px solid rgba(0,0,0,0.15); border-top-color: currentColor;
  animation: btnSpin 0.6s linear infinite; vertical-align: middle;
}
.btn.is-loading{ cursor: wait; opacity: 0.85; }
@keyframes btnSpin{ to{ transform: rotate(360deg); } }
.info-row--clickable{ cursor: pointer; border-radius: var(--radius-sm); margin: 0 -6px; padding: 4px 6px; transition: background 0.12s ease; }
.info-row--clickable:hover{ background: var(--paper-sunken); }
.info-row--clickable .link-val{ color: var(--accent); display: inline-flex; align-items: center; gap: 5px; }
.info-row--clickable .link-val i{ font-size: 9px; }
/* ---------- Colored label:value rows (Team/Services/Transactions cards) ---------- */
.info-row .val.green{ color: var(--stamp-green); }
.info-row .val.red{ color: var(--stamp-red); }
.info-row .val.amber{ color: var(--stamp-amber); }
.info-row .val.blue{ color: #1976D2; }
.stat-card .value.blue{ color: #1976D2; }
.calc-total-value.blue{ color: #1976D2; }

/* ============================================================
   FIRST-RUN ONBOARDING WIZARD
   ============================================================ */
#onboarding-overlay{
  position: fixed; inset: 0; z-index: 1000;
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
  background: rgba(0,0,0,0.45);
  opacity: 0; transition: opacity 0.28s ease;
}
#onboarding-overlay.open{ opacity: 1; }
.ob-card{
  width: 100%; max-width: 460px;
  max-height: calc(100dvh - 40px);
  display: flex; flex-direction: column;
  background: var(--paper-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: 26px 24px 22px;
  transform: translateY(14px) scale(0.98);
  transition: transform 0.3s cubic-bezier(0.34,1.3,0.64,1);
  overflow: hidden;
}
#onboarding-overlay.open .ob-card{ transform: translateY(0) scale(1); }

.ob-progress{ display: flex; gap: 7px; justify-content: center; margin-bottom: 18px; min-height: 8px; }
.ob-dot{ width: 8px; height: 8px; border-radius: 50%; background: var(--line-strong); transition: background 0.2s ease, width 0.2s ease; }
.ob-dot.active{ width: 22px; border-radius: 100px; background: var(--accent); }
.ob-dot.done{ background: var(--accent); opacity: 0.5; }

.ob-body{ overflow-y: auto; flex: 1; }
.ob-hero{ text-align: center; padding: 12px 0 8px; }
.ob-logo{ width: 72px; height: 72px; border-radius: 50%; object-fit: cover; box-shadow: 0 8px 22px rgba(28,34,48,0.18), 0 0 0 1px var(--line); margin-bottom: 14px; }
.ob-title{ font-family: var(--font-display); font-size: 24px; font-weight: 800; color: var(--ink); margin-bottom: 8px; }
.ob-title.sm{ font-size: 20px; text-align: left; }
.ob-sub{ font-size: 13.5px; color: var(--ink-soft); line-height: 1.5; margin-bottom: 6px; }
.ob-hero .ob-sub{ max-width: 340px; margin: 0 auto; }
.ob-body .field{ margin-top: 14px; }

.ob-check{
  width: 64px; height: 64px; border-radius: 50%; margin: 0 auto 14px;
  display: flex; align-items: center; justify-content: center;
  background: var(--stamp-green-bg); color: var(--stamp-green); font-size: 28px;
}

.ob-chips{ display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
.ob-chips__empty{ font-size: 12.5px; color: var(--ink-faint); }
.ob-chip{
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 8px 6px 12px; border-radius: 100px;
  background: var(--paper-sunken); border: 1px solid var(--line);
  font-size: 13px; font-weight: 600; color: var(--ink);
}
.ob-chip button{
  border: none; background: var(--line-strong); color: var(--paper-raised);
  width: 18px; height: 18px; border-radius: 50%; font-size: 9px;
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
}
.ob-chip button:hover{ background: var(--stamp-red); }

.ob-actions{ display: flex; gap: 10px; align-items: center; margin-top: 20px; }
.ob-actions .btn-block{ flex: 1; }
.ob-actions #ob-next{ margin-left: auto; }
.ob-actions #ob-back{ flex: 0 0 auto; }

/* ---------- Smooth tab-body swap on event detail (Team/Services/Payments/Notes) ---------- */
@keyframes tabFade{ from{ opacity: 0; transform: translateY(4px); } to{ opacity: 1; transform: none; } }

/* ---------- FY / card list spacing on mobile & tablet ----------
   .item-grid is a plain block below 1400px, and .card has no margin of its own,
   so stacked FY cards were rendering flush (rounded corners touching = "off").
   Give card children a consistent gap that matches the entity-card rhythm. */
.item-grid > .card{ margin-bottom: 12px; }
.item-grid > .card:last-child{ margin-bottom: 0; }

/* ---------- Whole event row clickable (desktop table), except the chevron ---------- */
.data-table tbody tr.row-clickable{ cursor: pointer; }
.data-table tbody tr.row-clickable .row-expand-btn{ cursor: pointer; }

/* ---------- Reserve tab-body height on desktop so switching to a short tab
   (e.g. Notes) doesn't let the document shrink below the viewport and yank
   the scroll upward. On mobile the content already exceeds the viewport. ---------- */
@media (min-width: 901px){
  #tab-body{ min-height: 460px; }
}

/* ---------- Inline field errors (onboarding & forms) ---------- */
.ob-field-error{ color: var(--stamp-red); font-size: 12px; font-weight: 600; margin-top: 6px; }
input.input-error, .input-error{ border-color: var(--stamp-red) !important; box-shadow: 0 0 0 3px rgba(193,57,43,0.12); }

/* ---------- Odometer (rolling numbers) ---------- */
.odo{ display: inline-flex; align-items: flex-start; line-height: 1; }
.odo-col{ display: inline-block; height: 1em; overflow: hidden; }
.odo-strip{ display: flex; flex-direction: column; transition: transform 0.42s cubic-bezier(0.2,0.85,0.25,1); will-change: transform; }
.odo-cell{ height: 1em; display: flex; align-items: center; justify-content: center; }
.odo-static{ display: inline-flex; align-items: center; height: 1em; }

/* ---------- 404 / not-found view ---------- */
.notfound-view{ max-width: 420px; margin: 60px auto; text-align: center; padding: 24px; }
.notfound-code{
  font-family: var(--font-display); font-weight: 800; font-size: 84px; line-height: 1;
  color: var(--accent); letter-spacing: -2px; margin-bottom: 8px;
}
.notfound-view h2{ font-family: var(--font-display); font-size: 22px; color: var(--ink); margin-bottom: 10px; }

/* ---------- Calendar cells: stacked event names + date-number slide-up ---------- */
/* Base cells are square (aspect-ratio:1). Cells WITH events must break out of that
   fixed square and grow to fit their name pills, or the content overflows and the
   grid looks broken. Grid stretch keeps the rest of the row aligned. */
.cal-day.has-events{
  aspect-ratio: auto; height: auto; min-height: 66px;
  justify-content: flex-start; padding: 5px 4px; overflow: hidden;
  background: var(--paper-raised); color: var(--ink);
}
.cal-day__num{ transition: transform 0.28s cubic-bezier(0.2,0.85,0.25,1); }
.cal-day.has-events .cal-day__num{ align-self: flex-start; font-size: 11px; line-height: 1; color: var(--ink); animation: calNumUp 0.32s ease; }
@keyframes calNumUp{ from{ transform: translateY(7px); } to{ transform: translateY(0); } }
.cal-day__events{ display: flex; flex-direction: column; gap: 3px; width: 100%; margin-top: 4px; overflow: hidden; }
.cal-day__evt{
  display: block; width: 100%; box-sizing: border-box;
  font-size: 9px; font-family: var(--font-body); font-weight: 700; line-height: 1.25;
  text-align: left; padding: 2px 7px; border: 1px solid var(--line); border-radius: 10px;
  /* Wrap long client names onto up to two lines instead of truncating with an ellipsis. */
  white-space: normal; overflow-wrap: anywhere; word-break: break-word;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.cal-day__more{ font-weight: 600; color: var(--ink-faint); border-color: transparent; padding-left: 3px; background: transparent !important; -webkit-line-clamp: 1; }

/* Mobile dots: one coloured dot per event, shown instead of text pills on phones. */
.cal-day__dots{ display: none; }
.cal-day__dot{ width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
.cal-day__dot-more{ font-size: 8px; font-weight: 700; color: var(--ink-faint); line-height: 1; }

@media (max-width: 640px){
  /* On phones the 7-column grid is too narrow for name pills — cells stay square
     and show colour dots; the day-detail panel carries the full names on tap. */
  .cal-day.has-events{ aspect-ratio: 1; height: auto; min-height: 0; justify-content: center; padding: 3px 2px; }
  .cal-day.has-events .cal-day__num{ align-self: center; font-size: 13px; }
  .cal-day__events{ display: none; }
  .cal-day__dots{ display: flex; flex-wrap: wrap; gap: 3px; align-items: center; justify-content: center; margin-top: 4px; max-width: 100%; }
}

/* ---------- Embedded tools (Quotation / Agreement generators) ---------- */
.tool-note{
  display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
  background: var(--paper-raised); border: 1px solid var(--line); border-radius: var(--radius-md);
  padding: 10px 14px; margin-bottom: 14px; font-size: 12.5px; color: var(--ink-soft);
}
.tool-note i.fa-circle-info{ color: var(--accent); margin-right: 4px; }
.tool-frame-wrap{
  width: 100%; border: 1px solid var(--line); border-radius: var(--radius-lg);
  overflow: hidden; background: #fff; box-shadow: var(--shadow-sm);
}
.tool-frame{ width: 100%; height: calc(100dvh - 240px); min-height: 520px; border: 0; display: block; background: #fff; }
@media (max-width: 900px){ .tool-frame{ height: calc(100dvh - 250px); } }

/* In-modal validation banner (UI.sheetError). The sheet itself is the scroll
   container and its content sits in a 20px-padded wrapper, so the banner is:
   - sticky at the top → stays visible even when you're scrolled to the Save
     button at the bottom of a long form (it used to scroll out of sight),
   - inset 20px to line up with the content and clear the rounded corners,
   - padded on the right so the absolute close-X never sits over the text. */
.sheet-error{
  position: sticky; top: 12px; z-index: 6;
  display: flex; align-items: center; gap: 9px;
  background: var(--stamp-red-bg); border: 1px solid var(--stamp-red);
  color: var(--stamp-red); font-size: 13px; font-weight: 600;
  border-radius: var(--radius-md); padding: 10px 48px 10px 13px;
  margin: 14px 20px -4px; line-height: 1.4;
  box-shadow: var(--shadow-md);
  animation: sheetErrIn 0.26s cubic-bezier(0.22, 1, 0.36, 1);
}
.sheet-error i{ font-size: 14px; flex-shrink: 0; }
.sheet-error span{ min-width: 0; }
@keyframes sheetErrIn{
  from{ opacity: 0; transform: translateY(-8px); }
  to{ opacity: 1; transform: translateY(0); }
}
.sheet-error.shake{ animation: sheetErrShake 0.32s ease; }
@keyframes sheetErrShake{
  0%,100%{ transform: translateX(0); }
  20%,60%{ transform: translateX(-5px); }
  40%,80%{ transform: translateX(5px); }
}

/* Reorder rows for Preferences roles & services (now arrow-driven). */
.reorder-list{ position: relative; }
.reorder-row{ transition: box-shadow 0.15s ease, background 0.15s ease; }
.reorder-row .list-row__title{ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Reorder arrows (Preferences roles & services) — replaced the old drag handle. */
.reorder-arrows{ display: inline-flex; flex-direction: column; gap: 2px; flex-shrink: 0; }
.reorder-arrow{
  width: 24px; height: 18px; padding: 0; display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--line-strong); background: var(--paper-raised); color: var(--ink-soft);
  border-radius: 6px; cursor: pointer; font-size: 10px; line-height: 1;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
}
.reorder-arrow:hover:not(:disabled){ background: var(--paper-sunken); color: var(--ink); border-color: var(--accent); }
.reorder-arrow:active:not(:disabled){ transform: scale(0.9); }
.reorder-arrow:disabled{ opacity: 0.32; cursor: default; }

/* Connectivity chip in the top bar — subtle green "Online", amber pulsing
   "Offline" pill. Labelled so the state is unmistakable at a glance. */
.net-chip{
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 6px; border-radius: 100px; flex-shrink: 0; white-space: nowrap;
  font-size: 11.5px; font-weight: 700; color: var(--ink-faint); align-self: center;
  transition: background 0.3s ease, color 0.3s ease, padding 0.3s ease;
}
.net-chip__dot{
  width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;
  background: var(--stamp-green); box-shadow: 0 0 0 3px var(--stamp-green-bg);
  transition: background 0.3s ease, box-shadow 0.3s ease;
}
.net-chip.is-offline{ padding: 4px 11px 4px 9px; background: var(--stamp-amber-bg); color: var(--stamp-amber); }
.net-chip.is-offline .net-chip__dot{ background: var(--stamp-amber); box-shadow: none; animation: netPulse 1.6s ease-in-out infinite; }
.net-chip--mobile{ flex-shrink: 0; }
@keyframes netPulse{ 0%,100%{ opacity: 1; } 50%{ opacity: 0.4; } }
/* On mobile the header is tight — show just the dot when online, and only expand
   to the "Offline" label when the connection actually drops. */
@media (max-width: 900px){
  .net-chip--mobile{ padding: 4px; }
  .net-chip--mobile .net-chip__label{ display: none; }
  .net-chip--mobile.is-offline{ padding: 4px 10px 4px 8px; }
  .net-chip--mobile.is-offline .net-chip__label{ display: inline; }
}

/* Optional "Record a payment now" block in the crew/service sheets — the fields
   stay hidden until the toggle is switched on. */
.paynow-block{ border-top: 1px solid var(--line); padding-top: 14px; margin: 4px 0 16px; }

/* Small screens: give the search box more room so its placeholder isn't clipped —
   the Team button collapses to just its icon and the input padding tightens. */
@media (max-width: 560px){
  .team-pick-btn__label{ display: none; }
  .team-pick-btn{ padding: 0 14px; }
  .search-box input{ padding-left: 40px; padding-right: 38px; }
}

/* Today's calendar cell that also has events: labelled "TODAY" instead of the
   date number, in the accent colour, in place of the blue outline. */
.cal-day__num.is-today{ color: var(--accent); font-weight: 800; letter-spacing: 0.2px; }
.cal-day.has-events .cal-day__num.is-today{ font-size: 9px; }
@media (max-width: 640px){
  .cal-day.has-events .cal-day__num.is-today{ font-size: 7.5px; letter-spacing: 0; }
}

/* ============================================================================
   NATIVE-APP FEEL — touch behaviour
   ----------------------------------------------------------------------------
   Removes the tells that this is a web page: the double-tap zoom delay, the
   long-press callout, and pull-to-refresh scroll chaining. Text fields opt back
   out so selection and copy/paste keep working normally.
   ========================================================================== */
html{ -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
body{
  touch-action: manipulation;      /* kills double-tap zoom + the 300ms tap delay */
  -webkit-touch-callout: none;     /* no long-press callout menu */
  overscroll-behavior-y: contain;  /* no pull-to-refresh / scroll chaining */
}
input, textarea, select, [contenteditable]{
  -webkit-touch-callout: default;
  -webkit-user-select: text; user-select: text;
}

/* ============================================================================
   MOTION — page transitions
   ----------------------------------------------------------------------------
   Each route fades in while its cards and rows rise in a short stagger, so
   navigating reads as a real screen change instead of a content swap. The
   stagger is capped so long lists don't cascade forever.
   All of this is disabled automatically by the prefers-reduced-motion rule in
   base.css.
   ========================================================================== */
@keyframes pageFade{ from{ opacity: 0; } to{ opacity: 1; } }
@keyframes riseIn{
  from{ opacity: 0; transform: translateY(12px); }
  to{ opacity: 1; transform: none; }
}
#page-root.page-enter{ animation: pageFade 0.22s ease backwards; }

/* Everything that rises + staggers when a page is navigated to. Grouped into one
   :is() so the delay steps below stay readable.
   Each subtree animates at exactly ONE level — animating a parent AND its children
   doubles the travel and multiplies the fade, which reads as mush. So:
     - .pref-pair > div > *      Preferences section titles + cards
     - :where(#fin-body) .card > .list-row   Financial transaction rows (the card
                                 around them deliberately doesn't animate)
     - .card:not(:has(.item-grid)):not(:has(.list-row))   plain cards only — this
                                 skips the Preferences roles/services card (its rows
                                 stagger instead) and the Financial txn card.
     :where() keeps the #fin-body ID from inflating specificity.
   NOTE: fill-mode is `backwards`, not `both`. Animations outrank normal
   declarations in the cascade, so a `both` fill would pin the end-frame's
   `transform: none` forever and silently kill the :active press-scale below. */
#page-root.page-enter :is(
  :is(.grid, .item-grid, .mobile-only, .roster-grid, .calc-pair, .data-table tbody) > *,
  .pref-pair > div > *,
  :where(#fin-body) .card > .list-row,
  .hub-link-card,
  .event-detail-header,
  .insight-card,
  .segmented,
  .card:not(:has(.item-grid)):not(:has(.list-row))
){
  animation: riseIn 0.36s cubic-bezier(0.22, 1, 0.36, 1) backwards;
}
#page-root.page-enter :is(
  :is(.grid, .item-grid, .mobile-only, .roster-grid, .calc-pair, .data-table tbody) > *,
  .pref-pair > div > *,
  :where(#fin-body) .card > .list-row,
  .hub-link-card,
  .event-detail-header,
  .insight-card,
  .segmented,
  .card:not(:has(.item-grid)):not(:has(.list-row))
):nth-child(1){ animation-delay: 0.02s; }
#page-root.page-enter :is(
  :is(.grid, .item-grid, .mobile-only, .roster-grid, .calc-pair, .data-table tbody) > *,
  .pref-pair > div > *,
  :where(#fin-body) .card > .list-row,
  .hub-link-card,
  .event-detail-header,
  .insight-card,
  .segmented,
  .card:not(:has(.item-grid)):not(:has(.list-row))
):nth-child(2){ animation-delay: 0.05s; }
#page-root.page-enter :is(
  :is(.grid, .item-grid, .mobile-only, .roster-grid, .calc-pair, .data-table tbody) > *,
  .pref-pair > div > *,
  :where(#fin-body) .card > .list-row,
  .hub-link-card,
  .event-detail-header,
  .insight-card,
  .segmented,
  .card:not(:has(.item-grid)):not(:has(.list-row))
):nth-child(3){ animation-delay: 0.08s; }
#page-root.page-enter :is(
  :is(.grid, .item-grid, .mobile-only, .roster-grid, .calc-pair, .data-table tbody) > *,
  .pref-pair > div > *,
  :where(#fin-body) .card > .list-row,
  .hub-link-card,
  .event-detail-header,
  .insight-card,
  .segmented,
  .card:not(:has(.item-grid)):not(:has(.list-row))
):nth-child(4){ animation-delay: 0.11s; }
#page-root.page-enter :is(
  :is(.grid, .item-grid, .mobile-only, .roster-grid, .calc-pair, .data-table tbody) > *,
  .pref-pair > div > *,
  :where(#fin-body) .card > .list-row,
  .hub-link-card,
  .event-detail-header,
  .insight-card,
  .segmented,
  .card:not(:has(.item-grid)):not(:has(.list-row))
):nth-child(5){ animation-delay: 0.14s; }
/* Cap the cascade — a 40-row table shouldn't take two seconds to arrive. */
#page-root.page-enter :is(
  :is(.grid, .item-grid, .mobile-only, .roster-grid, .calc-pair, .data-table tbody) > *,
  .pref-pair > div > *,
  :where(#fin-body) .card > .list-row,
  .hub-link-card,
  .event-detail-header,
  .insight-card,
  .segmented,
  .card:not(:has(.item-grid)):not(:has(.list-row))
):nth-child(n+6){ animation-delay: 0.17s; }

/* Search suggestion chips are built as you type — that's an in-place repaint, not a
   route change, so .page-enter never applies to them. They get their own entrance
   that replays every time the chip row is rebuilt. */
@keyframes chipIn{
  from{ opacity: 0; transform: translateY(-6px) scale(0.92); }
  to{ opacity: 1; transform: none; }
}
.search-suggest > .chip{ animation: chipIn 0.24s cubic-bezier(0.22, 1, 0.36, 1) backwards; }
.search-suggest > .chip:nth-child(1){ animation-delay: 0.01s; }
.search-suggest > .chip:nth-child(2){ animation-delay: 0.04s; }
.search-suggest > .chip:nth-child(3){ animation-delay: 0.07s; }
.search-suggest > .chip:nth-child(4){ animation-delay: 0.10s; }
.search-suggest > .chip:nth-child(5){ animation-delay: 0.13s; }
.search-suggest > .chip:nth-child(n+6){ animation-delay: 0.16s; }

/* ============================================================================
   MOTION — press feedback
   ----------------------------------------------------------------------------
   Buttons/chips already scale on :active. These are the tappable surfaces that
   didn't, so every touch target now answers back.
   ========================================================================== */
.card, .stat-card, .entity-card{
  transition: transform 0.13s cubic-bezier(0.22, 1, 0.36, 1), box-shadow 0.16s ease, background 0.14s ease;
}
/* Only the top half of a mobile event card opens the event, but the press should
   read on the whole card — :has lets the card respond to its own tappable region. */
.entity-card:has([data-open]:active){ transform: scale(0.99); }

/* Rows in the events table can't be transformed (table layout), so they answer
   with a background wash instead. */
.data-table tbody tr.row-clickable{ transition: background 0.14s ease; }
.data-table tbody tr.row-clickable:active{ background: var(--paper-sunken); }

/* Momentum scrolling inside modals and scroll panes. */
.sheet, .page-content{ -webkit-overflow-scrolling: touch; }

/* Disabled buttons — there was no generic rule, so a disabled .btn looked fully
   active (e.g. Preferences → Reset data before a scope is chosen). */
.btn:disabled, .btn[disabled]{
  opacity: 0.45; cursor: not-allowed; pointer-events: none; box-shadow: none;
}

/* ---------- Dashboard filters: changed-state + inline clear ---------- */
/* A dropdown that's off its default gets an accent outline and an X beside it —
   the select shrinks to make room, so the control stays on one row. */
.select-icon-field.is-changed > .select-uniform{
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}
.select-icon-field.is-changed > i{ color: var(--accent); }

.select-clear{
  flex: 0 0 auto;
  width: var(--field-h); height: var(--field-h);
  border-radius: var(--field-radius);
  border: 1px solid var(--accent); background: var(--accent-soft); color: var(--accent);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer; font-size: 13px; padding: 0;
  transition: background 0.15s ease, color 0.15s ease, transform 0.1s ease;
  animation: clearPop 0.22s cubic-bezier(0.34, 1.56, 0.64, 1) backwards;
}
.select-clear:hover{ background: var(--accent); color: #fff; }
.select-clear:active{ transform: scale(0.9); }
@keyframes clearPop{
  from{ opacity: 0; transform: scale(0.6); }
  to{ opacity: 1; transform: scale(1); }
}

/* Tapping a calendar day rewrites #cal-day-detail's contents directly (a plain
   innerHTML write, not a route change), so .page-enter never covers these. They get
   their own entrance that replays every time a different day is picked.
   The #cal-day-detail wrapper itself is deliberately NOT a stagger target — the
   calendar card next to it animates via the .card rule, and animating the wrapper
   too would double up on these children. */
#cal-day-detail > *{ animation: riseIn 0.32s cubic-bezier(0.22, 1, 0.36, 1) backwards; }
#cal-day-detail > *:nth-child(1){ animation-delay: 0.01s; }
#cal-day-detail > *:nth-child(2){ animation-delay: 0.05s; }
#cal-day-detail > *:nth-child(3){ animation-delay: 0.09s; }
#cal-day-detail > *:nth-child(n+4){ animation-delay: 0.12s; }

/* ---------- Owner profile: photo + name/firm ---------- */
/* Photo beside the fields on desktop, stacked on mobile. */
.owner-identity{ display: flex; align-items: flex-start; gap: 16px; }
.owner-identity__photo{ display: flex; flex-direction: column; align-items: center; gap: 8px; flex-shrink: 0; }
.owner-identity__fields{ flex: 1 1 auto; min-width: 0; }
@media (max-width: 700px){
  .owner-identity{ flex-direction: column; align-items: stretch; }
  .owner-identity__photo{ align-items: flex-start; }
}
.owner-photo-row{ display: flex; align-items: center; gap: 14px; }
.owner-photo-preview{
  width: 64px; height: 64px; border-radius: 50%; flex-shrink: 0; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--line-strong); background: var(--paper-sunken);
  color: var(--ink-faint); font-size: 22px;
}
.owner-photo-preview img{ width: 100%; height: 100%; object-fit: cover; display: block; }
.owner-photo-actions{ display: flex; flex-wrap: wrap; gap: 8px; min-width: 0; }
.photo-reqs{
  margin: 14px 0 0;
  font-size: 11.5px; color: var(--ink-faint); line-height: 1.65;
}
.photo-reqs strong{ color: var(--ink-soft); font-weight: 700; }

/* About & Legal link in the app footer. */
.app-footer__link{
  display: inline-flex; align-items: center; gap: 6px;
  margin-top: 10px; font-size: 11.5px; font-weight: 700; color: var(--accent);
  transition: opacity 0.15s ease;
}
.app-footer__link:hover{ opacity: 0.75; }
.app-footer__link i{ font-size: 11px; }

/* About & Legal — sits above the footer's divider line, at the foot of the nav. */
.sidebar__about{
  display: inline-flex; align-items: center; gap: 6px; align-self: flex-start;
  font-size: 10.5px; font-weight: 600; color: #9BA3AE;
  padding: 0 20px 12px; letter-spacing: 0.2px;
  transition: color 0.15s ease;
}
.sidebar__about:hover{ color: #fff; }
.sidebar__about i{ font-size: 10px; }

/* Native-app feel: UI chrome isn't selectable (a long-press or drag shouldn't start
   highlighting labels). Text fields opt back in below so typing, selecting and
   copy/paste all still work normally. */
body{ -webkit-user-select: none; user-select: none; }
input, textarea, select, [contenteditable]{ -webkit-user-select: text; user-select: text; }

/* First-time setup goes full-screen on phones — a floating rounded card with a
   blurred backdrop reads as a dialog you could dismiss, but setup is the whole job
   at that moment. Edge-to-edge, no backdrop, no rounding, and it owns the viewport
   (100dvh so the browser chrome collapsing doesn't clip the footer buttons). */
@media (max-width: 700px){
  #onboarding-overlay{
    padding: 0;
    background: var(--paper);
    -webkit-backdrop-filter: none; backdrop-filter: none;
    align-items: stretch; justify-content: stretch;
  }
  .ob-card{
    max-width: none; width: 100%;
    height: 100dvh; max-height: 100dvh;
    border: none; border-radius: 0; box-shadow: none;
    /* Breathing room at both ends: the progress dots sit down from the status bar
       and the buttons lift off the home indicator, rather than hugging the edges. */
    padding: calc(38px + env(safe-area-inset-top)) 18px calc(30px + env(safe-area-inset-bottom));
    transform: none;
  }
  /* The card fills the screen, so the entrance is a fade rather than a card lift. */
  #onboarding-overlay.open .ob-card{ transform: none; }
  /* Each step is centred in the space between the dots and the buttons instead of
     starting hard under the dots. `safe` matters: with plain `center`, a step taller
     than the screen (e.g. once the keyboard is up) would have its top scrolled out of
     reach — `safe` falls back to top-aligned in exactly that case. Browsers without
     it keep the plain `center` above. */
  .ob-body{
    display: flex; flex-direction: column;
    justify-content: center;
    justify-content: safe center;
  }
  .ob-actions{ margin-top: 26px; }
}

/* ---------- Combo list (replaces the native <datalist> popup) ---------- */
/* Fixed, and appended to <body>, so a sheet's overflow can't clip it. */
.combo__list{
  position: fixed; z-index: 400;
  background: var(--paper-raised);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  max-height: 240px; overflow-y: auto;
  padding: 5px;
  animation: comboIn 0.14s ease;
}
@keyframes comboIn{ from{ opacity: 0; transform: translateY(-4px); } to{ opacity: 1; transform: none; } }
.combo__opt{
  padding: 9px 11px; border-radius: var(--radius-sm);
  font-size: 13.5px; font-weight: 600; color: var(--ink);
  cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.combo__opt:hover, .combo__opt.active{ background: var(--accent-soft); color: var(--accent); }

/* A combo field is a "pick one, or type your own" control, so it carries the same
   chevron as a real <select> (Add Team Member → Role) — every dropdown in the app
   then looks and behaves the same way. */
.combo__input{
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none'><path d='M5.5 7.5l4.5 4.5 4.5-4.5' stroke='%235B6270' stroke-width='1.8' fill='none' stroke-linecap='round' stroke-linejoin='round'/></svg>");
  background-repeat: no-repeat; background-position: right 12px center; background-size: 12px 12px;
  padding-right: 34px !important;
}

/* Financials chip row now carries Contract Value as well, so let it wrap rather
   than squeeze five chips onto one line on narrow cards. */
.fin-chip-row{ flex-wrap: wrap; }
.fin-chip{ flex: 1 1 88px; min-width: 88px; }

/* "Self" marker under the Total Due figure — green, matching the SELF tag. */

/* Syncing state for the connectivity chip — distinct from Online/Offline. */
.net-chip.is-syncing{ background: var(--accent-soft); color: var(--accent); }
.net-chip.is-syncing .net-chip__dot{
  background: var(--accent); box-shadow: none;
  animation: netPulse 1.1s ease-in-out infinite;
}

/* ---- "Self" readouts -----------------------------------------------------
   You can't owe yourself, so wherever a payment figure would normally sit for
   the owner, the app prints a state instead. Two deliberately different looks:

   .amt--self      statement header. Plain — no dot, no green, no mono. It sits
                   where a headline number goes, and colouring it green there
                   would read as "₹0 paid", which is the opposite of the point.
   .stmt-self-amt  per-event cards + event-detail Status row. Green, because
                   here it sits in a column of amounts where green = settled.
   Neither is mono: "Self" is a word, not a figure. */
.statement-header .due-now .amt--self{
  font-family: var(--font-body);
  font-weight: 800;
  color: var(--ink);
}

.stmt-self-amt{
  font-family: var(--font-body);
  font-weight: 700;
  color: var(--stamp-green);
}

.status-text.self-green{
  color: var(--stamp-green);
  font-weight: 700;
}

/* Future Due is amber, never red: it isn't overdue, it just hasn't happened yet. */
.statement-header .stat-grid .val.amber{ color: var(--stamp-amber); }

/* Profile block: photo first and centred, fields stacked beneath it. The old
   side-by-side layout put the circle in a left gutter, which read as decoration
   rather than the first thing you're meant to set. */
.owner-identity--stacked{ flex-direction: column; align-items: stretch; gap: 14px; }
.owner-identity--stacked .owner-identity__photo{ align-items: center; }
.owner-identity--stacked .owner-identity__fields{ width: 100%; }

/* ---- Cards must be able to shrink ---------------------------------------
   .info-row is a flex row, and a flex item defaults to min-width:auto — it
   refuses to shrink below its content's intrinsic width. So a long client name
   or a multi-date list held .info-row open, which held .entity-card open, and
   the card simply stopped getting smaller as the viewport narrowed. Because
   body has `overflow-x: clip`, the excess wasn't even scrollable — it was cut
   off mid-word.

   This exact fix already existed, but scoped to #cal-day-detail only. Promoted
   here so every card that uses .info-row benefits. */
.info-row{ min-width: 0; }
.info-row .val{
  min-width: 0;
  overflow-wrap: anywhere;
  word-break: break-word;
  white-space: normal;
}
.entity-card{ max-width: 100%; }
.crew-mini-row, .crew-mini-row .who{ min-width: 0; overflow-wrap: anywhere; }

/* ---- Reminders bell ------------------------------------------------------
   Presence, not arithmetic. The count was plain text in brackets — "Reminders (3)"
   — which reads as part of the label rather than an alert. A dot answers the only
   question that matters at a glance: is there anything? The exact number is one tap
   away inside the panel. Nothing renders when you're caught up, so the bell is
   quiet rather than showing a zero. */
.reminder-bell{ position: relative; display: inline-flex; align-items: center; margin-right: 4px; }
.reminder-dot{
  position: absolute; top: -2px; right: -3px;
  width: 5px; height: 5px; border-radius: 999px;
  background: var(--stamp-red);
  border: 1px solid var(--paper-raised);
  box-sizing: content-box;
}
/* The earlier-years nudge isn't tied to one event, so it isn't clickable. */
.reminder-line--earlier{ cursor: default; opacity: 0.92; }

/* Force the filled bell face. `fa-solid` already asks for it, but if the solid
   font file is slow or blocked the browser silently falls back to the outline. */
#reminders-toggle .fa-bell{ font-weight: 900; }

/* ---- "Changed on another device" ----------------------------------------
   Dot on the Events tab, same idea as the bell: presence only, no number. */
.sidebar__link, .bottom-nav__item{ position: relative; }
.sidebar__link.has-news::after,
.bottom-nav__item.has-news::after{
  content: '';
  position: absolute; top: 6px; right: 8px;
  width: 8px; height: 8px; border-radius: 999px;
  background: var(--accent);
  border: 1.5px solid var(--paper-raised);
  box-sizing: content-box;
}
.bottom-nav__item.has-news::after{ top: 4px; right: calc(50% - 16px); }

/* The bar that actually loads the changes. An offer, never an automatic swap. */
.remote-news-bar{
  display: flex; align-items: center; gap: 9px;
  width: 100%; margin-bottom: 12px; padding: 9px 12px;
  background: var(--accent-soft); color: var(--accent);
  border: 1px solid var(--accent); border-radius: var(--radius-md);
  font-family: var(--font-body); font-size: 12.5px; font-weight: 700;
  cursor: pointer; text-align: left;
}
.remote-news-bar:hover{ filter: brightness(0.97); }
.remote-news-bar i{ margin-left: auto; font-size: 11px; }
.remote-news-dot{
  width: 8px; height: 8px; border-radius: 999px;
  background: var(--accent); flex-shrink: 0;
}

/* ---- Autocomplete --------------------------------------------------------
   Overlays rather than pushing the form around, so the fields below don't jump
   as you type. The parent .card is position:relative with no overflow clipping,
   so the panel is free to hang past the field. */
.ac{ position: relative; }
.ac__panel{
  position: absolute;
  top: calc(100% + 4px); left: 0; right: 0;
  z-index: 40;
  max-height: 240px;          /* ~6 rows, then it scrolls */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;   /* stop the page scrolling once the list ends */
  padding: 4px;
  background: var(--paper-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}
.ac__panel[hidden]{ display: none; }
.ac__opt{
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  color: var(--ink);
  cursor: pointer;
  overflow-wrap: anywhere;
}
.ac__opt[hidden]{ display: none; }
.ac__opt:hover,
.ac__opt.is-active{ background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.ac__empty{
  padding: 12px 10px;
  text-align: center;
  font-size: 12.5px;
  font-style: italic;
  color: var(--ink-soft);
}
.ac__empty[hidden]{ display: none; }

/* ---- PDF preview --------------------------------------------------------
   #sheet-content is flush to its edges by design, so this wrapper carries the
   modal's own padding — matching the 22/20 the .card modals use — and keeps the
   title clear of the sheet's top and left edges. */
/* Symmetric top/bottom padding, and deliberately NO safe-area inset here: the parent
   `.sheet` already applies `padding-bottom: env(safe-area-inset-bottom)`, so adding it
   again stacked the inset twice and left a visible gap under the buttons on phones
   with a home indicator. One inset, applied once, by the container that owns it. */
.pdf-preview{ padding: 20px 18px; }
.pdf-preview__actions{ margin-bottom: 0; }
.pdf-preview__title{
  margin: 0 0 2px; font-family: var(--font-display);
  font-size: 18px; font-weight: 700; color: var(--ink);
}
.pdf-preview__meta{
  font-size: 11.5px; color: var(--ink-soft); margin: 0 0 12px;
  font-family: var(--font-mono);
}
.pdf-preview__frame{
  position: relative;
  height: 56vh; min-height: 260px;
  border: 1px solid var(--line); border-radius: var(--radius-md);
  overflow: hidden; background: var(--paper-sunken, #f3f3f1);
}
.pdf-preview__frame iframe,
.pdf-preview__frame object{
  position: relative; z-index: 1;
  width: 100%; height: 100%; border: 0; display: block; background: #fff;
}
/* Sits behind the iframe: only ever seen when the iframe renders nothing (iOS). */
.pdf-preview__fallback{
  position: absolute; inset: 0; z-index: 0;
  display: flex; align-items: center; justify-content: center;
  text-align: center; padding: 20px;
  font-size: 12.5px; color: var(--ink-soft); line-height: 1.6;
}
/* Two actions sharing the full width, with a real gap between them. */
.pdf-preview__actions{
  display: flex; gap: 10px; margin-top: 14px;
}
.pdf-preview__actions .btn{ flex: 1 1 0; min-width: 0; justify-content: center; }
/* Close: full-width below, with a visible border so it doesn't read as inert text. */
#pdf-preview-close{
  margin-top: 10px;
  border: 1px solid var(--line-strong, var(--line));
  color: var(--ink); font-weight: 600;
}
#pdf-preview-close:hover{ background: var(--paper-sunken, var(--accent-soft)); }

/* ---- Update-available banner (WhatsApp-web style) -----------------------
   Slides in at the bottom when a fresh service worker is waiting. Tapping it
   jumps to Preferences → App version, where "Update now" applies it. */
.update-banner{
  position: fixed; left: 50%; bottom: calc(16px + env(safe-area-inset-bottom));
  transform: translate(-50%, 140%);
  z-index: 200; width: min(520px, 92%); max-width: 92%;
  display: flex; align-items: center; justify-content: space-between; gap: 4px;
  padding: 4px 6px 4px 4px; border: none;
  background: #2563eb; color: #fff;
  border-radius: 999px; box-shadow: var(--shadow-lg);
  font-family: var(--font-body); font-size: 13.5px; font-weight: 600;
  transition: transform 0.35s cubic-bezier(0.34,1.3,0.64,1);
}
.update-banner.open{ transform: translate(-50%, 0); }
.update-banner__main{
  flex: 1 1 auto; display: flex; align-items: center; justify-content: center; gap: 10px;
  padding: 9px 12px; border: none; cursor: pointer;
  background: transparent; color: inherit;
  font-family: inherit; font-size: inherit; font-weight: inherit;
  border-radius: 999px;
}
.update-banner__close{
  flex-shrink: 0; width: 34px; height: 34px; border: none; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,0.16); color: #fff; border-radius: 999px;
  transition: background 0.15s ease;
}
.update-banner__close:hover, .update-banner__close:active{ background: rgba(255,255,255,0.30); }
.update-banner__close i{ font-size: 14px; opacity: 0.95; }
.update-banner__dot{
  width: 8px; height: 8px; border-radius: 999px; background: #fff; flex-shrink: 0;
  animation: update-pulse 1.4s ease-in-out infinite;
}
.update-banner__main i{ font-size: 12px; opacity: 0.85; }
@keyframes update-pulse{ 0%,100%{ opacity: 1; } 50%{ opacity: 0.35; } }
/* On mobile the bottom nav owns the bottom edge — lift the banner above it. */
@media (max-width: 900px){
  .update-banner{ bottom: calc(var(--bottomnav-h) + 28px + env(safe-area-inset-bottom)); }
}
/* Brief attention pull on the Update button when arrived-at from the banner. */
.pulse-once{ animation: btn-pulse 0.8s ease 2; }
@keyframes btn-pulse{ 0%,100%{ box-shadow: 0 0 0 0 rgba(42,76,122,0.5); } 50%{ box-shadow: 0 0 0 6px rgba(42,76,122,0); } }

/* ---- Onboarding: equal-size cards across steps -------------------------
   The overlay card grew and shrank per step (welcome short, team/services
   tall), so it jumped as you advanced. On desktop, give the body a consistent
   min-height so every step occupies the same footprint. Mobile is full-screen
   already, so this only applies at desktop widths. */
@media (min-width: 901px){
  .ob-body{ min-height: 320px; display: flex; flex-direction: column; justify-content: center; }
}

/* =====================================================================
   v61 device-feedback fixes (iOS/desktop)
   ===================================================================== */

/* 1. Date & time inputs render huge on iOS Safari — it ignores height and uses
   its own intrinsic sizing unless appearance is stripped. Force them to match
   every other field (36px, same padding, same font). */
input[type="date"],
input[type="time"],
input[type="datetime-local"]{
  -webkit-appearance: none;
  appearance: none;
  min-height: 40px; height: 40px;
  line-height: normal;
  font-size: 15px;               /* 16px avoids iOS zoom-on-focus but 15 is our field size; see focus rule below */
  font-family: var(--font-body);
  box-sizing: border-box;
  -webkit-box-sizing: border-box;
}
/* iOS zooms the page when focusing an input under 16px. Bumping to 16px on focus-
   capable small screens keeps the date/time fields from triggering that jump. */
@media (max-width: 900px){
  input[type="date"], input[type="time"], input[type="datetime-local"]{ font-size: 16px; }
}
/* Kill the extra inner spacing WebKit adds around the editable date fields. */
input[type="date"]::-webkit-date-and-time-value,
input[type="time"]::-webkit-date-and-time-value{ margin: 0; text-align: left; }
input[type="date"]::-webkit-datetime-edit,
input[type="time"]::-webkit-datetime-edit{ padding: 0; }

/* 2. Reminder card open/close stutter. Measuring scrollHeight and animating
   max-height to it janks when fonts settle. A capped max-height with a snappier
   curve removes the measure step and the jump. The panel is short, so a generous
   fixed cap animates cleanly and never clips. */
.fin-expand{ transition: max-height 0.42s cubic-bezier(0.16, 1, 0.3, 1); will-change: max-height; }
#reminders-expand.open{ max-height: 640px; }

/* 3. Bottom menu bar covers the last bit of content — page can't scroll far
   enough to tap a button sitting at the very end. The base rule gives 100px but
   that doesn't account for the home-indicator safe area on notched iPhones, so
   the final button still sits under the bar. Make it safe-area aware. */
@media (max-width: 900px){
  .page-content{
    padding-bottom: calc(100px + env(safe-area-inset-bottom)) !important;
  }
}

/* ---- Field hints (small helper text under an input) ---------------------- */
.field-hint{ font-size: 12px; color: var(--ink-faint); margin: 6px 2px 0; line-height: 1.45; }
.field-hint strong{ color: var(--ink-soft); font-weight: 700; }
.field-hint--warn{ color: var(--stamp-red); }
.field-hint--warn strong{ color: var(--stamp-red); }

/* ---------- Invoice watermark ------------------------------------------
   The studio logo sits behind the receipt at 5% so it carries the brand when
   the card is shared, without competing with the figures. It's positioned and
   sized in % of the card so it scales with html2canvas capture, and it's
   pointer-events:none + aria-hidden so it never intercepts taps or gets read
   out. If the SVG is missing the img hides itself and the receipt is unchanged. */
.invoice-card{ position: relative; overflow: hidden; }
.invoice-watermark{
  position: absolute; top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  /* An SVG with no width/height/viewBox has NO intrinsic size, so `height:auto`
     resolves to 0 and the watermark is invisible even though it loaded fine.
     aspect-ratio guarantees a box, object-fit keeps the artwork undistorted. */
  /* PNG logo (has intrinsic dimensions, so html2canvas renders it reliably).
     Sized at half of the previous watermark (was 62% / 260px). */
  width: 31%; max-width: 130px; height: auto;
  opacity: 0.05;
  pointer-events: none; user-select: none;
  z-index: 0;
}
/* Keep the actual receipt content above the watermark. */
.invoice-card > *:not(.invoice-watermark){ position: relative; z-index: 1; }

/* ---------- Smooth scrolling -------------------------------------------
   Momentum + smooth anchor scrolling so the PWA feels native rather than
   jumpy. Respects prefers-reduced-motion, which is a genuine accessibility
   need (smooth scrolling can trigger motion sickness), so it's opt-out. */
@media (prefers-reduced-motion: no-preference){
  html{ scroll-behavior: smooth; }
}
.page-content, .sheet, #page-root{ -webkit-overflow-scrolling: touch; }

/* ---------- QG / AG "Currently editing: <file>" -------------------------
   Long generated filenames were being clipped on one line, so you couldn't tell
   which file you had open. Let it wrap to a second line and break anywhere,
   since these names are long unbroken tokens. */
/* NB: deliberately no `display` here. This is an ID selector, so a display value
   would outrank the `.qg-scope .file-name-display{display:none}` base rule and pin
   the indicator permanently visible — which is exactly what made an empty pill (bare
   pencil + dot) show when nothing had been imported. Visibility stays JS-driven. */
#fileNameDisplay{
  white-space: normal; overflow-wrap: anywhere; word-break: break-word;
  line-height: 1.45; max-width: 100%;
}

/* ---------- QG/AG "Currently editing" indicator ------------------------
   The element is styled with a pencil ::before and a status ::after dot, so when it
   was left in the DOM with no text it rendered as a stray icon-and-dot with nothing
   between them. Only show it once it actually has a filename — :empty covers the
   "no import yet" case even if the inline display style is cleared by script. */
.qg-scope .file-name-display:empty,
.ag-scope .file-name-display:empty,
.esign-scope .file-name-display:empty{ display: none !important; }

/* ---------- Preferences: role / service rows ---------------------------
   Keep the name and its rate on ONE line, always. The name is the only flexible
   part: it shrinks (min-width:0 — a flex item defaults to min-width:auto and
   otherwise refuses to go below its content, which is what pushed the rate off the
   row), wraps to a maximum of TWO lines, then truncates with an ellipsis. The rate
   and the action buttons never move or wrap, so the row reads the same at every
   width. Aligned to flex-start so a two-line name doesn't drag the rate down. */
/* No align-items override here on purpose. `.list-row` and `.flex-row` both centre
   their children, and forcing flex-start pushed every title to the top of the row
   while the rate stayed centred — so ALL rows looked misaligned, not just long ones.
   Centring is correct for a one-line title and still fine for a wrapped two-line one. */
.reorder-row > .flex-row:first-child{ flex: 1 1 auto; min-width: 0; }
.reorder-row .list-row__title{
  min-width: 0;
  /* break-word, NOT anywhere: `anywhere` also shrinks the element's intrinsic
     min-content width, so flex would squeeze the name and wrap it even when there
     was room on one line. `break-word` leaves the natural width preference intact,
     so the name stays on ONE line whenever it fits and only breaks when it truly
     overflows. line-clamp then caps it at two lines with an ellipsis. */
  overflow-wrap: break-word;
  /* An earlier rule in this file sets `white-space: nowrap` on the same selector,
     which would stop the text wrapping at all and make the two-line clamp a no-op.
     Reset it explicitly — same specificity, and this rule comes later, so it wins. */
  white-space: normal;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; line-height: 1.35;
}
.reorder-row > .flex-row:last-child{ flex: 0 0 auto; }
.reorder-row .list-row__amount{ white-space: nowrap; }

/* ---------- Invoice footer typeface (firm name) --------------------------
   Loaded over the network rather than self-hosted. The stack is ordered so the
   real face wins whenever it's available and there's always a graceful fallback:
     1. QuestaGrande  — resolves if you load it from your own licensed source
                        (Adobe Fonts kit, or your CDN). Questa Grande is a
                        COMMERCIAL typeface and is not on Google Fonts, so it can't
                        be pulled from a free CDN without a licence.
     2. Playfair Display — loaded via the Google Fonts <link> in index.html. A high-contrast serif
                        that reads very close to Questa Grande, so the receipt looks
                        right immediately with nothing to upload.
     3. the app's display font, then Georgia/serif.
   Note it must be a webfont that has actually finished loading before the receipt
   is rasterised, or the shared image falls back to a different face than the
   preview — which is why the capture awaits document.fonts.ready. */
.invoice-footer{
  font-family: 'QuestaGrande', 'Playfair Display', var(--font-display), Georgia, serif;
  font-weight: 500;          /* the face is a text weight; 800 would synthesise a fake bold */
  letter-spacing: 0.3px;
}

/* ---------- Connectivity chip: cloud icon (replaces the plain dot) --------
   Same three states and the SAME colours as before — only the glyph changed:
     online   → a plain cloud, green
     syncing  → cloud with an up-arrow, accent, pulsing
     offline  → a device, amber, pulsing (the data is on this device)
   All three glyphs are from the FREE Font Awesome 6.5.2 set this project loads —
   fa-cloud-slash would have been the obvious offline icon but it is Pro-only and
   would silently render an empty box. */
.net-chip__icon{
  font-size: 12px; line-height: 1; flex-shrink: 0; position: relative;
  color: var(--stamp-green);
  transition: color 0.3s ease;
}
.net-chip.is-syncing .net-chip__icon{
  color: var(--accent);
  animation: netPulse 1.1s ease-in-out infinite;
}
.net-chip.is-offline .net-chip__icon{
  color: var(--stamp-amber);
  animation: netPulse 1.6s ease-in-out infinite;
}

/* ---------- "Re-run first-time setup" — one-shot glow --------------------
   A single sweep on load, the way Google pulses a new feature once, then settles —
   rather than looping forever, which turns into visual nagging on a page you sit on.
   `forwards` holds the resting state so the button keeps a quiet accent border
   afterwards instead of snapping back to plain. All values derive from --accent, so
   it re-tints with the chosen theme. Reduced-motion users get the resting state
   immediately with no animation at all. */
#rerun-setup-btn{
  border: 1px solid var(--accent);
  color: var(--accent);
}
#rerun-setup-btn.glow-once{
  animation: rerunGlow 1.8s ease-out 0.4s 1 forwards;
}
#rerun-setup-btn:hover{
  background: var(--accent);
  color: #fff;
}
@keyframes rerunGlow{
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 45%, transparent); }
  45%  { box-shadow: 0 0 16px 3px color-mix(in srgb, var(--accent) 45%, transparent); }
  100% { box-shadow: 0 0 5px 0 color-mix(in srgb, var(--accent) 18%, transparent); }
}
@media (prefers-reduced-motion: reduce){
  #rerun-setup-btn{
    animation: none;
    box-shadow: 0 0 5px 0 color-mix(in srgb, var(--accent) 18%, transparent);
  }
}

/* What's-new modal — reuses the onboarding .ob-card look; full-screen on mobile,
   centred card on desktop. (.ob-card base + mobile-fullscreen rules apply globally.) */
#whatsnew-overlay{
  position: fixed; inset: 0; z-index: 1000;
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
  background: rgba(0,0,0,0.45);
  opacity: 0; transition: opacity 0.28s ease;
}
#whatsnew-overlay.open{ opacity: 1; }
#whatsnew-overlay.open .ob-card{ transform: translateY(0) scale(1); }
@media (max-width: 900px){
  #whatsnew-overlay{
    padding: 0; background: var(--paper);
    -webkit-backdrop-filter: none; backdrop-filter: none;
    align-items: stretch; justify-content: stretch;
  }
  #whatsnew-overlay.open .ob-card{ transform: none; }
}

/* Mobile top-left: on pages with a back button (event detail + More tool pages) show a
   back chevron in place of the logo/name. Desktop uses the sidebar, so it's unaffected. */
.mobile-topbar__back{ display: none; align-items: center; justify-content: center; width: 38px; height: 38px; margin-left: -2px; border: 1px solid var(--glass-border); border-radius: 50%; background: var(--glass-bg); -webkit-backdrop-filter: blur(var(--glass-blur)); backdrop-filter: blur(var(--glass-blur)); box-shadow: 0 0 0 1px rgba(0,0,0,0.04), 0 1px 6px rgba(28,34,48,0.06); color: var(--ink); font-size: 15px; cursor: pointer; padding: 0; }
.mobile-topbar__back:active{ transform: scale(0.92); }
body.detail-mode .mobile-topbar__back, body.subpage-mode .mobile-topbar__back{ display: inline-flex; }
body.detail-mode .mobile-topbar__brand, body.subpage-mode .mobile-topbar__brand{ display: none; }

/* iOS fix: the top bar (sticky), the menubar and the event-detail float bar are all
   position:fixed/sticky WITH backdrop-filter — a combination WebKit intermittently
   repaints at the wrong position (the "floating bar drifts mid-screen" glitch) and can
   leave the back chevron unpainted. Promoting each to its own compositing layer with a
   stable backing pins them where they belong. None have fixed descendants, so this is safe. */
.mobile-topbar, .bottom-bar, .detail-float-nav{
  -webkit-backface-visibility: hidden;
  backface-visibility: hidden;
  -webkit-transform: translateZ(0);
  will-change: transform;
}
/* The float bar keeps its horizontal centring on top of the layer-promotion transform. */
.detail-float-nav{ -webkit-transform: translateX(-50%) translateZ(0); transform: translateX(-50%) translateZ(0); }

/* ---------- Event progress tracker ---------- */
.entity-card{ position: relative; }
.track-icon{ position: absolute; top: 14px; right: 14px; width: 28px; height: 28px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-size: 13px; z-index: 1; }
.track-pill{ display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 600; padding: 4px 11px; border-radius: 100px; white-space: nowrap; }
.track-card{ margin-bottom: 12px; }
.track-choices{ display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 14px; }
.track-choice{ display: inline-flex; align-items: center; gap: 6px; font-size: 13px; font-weight: 500; padding: 7px 13px; border-radius: 100px; border: 1px solid var(--line); background: var(--paper-raised); color: var(--ink-soft); cursor: pointer; transition: transform 0.1s ease; }
.track-choice:active{ transform: scale(0.96); }
.track-ms-grid{ display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.track-ms{ display: flex; align-items: center; gap: 10px; padding: 10px 12px; border: 1px solid var(--line); border-radius: 12px; background: var(--paper-raised); cursor: pointer; text-align: left; transition: transform 0.1s ease; }
.track-ms:active{ transform: scale(0.98); }
.track-ms__ic{ width: 30px; height: 30px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 15px; flex-shrink: 0; background: var(--paper-sunken); color: var(--ink-faint); }
.track-ms.on .track-ms__ic{ background: var(--stamp-green-bg); color: var(--stamp-green); }
.track-ms__tx{ display: flex; flex-direction: column; }
.track-ms__lb{ font-size: 14px; color: var(--ink); }
.track-ms__st{ font-size: 12px; color: var(--stamp-amber); }
.track-ms.on .track-ms__st{ color: var(--stamp-green); }
.track-auto{ margin-top: 13px; font-size: 12px; color: var(--ink-faint); display: flex; align-items: center; gap: 7px; }
@media (max-width: 480px){ .track-ms-grid{ grid-template-columns: 1fr; } }
.entity-card.has-status [data-open] .info-row:first-child .val{ padding-right: 34px; }

/* ---------- "Events today" grouping ---------- */
.today-banner{ display: flex; align-items: center; gap: 8px; font-size: 13px; font-weight: 700; letter-spacing: 0.3px; text-transform: uppercase; color: var(--stamp-amber); margin: 2px 2px 12px; }
.today-banner__count{ font-size: 11px; font-weight: 700; background: var(--stamp-amber-bg); color: var(--stamp-amber); border-radius: 100px; padding: 1px 8px; letter-spacing: 0; }
.today-rest-head{ font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.4px; color: var(--ink-faint); margin: 22px 2px 12px; padding-top: 16px; border-top: 1px solid var(--line); }

/* Save button: idle (nothing to save) shows grey; it goes green the moment there are
   unsaved Entry-Details changes (see updateSaveState in eventDetail.js). */
.detail-float-btn.save.save-idle{ background: var(--stamp-green-bg); color: var(--stamp-green); border-color: color-mix(in srgb, var(--stamp-green) 30%, transparent); box-shadow: none; }
.detail-float-btn.save{ transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease; }

/* Progress tab: labels, helper text, on-hold note */
.track-sub{ font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; color: var(--ink-faint); margin: 0 0 10px 2px; }
.track-hint{ margin-top: 11px; font-size: 12px; color: var(--ink-faint); display: flex; align-items: center; gap: 7px; }
.track-hold{ font-size: 13px; color: var(--ink-soft); display: flex; align-items: center; gap: 9px; padding: 14px; border: 1px dashed var(--line-strong); border-radius: 12px; background: var(--paper-sunken); }
.track-hold i{ color: var(--ink-faint); }

/* "All events" divider row inside the combined events table (keeps columns aligned) */
.data-table .table-divider-row td{ padding: 20px 16px 8px; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.4px; color: var(--ink-faint); border-top: 1px solid var(--line); }
.data-table .table-divider-row, .data-table .table-divider-row:hover{ background: transparent; cursor: default; }
.track-ms--locked{ cursor: default; }
.data-table .table-divider-row--first td{ border-top: none; padding-top: 12px; }

/* #7 Payment-history toggle + #8 roster edit button: give the ghost buttons a hairline
   border so they read as buttons instead of floating text. */
[data-toggle-hist]{ border: 1px solid var(--line); }
/* #3 Edit icon buttons: match the delete (btn-x) 27px size and add a border, so edit and
   delete read as a consistent pair (event types, member types, and the roster). */
[data-edit].btn-icon, [data-edit-type].btn-icon, [data-edit-roster].btn-icon{
  width: 27px; height: 27px; padding: 0; border-radius: 27px; flex-shrink: 0;
  border: 1px solid var(--line-strong); font-size: 11px;
}

/* #10 Progress chip beside the client reminder button */
.stats-action-row{ display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: -8px 0 20px; }
.stats-action-row .btn{ margin: 0; }
.stats-cal-btn{ white-space: nowrap; flex: 0 0 auto; }
/* All three items (chip, Add to Calendar, reminder) share ONE height and the same outline
   style — same border colour and radius as a button — so only their width differs. */
.stats-action-row .progress-chip,
.stats-action-row .btn{ height: 44px; box-sizing: border-box; }
.stats-action-row .progress-chip{ border-color: var(--line-strong); border-radius: 27px; }
/* Reminder fills the remaining width so on wide screens the chip, calendar and reminder all
   sit on one line. */
.stats-action-row #remind-client-btn{ flex: 1 1 auto; margin: 0; }
/* Mobile layout (matches the app's sidebar breakpoint at 900px): lay the action row out on
   the SAME two-column grid as the stat cards above (equal columns, same 16px gutter) so the
   chip and calendar line up exactly under Left Balance / Profit and are a true 50/50. The
   reminder spans both columns on its own row. minmax(0,1fr) forces exact halves regardless
   of content width. */
@media (max-width: 900px){
  .stats-action-row{ display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr); gap: 16px; align-items: center; }
  .stats-action-row .progress-chip{ justify-content: center; }
  .stats-cal-btn{ justify-content: center; }
  .stats-action-row #remind-client-btn{ grid-column: 1 / -1; }
}
/* Small phones only: shrink the chip's CONTENTS (pill, milestones, arrow) so they sit
   comfortably in the half-width cell — the card's height and style are unchanged. */
@media (max-width: 560px){
  .stats-action-row .progress-chip .track-pill{ font-size: 11px; padding: 3px 8px; gap: 4px; }
  .stats-action-row .progress-chip .pchip-ms{ width: 21px; height: 21px; font-size: 11px; border-radius: 7px; }
  .stats-action-row .progress-chip .pchip-arrow{ font-size: 11px; }
}
.progress-chip{ flex: 0 0 auto; display: inline-flex; align-items: center; gap: 9px; min-height: 42px; padding: 6px 12px 6px 8px; border: 1px solid var(--line); border-radius: 100px; background: var(--paper-raised); cursor: pointer; }
.progress-chip:active{ transform: scale(0.98); }
.progress-chip .track-pill{ flex: 0 0 auto; }
.pchip-ms{ flex: 0 0 auto; width: 27px; height: 27px; border-radius: 8px; display: inline-flex; align-items: center; justify-content: center; font-size: 13px; }
.pchip-ms.done{ background: var(--stamp-green-bg); color: var(--stamp-green); }
.pchip-ms.pending{ background: var(--stamp-amber-bg); color: var(--stamp-amber); }
.pchip-arrow{ flex: 0 0 auto; color: var(--ink-faint); font-size: 12px; padding: 0 3px; }

/* #1 Date fields: the ::-webkit-datetime-edit pseudo-elements don't inherit the field
   font-size, so the date read at a different size than the client-name field. Pin them
   to the same 14px so start/end dates match the other inputs. */
input[type="date"], input[type="date"]::-webkit-datetime-edit,
input[type="date"]::-webkit-datetime-edit-fields-wrapper,
input[type="date"]::-webkit-datetime-edit-text,
input[type="date"]::-webkit-datetime-edit-month-field,
input[type="date"]::-webkit-datetime-edit-day-field,
input[type="date"]::-webkit-datetime-edit-year-field{
  font-family: inherit; font-size: 14px; font-weight: inherit;
}

/* #3 Optional menubar labels: hiding them zeroes the label text (a bare text node) while
   the icon keeps its own explicit size, giving a clean icon-only tab bar. */
body.nav-labels-off .bottom-nav__item{ font-size: 0; gap: 0; }
body.nav-labels-off .bottom-nav__item i{ font-size: 21px; }

/* #4 FY page: Method / Type as segmented tabs — side by side on desktop, stacked on mobile */
.fy-filter-row{ display: flex; gap: 16px; margin-bottom: 16px; }
.fy-filter{ flex: 1 1 0; min-width: 0; }
.fy-seg{ display: flex; width: 100%; }
.fy-seg .seg{ flex: 1 1 0; text-align: center; justify-content: center; white-space: nowrap; }
@media (max-width: 640px){ .fy-filter-row{ flex-direction: column; gap: 12px; } }

/* Shared frosted round back-chevron (statement sheet, and reusable elsewhere) — matches
   the mobile top-bar back button. */
.btn-back-chev{ width: 38px; height: 38px; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; border: 1px solid var(--glass-border); border-radius: 50%; background: var(--glass-bg); -webkit-backdrop-filter: blur(var(--glass-blur)); backdrop-filter: blur(var(--glass-blur)); box-shadow: 0 0 0 1px rgba(0,0,0,0.04), 0 1px 6px rgba(28,34,48,0.06); color: var(--ink); font-size: 15px; cursor: pointer; padding: 0; }
.btn-back-chev:active{ transform: scale(0.92); }

/* PDF preview: loading state inside the frame until the document renders */
.pdf-preview__frame{ position: relative; }
.pdf-preview__loading{ position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; background: var(--paper); color: var(--ink-faint); font-size: 13px; z-index: 2; }
.pdf-spinner{ width: 28px; height: 28px; border: 3px solid var(--line); border-top-color: var(--accent); border-radius: 50%; animation: pdfspin 0.7s linear infinite; }
@keyframes pdfspin{ to { transform: rotate(360deg); } }

/* #3 Cross-FY search: "N also found in FY …" rows below the results */
.cross-fy-block{ display: flex; flex-direction: column; gap: 8px; margin-top: 16px; }
.cross-fy-row{ display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 12px 14px; border: 1px solid var(--line); border-radius: 12px; background: var(--paper-raised); color: var(--ink-soft); font-size: 13.5px; cursor: pointer; text-align: left; width: 100%; transition: background 0.15s ease; }
.cross-fy-row:hover{ background: var(--paper-sunken); }
.cross-fy-row:active{ transform: scale(0.99); }
.cross-fy-row strong{ color: var(--ink); font-weight: 600; }
.cross-fy-row > i{ color: var(--ink-faint); flex-shrink: 0; }

/* Skeleton loading state (replaces the spinner) — mimics the real card/table so the wait
   feels like content materialising. Retry still fades in only after a short delay. */
.skel{ display: inline-block; height: 12px; border-radius: 6px; background: linear-gradient(90deg, var(--paper-sunken) 25%, var(--line) 40%, var(--paper-sunken) 60%); background-size: 300% 100%; animation: skelShimmer 1.3s ease-in-out infinite; }
@keyframes skelShimmer{ 0%{ background-position: 130% 0; } 100%{ background-position: -130% 0; } }
.skel-sm{ width: 58px; }
.skel-md{ width: 108px; }
.skel-lg{ width: 150px; }
.skel-pill{ width: 88px; height: 22px; border-radius: 100px; }
.skel-label{ width: 46px; opacity: 0.55; }
.skeleton-card .skel-row{ display: flex; align-items: center; justify-content: space-between; padding: 8px 0; }
.skeleton-card .skel-row:not(:last-child){ border-bottom: 1px solid var(--line); }
.skeleton-table td{ padding: 15px 16px; }
.skeleton-retry{ display: flex; justify-content: center; margin-top: 22px; }
.data-retry-btn{ opacity: 0; pointer-events: none; animation: dataRetryIn 0.4s ease 4s forwards; }
@keyframes dataRetryIn{ to { opacity: 1; pointer-events: auto; } }


/* Export to Excel — a distinct green (spreadsheet-ish) outline so it doesn't blend in with
   the other outlined buttons around it in Preferences. */
#export-events-btn{ border-color: var(--stamp-green); color: var(--stamp-green); }
#export-events-btn:hover{ background: var(--stamp-green-bg); }

/* Lock now — a red outline (same treatment as Export to Excel's green one) so this
   security-sensitive action stands out from the other buttons on App & Updates. */
#bio-lock-now-btn{ border-color: var(--stamp-red); color: var(--stamp-red); }
#bio-lock-now-btn:hover{ background: var(--stamp-red-bg); }

/* Sticky modal header — back chevron + title + close stay pinned while the body scrolls
   under them (used by big sheets like the member statement). Spans the sheet's full width
   and matches its rounded top and background so content disappears cleanly beneath it. */
.sheet-sticky-head{
  position: sticky; top: 0; z-index: 6;
  display: flex; align-items: center; gap: 10px;
  margin: 0 -20px 14px; padding: 14px 20px;
  background: var(--paper-raised);
  border-top-left-radius: var(--radius-lg); border-top-right-radius: var(--radius-lg);
}
.sheet-close-inline{ position: static !important; margin-left: auto; flex-shrink: 0; }

/* ── Signature pad (js/esign.js) ─────────────────────────────────────────────
   A bordered drawing area that reads as "sign here" — dashed line + a small ×
   near the baseline, like a paper signature line — rather than a blank box.

   The pad is intentionally a FIXED light surface in every theme (not the themeable
   --paper-sunken): a signature is always black ink, so it must be drawn and shown on
   light paper to stay visible — in dark mode a black stroke on a dark pad would be
   invisible while signing. This mirrors the white PDF page the signature ends up on, so
   what you draw is what you get. Theme-independent colours below, on purpose. */
.esign-pad-wrap{
  position: relative; width: 100%; height: 170px;
  border: 1.5px dashed #C4C8BF; border-radius: var(--radius-md);
  background: #FFFFFF; overflow: hidden;
}
.esign-canvas{ position: relative; z-index: 2; display: block; touch-action: none; cursor: crosshair; }
.esign-pad-line{
  position: absolute; left: 24px; right: 24px; bottom: 40px; height: 0;
  border-top: 1.5px dashed #D8DAD3; z-index: 1; pointer-events: none;
}
.esign-pad-x{
  position: absolute; left: 22px; bottom: 34px; z-index: 1; pointer-events: none;
  font-size: 15px; color: #9AA0A6; font-weight: 700;
}

/* Saved-signature preview shown on Agreement / Sign-a-PDF once one exists. The signature
   image sits on its own fixed white swatch so the (always-black) ink is clearly visible in
   every theme — including dark, where the card background is dark — and previews exactly as
   it will look on the white PDF. The card chrome stays themed; only the ink swatch is fixed. */
.esign-sig-preview{
  display: flex; align-items: center; gap: 14px;
  padding: 12px 14px; border: 1px solid var(--line); border-radius: var(--radius-md);
  background: var(--paper-sunken); margin-bottom: 12px;
}
.esign-sig-preview img{
  height: 44px; max-width: 160px; object-fit: contain; flex-shrink: 0;
  background: #FFFFFF; border-radius: 8px; padding: 4px 6px;
}
.esign-sig-preview__actions{ display: flex; gap: 8px; margin-left: auto; flex-shrink: 0; }

/* Lean footer on the More hub (just a version stamp) — the full footer (credit, copyright,
   About & Legal, badges) now lives on App & Updates, where the rest of the version info is. */
.app-footer--minimal{ padding: 14px 0 90px; opacity: 0.65; }
@media (min-width: 901px){ .app-footer--minimal{ padding-bottom: 20px; } }

/* ── Sign a PDF: live page preview + draggable overlay ──────────────────────
   The preview card centers a canvas rendering of the actual PDF page; the
   signature/text sits on top as a drag-to-move, drag-the-corner-to-resize box. */
.esign-preview-card{ display: flex; flex-direction: column; align-items: center; padding: 14px 10px; }
.esign-preview-wrap{
  position: relative; margin: 0 auto; max-width: 100%;
  border-radius: var(--radius-sm); overflow: visible;
  box-shadow: 0 2px 10px rgba(28,34,48,0.14), 0 0 0 1px var(--line);
  background: #fff;
}
.esign-preview-canvas{ display: block; max-width: 100%; }
.esign-preview-loading{
  display: flex; align-items: center; justify-content: center; gap: 8px;
  height: 320px; width: 100%; color: var(--ink-faint); font-size: 13.5px;
}
.esign-overlay{
  position: absolute; touch-action: none; cursor: move;
  border: 1.5px dashed var(--accent);
  background: color-mix(in srgb, var(--accent) 6%, transparent);
  border-radius: 4px;
}
.esign-overlay img{ width: 100%; height: 100%; object-fit: contain; pointer-events: none; user-select: none; display: block; }
/* Bigger, higher-contrast handle with a visible resize glyph — the earlier plain dot was
   easy to miss, especially on a short/wide text box where there's little else to draw the
   eye to it. */
.esign-overlay-handle{
  position: absolute; right: -12px; bottom: -12px; width: 26px; height: 26px;
  display: flex; align-items: center; justify-content: center;
  background: var(--accent); border: 2.5px solid #fff; border-radius: 50%;
  box-shadow: 0 2px 6px rgba(0,0,0,0.35); cursor: nwse-resize; touch-action: none;
  z-index: 2;
}
.esign-overlay-handle::before{
  content: "\f424"; /* fa-up-right-and-down-left-from-center */
  font-family: "Font Awesome 6 Free"; font-weight: 900; font-size: 10px; color: #fff;
}
/* Already-added items shown on the current page in Step 2 — solid border (vs the live
   overlay's dashed one) and no resize handle, so it reads clearly as "already placed,
   tap to edit" rather than "still being positioned". */
.esign-chip{
  position: absolute; cursor: pointer;
  border: 1.5px solid var(--line-strong); border-radius: 4px;
  background: rgba(255,255,255,0.5);
  transition: border-color 0.15s ease, background 0.15s ease;
}
.esign-chip:hover{ border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, transparent); }
.esign-chip img{ width: 100%; height: 100%; object-fit: contain; pointer-events: none; user-select: none; display: block; }
@media (min-width: 1100px){
  .esign-preview-loading{ height: 420px; }
}
