/* Sky Portal — app-shell extras (sidebar nav, listings, view-as bar)
   Adapted from prototype/extra.css for the v2 warm-cream shell. */

/* ---------- App shell ---------- */
.shell { display: flex; min-height: 100vh; align-items: stretch; }

.sidebar {
  width: var(--sidebar-w); flex: 0 0 var(--sidebar-w);
  background: linear-gradient(180deg, var(--sidebar-1), var(--sidebar-2));
  border-right: 1px solid var(--line-2);
  box-shadow: 6px 0 24px -18px var(--sidebar-shadow);
  display: flex; flex-direction: column;
  position: sticky; top: 0; height: 100vh;
}
.sidebar__brand { padding: 24px 20px 16px; display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.sidebar__brand img { height: 52px; width: auto; max-width: 100%; flex-shrink: 1; min-width: 0; }

/* Desktop sidebar collapse (Task: collapsible bookings panels). #btn-sidebar-collapse always
   sits here, inside the panel it controls; app.js keeps its label/glyph in sync with
   state.sidebarCollapsed via updateSidebarCollapseBtn(). */
.sidebar__collapse-btn {
  flex-shrink: 0; border: 1px solid var(--line-2); background: var(--panel-2); color: var(--muted);
  border-radius: var(--radius-sm); width: 26px; height: 26px; font-size: 14px; line-height: 1;
  cursor: pointer; font-family: inherit; display: inline-flex; align-items: center; justify-content: center;
}
.sidebar__collapse-btn:hover { color: var(--text); border-color: var(--line); }

.sidebar__nav { flex: 1; overflow-y: auto; padding: 8px 12px; display: flex; flex-direction: column; gap: 2px; }
.navsec { font-size: 10.5px; letter-spacing: .14em; text-transform: uppercase; color: var(--muted-2); padding: 14px 10px 6px; }

/* The enabled items are <a> so the browser can offer middle-click and "open in new tab" (see
   app.js). `text-decoration: none` is the one property that has to be said out loud for that: an
   anchor underlines by default, and every section in the sidebar would be underlined. The rest of
   this rule already worked for both elements — it is class-based and sets colour, font and
   background explicitly. The disabled "Coming soon" item is still a <div> and shares it. */
.navitem {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; border-radius: 10px; font-size: 14px; font-weight: 500;
  color: var(--text); cursor: pointer; border: 1px solid transparent; background: none;
  width: 100%; text-align: left; font-family: inherit; text-decoration: none;
}
.navitem:hover:not(.navitem--active):not(.navitem--disabled) { background: var(--scrim-a05); }
.navitem--active { background: var(--active-bg); color: var(--active-text); font-weight: 600; }
.navitem--disabled { color: var(--muted-2); cursor: default; }
.navitem__lbl, .navitem .lbl { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.navitem__count {
  font-size: 12px; font-weight: 500; color: var(--muted-2); flex-shrink: 0; font-variant-numeric: tabular-nums;
}
.navitem--active .navitem__count { color: var(--active-text); }
.navitem .soon { font-size: 9.5px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted-2); background: var(--scrim-a05); padding: 2px 7px; border-radius: var(--radius-chip); flex-shrink: 0; }
.navitem .badge { font-size: 11px; font-weight: 600; color: var(--active-text); background: var(--active-bg); padding: 1px 8px; border-radius: var(--radius-chip); flex-shrink: 0; }

.sidebar__user {
  border-top: 1px solid var(--line-2); padding: 16px 18px;
  display: flex; align-items: center; gap: 11px;
}
.sidebar__user-btn {
  display: flex; align-items: center; gap: 11px; flex: 1; min-width: 0;
  background: none; border: none; padding: 4px; margin: -4px; border-radius: 10px;
  cursor: pointer; text-align: left; font-family: inherit; color: inherit;
  transition: background .15s;
}
.sidebar__user-btn:hover { background: var(--scrim-a05); }
.avatar {
  width: 36px; height: 36px; border-radius: 10px; background: var(--active-bg); color: var(--active-text);
  display: grid; place-items: center; font-weight: 700; font-size: 13px; flex-shrink: 0;
}
.avatar--lg { width: 44px; height: 44px; font-size: 15px; border-radius: 12px; }
.sidebar__user .who { flex: 1; min-width: 0; }
.sidebar__user .nm { font-size: 13.5px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sidebar__user .role { font-size: 11px; color: var(--label); text-transform: capitalize; margin-top: 2px; }
.sidebar__user .signout {
  flex-shrink: 0; background: none; border: 1px solid transparent; color: var(--muted);
  cursor: pointer; font-size: 12px; padding: 6px 8px; border-radius: var(--radius-sm); font-family: inherit;
  transition: border-color .15s, background .15s, color .15s;
}
.sidebar__user .signout:hover { background: var(--panel-2); border-color: var(--line-2); color: var(--text); }
/* Shares the collapse chevron's shape but carries an emoji, which needs its own font-size, and
   the brand row must not wrap once it holds two buttons. */
.sidebar__theme-btn { font-size: 14px; line-height: 1; }

/* ---------- Main content ---------- */
.main { flex: 1; min-width: 0; }
/* Form/detail views: a readable cap that still scales up on large monitors (fills the space
   on normal screens, only caps on very wide ones). */
.main__inner { max-width: min(1360px, 100%); padding: 36px 44px 60px; }
/* Wide views (week calendar, photo grids) fill the full width instead of the readable cap. */
.main__inner--wide { max-width: none; }
/* Data/table views (listings, agencies, invoicing): a large fluid cap — fills normal monitors
   completely, and caps on ultrawide screens so sparse table columns don't spread too thin. */
.main__inner--data { max-width: min(2000px, 100%); }

.headrow { display: flex; align-items: flex-end; gap: 16px; flex-wrap: wrap; }
.headrow .spacer { flex: 1; }

/* ---------- View as (floating dark pill, shared across all screens) ---------- */
.viewas-pill {
  position: fixed; right: 24px; bottom: 24px; z-index: 60;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  background: var(--dark); color: var(--dark-ink);
  border-radius: 999px; padding: 8px 10px 8px 18px;
  box-shadow: 0 16px 36px -12px rgba(0,0,0,.45);
  font-size: 12.5px; max-width: calc(100vw - 48px);
}
.viewas-pill__label {
  font-size: 10.5px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: var(--ink-a55); white-space: nowrap;
}
.viewas-pill select, .viewas-pill input {
  background: var(--ink-a08); border: 1px solid var(--ink-a16); color: var(--dark-ink);
  border-radius: 999px; padding: 7px 12px; font-size: 12.5px; font-family: inherit;
}
/* The closed select shows white text on the dark pill; force the dropdown option list to
   dark-on-white so the names are readable (they inherited the pill's white text before). */
.viewas-pill select { color-scheme: light; max-width: 260px; }
.viewas-pill select option { color: var(--text); background: var(--panel); }
.viewas-pill select optgroup { color: var(--text); background: var(--panel); font-weight: 700; }
.viewas-pill input { width: 150px; }
.viewas-pill input::placeholder { color: var(--ink-a40); }
.viewas-pill select:focus, .viewas-pill input:focus { outline: none; border-color: var(--ink-a50); }
.viewas-pill__btn {
  background: var(--ink-a10); border: none; color: var(--dark-ink); border-radius: 999px;
  padding: 7px 14px; font-size: 12.5px; font-weight: 600; cursor: pointer; font-family: inherit; white-space: nowrap;
}
.viewas-pill__btn:hover { background: var(--ink-a20); }
/* This button sits INSIDE the view-as pill, and the pill inverts with the page: dark pill on a
   light day, light pill on a dark night. Without an ink of its own it inherits the PILL's
   --dark-ink, which in dark mode is near-black, and lands on a fill that is now dark too. The
   fill and its ink have to come from the same family. */
.viewas-pill__btn--primary { background: var(--brand-strong); color: var(--brand-ink); }
.viewas-pill__btn--primary:hover { background: var(--brand-hover); }
.viewas-pill__btn--icon {
  width: 32px; height: 32px; padding: 0; border-radius: 50%; font-size: 15px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
}

/* Custom "View as" dropdown: a trigger + a light menu (opens upward) with collapsible
   per-agency tabs. Replaces the native <select> so agencies can expand/collapse. */
.viewas-dd { position: relative; }
.viewas-dd__trigger {
  display: inline-flex; align-items: center; gap: 8px; max-width: 230px;
  background: var(--ink-a08); border: 1px solid var(--ink-a16); color: var(--dark-ink);
  border-radius: 999px; padding: 7px 14px; font: inherit; font-size: 12.5px; cursor: pointer;
}
.viewas-dd__trigger:hover { border-color: var(--ink-a40); }
.viewas-dd__current { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The phone's short form of the same label ("Admin" at rest). Hidden here; the ≤820px block near
   the bottom of this file swaps which of the two is shown. */
.viewas-dd__short { display: none; }
.viewas-dd__caret { opacity: .7; flex-shrink: 0; }
.viewas-dd__menu {
  position: absolute; bottom: calc(100% + 8px); right: 0; width: 300px; max-width: calc(100vw - 48px);
  max-height: 400px; overflow-y: auto; background: var(--panel); color: var(--text); border-radius: 12px;
  box-shadow: 0 18px 44px -10px rgba(0,0,0,.45); padding: 6px; z-index: 70;
}
/* Scoped under .viewas-pill so it outranks the legacy `.viewas-pill input` rule (a class+type
   selector), which otherwise paints this box white-on-white and hides what you type. */
.viewas-pill .viewas-dd__search {
  position: sticky; top: -6px; z-index: 1; box-sizing: border-box; width: 100%; margin-bottom: 4px;
  padding: 8px 12px; border: 1px solid var(--line); border-radius: 7px; font: inherit; font-size: 13px;
  background: var(--panel); color: var(--text);
}
.viewas-pill .viewas-dd__search:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-a16); }
.viewas-dd__opt {
  display: block; width: 100%; text-align: left; background: none; border: none; cursor: pointer;
  font: inherit; font-size: 13px; color: var(--text); padding: 8px 12px; border-radius: 7px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.viewas-dd__opt:hover { background: var(--brand-a06); }
.viewas-dd__opt.is-active { background: var(--brand-strong); color: var(--brand-ink); }
.viewas-grp__head {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; background: none;
  border: none; cursor: pointer; font: inherit; font-size: 13px; font-weight: 700; color: var(--text);
  padding: 8px 12px; border-radius: 7px;
}
.viewas-grp__head:hover { background: var(--brand-a06); }
.viewas-grp__caret { font-size: 10px; color: var(--muted); transition: transform .12s ease; }
.viewas-grp:not(.viewas-grp--collapsed) .viewas-grp__caret { transform: rotate(90deg); }
.viewas-grp__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.viewas-grp__count { flex-shrink: 0; font-size: 11px; color: var(--muted); font-weight: 600; }
.viewas-grp__body { padding-left: 14px; }
.viewas-grp--collapsed .viewas-grp__body { display: none; }

@media (max-width: 640px) {
  /* No .viewas-pill placement here: below 820px it is a fixed chip in the top bar, set once in
     the "VIEW AS on a phone" block near the bottom of this file. A second placement rule at this
     narrower breakpoint would be dead code that reads live. */
  .viewas-pill input { width: 120px; }
}

/* ---------- Install app (floating button, admin only — see pwa.js) ----------
   No top/right/bottom/left here: pwa.js sets those inline. On desktop the button must clear the
   View-as pill, whose height changes with its content (a long agent name wraps it, starting/
   clearing impersonation adds or removes its "clear" icon button), so a fixed CSS offset would
   drift out of sync — pwa.js measures the pill's live height instead. Below 820px the pill
   becomes a chip docked in the mobile top bar (see the "VIEW AS on a phone" block below), which
   frees the bottom-right corner, so pwa.js pins the button to bottom:16px/right:16px there.

   z-index 40: above ordinary page content, but below both the mobile nav drawer (50) and its
   backdrop (45) — an open drawer must cover this button, not the other way round. Same reasoning
   as the View-as chip's z-index 31 below, just measured off a plain floating button instead of
   something docked inside the top bar. */
.install-btn {
  position: fixed; z-index: 40;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 11px 18px; border: none; border-radius: 999px;
  background: var(--brand-strong); color: var(--brand-ink);
  font-size: 13px; font-weight: 600; line-height: 1;
  box-shadow: 0 12px 28px -10px var(--scrim-a50);
  cursor: pointer;
}
.install-btn:hover { background: var(--brand-hover); }
.install-btn:disabled { cursor: default; opacity: .7; }

/* ---------- Mobile top bar + hamburger (hidden on desktop) ---------- */
.mobile-topbar { display: none; }
.hamburger {
  display: inline-flex; flex-direction: column; justify-content: center; gap: 4px;
  width: 40px; height: 40px; padding: 9px; border: 1px solid var(--line);
  background: var(--panel); border-radius: var(--radius-sm); cursor: pointer;
}
.hamburger span { display: block; height: 2px; width: 100%; background: var(--text); border-radius: 2px; }
.sidebar-backdrop { display: none; }

/* ---------- Desktop sidebar collapse ----------
   #btn-sidebar-expand visually reuses .hamburger (see index.html) but is a distinct element from
   #btn-hamburger: that one lives in .mobile-topbar, which is display:none here, so it cannot
   double as the desktop "bring it back" control. Hidden by default; the min-width query below is
   the ONLY place that reveals it, and only once .sidebar-collapsed is on #shell — so a narrow
   window never shows this alongside the phone hamburger (they're mutually exclusive by width).
   z-index 40: same reasoning as .install-btn above — clears ordinary content but sits below the
   drawer (50) and its backdrop (45), which must cover it while open, not the other way round. */
.sidebar-expand-btn { display: none; }

@media (min-width: 821px) {
  .sidebar-collapsed .sidebar-expand-btn {
    display: inline-flex;
    position: fixed; top: 16px; left: 16px; z-index: 40;
  }

  /* The button is position:fixed, so it sits OVER whatever the freed width now puts at the top
     left — on the bookings view that is the date range, and "Aug" disappeared under it.
     Indenting only the first row clears it while leaving the calendar grid full-bleed, which is
     the entire reason for collapsing the sidebar. Padding .main__inner instead would give back a
     56px strip down the whole page. */
  .sidebar-collapsed .page-head,
  .sidebar-collapsed .cal-toolbar,
  .sidebar-collapsed #viewas-bar { padding-left: 56px; }

  /* Collapsed: the sidebar leaves the .shell flex row entirely (position:fixed takes it out of
     flow, exactly like the <=820px drawer below) so .main claims the freed width immediately —
     no separate width rule needed on .main, same as how the phone layout already works. Reopening
     via #btn-sidebar-expand or #btn-sidebar-collapse then slides it back in as an OVERLAY over
     the content rather than pushing the layout apart, using the identical transform + backdrop
     pair the <=820px block below defines — deliberately duplicated rather than merged into one
     shared selector, because that block is gated by a max-width query and this one by a class, and
     combining the two conditions needs two rulesets in plain CSS regardless. Gated to
     min-width:821px so a narrow window keeps using the phone drawer rules untouched — collapsing
     the desktop sidebar and then shrinking the window must not leave two conflicting definitions
     of "sidebar open" fighting each other. */
  .sidebar-collapsed .sidebar {
    position: fixed; top: 0; left: 0; z-index: 50; height: 100vh;
    transform: translateX(-100%); transition: transform .2s ease;
    box-shadow: 0 0 40px -10px var(--scrim-a35);
  }
  .sidebar-collapsed .sidebar.open { transform: translateX(0); }

  .sidebar-collapsed .sidebar-backdrop {
    display: block; position: fixed; inset: 0; z-index: 45;
    background: var(--scrim-a35); opacity: 0; pointer-events: none;
    transition: opacity .2s ease;
  }
  .sidebar-collapsed .sidebar-backdrop.open { opacity: 1; pointer-events: auto; }
}

/* Status chips read as pills, each with a distinct colour */
.pill {
  font-size: 11px; font-weight: 600; letter-spacing: .03em;
  padding: 4px 11px; border-radius: 999px; border: 1px solid var(--line); color: var(--muted);
}
.pill--Delivered { color: var(--ok); border-color: var(--ok-line); background: var(--ok-bg); }
.pill--Uploaded  { color: var(--warn); border-color: var(--warn-line); background: var(--warn-bg); }
.pill--Created   { color: var(--neutral); border-color: var(--neutral-line); background: var(--neutral-bg); }
/* Marks the agency's primary-contact parent account (agency_admin) in the team list. */
/* Ink is --active-text, not --brand: --brand is the decorative accent and reads 3.17:1 as text on
   this fill by day. The border stays --brand, which is non-text and clears its 3:1 floor. */
.pill--primary { color: var(--active-text); border-color: var(--brand); background: var(--active-bg); }

.empty { color: var(--muted); font-size: 14px; margin-top: 28px; padding: 40px 0; text-align: center; border: 1px dashed var(--line); border-radius: var(--radius); }

/* ---------- Table (shared component — reuse `.tbl` for later list screens) ---------- */
.tbl-wrap {
  margin-top: 24px; background: var(--panel); border: 1px solid var(--card-line);
  border-radius: var(--radius); overflow: hidden; overflow-x: auto;
}
.tbl { width: 100%; border-collapse: collapse; }
.tbl thead th {
  text-align: left; font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted); padding: 14px 20px; border-bottom: 1px solid var(--card-line); background: var(--panel-2);
  white-space: nowrap;
}
/* A sortable heading is a real <button> inside the <th>, so it is reachable by keyboard and
   announced as a control. It has to inherit every type property: a button takes the UA's font,
   size and colour otherwise, and one heading in 13px system sans among five in 11px uppercase
   letter-spaced muted is worse than no sorting at all.
   The arrow keeps its box whether or not it is showing a glyph — without the reserved width the
   heading shifts sideways the moment its column becomes the active one, and every OTHER heading
   shifts with it. */
.tbl thead th .tbl__sort {
  font: inherit; letter-spacing: inherit; text-transform: inherit; color: inherit;
  background: none; border: 0; padding: 0; margin: 0; cursor: pointer;
  display: inline-flex; align-items: center; gap: 4px;
}
.tbl thead th .tbl__sort:hover { color: var(--text); }
/* Ink is --active-text, not --brand, for the same reason .pill--primary above uses it: --brand is
   the decorative accent and reads 3.34:1 as text on this header fill by day, under the 4.5:1 AA
   floor. The arrow is a glyph a person reads, so it takes text ink. axe flagged this on live for
   every role and both viewports; tests/ui/table-sort-ink.test.ts pins it. */
.tbl thead th .tbl__sort--active { color: var(--active-text); }
.tbl thead th .tbl__sort:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; border-radius: 3px; }
.tbl__sort-arrow { display: inline-block; min-width: 8px; font-size: 9px; line-height: 1; }

/* The sort bar: the card-list counterpart to a sortable table heading, for the pages whose rows
   stack their fields instead of lining them up in columns (Invoicing, Photographers, Packages).
   Deliberately quieter than the content it orders — it is a control you reach for occasionally,
   not a header. Wraps, because Invoicing has five options and a narrow window.
   The arrow keeps its box whether or not it holds a glyph, so pressing an option does not shunt
   every option after it sideways. */
.sortbar { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin: 0 0 12px; }
.sortbar__label {
  font-size: 11px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  color: var(--muted-2); margin-right: 2px;
}
.sortbar__opt {
  display: inline-flex; align-items: center; gap: 4px;
  font-family: inherit; font-size: 12px; font-weight: 500; color: var(--muted);
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 999px;
  padding: 4px 10px; cursor: pointer;
}
.sortbar__opt:hover { color: var(--text); }
/* --active-text, not --brand. The plate and the ink on it are a pair: --brand is the DECORATIVE
   blue (rings, borders), and on --active-bg it measured 3.17:1 in light and 3.82:1 in dark.
   --active-text is the token that exists for text on this plate — it is what the nav pill uses. */
.sortbar__opt--active { color: var(--active-text); border-color: var(--active-line); background: var(--active-bg); }
.sortbar__opt:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.sortbar__arrow { display: inline-block; min-width: 8px; font-size: 9px; line-height: 1; }
.tbl tbody tr.tbl__row { cursor: pointer; transition: background-color .12s; }
.tbl tbody tr.tbl__row:hover, .tbl tbody tr.tbl__row:focus-visible { background: var(--panel-2); }
.tbl tbody tr.tbl__row:focus-visible { outline: none; box-shadow: inset 0 0 0 2px var(--brand-a30); }
.tbl tbody tr.tbl__row + tr.tbl__row { border-top: 1px solid var(--line); }
.tbl td.tbl__cell { padding: 16px 20px; font-size: 13.5px; color: var(--text); vertical-align: middle; }
.tbl__addr { font-weight: 600; letter-spacing: -.005em; }
.tbl__locality { font-size: 12px; color: var(--muted); margin-top: 3px; }
/* The address is an <a> so the browser can offer middle-click and "open in new tab" (see
   app.js). It must not LOOK like a link: the whole row is already the click target, and a blue
   underlined address among plain cells reads as a different kind of thing. `display: block` so
   the hit area is the whole cell rather than the text run, and the focus ring is drawn here
   rather than left to the default outline, which would trace the text and not the block. */
.tbl__addr-link { display: block; color: inherit; text-decoration: none; }
.tbl__addr-link:hover .tbl__addr { color: var(--brand); }
.tbl__addr-link:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: var(--radius-sm); }
.tbl__cell--date { font-family: var(--font-mono); font-size: 12px; color: var(--muted); white-space: nowrap; }
.tbl__cell--muted { color: var(--muted); font-size: 12.5px; }
.tbl__cell--assets { text-align: right; font-variant-numeric: tabular-nums; color: var(--muted); }
/* The actions cell carries more than one plain-text button (Merge, Delete), and `.row-action` has
   no margin of its own, so they ran together as "MergeDelete". */
.tbl__cell--actions { white-space: nowrap; }
.tbl__cell--actions .row-action + .row-action { margin-left: 14px; }

/* Agency group header inside the admin listings table (the table is grouped by agency, so the
   name sits here instead of repeating in a column on every row). */
.tbl__grouprow > .tbl__group {
  padding: 14px 20px 10px; background: var(--panel-2); border-top: 1px solid var(--line);
}
.tbl tbody tr.tbl__grouprow:first-child > .tbl__group { border-top: none; }
/* The whole header is the collapse control, so it spans the row rather than being a small target
   floating next to the name. Reset to look exactly like the static header it replaced. */
.tbl__group-toggle {
  display: flex; align-items: center; gap: 8px; width: 100%;
  background: none; border: 0; padding: 0; margin: 0; text-align: left; cursor: pointer;
  font: inherit; color: inherit;
}
/* Ink is --active-text, for the third time in this file: --brand is the decorative accent and
   reads 3.34:1 on the --panel-2 group fill by day, under the 4.5:1 AA floor. The outline below
   keeps --brand because a focus ring is non-text and clears its own 3:1 floor. axe found this only
   once e2e/a11y.spec.ts learned to EXPAND an agency group, since the click leaves the pointer
   hovering; tests/ui/group-header-ink.test.ts pins it without needing a browser. */
.tbl__group-toggle:hover .tbl__group-name { color: var(--active-text); }
.tbl__group-toggle:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; }
.tbl__group-chevron { font-size: 10px; color: var(--muted); line-height: 1; }
.tbl__group-name { font-size: 12px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase; color: var(--text); }
.tbl__group-count {
  margin-left: 10px; font-size: 11.5px; font-variant-numeric: tabular-nums; color: var(--muted);
  background: var(--panel); border: 1px solid var(--line); border-radius: 999px; padding: 1px 8px;
}

/* Listings table "Invoice" cell: a cyan "invoiced" badge, or an outlined action button for
   jobs that aren't billed yet (matches the prototype's Xero-cyan invoice affordance). */
.inv-badge {
  display: inline-flex; align-items: center; gap: 4px; white-space: nowrap;
  font-size: 11.5px; font-weight: 600; padding: 3px 9px; border-radius: 999px;
  color: var(--link); background: var(--brand-a06);
}
/* "Not delivered" on an uninvoiced row. Deliberately NOT the cyan above, which means "invoiced"
   and reads as good news — this is a neutral statement of fact, so it borrows the same neutral
   tokens as .chip--created rather than introducing a colour. Both themes already define them. */
.inv-badge--undelivered {
  color: var(--neutral); background: var(--neutral-bg); border: 1px solid var(--neutral-line);
}
.inv-action {
  display: inline-flex; align-items: center; gap: 4px; white-space: nowrap;
  font-size: 12px; font-weight: 600; padding: 4px 10px; border-radius: 7px;
  color: var(--xero-text); background: none; border: 1px solid var(--xero); cursor: pointer; font-family: inherit;
}
.inv-action:hover { background: var(--brand-a06); }

/* Status chips (shared component — Created/Uploaded/Delivered, reuse `.chip--*` on later screens) */
.chip--created, .chip--uploaded, .chip--delivered {
  display: inline-flex; align-items: center; font-size: 11px; font-weight: 600; letter-spacing: .03em;
  padding: 4px 11px; border-radius: 999px; border: 1px solid;
}
.chip--created   { color: var(--neutral); border-color: var(--neutral-line); background: var(--neutral-bg); }
.chip--uploaded  { color: var(--warn); border-color: var(--warn-line); background: var(--warn-bg); }
.chip--delivered { color: var(--active-text); border-color: var(--active-line); background: var(--active-bg); }

/* ---------- Account & security modal ---------- */
.modal-overlay {
  position: fixed; inset: 0; z-index: 100;
  background: var(--scrim-a40);
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
}
.modal {
  width: 100%; max-width: 440px; max-height: calc(100vh - 48px); overflow-y: auto;
  background: var(--panel); border: 1px solid var(--card-line); border-radius: 20px;
  box-shadow: 0 30px 80px -30px var(--scrim-a35);
  padding: 26px 26px 28px;
}
.modal__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 18px; gap: 12px; }
.modal__head h2 { font-size: 17px; font-weight: 600; margin: 0; letter-spacing: -.005em; }

/* "Booking | Time off" segmented toggle at the top of the create window (createModeToggleHtml). */
.create-toggle {
  display: inline-flex; gap: 3px; padding: 3px; margin: 0 0 18px;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 10px;
}
.create-toggle__opt {
  font: inherit; font-size: 13px; font-weight: 600; color: var(--muted); cursor: pointer;
  border: none; background: none; padding: 7px 20px; border-radius: 7px; transition: background-color .12s ease, color .12s ease;
}
.create-toggle__opt:hover { color: var(--text); }
.create-toggle__opt.is-active {
  background: var(--panel); color: var(--active-text); box-shadow: 0 1px 3px rgba(0,0,0,.12);
}
/* Lead-in line under a modal's toggle (e.g. the time-off explainer). */
.modal__lede { margin: -2px 0 16px; font-size: 13px; color: var(--muted); line-height: 1.5; }

/* Time-off "Add" rule builder (renderTimeOffBuilder): a section label, the added-day chips,
   the bordered day/time builder card, and its "+ Add this day" button. */
.ua-section-label {
  font-size: 12px; letter-spacing: .05em; text-transform: uppercase; color: var(--muted);
  font-weight: 600; margin: 18px 0 8px;
}
.ua-chips { display: flex; flex-direction: column; gap: 6px; margin-bottom: 8px; }
.ua-chip {
  display: flex; align-items: center; gap: 10px; padding: 8px 12px; font-size: 13.5px;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 8px;
}
.ua-chip__mark { color: var(--brand); }
.ua-chip__label { flex: 1; min-width: 0; font-weight: 600; }
.ua-chip__x {
  background: none; border: none; cursor: pointer; color: var(--muted); font-size: 17px; line-height: 1; padding: 0;
}
.ua-chip__x:hover { color: var(--text); }
.ua-builder { border: 1px solid var(--line); border-radius: 10px; padding: 12px; background: var(--panel); }
.ua-allday-field { display: flex; align-items: flex-end; }
.ua-allday-field .check { padding-bottom: 9px; white-space: nowrap; }
.ua-add-day {
  display: block; width: 100%; margin-top: 10px; text-align: center; cursor: pointer;
  border: 1.5px dashed var(--brand); color: var(--brand); background: none;
  border-radius: 8px; padding: 8px; font: inherit; font-size: 13px; font-weight: 600;
}
.ua-add-day:hover { background: var(--brand-a06); }
.modal__close {
  background: none; border: none; cursor: pointer; color: var(--muted);
  font-size: 22px; line-height: 1; padding: 4px 8px; border-radius: var(--radius-sm); flex-shrink: 0;
}
.modal__close:hover { background: var(--panel-2); color: var(--text); }

.sec-profile { display: flex; align-items: center; gap: 12px; margin-bottom: 22px; }
.sec-name { font-size: 14.5px; font-weight: 600; }
.sec-email { font-size: 12.5px; color: var(--muted); margin-top: 2px; word-break: break-all; }

.sec-section { border-top: 1px solid var(--line); padding-top: 20px; }
.sec-section h3 { font-size: 11.5px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); margin: 0 0 14px; font-weight: 600; }
.sec-ok { color: var(--ok); font-weight: 500; }
.sec-actions { display: flex; gap: 10px; margin-top: 4px; }

/* ---------- Agencies & Agents (Task R3 re-skin) ---------- */

/* Circular initials avatar — modifier on the existing sidebar `.avatar` (which is a rounded
   square). Sized via the existing `.avatar` / `.avatar--lg` rules; this only rounds it off. */
.avatar--circle { border-radius: 50%; }

/* Two top-right actions side by side in a `.page-head` (the base `.page-head__action` singular
   slot only fits one button). */
.page-head__actions { flex-shrink: 0; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* Video previews: the YouTube embed and the uploaded MP4 player. Capped so a 16:9 frame doesn't
   dominate the panel on a wide screen. */
.video-embed {
  width: 100%; max-width: 640px; aspect-ratio: 16 / 9; margin-top: 12px;
  background: #000; border-radius: var(--radius-sm); overflow: hidden;
}
.video-embed iframe, .video-embed video { width: 100%; height: 100%; border: 0; display: block; }
.video-embed--file { margin-top: 10px; }

/* Gallery Video folder: the YouTube embed and the uploaded master sit side by side on a wide
   screen, each labelled and carrying only its own buttons. They collapse to one column below
   ~760px of available width. */
.gallery-video__panes {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 24px; align-items: start;
}
/* Capped so a 16:9 frame doesn't grow to half a metre of screen on an ultrawide monitor. */
.video-pane { min-width: 0; max-width: 700px; }
.video-pane__head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
.video-pane__label { font-size: 14px; font-weight: 600; color: var(--text); }
.video-pane__hint { font-size: 12px; color: var(--muted); overflow-wrap: anywhere; }
.video-pane .video-embed { max-width: none; margin-top: 0; }
.video-pane__actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 12px; }
/* The YouTube link shown as text on the share page, so a vendor can see there IS a link. */
.video-link__url {
  display: block; width: 100%; box-sizing: border-box; margin-top: 12px; padding: 8px 10px;
  font: inherit; font-size: 13px; color: var(--text); background: var(--panel-2);
  border: 1px solid var(--line); border-radius: 8px; text-overflow: ellipsis;
}
.video-link__url:focus { outline: 2px solid var(--active-line); outline-offset: 1px; }
/* Copy link / View on YouTube, sitting under the URL field. */
.video-links { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.video-links .btn { text-decoration: none; }

/* Cancel control on an in-flight upload (photo rows and the video progress line). */
.upload-row__cancel {
  flex: 0 0 auto; border: 1px solid var(--line); background: var(--panel); color: var(--muted);
  border-radius: 6px; padding: 3px 10px; font: inherit; font-size: 12px; cursor: pointer;
  transition: color .15s, border-color .15s, background .15s;
}
.upload-row__cancel:hover { color: var(--danger); border-color: var(--danger-line); background: var(--danger-bg); }
.upload-row--canceled .upload-row__status { color: var(--muted-2); }

/* Video upload progress: reuses the photo upload bar, full width under the drop box. */
.video-mp4__progress { display: flex; align-items: center; gap: 12px; width: 100%; margin-top: 10px; }
.video-mp4__progress .upload-bar { flex: 1; }
.video-mp4__progress .video-mp4__status { flex: 0 0 auto; font-variant-numeric: tabular-nums; }

/* Video drop box: the same zone as photos, sized down since it takes a single file and sits in
   the smaller Video panel. */
.upload-zone--video { padding: 20px 16px; margin-top: 10px; }
.upload-zone--video .upload-zone__icon { font-size: 18px; }
.upload-zone--video .upload-zone__prompt { font-size: 13.5px; }

/* Inline rename of a listing's name in the gallery header. The Rename button stays quiet until
   the title is hovered, so it doesn't compete with the heading. */
.gh-title { display: flex; align-items: baseline; gap: 10px; }
.gh-title__edit {
  border: 0; background: none; padding: 2px 6px; cursor: pointer; border-radius: 6px;
  font: inherit; font-size: 12.5px; font-weight: 500; color: var(--muted);
  opacity: 0; transition: opacity .15s, color .15s, background .15s;
}
.gh-title:hover .gh-title__edit, .gh-title__edit:focus-visible { opacity: 1; }
.gh-title__edit:hover { color: var(--active-text); background: var(--active-bg); }
.gh-rename { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.gh-rename__input {
  flex: 0 1 420px; min-width: 200px; padding: 8px 11px; font: inherit; font-size: 20px;
  font-weight: 600; letter-spacing: -.01em; color: var(--text);
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.gh-rename__input:focus {
  outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-a16);
}
.gh-rename__error { flex-basis: 100%; }

/* All Listings search box — sits in the page head beside the actions. */
.listings-search {
  flex: 0 1 320px; min-width: 180px; margin-left: auto;
  padding: 9px 12px; font: inherit; font-size: 13.5px; color: var(--text);
  background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius-sm);
  transition: border-color .15s, box-shadow .15s;
}
.listings-search::placeholder { color: var(--muted-2); }
.listings-search:focus {
  outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-a16);
}

/* Plain-text row actions ("Edit · + Add agent · Remove agency", "Delivery CC · Edit · Remove") —
   unlike `.linklike` these aren't underlined, matching the prototype's quiet inline-button look. */
.row-action {
  background: none; border: none; padding: 0; margin: 0; cursor: pointer; font: inherit;
  font-size: 12.5px; font-weight: 600; color: var(--text); white-space: nowrap;
}
.row-action:hover { color: var(--brand); }
.row-action--accent { color: var(--link); }
.row-action--accent:hover { color: var(--link-hover); }
.row-action--danger { color: var(--danger); }
.row-action--danger:hover { color: var(--danger); }
.row-action:disabled { color: var(--muted-2); cursor: not-allowed; }
/* Deliver is the one row action that reaches a person outside the business — it emails the agent.
   Given weight so it does not read as a third neutral word beside Merge and Delete, and so the eye
   has to stop on it. It renders first, furthest from Delete: those two adjacent is how a mis-aimed
   click turns into an incident. */
.row-action--deliver {
  color: var(--active-text); background: var(--active-bg); border: 1px solid var(--active-line);
  border-radius: var(--radius-chip); padding: 3px 10px;
}
.row-action--deliver:hover { color: var(--active-text); filter: brightness(0.97); }
.row-action:disabled:hover { color: var(--muted-2); }

/* Admin flat list: one card per agency, its team shown inline underneath — no separate
   click-through "detail" page for admins any more (that view is still used by agency_admin's
   own single-agency home page, styled separately below). */
.agency-list { display: flex; flex-direction: column; gap: 20px; margin-top: 24px; }
.agency-block {
  background: var(--panel); border: 1px solid var(--card-line); border-radius: var(--radius);
  box-shadow: 0 14px 34px -28px var(--scrim-a30); overflow: hidden;
}
.agency-block__head { display: flex; align-items: flex-start; gap: 14px; padding: 20px 22px; border-bottom: 1px solid var(--line); }
.agency-block__head > .avatar { flex-shrink: 0; }
.agency-block__info { flex: 1; min-width: 0; }
.agency-block__name { font-size: 15.5px; font-weight: 700; display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.agency-block__sub { font-size: 12.5px; color: var(--muted); margin-top: 4px; }
.agency-block__actions { display: flex; align-items: center; gap: 14px; flex-shrink: 0; flex-wrap: wrap; padding-top: 3px; }
.agency-block__body { padding: 4px 22px 16px; }

/* Per-agency show/hide-agents toggle in the flat admin list. */
.agency-block__toggle {
  flex-shrink: 0; align-self: center; cursor: pointer; white-space: nowrap;
  border: 1px solid var(--card-line); background: var(--panel-2); color: var(--text);
  font-family: inherit; font-size: 12px; font-weight: 600; padding: 6px 10px;
  border-radius: var(--radius-sm, 8px);
}
.agency-block__toggle:hover { border-color: var(--brand); color: var(--brand); }
/* Nothing sits under the header when collapsed, so drop its divider. */
.agency-block--collapsed .agency-block__head { border-bottom: none; }

.agency-rows { display: flex; flex-direction: column; }
.agency-row { display: flex; align-items: flex-start; gap: 12px; padding: 14px 0; border-top: 1px solid var(--line); }
.agency-row:first-child { border-top: none; }
.agency-row__info { flex: 1; min-width: 0; }
.agency-row__name { font-size: 13.5px; font-weight: 600; }
.agency-row__meta { font-size: 12px; color: var(--muted); margin-top: 2px; }
.agency-row__cc { font-size: 11.5px; color: var(--muted-2); margin-top: 2px; }
.agency-row__actions { display: flex; align-items: center; gap: 12px; flex-shrink: 0; flex-wrap: wrap; padding-top: 2px; }
.agency-row__toggles { display: flex; gap: 14px; flex-wrap: wrap; margin-top: 8px; }
.agency-row__assistants { font-size: 12.5px; color: var(--text); margin-top: 8px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

.agency-team { margin-top: 4px; }
.agency-team__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 0 6px; border-top: 1px solid var(--line); }
.agency-team__head h4 { font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); margin: 0; font-weight: 700; }

.agency-panel {
  background: var(--panel); border: 1px solid var(--card-line); border-radius: var(--radius);
  padding: 22px 24px; margin: 20px 0 28px;
}
.agency-fields { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 16px 24px; }
.agency-field__label { font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); margin-bottom: 4px; }
.agency-field__value { font-size: 14px; color: var(--text); word-break: break-word; }
.muted { color: var(--muted-2); }

.team-section { margin-bottom: 28px; }
.team-section__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
.team-section__head h3 { font-size: 13px; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); margin: 0; font-weight: 600; }
.team-list { display: flex; flex-direction: column; gap: 12px; }

.team-row {
  background: var(--panel); border: 1px solid var(--card-line); border-radius: var(--radius);
  padding: 16px 18px; display: flex; flex-direction: column; gap: 12px;
}
.team-row__top { display: flex; align-items: flex-start; gap: 12px; flex-wrap: wrap; }
.team-row__info { flex: 1; min-width: 160px; }
.team-row__name { font-size: 14.5px; font-weight: 600; }
.team-row__meta { font-size: 12.5px; color: var(--muted); margin-top: 3px; }
.team-row__actions { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; flex-shrink: 0; }
.team-row__top > .avatar, .agency-row > .avatar { flex-shrink: 0; }
.team-row__toggles { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; padding-top: 4px; border-top: 1px solid var(--line); }
.team-row__assistants { font-size: 13px; color: var(--text); display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.team-row__label { font-size: 11.5px; color: var(--muted); font-weight: 600; }

.check { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--label); cursor: pointer; white-space: nowrap; }
.check input { cursor: pointer; }

.chip {
  display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: var(--radius-chip);
  padding: 3px 6px 3px 10px;
}
.chip__x {
  background: none; border: none; cursor: pointer; color: var(--muted); font-size: 15px; line-height: 1;
  padding: 2px 4px; border-radius: 4px; font-family: inherit;
}
.chip__x:hover { background: var(--line); color: var(--text); }

.link-select {
  font-size: 12.5px; padding: 5px 8px; border-radius: var(--radius-chip);
  border: 1px solid var(--input-line); background: var(--input-bg); color: var(--text); font-family: inherit;
}

.pill--Inactive { color: var(--muted); border-color: var(--line); background: var(--panel-2); }

.invite-link-row { display: flex; gap: 8px; margin: 6px 0 18px; }
.invite-link-row input {
  flex: 1; min-width: 0; padding: 10px 12px; border-radius: var(--radius-sm);
  border: 1px solid var(--input-line); background: var(--panel-2); color: var(--text);
  font-size: 12.5px; font-family: var(--font-mono);
}

.field textarea {
  width: 100%; padding: 12px 14px; border-radius: var(--radius-sm); min-height: 84px; resize: vertical;
  background: var(--input-bg); border: 1px solid var(--input-line); color: var(--text);
  font-size: 14px; font-family: inherit;
}
.field textarea:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-a16); }
.field input:disabled { background: var(--panel-2); color: var(--muted); }

/* Plain-text "N listings" label on an agent row, before the Delivery CC/Edit/Remove actions —
   matches the prototype's agenciesView `g.listLabel`. */
.agency-row__listings { font-size: 12.5px; color: var(--muted); white-space: nowrap; }

/* ---------- Agency detail drill-in (admin "View jobs →", prototype: isAgencyDetail) ---------- */
.ad-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-top: 14px; }
.ad-head__id { display: flex; align-items: center; gap: 14px; min-width: 0; }
.ad-head__text { min-width: 0; }
.ad-head__name { font-size: 22px; font-weight: 600; letter-spacing: -.01em; }
.ad-head__sub { font-size: 13px; color: var(--muted); margin-top: 3px; }

.ad-agents { display: flex; flex-wrap: wrap; gap: 10px; margin: 20px 0 0; }
.ad-agent-card {
  border: 1px solid var(--card-line); background: var(--panel); border-radius: var(--radius);
  padding: 12px 14px; display: flex; align-items: center; gap: 11px; min-width: 220px;
}
.ad-agent-card__info { flex: 1; min-width: 0; }
.ad-agent-card__name { font-size: 14px; font-weight: 600; }
.ad-agent-card__meta { font-size: 12px; color: var(--muted); margin-top: 2px; }

.ad-jobs-head {
  font-size: 12px; letter-spacing: .07em; text-transform: uppercase; color: var(--muted);
  font-weight: 600; margin-top: 26px;
}

/* ---------- Photographers (crew) ---------- */
.color-picker { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.color-swatches { display: flex; gap: 8px; flex-wrap: wrap; }
.color-swatch {
  width: 26px; height: 26px; border-radius: 50%; border: 2px solid transparent;
  cursor: pointer; padding: 0; box-shadow: inset 0 0 0 1px var(--scrim-a12);
}
.color-swatch:hover { transform: translateY(-1px); }
.color-swatch--selected { border-color: var(--text); box-shadow: inset 0 0 0 1px var(--scrim-a12), 0 0 0 2px var(--panel); }
.pf-roles { display: flex; gap: 10px; flex-wrap: wrap; }
/* Skills pill-toggle (checkbox visually hidden but focusable — a11y-safe); shared by
   the "Add crew member" form and the inline row-edit form. */
.pf-role-check {
  position: relative; display: inline-flex; align-items: center; font-size: 13px; font-weight: 600;
  padding: 8px 16px; border-radius: 999px; border: 1px solid var(--line);
  background: var(--panel-2); color: var(--muted); cursor: pointer; transition: border-color .15s, background .15s, color .15s;
}
.pf-role-check:has(input:checked) { color: var(--active-text); border-color: var(--active-line); background: var(--active-bg); }
.pf-role-check:has(input:focus-visible) { outline: 2px solid var(--brand); outline-offset: 2px; }
.pf-role-check input {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
/* Reset the global .field input rule (width:100%; padding) which otherwise
   stretches checkboxes placed inside a .field wrapper. */
.field input[type="checkbox"] { width: auto; padding: 0; border: none; background: none; }

/* Photographers modal-styled card (Task R5 — full-page view dressed as the
   prototype's "Photographers & videographers" dialog; no real overlay/backdrop
   since this is still a sidebar-nav page, not an actual modal). */
.pf-card { max-width: 640px; margin: 28px auto 0; }
.pf-card__head { margin-bottom: 6px; }
.pf-card__title { font-size: 20px; font-weight: 600; margin: 0; letter-spacing: -.01em; }
.pf-card__sub { color: var(--muted); font-size: 13.5px; margin-top: 6px; line-height: 1.5; }

/* Photographers page: full-width roster grid + sticky "add crew" side panel. Replaces the old
   640px centred card so the page fills wide screens like the other admin views. */
.pf-page__head { margin-bottom: 22px; }
.pf-page__title { font-size: 22px; font-weight: 600; margin: 0; letter-spacing: -.01em; }
.pf-page__sub { color: var(--muted); font-size: 13.5px; margin-top: 6px; line-height: 1.5; max-width: 62ch; }
.pf-layout { display: grid; grid-template-columns: minmax(0, 1fr) 380px; gap: 28px; align-items: start; }
.pf-aside {
  position: sticky; top: 24px;
  background: var(--panel); border: 1px solid var(--card-line); border-radius: var(--radius);
  padding: 20px 20px 22px;
}
.pf-aside .crew-add { margin-top: 0; border-top: none; padding-top: 0; }
/* Roster cards flow into as many columns as fit; an inline edit card takes the full row. */
.crew-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 14px; }
.crew-list .crew-row--editing { grid-column: 1 / -1; }
.crew-row__actions { justify-content: space-between; padding-top: 12px; border-top: 1px solid var(--line); }
.crew-row__links { display: flex; align-items: center; gap: 14px; }
@media (max-width: 1024px) {
  .pf-layout { grid-template-columns: 1fr; }
  .pf-aside { position: static; }
}
.crew-row__avatar {
  width: 26px; height: 26px; font-size: 11px; font-weight: 700; color: var(--on-solid);
  display: flex; align-items: center; justify-content: center;
}
.crew-row__cc { font-size: 11.5px; color: var(--muted-2); margin-top: 1px; }

/* "Agent-bookable" pill toggle on a crew row */
.crew-bookable {
  display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; border-radius: 999px;
  font-size: 12px; font-weight: 600; cursor: pointer; white-space: nowrap; font-family: inherit;
  border: 1px solid var(--line); background: var(--panel); color: var(--muted);
}
.crew-bookable--on { border-color: var(--active-line); background: var(--active-bg); color: var(--active-text); }
.crew-row__link {
  font-size: 12.5px; font-weight: 600; cursor: pointer; padding: 5px 9px; border-radius: 6px;
  background: none; border: none; font-family: inherit; white-space: nowrap;
}
.crew-row__link--edit { color: var(--brand); }
.crew-row__link--edit:hover { background: var(--active-bg); }
.crew-row__link--remove { color: var(--danger); }
.crew-row__link--remove:hover { background: var(--danger-bg); }

.crew-row--editing { gap: 0; }
.crew-row--editing .field { margin-bottom: 12px; }
.crew-edit__actions { display: flex; justify-content: flex-end; gap: 8px; margin-top: 4px; }

/* Inline "Add crew member" section beneath the crew list */
.crew-add { margin-top: 18px; border-top: 1px solid var(--line); padding-top: 16px; }
.crew-add__label { font-size: 13px; font-weight: 600; color: var(--text); margin-bottom: 10px; }
.crew-add__note {
  display: flex; align-items: center; gap: 8px; margin-top: 4px; font-size: 12px; color: var(--link-hover);
  background: var(--brand-a06); border: 1px solid var(--active-line); border-radius: 8px; padding: 8px 11px; line-height: 1.45;
}
.crew-add__actions { display: flex; justify-content: flex-end; margin-top: 12px; }

/* ---------- Gallery (admin listing detail + client view) ---------- */
.pill--Published { color: var(--ok); border-color: var(--ok-line); background: var(--ok-bg); }
.pill--Draft { color: var(--neutral); border-color: var(--neutral-line); background: var(--neutral-bg); }

/* Page header: back link + address title + status chip/meta line + top-right action buttons. */
.gallery-head { margin-bottom: 4px; }
.gallery-back {
  display: inline-flex; align-items: center; gap: 6px;
  background: none; border: none; padding: 0; margin-bottom: 14px; cursor: pointer;
  font-family: inherit; font-size: 13px; font-weight: 600; color: var(--muted);
}
.gallery-back:hover { color: var(--text); }

/* The read-only view's back control. It used to be a deliberately quiet breadcrumb — 12px,
   --muted-2, no arrow, reading "Listings /" — and an agent could not tell it was a button at
   all. Now matched to `.gallery-back` above, which the admin view has had all along: same size,
   same weight, same hover. There is no reason the client-facing half of the app should have the
   weaker way out. */
.gallery-head__breadcrumb {
  display: inline-flex; align-items: center; gap: 6px;
  background: none; border: none; padding: 0; margin: 0 0 14px;
  cursor: pointer; font-family: inherit; font-size: 13px; font-weight: 600; color: var(--muted);
}
.gallery-head__breadcrumb:hover { color: var(--text); }

.gallery-head__row { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; flex-wrap: wrap; }
.gallery-head__text { min-width: 0; }
.gallery-head__meta { display: flex; align-items: center; gap: 10px; margin-top: 10px; flex-wrap: wrap; }
.gallery-head__metaline { color: var(--muted); font-size: 13px; }
/* Also used as a standalone <p> in the client gallery header (renderHeader in gallery-view.js) —
   scoped to the client view since the admin view's usage is an inline <span> inside a flex row. */
#view-gallery-view .gallery-head__metaline { margin: 8px 0 0; }
.gallery-head__actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; flex-shrink: 0; }

.gallery-tabs { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin: 24px 0 18px; }
.gallery-tabs__list { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; flex: 1; min-width: 0; }

/* Folder tabs — pills; the active folder is a dark filled pill, others are light pills. */
.gtab {
  display: flex; align-items: center; gap: 2px;
  background: var(--panel-2); border: 1px solid var(--card-line); border-radius: 999px;
  padding: 3px 4px 3px 14px;
}
.gtab--active { border-color: var(--dark); background: var(--dark); }
.gtab__select {
  background: none; border: none; cursor: pointer; font-family: inherit; color: var(--text);
  font-size: 13px; font-weight: 600; padding: 6px 8px 6px 0; border-radius: 999px;
}
.gtab--active .gtab__select { color: var(--dark-ink); }
.gtab__count { color: var(--muted); font-weight: 500; font-size: 11.5px; margin-left: 2px; }
.gtab--active .gtab__count { color: var(--ink-a65); }
.gtab__actions { display: flex; align-items: center; gap: 1px; opacity: 0; transition: opacity .12s; }
.gtab:hover .gtab__actions, .gtab:focus-within .gtab__actions { opacity: 1; }
.gtab__icon {
  background: none; border: none; cursor: pointer; color: var(--muted); font-family: inherit;
  font-size: 13px; line-height: 1; width: 22px; height: 22px; border-radius: 50%; margin-right: 2px;
  display: inline-flex; align-items: center; justify-content: center;
}
.gtab--active .gtab__icon { color: var(--ink-a70); }
.gtab__icon:hover { background: var(--scrim-a08); color: var(--text); }
.gtab--active .gtab__icon:hover { background: var(--ink-a16); color: var(--on-solid); }
.gtab__icon:disabled { opacity: .35; cursor: default; }
.gtab__icon:disabled:hover { background: none; }
.gtab__icon--danger:hover { background: var(--danger-bg); color: var(--danger); }
.gtab--active .gtab__icon--danger:hover { background: var(--ink-a16); color: var(--danger); }

.gtab--add {
  background: var(--panel-2); border: 1px dashed var(--line); border-radius: 999px; padding: 9px 16px 9px 14px;
  font-size: 13px; font-weight: 600; color: var(--muted); cursor: pointer; font-family: inherit;
}
.gtab--add:hover { color: var(--text); border-color: var(--btn-hover-line); }

/* Admin-only strip below the header: "Assigned to" reassign, Delete listing, Manage folders
   (prototype's "Admin · Assigned to … Delete listing · Manage folders" dashed bar). */
.gallery-admin-strip {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-top: 16px; background: var(--panel); border: 1px dashed var(--line); border-radius: 10px;
  padding: 10px 14px; font-size: 13.5px; color: var(--label);
}
.gallery-admin-strip__label { font-weight: 600; color: var(--active-text); }
.gallery-admin-strip select {
  padding: 7px 10px; border: 1px solid var(--input-line); border-radius: 7px;
  font-size: 13.5px; background: var(--input-bg); font-family: inherit; color: var(--text);
}
.gallery-admin-strip__delete { margin-left: auto; }

/* Version-generation progress strip (Task 8) — sits between the admin strip and the tabs while
   pre-generated download versions are still being built. Reuses .dl-bar/.dl-bar__fill from the
   download-progress overlay for the fill bar itself. */
.variant-progress {
  display: flex; flex-direction: column; gap: 8px;
  margin-top: 12px; background: var(--panel); border: 1px dashed var(--line); border-radius: 10px;
  padding: 10px 14px;
}
.variant-progress--done { flex-direction: row; align-items: center; padding: 8px 14px; opacity: .75; }
.variant-progress__row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.variant-progress__label { font-size: 13px; color: var(--label); }
.variant-progress__failed { font-size: 13px; font-weight: 600; color: var(--danger); }

.btn--danger { border-color: var(--danger-line); color: var(--danger); background: var(--panel); }
.btn--danger:hover { background: var(--danger-bg); border-color: var(--danger); }

/* Right-aligned Cancel/confirm row for simple confirmation modals (Delete listing). */
.modal-footer { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; }

/* Manage-folders modal: reorderable rows (add/rename/reorder/remove all live here — the folder
   pills above the grid stay plain name+count, matching the prototype). */
.mf-rows { display: flex; flex-direction: column; gap: 6px; margin-top: 16px; }
.mf-row { display: flex; align-items: center; gap: 12px; background: var(--panel-2); border-radius: 9px; padding: 11px 14px; }
.mf-row__order { display: flex; flex-direction: column; gap: 1px; }
.mf-row__arrow { background: none; border: none; padding: 0; cursor: pointer; font-size: 9px; line-height: 11px; color: var(--muted); }
.mf-row__arrow:hover:not(:disabled) { color: var(--text); }
.mf-row__arrow:disabled { color: var(--line); cursor: default; }
.mf-row__icon {
  width: 24px; height: 24px; border-radius: 6px; background: var(--active-bg); color: var(--active-text);
  display: flex; align-items: center; justify-content: center; font-size: 12px; flex-shrink: 0;
}
.mf-row__name {
  flex: 1; min-width: 0; font-weight: 600; font-size: 14px; font-family: inherit; color: var(--text);
  background: none; border: none; border-bottom: 1px solid transparent; padding: 2px 0;
}
.mf-row__name:hover, .mf-row__name:focus { border-bottom-color: var(--line); outline: none; }
.mf-row__count { color: var(--muted); font-size: 13px; white-space: nowrap; }

.mf-section-label { font-size: 12px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); margin-top: 18px; }
.mf-presets { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.mf-preset {
  border: 1px solid var(--input-line); border-radius: 999px; padding: 7px 14px; font-size: 13.5px; font-weight: 600;
  color: var(--text); background: var(--input-bg); cursor: pointer; white-space: nowrap; font-family: inherit;
}
.mf-preset:hover { border-color: var(--brand); color: var(--brand); }
.mf-add-row { display: flex; gap: 8px; margin-top: 8px; }
.mf-add-row input { flex: 1; padding: 10px 14px; border: 1px solid var(--input-line); border-radius: 8px; font-size: 14px; font-family: inherit; }

/* "+ Add first folder" inside the empty-grid panel. */
.empty__action { margin-top: 14px; }

.upload-zone {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px;
  border: 2px dashed var(--line); border-radius: var(--radius); padding: 34px 18px;
  margin-bottom: 14px; text-align: center; cursor: pointer;
  transition: border-color .15s, background .15s;
}
.upload-zone:hover { border-color: var(--brand); background: var(--active-bg); }
.upload-zone--drag { border-color: var(--brand); background: var(--active-bg); }
.upload-zone--drag .upload-zone__prompt { color: var(--brand); }
.upload-zone__icon {
  width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center;
  background: var(--active-bg); color: var(--brand); font-size: 20px; font-weight: 700; line-height: 1;
}
.upload-zone__prompt { font-size: 14px; font-weight: 600; color: var(--text); }
.upload-zone__or { font-size: 12px; color: var(--muted); }
.upload-zone__hint { color: var(--muted); font-size: 12px; }

.upload-progress { display: flex; flex-direction: column; gap: 6px; margin-bottom: 18px; align-items: flex-start; }
.upload-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px; width: 100%;
  background: var(--panel); border: 1px solid var(--card-line); border-radius: var(--radius-sm);
  padding: 8px 12px; font-size: 12.5px;
}
.upload-row__name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; min-width: 0; }
.upload-row__status { color: var(--muted); flex-shrink: 0; }
.upload-row--done .upload-row__status { color: var(--ok); }
.upload-row--error { border-color: var(--warn-line); }
.upload-row--error .upload-row__status { color: var(--danger); }
/* Staged (waiting) + the "Upload N photos" prompt header. */
.upload-staged__head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px; width: 100%;
  font-size: 12.5px; font-weight: 600; margin-bottom: 2px;
}
.upload-row--staged { background: var(--panel-2); }
.upload-row__x {
  border: none; background: none; cursor: pointer; color: var(--muted); font-size: 16px; line-height: 1;
  padding: 0 2px; flex-shrink: 0; border-radius: 50%;
}
.upload-row__x:hover { color: var(--danger); }
.upload-progress__foot { width: 100%; }
/* Per-file upload progress bar. */
.upload-bar {
  flex: 1; min-width: 60px; height: 6px; background: var(--card-line); border-radius: 3px; overflow: hidden;
}
.upload-bar__fill { display: block; height: 100%; background: var(--brand); width: 0; transition: width .15s ease; }

/* Photo lightbox (click a thumbnail to view full size). */
.photo-tile__img { cursor: zoom-in; }
.lightbox {
  position: fixed; inset: 0; z-index: 200; display: flex; align-items: center; justify-content: center;
  background: rgba(15,13,11,.92); padding: 40px 64px;
}
.lightbox__img {
  max-width: 100%; max-height: calc(100vh - 96px); object-fit: contain; border-radius: 4px;
  box-shadow: 0 10px 40px rgba(0,0,0,.5); background: #222;
}
.lightbox__img.is-loading { min-width: 200px; min-height: 140px; opacity: .4; }
.lightbox__close {
  position: absolute; top: 16px; right: 20px; width: 40px; height: 40px; border-radius: 50%;
  border: none; background: rgba(255,255,255,.12); color: var(--on-solid); font-size: 26px; line-height: 1; cursor: pointer;
}
.lightbox__close:hover { background: rgba(255,255,255,.25); }
.lightbox__nav {
  position: absolute; top: 50%; transform: translateY(-50%); width: 48px; height: 72px;
  border: none; background: rgba(255,255,255,.1); color: var(--on-solid); font-size: 34px; line-height: 1; cursor: pointer;
  border-radius: 8px;
}
.lightbox__nav:hover { background: rgba(255,255,255,.22); }
.lightbox__nav--prev { left: 12px; }
.lightbox__nav--next { right: 12px; }
.lightbox__caption {
  position: absolute; bottom: 16px; left: 0; right: 0; text-align: center; color: rgba(255,255,255,.85);
  font-size: 12.5px;
}
@media (max-width: 640px) { .lightbox { padding: 24px 8px; } .lightbox__nav { width: 38px; height: 56px; } }

.photo-bulk-bar { display: flex; align-items: center; gap: 12px; font-size: 13px; color: var(--text); margin-bottom: 12px; }

.photo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 14px; margin-bottom: 32px; }
/* Client gallery uses larger tiles than the admin management grid. */
#view-gallery-view .photo-grid { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
.photo-tile { background: var(--panel-2); border: 1px solid var(--card-line); border-radius: var(--radius); overflow: hidden; cursor: grab; }
.photo-tile--selected { border-color: var(--brand); box-shadow: 0 0 0 2px var(--brand-a16); }
.photo-tile__img-wrap { position: relative; aspect-ratio: 4/3; background: var(--panel-2); }
.photo-tile__img-wrap img { width: 100%; height: 100%; object-fit: cover; display: block; }
.photo-tile__placeholder { width: 100%; height: 100%; background: linear-gradient(135deg, var(--panel-2), var(--line)); }
.photo-tile__cover-badge {
  position: absolute; bottom: 8px; left: 8px; font-size: 10.5px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--on-solid); background: var(--scrim-a72); padding: 3px 8px; border-radius: var(--radius-chip);
  z-index: 2;
}
.photo-tile__check {
  position: absolute; top: 8px; left: 8px; width: 22px; height: 22px; border-radius: 6px;
  background: rgba(255,255,255,.92); display: flex; align-items: center; justify-content: center; z-index: 2;
}
.photo-tile__check input { margin: 0; cursor: pointer; }
.photo-tile__remove {
  position: absolute; top: 8px; right: 8px; width: 22px; height: 22px; border-radius: 50%;
  background: var(--scrim-a72); color: var(--on-solid); border: none; cursor: pointer; font-size: 15px; line-height: 1;
  display: flex; align-items: center; justify-content: center; z-index: 2; padding: 0;
}
.photo-tile__remove:hover { background: var(--scrim-a90); }
.photo-tile__name {
  padding: 8px 10px 0; font-size: 11.5px; color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.photo-tile__actions { display: flex; flex-wrap: wrap; gap: 6px; padding: 6px 8px 8px; }
.photo-tile__actions .btn { flex: 1; min-width: 0; }
.photo-tile__actions select.link-select { flex: 1; min-width: 64px; }

.gallery-video { background: var(--panel); border: 1px solid var(--card-line); border-radius: var(--radius); padding: 20px 22px; margin-bottom: 32px; }
.gallery-video h3 { font-size: 13px; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); margin: 0 0 14px; font-weight: 600; }
.video-yt-form { display: flex; gap: 10px; margin-bottom: 14px; }
.video-yt-form input {
  flex: 1; padding: 10px 12px; border-radius: var(--radius-sm); border: 1px solid var(--input-line);
  background: var(--input-bg); color: var(--text); font-size: 13.5px; font-family: inherit;
}
.video-yt-form input:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-a16); }
.video-mp4 { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; font-size: 12.5px; color: var(--muted); }
.video-mp4__current { color: var(--text); }
.video-mp4__status { color: var(--active-text); }

/* ---------- Client gallery (read-only view) ---------- */
.gallery-video__actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* Read-only thumbnail tiles open the lightbox rather than supporting drag-reorder. */
.photo-tile--view { cursor: pointer; }
.photo-tile--view:hover .photo-tile__img-wrap img { opacity: .92; }
.photo-tile--view img { cursor: zoom-in; }

/* Branded footer at the bottom of the client gallery (renderFooter in gallery-view.js). */
.gallery-foot {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  margin-top: 48px; padding: 28px 0 8px; border-top: 1px solid var(--line); text-align: center;
}
.gallery-foot__logo { height: 28px; width: auto; opacity: .85; }
.gallery-foot__meta { color: var(--muted-2); font-size: 12px; }

.lightbox-overlay {
  position: fixed; inset: 0; z-index: 80; background: rgba(20,17,14,.92);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: 24px;
}
.lightbox__stage { max-width: min(92vw, 1400px); max-height: 78vh; display: flex; align-items: center; justify-content: center; }
.lightbox__img { max-width: 100%; max-height: 78vh; object-fit: contain; border-radius: 4px; display: block; }
.lightbox__loading { color: var(--on-solid); font-size: 13px; }
.lightbox__close {
  position: absolute; top: 18px; right: 22px; background: none; border: none; color: var(--on-solid);
  font-size: 30px; line-height: 1; cursor: pointer; opacity: .8; padding: 4px 8px;
}
.lightbox__close:hover { opacity: 1; }
.lightbox__nav {
  position: absolute; top: 50%; transform: translateY(-50%); background: rgba(255,255,255,.1);
  border: none; color: var(--on-solid); font-size: 34px; line-height: 1; width: 52px; height: 64px;
  cursor: pointer; border-radius: var(--radius-sm); display: flex; align-items: center; justify-content: center;
}
.lightbox__nav:hover { background: rgba(255,255,255,.2); }
.lightbox__nav--prev { left: 18px; }
.lightbox__nav--next { right: 18px; }
.lightbox__footer {
  margin-top: 16px; display: flex; align-items: center; gap: 16px; color: var(--on-solid); font-size: 12.5px;
}
.lightbox__counter { color: rgba(255,255,255,.75); }

/* ---------- Download version menu (Task 9 — header "Download all" + lightbox per-photo).
   Scoped under #view-gallery-view and the share page's #share-root: the class names are new (no admin-gallery collision), but
   scoping keeps this consistent with the rest of the client-gallery redesign. ---------- */
#view-gallery-view .dlmenu,
#share-root .dlmenu { position: relative; display: inline-flex; }
#view-gallery-view .dlmenu__trigger,
#share-root .dlmenu__trigger { white-space: nowrap; }
#view-gallery-view .dlmenu__list,
#share-root .dlmenu__list {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 90; width: 224px;
  background: var(--panel); color: var(--text); border-radius: 12px;
  box-shadow: 0 18px 44px -10px rgba(0,0,0,.45); padding: 6px;
  display: flex; flex-direction: column; gap: 1px;
}
/* The class rule above sets display:flex, which outranks the UA [hidden]{display:none};
   restore hiding for the closed state (the JS toggles the hidden attribute). */
#view-gallery-view .dlmenu__list[hidden],
#share-root .dlmenu__list[hidden] { display: none; }
/* The lightbox footer sits near the bottom of the viewport — open its menu upward instead. */
#view-gallery-view .lightbox__footer .dlmenu__list,
#share-root .lightbox__footer .dlmenu__list { top: auto; bottom: calc(100% + 8px); }
#view-gallery-view .dlmenu__opt,
#share-root .dlmenu__opt {
  display: flex; align-items: center; gap: 8px; width: 100%; cursor: pointer;
  font: inherit; font-size: 13px; color: var(--text); padding: 9px 12px; border-radius: 7px;
  white-space: nowrap;
}
#view-gallery-view .dlmenu__opt:hover:not(.dlmenu__opt--disabled),
#share-root .dlmenu__opt:hover:not(.dlmenu__opt--disabled) { background: var(--brand-a06); }
#view-gallery-view .dlmenu__opt--disabled,
#share-root .dlmenu__opt--disabled { color: var(--muted-2); cursor: not-allowed; }
#view-gallery-view .dlmenu__cb,
#share-root .dlmenu__cb { margin: 0; flex: none; cursor: inherit; }
#view-gallery-view .dlmenu__go,
#share-root .dlmenu__go {
  margin-top: 7px; width: 100%; justify-content: center; text-align: center;
}
#view-gallery-view .dlmenu__go:disabled,
#share-root .dlmenu__go:disabled { opacity: .5; cursor: not-allowed; }
/* The share page's "Preparing photos: 3 of 12" load bar under the header; takes no room when
   empty. A panel with dark bold text and a thick bar (Dave, 2026-11-04): the old grey 13px line
   was easy to miss through minutes of preparing. */
.share-dl-status {
  margin: -4px 0 18px; padding: 14px 16px; border-radius: 12px;
  background: var(--panel); border: 1px solid var(--line);
}
.share-dl-status:empty { display: none; }
.share-dl-status__text {
  margin: 0 0 10px; font-size: 15px; font-weight: 600; color: var(--text);
  font-variant-numeric: tabular-nums;
}
.share-dl-status__bar { height: 10px; background: var(--line); border-radius: 999px; overflow: hidden; }
.share-dl-status__fill { height: 100%; background: var(--brand); border-radius: 999px; transition: width .25s ease; }

/* Download-with-progress overlay (mounted on <body>, so unscoped). */
.dl-overlay { position: fixed; inset: 0; z-index: 210; display: flex; align-items: center; justify-content: center; background: rgba(15,13,11,.55); }
.dl-card { background: var(--panel); color: var(--text); border-radius: 14px; padding: 22px 24px; width: 360px; max-width: calc(100vw - 40px); box-shadow: 0 24px 60px -12px rgba(0,0,0,.5); text-align: center; }
.dl-card__title { font-weight: 700; font-size: 15px; }
.dl-card__file { font-size: 12.5px; color: var(--muted); margin: 4px 0 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dl-card__phase { font-size: 13px; font-weight: 600; color: var(--text); margin: 0 0 12px; }
.dl-bar { height: 8px; background: var(--line); border-radius: 999px; overflow: hidden; }
.dl-bar__fill { height: 100%; background: var(--brand); border-radius: 999px; transition: width .2s ease; }
.dl-card__meta { font-size: 12.5px; color: var(--muted); margin-top: 10px; font-variant-numeric: tabular-nums; }
.dl-card__cancel { margin-top: 14px; }

/* ---------- Shared links panel (Task G6 — admin gallery view + agent "Share" action) ---------- */
.share-links-list { display: flex; flex-direction: column; gap: 10px; margin-bottom: 22px; }
/* Wraps so the Email form can drop onto its own full-width line under the link it belongs to,
   rather than squeezing in beside the buttons. */
.share-link-row {
  display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap;
  border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 12px 14px; background: var(--panel-2);
}
.share-link-row__main { min-width: 0; flex: 1; }
.share-link-row__url {
  font-family: var(--font-mono, monospace); font-size: 12.5px; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-bottom: 6px;
}
.share-link-row__meta { display: flex; flex-wrap: wrap; gap: 10px; font-size: 12px; color: var(--muted); align-items: center; }
.share-link-row__actions { display: flex; gap: 8px; flex-shrink: 0; }
.share-link-create { border-top: 1px solid var(--line); padding-top: 18px; }

/* Email-this-link form, opened under one row at a time. */
.share-email { flex-basis: 100%; border-top: 1px solid var(--line); margin-top: 12px; padding-top: 14px; }
.share-email .field { margin-bottom: 12px; }
.share-email__to { display: block; width: 100%; margin-bottom: 6px; }
.share-email__hint { font-size: 12px; color: var(--muted); margin: 2px 0 0; }
.share-email textarea { width: 100%; resize: vertical; }
.share-email__actions { display: flex; gap: 8px; align-items: center; }
.share-email__notice {
  font-size: 13px; color: var(--ok); background: var(--ok-bg); border: 1px solid var(--ok-line);
  border-radius: var(--radius-sm); padding: 9px 12px; margin-bottom: 14px;
}

/* ---------- Public share page (Task G6 — no app shell, no sidebar) ---------- */
.share-shell { min-height: 100vh; display: flex; flex-direction: column; align-items: center; padding: 32px 20px 64px; }
/* Height is reserved up front: the mark is only chosen once the gallery data says which agency
   this listing belongs to, and without a reserved box everything below would jump when it lands. */
.share-shell__brand { margin-bottom: 28px; min-height: 52px; display: flex; align-items: center; }
/* Agency logos vary wildly in aspect ratio, so cap both dimensions rather than fixing height:
   a wide horizontal lockup at height:40px would run off the page. */
.share-shell__brand img { max-height: 52px; max-width: 260px; width: auto; object-fit: contain; }
.share-shell__main { width: 100%; max-width: 1100px; }
.share-shell__foot { margin-top: 40px; font-size: 12px; color: var(--muted-2); }
.share-notice { max-width: 460px; margin: 72px auto 0; text-align: center; }
.share-notice h1 { font-size: 20px; margin: 0 0 10px; letter-spacing: -.01em; }

/* ---------- Bookings (week-view calendar, Task B6) ---------- */
.cal-nav { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 22px 0 14px; }
.cal-nav__label { font-size: 14px; font-weight: 600; margin-left: 6px; }

.cal-legend { display: flex; align-items: center; gap: 8px 18px; flex-wrap: wrap; margin-bottom: 18px; font-size: 12.5px; color: var(--text); }
.cal-legend__item { position: relative; display: inline-flex; align-items: center; gap: 7px; }

/* A photographer's calendar-colour chip. Used in the legend (clickable, to recolour) and the
   booking modal's crew list. Without this rule the span had no size and was invisible. */
.color-dot {
  display: inline-block; width: 13px; height: 13px; border-radius: 3px; flex: none;
  border: 1.5px solid rgba(0,0,0,.08); box-sizing: border-box; vertical-align: middle;
}
.color-dot--btn { padding: 0; cursor: pointer; }
.color-dot--btn:hover { box-shadow: 0 0 0 2px var(--brand-a40); }

/* Colour-picker popover anchored under a legend chip. */
.cal-legend__pop {
  position: absolute; top: 24px; left: 0; z-index: 20; width: 186px;
  display: flex; flex-wrap: wrap; gap: 7px; padding: 11px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 10px; box-shadow: 0 12px 32px rgba(0,0,0,.16);
}
.cal-legend__swatch {
  width: 24px; height: 24px; border-radius: 50%; padding: 0; cursor: pointer;
  border: 2px solid var(--panel); box-shadow: 0 0 0 1px var(--line); box-sizing: border-box;
}
.cal-legend__swatch--selected { border: 3px solid var(--dark); }

.cal-grid-wrap { overflow-x: auto; padding-bottom: 8px; }
.cal-grid {
  display: grid; grid-template-columns: 52px 1fr; grid-template-rows: auto 1fr;
  min-width: 760px; background: var(--panel); border: 1px solid var(--card-line); border-radius: var(--radius);
  /* The whole day is scaled (PX_PER_MIN in booking.js) to fit the viewport — no inner VERTICAL
     scroll, hence overflow-y: hidden, which also keeps cards inside the rounded corners.
     Sideways is a different question. This was `overflow: hidden` on both axes, which swallowed
     the overflow before .cal-grid-wrap's own overflow-x: auto could scroll it: measured with 9
     photographers, the grid's box sat at 760px while its columns wanted 1021px, and the wrapper
     would only scroll to 760. The last day was not off to the right waiting to be scrolled to,
     it was clipped and unreachable. */
  overflow-x: auto; overflow-y: hidden;
}
.cal-grid__corner {
  grid-column: 1; grid-row: 1; border-bottom: 1px solid var(--card-line);
  position: sticky; top: 0; z-index: 4; background: var(--panel);
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
}
/* Up/down arrows that pan the visible window to the hours cropped off the top/bottom. */
.cal-vscroll {
  border: 1px solid var(--card-line); background: var(--panel-2); color: var(--muted);
  width: 24px; height: 20px; border-radius: 5px; cursor: pointer; padding: 0; line-height: 1;
  font-size: 9px; display: inline-flex; align-items: center; justify-content: center;
}
.cal-vscroll:hover:not(:disabled) { background: var(--brand-strong); color: var(--brand-ink); border-color: var(--brand-strong); }
.cal-vscroll:disabled { opacity: .32; cursor: default; }
.cal-grid__headers {
  grid-column: 2; grid-row: 1; display: grid; grid-template-columns: repeat(var(--day-count, 7), 1fr);
  border-bottom: 1px solid var(--card-line);
  position: sticky; top: 0; z-index: 3; background: var(--panel);
}
.cal-day-head {
  padding: 10px 8px; text-align: center; font-size: 12.5px; color: var(--muted);
  border-left: 1px solid var(--card-line);
}
.cal-day-head:first-child { border-left: none; }
.cal-day-head__dow { font-weight: 600; color: var(--text); }
/* Sunrise / sunset times under the date (Melbourne). Two compact items, wraps if narrow. */
.cal-day-head__sun {
  display: flex; justify-content: center; flex-wrap: wrap; gap: 4px 10px; margin-top: 3px;
  font-size: 10px; font-weight: 600; color: var(--muted); letter-spacing: 0;
}
.cal-day-head__sun .sun-ev { white-space: nowrap; }
.cal-day-head--today .cal-day-head__sun { color: var(--active-text); opacity: .85; }
.cal-day-head:nth-child(even):not(.cal-day-head--today) { background-color: var(--scrim-a05); }
.cal-day-head--today { background: var(--active-bg); color: var(--active-text); }
.cal-day-head--today .cal-day-head__dow { color: var(--active-text); }

.cal-grid__time-col { grid-column: 1; grid-row: 2; position: relative; }
.cal-hour-label {
  position: absolute; right: 8px; transform: translateY(-50%);
  font-size: 10.5px; color: var(--muted-2); white-space: nowrap;
}
.cal-grid__body { grid-column: 2; grid-row: 2; display: grid; grid-template-columns: repeat(var(--day-count, 7), 1fr); position: relative; }

.cal-day-col {
  position: relative; border-left: 1px solid var(--card-line); cursor: pointer;
  /* Two layers: a faint line every 15 min (--slot-px) for fine snapping, and a stronger
     line every hour (--hour-px) for orientation. The hour layer paints on top. */
  background-image:
    repeating-linear-gradient(to bottom, var(--line) 0, var(--line) 1px, transparent 1px, transparent var(--hour-px, 60px)),
    repeating-linear-gradient(to bottom, var(--scrim-a05) 0, var(--scrim-a05) 1px, transparent 1px, transparent var(--slot-px, 15px));
  /* The column's top edge is viewStartMin, not midnight, so the patterns above repeat from
     whatever minute the window happens to start on. --hour-offset-px / --slot-offset-px (set
     inline per render, in grid.js) shift each layer down to the next real hour / 15-min
     boundary. Order matches background-image: hour layer first, slot layer second. */
  background-position: 0 var(--hour-offset-px, 0px), 0 var(--slot-offset-px, 0px);
  /* Let pointerdown/move drive vertical drag-to-create instead of the browser's default
     touch scroll, but keep horizontal panning available (the grid's .cal-grid-wrap
     scrolls horizontally on narrow viewports — see min-width:760px on .cal-grid). */
  touch-action: pan-x;
}
.cal-day-col:first-child { border-left: none; }
.cal-day-col:nth-child(even):not(.cal-day-col--today) { background-color: var(--scrim-a05); }
.cal-day-col--today { background-color: var(--brand-a06); }
.cal-day-col:hover:not(.cal-day-col--today) { background-color: var(--brand-a06); }

/* Click-and-drag "create a booking" ghost selection (see wireReadyView's pointer handlers
   on .cal-day-col in booking.js). Sits above the hour gridlines/time-off stripes but is
   purely visual — pointer-events:none so it never itself becomes a drag/click target. */
/* Matches .cal-card's box exactly, gutter included — what you draw has to be what you get. */
.cal-drag-ghost {
  position: absolute; left: 3px; right: var(--cal-gutter); z-index: 6; pointer-events: none;
  border: 2px dashed var(--brand); background: var(--brand-a16); border-radius: var(--radius-sm);
  padding: 4px 8px; font-size: 11px; font-weight: 700; color: var(--active-text); overflow: hidden;
}

.cal-card {
  position: absolute; left: 3px; right: var(--cal-gutter); border-radius: 6px;
  padding: 4px 8px; font-size: 11px; line-height: 1.35; overflow: hidden; cursor: pointer;
  box-shadow: 0 1px 4px rgba(0,0,0,.18);
  /* Filled with the photographer's colour (set inline) — white text with a soft shadow so it
     stays legible on lighter calendar colours (e.g. the ambers). */
  color: var(--on-solid); text-shadow: 0 1px 1px rgba(0,0,0,.28);
}

/* Resize handles at the top/bottom edge of a booking card (see the resize pointer-drag
   wiring in wireReadyView). Invisible by default; a faint grab bar appears on card hover
   so the affordance doesn't clutter the calendar at rest. Sit below .cal-card__x (z-index
   4) so the unschedule button stays clickable even though the top handle spans the card's
   full width underneath it. */
.cal-card__resize { position: absolute; left: 0; right: 0; height: 7px; cursor: ns-resize; z-index: 3; touch-action: none; }
.cal-card__resize--top { top: 0; }
.cal-card__resize--bottom { bottom: 0; }
.cal-card__resize::after {
  content: ""; position: absolute; left: 50%; top: 50%; width: 22px; height: 2px;
  transform: translate(-50%, -50%); border-radius: 1px; background: transparent; transition: background-color .12s ease;
}
.cal-card:hover .cal-card__resize::after { background: var(--scrim-a30); }
.cal-card__crew { font-weight: 700; font-size: 10.5px; letter-spacing: .02em; }
.cal-card__addr { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* An unnamed job's headline is its address, so it carries the map pin (see headline in grid.js). */
.cal-card__addr--pinned { display: flex; align-items: center; gap: 4px; }
/* Full white, same reason as .cal-card__property below: the fill under it is mixed to carry
   white at AA and no further, so .88 landed at 3.87:1. */
.cal-card__service { color: var(--on-solid); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The property address under a job-named booking, with its map pin. The card is a crew-coloured
   gradient in both themes, so the white alphas here are correct and must not follow the palette —
   same family as .cal-card__service above. min-width:0 on the text is what lets the ellipsis work
   inside a flex row; without it the address pushes the pin off the card instead of truncating. */
/* FULL white, not an alpha. The card fill is now mixed to carry white text at AA and no further
   (C.readableFill), so an ink drawn at .82 over it landed at 3.58:1 — and this line is the
   address, which is the thing a driving day gets read off. Hierarchy against the time above it
   comes from size and weight instead, which costs no contrast.
   tests/ui/calendar-crew-ink.test.ts reads this alpha back out and re-checks it against every
   crew colour, so dimming it again fails rather than quietly going grey. */
.cal-card__property {
  display: flex; align-items: center; gap: 4px;
  color: var(--on-solid); font-size: 10.5px; line-height: 1.25;
}
.cal-card__property-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
/* The suburb after the street, on the grid card and the tray card alike (C.addressHtml). Dave:
   "i really need to be able to see the suburb at all times". Flex shrink is weighted by size
   times factor, so the street's 1000 against the suburb's 1 means the STREET clips first,
   "13 Albert…, Beaufort", and the suburb only gives way once the street is gone. */
.cal-card__property-text, .tray-card__addr-text { flex-shrink: 1000; }
.cal-card__suburb { flex-shrink: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cal-card__map {
  flex-shrink: 0; text-decoration: none; font-size: 11px; line-height: 1; cursor: pointer;
  /* No opacity dimming. The pin sits on a fill mixed for full-strength ink, and .85 put this
     control at 3.72:1. The hover state below still carries the affordance with a background. */
  padding: 1px 2px; border-radius: 3px;
}
.cal-card__map:hover, .cal-card__map:focus-visible { opacity: 1; background: rgba(255,255,255,.18); }
.cal-card__avatar { border: 1.5px solid rgba(255,255,255,.9); box-sizing: border-box; }
.cal-card__crew-row--empty { color: var(--on-solid); }
.cal-card { cursor: grab; }
.cal-card--dragging { opacity: .4; }

/* Time-off = a thin hatched sliver (renderTimeOffSliver) down the day column's left edge, in
   the photographer's colour (set inline). Lane offset (left) is inline; overlapping slivers
   stack side by side. Resizable via the reused .cal-card__resize handles (no visible grab bar
   at this width) and click-to-edit; the booking cards are inset to its right. */
.cal-timeoff {
  position: absolute; width: 8px; border-radius: 4px; z-index: 5; cursor: pointer;
  box-shadow: 0 0 0 1px rgba(255,255,255,.7);
}
.cal-timeoff:hover { box-shadow: 0 0 0 1px var(--scrim-a40); }
.cal-timeoff .cal-card__resize::after { display: none; }

/* Cards layer: holds the booking cards, inset from the left (inline `left`) so the
   time-off slivers have their own gutter. pointer-events:none lets clicks/drags on empty
   space fall through to the day column (drag-to-create); the cards re-enable it. */
.cal-col-cards { position: absolute; top: 0; bottom: 0; right: 0; pointer-events: none; }
.cal-col-cards .cal-card { pointer-events: auto; }

/* Read-only busy block mirroring one of the owner's other Outlook events (renderBusyBlock in
   booking.js). Muted, hatched grey to read as "not a booking"; sits below booking cards and
   is pointer-events:none so it never intercepts a click/drag on the day column beneath it. */
.cal-busy {
  position: absolute; left: 3px; right: 3px; z-index: 1; pointer-events: none;
  border-radius: 6px; border-left: 4px solid var(--line); padding: 3px 7px;
  font-size: 10.5px; line-height: 1.3; overflow: hidden; color: var(--muted);
  background: repeating-linear-gradient(45deg, var(--scrim-a16) 0 5px, var(--scrim-a05) 5px 10px);
}
.cal-busy__subject { font-weight: 600; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cal-busy__time { color: var(--muted); }
.cal-day-col--dragover { background-color: var(--brand-a12); outline: 2px dashed var(--brand); outline-offset: -2px; }

/* Current-time line (repositionNowLine in grid.js): a thin marker across TODAY's column at
   the current local time. z-index 7 — one above .cal-drag-ghost (6), the highest value already
   in use inside a day column — so the line reads on top of everything else there (gridlines,
   time-off slivers at 5, cards, even an in-progress drag-to-create ghost), which is the whole
   point of a "how far through the day are we" marker. pointer-events:none (also set inline in
   grid.js, since this is load-bearing rather than merely visual) so it can never itself become
   a target for drag-to-create/move or resize-by-drag underneath it. */
/* The time under the cursor while hovering a day column. pointer-events:none throughout — it sits
   over the grid the drag-to-create handlers are wired to, and must never become the target. Below
   .cal-drag-ghost (z-index 6) so an in-progress selection stays on top. */
.cal-hover-time {
  position: absolute; left: 0; right: 0; height: 0; z-index: 5; pointer-events: none;
  border-top: 1px dashed var(--brand-a40);
}
/* Pinned RIGHT, over the click strip. On the left it sat across the cards and fought the card's
   own time range for attention; the strip is empty by construction, so the chip has it to itself
   and lands where the cursor already is when you go to drag a new job. */
.cal-hover-time__chip {
  position: absolute; right: 4px; left: auto; top: 0; transform: translateY(-50%);
  background: var(--brand-strong); color: var(--brand-ink);
  font-size: 10px; font-weight: 700; line-height: 1; letter-spacing: .02em;
  padding: 3px 6px; border-radius: var(--radius-chip); white-space: nowrap;
  box-shadow: 0 1px 3px var(--scrim-a30);
}

.cal-now-line {
  position: absolute; left: 0; right: 0; height: 2px; background: var(--brand);
  z-index: 7; pointer-events: none;
}
/* The knob at the left edge, so the line reads as a time marker rather than a stray gridline. */
.cal-now-line::before {
  content: ""; position: absolute; left: 0; top: 50%; width: 8px; height: 8px;
  border-radius: 50%; background: var(--brand); transform: translate(-50%, -50%);
  box-shadow: 0 0 0 1.5px rgba(255,255,255,.85);
}

/* Time-off side-stripe: a thin colored bar along the day column's edge, tinted by the
   photographer's calendar color. Multiple photographers off the same day stack side by
   side (see the `right` offset computed per-instance in booking.js), leaving booking
   cards (inset from the column edges) fully visible. */
/* Time-off = a semi-transparent diagonally-striped bar (the background hatch is built
   per-photographer in renderStripe from their calendar colour). */
.cal-stripe { position: absolute; width: 11px; border-radius: 3px; cursor: pointer; z-index: 1; }
.cal-stripe:hover { box-shadow: 0 0 0 1px var(--scrim-a25); }
/* Crew Outlook free/busy stripe (renderCrewBusyStripe): same striped look as a time-off stripe,
   but read-only — it mirrors a photographer's own Outlook unavailability, not an editable rule. */
.cal-stripe--busy { cursor: default; }

/* ---------- Pending tray (Task B7) ---------- */
.cal-layout { display: flex; align-items: flex-start; gap: 18px; flex-wrap: wrap; }
.cal-layout .cal-grid-wrap { flex: 1 1 600px; min-width: 0; }

.tray-panel {
  flex: 0 0 260px; width: 260px; background: var(--panel); border: 1px solid var(--card-line);
  border-radius: var(--radius); padding: 16px; transition: background-color .12s ease;
}
/* The whole rail lights up while a scheduled booking is dragged over it (drop = unschedule). */
.tray-panel--dragover { background-color: var(--brand-a10); outline: 2px dashed var(--brand); outline-offset: -3px; }
.tray-panel__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px; }
.tray-panel__head h2 { font-size: 14px; font-weight: 600; margin: 0; letter-spacing: -.005em; }
/* Collapse toggle (Task: collapsible bookings panels), desktop-only — tray.js's renderTray only
   emits #btn-tray-toggle when !C.view.isPhone, so below 820px this never renders at all rather
   than needing a media query to hide it here. */
.tray-panel__toggle {
  border: none; background: none; cursor: pointer; font-family: inherit; font-size: 16px;
  color: var(--muted); padding: 2px 6px; line-height: 1; border-radius: var(--radius-sm); flex-shrink: 0;
}
.tray-panel__toggle:hover { color: var(--text); background: var(--panel-2); }

.tray-list {
  display: flex; flex-direction: column; gap: 8px; margin-top: 12px; min-height: 60px;
  border-radius: var(--radius-sm); transition: background-color .12s ease;
}
.tray-list--dragover { background-color: var(--brand-a10); outline: 2px dashed var(--brand); outline-offset: -2px; }
.tray-empty { margin: 0; }

.tray-card {
  position: relative; border: 1px solid var(--card-line); border-radius: var(--radius-sm);
  background: var(--panel-2); padding: 8px 10px; font-size: 11.5px; line-height: 1.4;
  cursor: grab; color: var(--text);
}
.tray-card--dragging { opacity: .4; }
.tray-card--agent { border-style: dashed; border-color: var(--warn-line); background: var(--warn-bg); }
.tray-card__badge {
  display: inline-block; font-size: 10px; font-weight: 600; letter-spacing: .02em; text-transform: uppercase;
  color: var(--warn); margin-bottom: 4px;
}
/* A flex row now, so the map pin sits beside the address and the address alone truncates.
   min-width:0 is what lets the ellipsis happen inside the flex row. */
.tray-card__addr { font-weight: 600; display: flex; align-items: center; gap: 4px; }
.tray-card__addr-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
/* The tray card is a PANEL surface, not a crew-coloured gradient like the grid card, so its pin
   takes a themed hover rather than the white alpha .cal-card__map uses. */
.tray-card__map:hover, .tray-card__map:focus-visible { background: var(--brand-a12); }
.tray-card__service { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tray-card__crew { color: var(--muted-2); margin-top: 2px; }

/* The stacking rules that used to live here at 980px were dead: `.cal-layout { flex-direction:
   row }` is declared later in this file at top level, so it won at every width and the pending
   rail sat beside the grid even on a 400px phone. That is what squeezed the calendar into a
   ~200px strip. The live rules are now in the 820px block at the end of the file, where source
   order puts them after the row declaration. Keep them there. */

/* ---------- Booking modal ---------- */
/* Wide enough for two columns, capped so a field label never drifts far from its input on a big
   screen. min() rather than a bare max-width so a narrow window shrinks it instead of overflowing.
   The constraint that actually varies across 1080p -> 4K is HEIGHT, not width: 1080p leaves about
   950px of usable window, and stacked this form ran ~1240px. Two columns is what fixes that. */
.modal--wide { max-width: min(1180px, 94vw); }

.bk-cols { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 24px; align-items: start; }
.bk-col { min-width: 0; }

/* Below this the two columns are narrower than the crew grid and the date row want, so fall back
   to the single column this form had before — a laptop gets today's layout, not two cramped ones. */
@media (max-width: 900px) {
  .bk-cols { grid-template-columns: 1fr; gap: 0; }
}
.field-row { display: flex; gap: 14px; }
.field-row .field { flex: 1; min-width: 0; }

.bk-crew-list { display: flex; flex-direction: column; gap: 8px; }
.bk-crew-check { gap: 8px; }
/* Name on its own line, chips beneath it. They were side by side, which could not work: in
   .modal--wide the crew list is a two-column grid, and four chips ("Floorplan" alone is ~58px)
   plus a name never fit one cell. The chips were flex: 0 0 auto so they refused to shrink, so the
   name was squeezed and the chips overflowed the cell — names and chips visibly overlapping.
   Stacking cannot overflow regardless of how long a name is or how narrow the column gets. */
.bk-crew-row { display: flex; flex-direction: column; align-items: stretch; gap: 3px; }
.bk-crew-row .bk-crew-check { min-width: 0; }
/* Name on the left, invite status on the right of the same line. The status only exists for crew
   already on the saved booking, so most rows are this wrapper with a single child and look exactly
   as they did before it was added. */
.bk-crew-head { display: flex; align-items: center; gap: 8px; min-width: 0; }
.bk-crew-head .bk-crew-check { flex: 1 1 auto; }
/* The same five states the calendar card shows as a ring, in words, because this is where a
   decline gets acted on. Styled after .cal-card__flag / .tray-card__flag. */
.bk-crew-rsvp {
  flex: 0 0 auto; padding: 1px 7px; border-radius: 4px; font-size: 11px; line-height: 1.6;
  letter-spacing: .02em; white-space: nowrap; background: var(--neutral-bg); color: var(--muted);
}
.bk-crew-rsvp--accepted { background: var(--ok-bg); color: var(--ok); }
.bk-crew-rsvp--declined { background: var(--danger-bg); color: var(--danger); }
.bk-crew-rsvp--tentative { background: var(--warn-bg); color: var(--warn); }
.bk-crew-rsvp--uninvited { background: var(--neutral-bg); color: var(--muted); }

/* Per-crew role toggle chips — a compact version of .pf-role-check's pill style (see
   public/photographers.js's roleChipsHtml). `flex-wrap` is the backstop: if a column is ever
   narrower than four chips, they wrap to a second line rather than escaping the cell. */
.bk-role-chips { display: flex; flex-wrap: wrap; gap: 4px; padding-left: 24px; }
.bk-role-chip {
  font-size: 10.5px; font-weight: 600; line-height: 1; padding: 4px 7px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--panel-2); color: var(--muted); cursor: pointer;
  transition: border-color .15s, background .15s, color .15s;
}
.bk-role-chip:hover:not(:disabled) { border-color: var(--muted); }
.bk-role-chip.is-on { color: var(--active-text); border-color: var(--active-line); background: var(--active-bg); }
.bk-role-chip:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.bk-role-chip:disabled { opacity: .45; cursor: not-allowed; }

/* Duration as hours + minutes (bk-dur-h / bk-dur-m). */
.bk-dur { display: flex; align-items: center; gap: 6px; }
.bk-dur input { width: 62px; text-align: center; }
.bk-dur__u { color: var(--muted); font-size: 13px; margin-right: 4px; }

/* Compact the create/edit booking + time-off window and pin Save to the bottom, so the action
   bar is always reachable without scrolling the (often long) form. Scoped to .modal--wide. */
.modal--wide { padding: 22px 24px 0; }
.modal--wide .field { margin-bottom: 11px; }
.modal--wide .create-toggle { margin-bottom: 12px; }
.modal--wide .modal__lede { margin-bottom: 12px; }
.modal--wide .bk-crew-list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px 16px; }
.modal--wide .bk-invite-field { margin-bottom: 8px; }
.modal--wide .bk-invite-field .field-hint { margin-top: 2px; }
.modal--wide .bk-cc-groups { max-height: 168px; }
.modal--wide textarea { min-height: 46px; }
.modal--wide .field-hint { margin-top: 4px; }
.modal--wide .sec-actions {
  position: sticky; bottom: 0; margin: 10px -24px 0; padding: 12px 24px;
  background: var(--panel); border-top: 1px solid var(--line); z-index: 4;
}

.bk-listing-search { margin-top: 10px; position: relative; }
.bk-listing-search input {
  width: 100%; padding: 9px 12px; border-radius: var(--radius-sm); border: 1px solid var(--input-line);
  background: var(--input-bg); color: var(--text); font-size: 13px; font-family: inherit;
}
.bk-listing-search input:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-a16); }
.bk-listing-results {
  display: flex; flex-direction: column; gap: 4px; margin-top: 6px; max-height: 160px; overflow-y: auto;
}
.bk-listing-result {
  text-align: left; font: inherit; font-size: 12.5px; padding: 7px 10px; border-radius: var(--radius-sm);
  border: 1px solid var(--line); background: var(--panel-2); color: var(--text); cursor: pointer;
}
.bk-listing-result:hover { border-color: var(--btn-hover-line); }
.bk-listing-result--empty { color: var(--muted); cursor: default; }
.bk-listing-result--empty:hover { border-color: var(--line); }

/* CC-agents picker: search box + agents grouped by agency into collapsible tabs. */
.bk-cc { display: flex; flex-direction: column; gap: 8px; }
.bk-cc-search {
  width: 100%; padding: 9px 12px; border-radius: var(--radius-sm); border: 1px solid var(--input-line);
  background: var(--input-bg); color: var(--text); font-size: 13px; font-family: inherit;
}
.bk-cc-search:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-a16); }
.bk-cc-groups { display: flex; flex-direction: column; gap: 6px; max-height: 260px; overflow-y: auto; }
.bk-cc-group { border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel-2); }
.bk-cc-group__head {
  display: flex; align-items: center; gap: 8px; width: 100%; text-align: left; cursor: pointer;
  background: none; border: none; font: inherit; color: var(--text); padding: 9px 12px; border-radius: var(--radius-sm);
}
.bk-cc-group__head:hover { color: var(--brand); }
.bk-cc-group__caret { font-size: 10px; color: var(--muted); transition: transform .12s ease; }
.bk-cc-group--collapsed .bk-cc-group__caret { transform: rotate(0deg); }
.bk-cc-group:not(.bk-cc-group--collapsed) .bk-cc-group__caret { transform: rotate(90deg); }
.bk-cc-group__name { flex: 1; min-width: 0; font-size: 13px; font-weight: 600; }
.bk-cc-group__count { flex-shrink: 0; font-size: 11.5px; color: var(--muted); }
.bk-cc-group__body { display: flex; flex-direction: column; gap: 8px; padding: 4px 12px 12px 26px; }
.bk-cc-group--collapsed .bk-cc-group__body { display: none; }
.bk-cc-empty { font-size: 12.5px; color: var(--muted); margin: 2px 0 0; }

/* Package picker: type-to-filter search + results, or the chosen-package chip. */
.bk-pkg-search {
  width: 100%; padding: 9px 12px; border-radius: var(--radius-sm); border: 1px solid var(--input-line);
  background: var(--input-bg); color: var(--text); font-size: 13px; font-family: inherit;
}
.bk-pkg-search:disabled { background: var(--panel-2); color: var(--muted); cursor: not-allowed; }
.bk-pkg-search:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-a16); }
.bk-pkg-search-wrap { position: relative; }
.bk-pkg-results { display: flex; flex-direction: column; gap: 4px; margin-top: 6px; max-height: 200px; overflow-y: auto; }
.bk-pkg-result {
  display: flex; align-items: center; justify-content: space-between; gap: 12px; text-align: left;
  font: inherit; font-size: 12.5px; padding: 8px 11px; border-radius: var(--radius-sm);
  border: 1px solid var(--line); background: var(--panel-2); color: var(--text); cursor: pointer;
}
.bk-pkg-result:hover { border-color: var(--brand); }
.bk-pkg-result__name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bk-pkg-result__price { flex-shrink: 0; font-variant-numeric: tabular-nums; color: var(--muted); }
/* Agent search rows: the agent name leads and takes priority; the (optional) agency is muted and
   truncates first, so the name is never crushed the way __price (flex-shrink:0) did. */
.bk-agent-result__name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; }
.bk-agent-result__agency { flex: 0 1 auto; min-width: 0; max-width: 45%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); font-size: 11.5px; }
.bk-pkg-result--empty { color: var(--muted); cursor: default; }
.bk-pkg-result--empty:hover { border-color: var(--line); }
.bk-pkg-chosen {
  display: flex; align-items: center; gap: 12px; padding: 9px 12px; border-radius: var(--radius-sm);
  border: 1px solid var(--line); background: var(--panel-2);
}
.bk-pkg-chosen__name { flex: 1; min-width: 0; font-size: 13px; font-weight: 600; }

/* Multi-select package chips + running total. */
.bk-pkg-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.bk-pkg-chip {
  display: inline-flex; align-items: center; gap: 8px; padding: 5px 6px 5px 10px;
  border: 1px solid var(--card-line); background: var(--panel-2); border-radius: 999px; font-size: 12.5px;
}
.bk-pkg-chip__name { font-weight: 600; }
.bk-pkg-chip__price { color: var(--muted); }
.bk-pkg-chip__x {
  border: none; background: none; cursor: pointer; color: var(--muted); font-size: 15px; line-height: 1;
  padding: 0 2px; border-radius: 50%;
}
.bk-pkg-chip__x:hover { color: var(--danger); }
.bk-pkg-total { font-size: 12.5px; font-weight: 700; margin-bottom: 8px; }
/* Free for the customer on purpose. Sits next to the money because that is the question it
   answers, and stays visible with no packages chosen — a free job usually has none. */
/* The booking's flag switches (No charge, Tentative), which sit inside #bk-package-field — a
   `.field`. That matters: `.field label` in styles.css is display:block at specificity (0,1,1),
   which BEAT the old bare `.bk-no-charge` (0,1,0) and flattened these labels, so the checkbox lost
   its vertical centring and the 6px gap. Dave, 2026-08-21: "these arent centred and look bad."
   Qualifying with `.field label` takes the specificity to (0,2,1) so the flex layout actually
   applies. It was wrong for No charge from the day it shipped; adding Tentative beside it turned
   one crooked row into two.
   `align-items: center` is what centres the box against its text; the explicit box sizing stops a
   browser's default checkbox metrics shifting the baseline. */
.bk-flags { display: flex; flex-wrap: wrap; align-items: center; gap: 18px; margin: 2px 0 10px; }
.field label.bk-flag {
  display: inline-flex; align-items: center; gap: 8px; margin-bottom: 0;
  font-size: 12.5px; color: var(--muted); font-weight: 500; letter-spacing: 0;
  cursor: pointer; user-select: none;
}
.field label.bk-flag input[type="checkbox"] {
  width: 15px; height: 15px; margin: 0; flex: none; cursor: pointer; accent-color: var(--brand);
}
.field label.bk-flag span { line-height: 1; }
.bk-pkg-total--free { color: var(--muted); font-style: italic; }
/* One-off custom line adder (dashed chip marks a custom line vs a saved package). */
.bk-pkg-chip--custom { border: 1px dashed var(--line); background: transparent; }
.bk-custom { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 8px; }
.bk-custom__name { flex: 1 1 220px; min-width: 160px; padding: 8px 11px; border: 1px solid var(--line); border-radius: var(--radius-sm); font: inherit; font-size: 13px; }
.bk-custom__price { display: inline-flex; align-items: center; gap: 4px; font-size: 13px; color: var(--muted); }
.bk-custom__cost { width: 92px; padding: 8px; border: 1px solid var(--line); border-radius: var(--radius-sm); font: inherit; font-size: 13px; }
.bk-custom__save { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--muted); white-space: nowrap; }
.bk-custom__err { flex-basis: 100%; color: var(--danger); font-size: 12px; margin: 0; }

/* Booking attachments list + "attach" control. */
.bk-files-list { display: flex; flex-direction: column; gap: 6px; margin-bottom: 8px; }
.bk-file {
  display: flex; flex-direction: column; gap: 4px; padding: 8px 12px; font-size: 13px;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 8px;
}
.bk-file__row { display: flex; align-items: center; gap: 10px; }
.bk-file__name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
.bk-file__size { flex-shrink: 0; font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.bk-file__dl { flex-shrink: 0; }
.bk-file__x { flex-shrink: 0; background: none; border: none; cursor: pointer; color: var(--muted); font-size: 17px; line-height: 1; padding: 0; }
.bk-file__x:hover { color: var(--danger); }
/* S1: the crew-invite download link for an oversize non-image file (calendarSync.ts's
   buildOversizeFileLinksHtml) — most files never have one, so this only shows up next to the rare
   file that does, with a way to kill it without digging the token out of the database by hand. */
.bk-file__share { display: flex; align-items: center; gap: 10px; padding-left: 2px; }
.bk-file__share-status { font-size: 11.5px; color: var(--muted); }
.bk-file__share-status--active { color: var(--brand); }
.bk-file__share-revoke { font-size: 11.5px; color: var(--danger); }
/* Doubles as a drop target — full width so there's a real area to aim at, not just a button.
   The dashed border already read as "drop here", so dragging just intensifies it. */
.bk-file-add {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
  cursor: pointer; text-align: center;
  border: 1.5px dashed var(--brand); color: var(--brand); background: none;
  border-radius: 8px; padding: 14px; font-size: 13px; font-weight: 600;
  transition: background .12s ease, border-color .12s ease;
}
.bk-file-add:hover { background: var(--brand-a06); }
.bk-file-add__or { font-size: 11.5px; font-weight: 500; opacity: .7; }
/* Dragging over: solid border + stronger fill so the target is unmistakable mid-drag. */
.bk-file-add--over {
  background: var(--brand-a12); border-style: solid; border-width: 2px; padding: 13.5px;
}
/* The children would otherwise fire their own dragenter/dragleave as the pointer crosses them.
   The JS depth counter handles that, but ignoring pointer events on them is cheaper and steadier. */
.bk-file-add--over * { pointer-events: none; }

.linklike {
  background: none; border: none; padding: 0; color: var(--link); text-decoration: underline;
  text-underline-offset: 2px; cursor: pointer; font: inherit; font-size: 12px;
}
.linklike:hover { color: var(--link-hover); }

.bk-conflicts { display: flex; flex-direction: column; gap: 6px; margin: -6px 0 16px; }
.bk-conflict-warning {
  font-size: 12.5px; color: var(--warn); background: var(--warn-bg); border: 1px solid var(--warn-line);
  border-radius: var(--radius-sm); padding: 8px 12px;
}
/* One row per existing listing inside the duplicate-address warning: the address on the left, a
   "Use this listing" link on the right, so linking is one press rather than a hunt through the
   search box beside it. */
.bk-duplicate__row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  margin-top: 6px;
}
.bk-duplicate__row button { flex: none; }
.bk-delete-btn { color: var(--danger); }
/* Inline "+ New" agency/agent add inside the booking modal's Agency/Agent fields. */
.bk-add-link { font-size: 11.5px; font-weight: 600; margin-left: 6px; }
.bk-inline-add { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.bk-inline-add input { flex: 1 1 120px; min-width: 0; }

/* ---------- Manage time off (bulk remove) ---------- */
.mo-toolbar { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
.mo-selall { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; font-weight: 600; color: var(--text); cursor: pointer; }
.mo-filter { max-width: 200px; }
.mo-list { max-height: 46vh; overflow-y: auto; border: 1px solid var(--card-line); border-radius: var(--radius-sm); }
.mo-row {
  display: flex; align-items: center; gap: 10px; padding: 8px 12px; font-size: 12.5px; cursor: pointer;
  border-top: 1px solid var(--card-line);
}
.mo-row:first-child { border-top: none; }
.mo-row:hover { background: var(--panel-2); }
/* The row is a wrapper now: this label carries the checkbox and every column, and the Edit button
   sits outside it so clicking Edit cannot tick the row for deletion. min-width:0 is what lets a
   long reason ellipsis instead of pushing the button off the end. */
.mo-row__main { display: flex; align-items: center; gap: 10px; flex: 1 1 auto; min-width: 0; cursor: pointer; }
.mo-edit { flex: none; padding: 4px 12px; font-size: 12px; }
.mo-check { flex: none; }
.mo-dot { width: 10px; height: 10px; border-radius: 50%; flex: none; }
.mo-name { font-weight: 600; min-width: 120px; }
.mo-date { color: var(--muted); min-width: 150px; }
.mo-time { color: var(--muted); }
.mo-badge {
  font-size: 10.5px; font-weight: 700; color: var(--active-text); background: var(--active-bg);
  border-radius: 999px; padding: 2px 8px; letter-spacing: .02em;
}
.mo-note { color: var(--muted-2); font-style: italic; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#mo-delete:not(:disabled) { color: var(--danger-ink); background: var(--danger); }

/* Recently-deleted booking rows (restore). */
.del-row { gap: 12px; }
.del-row__main { display: flex; flex-direction: column; gap: 1px; flex: 1; min-width: 0; }
.del-row__ago { color: var(--muted); font-size: 11.5px; white-space: nowrap; }

/* Recently-deleted listings modal (app.js). */
.dl-list { border: 1px solid var(--card-line); border-radius: var(--radius-sm); max-height: 46vh; overflow-y: auto; }
.dl-row { display: flex; align-items: center; gap: 12px; padding: 9px 12px; font-size: 12.5px; border-top: 1px solid var(--card-line); }
.dl-row:first-child { border-top: none; }
.dl-row__main { display: flex; flex-direction: column; gap: 1px; flex: 1; min-width: 0; }
.dl-row__addr { font-weight: 600; }
.dl-row__loc { color: var(--muted); }
.dl-row__ago { color: var(--muted); font-size: 11.5px; white-space: nowrap; }

/* ---------- Packages ---------- */
.pkg-toolbar { margin: 18px 0 26px; }
.pkg-groups { margin-top: 0; }
.pkg-card { background: var(--panel); border: 1px solid var(--card-line); border-radius: var(--radius); padding: 0; margin-bottom: 16px; overflow: hidden; }
.pkg-card__head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 15px 20px; background: var(--panel-2); border-bottom: 1px solid var(--card-line);
}
.pkg-card__name { font-size: 14.5px; font-weight: 700; color: var(--text); }
.pkg-card__count { font-size: 12.5px; color: var(--muted); white-space: nowrap; flex-shrink: 0; }
/* Clickable client header that expands/collapses its package list. Re-declares the head's
   background + bottom border so the native <button> matches the old <div> head. */
.pkg-card__toggle {
  width: 100%; text-align: left; font-family: inherit; cursor: pointer;
  border: 0; border-bottom: 1px solid var(--card-line); background: var(--panel-2);
}
.pkg-card__headline { display: flex; align-items: center; gap: 9px; min-width: 0; }
.pkg-card__chevron { font-size: 11px; color: var(--muted); flex-shrink: 0; }
.pkg-card__toggle:hover .pkg-card__name { color: var(--brand); }
.pkg-card--collapsed .pkg-card__head { border-bottom: none; }
/* Banner naming how many of this agency's packages Xero hasn't seen, with a one-click fix. Shown
   whether the card is expanded or collapsed, so it carries its own border rather than relying on
   a neighbour's — the warn tokens match the "Not in Xero" chip inside the rows below it. */
.pkg-card__pending {
  display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
  padding: 10px 20px; font-size: 12.5px; font-weight: 600;
  color: var(--warn); background: var(--warn-bg); border-bottom: 1px solid var(--warn-line);
}
.pkg-list { display: flex; flex-direction: column; padding: 2px 20px; }
.pkg-list > .empty { margin: 20px 0; }
.pkg-add-body { padding: 4px 20px 20px; }

.pkg-row {
  display: flex; align-items: center; justify-content: space-between; gap: 14px; flex-wrap: wrap;
  padding: 14px 0; border-top: 1px solid var(--line);
}
.pkg-row:first-child { border-top: none; }
.pkg-row__main { min-width: 0; }
.pkg-row__name { font-size: 13.5px; font-weight: 600; }
/* "1 booking" / "3 bookings" / "Not yet used" — usage sub-label under the package name. */
.pkg-row__usage { font-size: 12px; color: var(--muted-2); margin-top: 2px; }
.pkg-row__meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.pkg-row__actions { display: flex; align-items: center; gap: 16px; flex-shrink: 0; }

.pill--price { color: var(--ok); border-color: var(--ok-line); background: var(--ok-bg); }
/* Same warn coding as `.chip--uploaded` / `.pill--Uploaded` elsewhere — this row's local state
   doesn't match what Xero holds yet. */
.chip--warn { color: var(--warn); border-color: var(--warn-line); background: var(--warn-bg); }
.chip--xero { font-family: var(--font-mono); font-size: 11.5px; color: var(--muted); background: var(--panel-2); border-color: var(--line); }
/* Distinct, cooler mono chip used for Xero item codes inside the sync modal (vs. the warmer
   `.chip--xero` used for a package's own code in the list). */
.chip--code { font-family: var(--font-mono); font-size: 11.5px; font-weight: 700; color: var(--muted); background: var(--panel-2); border-color: var(--line); }
.chip--added { color: var(--warn); background: var(--warn-bg); border-color: var(--warn-line); font-size: 11px; }

.pkg-add-card .pkg-add-form { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 4px 18px; align-items: end; }
.pkg-add-submit { display: flex; align-items: flex-end; }

/* Edit-package modal footer: "Delete package" link on the left, Cancel/Save on the right. */
.sec-actions--split { display: flex; align-items: center; justify-content: space-between; }
.pkg-delete-link { color: var(--danger); }
.pkg-delete-link:hover { color: var(--danger); }

/* $-prefixed money input (Add/Edit package cost) */
.input-prefix { position: relative; }
.input-prefix input { padding-left: 26px; }
.input-prefix__symbol {
  position: absolute; left: 14px; top: 50%; transform: translateY(-50%);
  color: var(--muted); font-size: 14px; pointer-events: none;
}
/* Monospace field (Xero codes) */
.field input.mono { font-family: var(--font-mono); }

/* Sync-from-Xero button: Xero's own cyan, with a small white "X" chip. */
.btn--xero { display: inline-flex; align-items: center; gap: 8px; background: var(--xero); border-color: var(--xero); color: var(--on-solid); }
.btn--xero:hover { background: var(--xero); border-color: var(--xero); }
.xero-chip {
  display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px;
  border-radius: 4px; background: var(--on-solid); color: var(--xero); font-weight: 800; font-size: 11px; line-height: 1;
}

/* Sync-from-Xero modal — cyan-headed like the prototype's "Import items from Xero" dialog:
   a full-bleed brand header, a scrollable body, and a footer pinned below. `.modal--xero`
   strips the base `.modal`'s uniform padding so `.xs-head`/`.xs-body`/`.xs-footer` can each
   own their own. */
.modal--xero { padding: 0; display: flex; flex-direction: column; max-height: calc(100vh - 48px); }
.xs-head {
  display: flex; align-items: center; gap: 12px; padding: 20px 26px 18px;
  background: var(--xero); color: var(--on-solid); border-radius: 20px 20px 0 0; flex-shrink: 0;
}
.xs-head__icon {
  display: flex; align-items: center; justify-content: center; width: 36px; height: 36px; flex-shrink: 0;
  border-radius: 9px; background: var(--ink-a20); font-weight: 800; font-size: 16px;
}
.xs-head__text { flex: 1; min-width: 0; }
.xs-head__title { font-size: 17px; font-weight: 700; }
.xs-head__sub { font-size: 12.5px; opacity: .92; margin-top: 1px; }
.xs-head .modal__close { color: var(--on-solid); }
.xs-head .modal__close:hover { background: var(--ink-a22); color: var(--on-solid); }
/* Invoice-number-preview pill in the New invoice modal's Xero header (prototype's invNumberPreview badge). */
.xs-head__badge {
  font-size: 12px; font-weight: 600; color: var(--on-solid); background: var(--ink-a20);
  padding: 5px 11px; border-radius: 999px; white-space: nowrap; flex-shrink: 0;
}

/* Subtotal / GST / Total breakdown (New invoice + Batch invoice modals). */
.inv-totals { display: flex; flex-direction: column; align-items: flex-end; gap: 5px; margin-top: 14px; font-size: 13.5px; }
.inv-totals__row { display: flex; gap: 40px; color: var(--muted); }
.inv-totals__row span:last-child { min-width: 90px; text-align: right; }
.inv-totals__row--total { font-weight: 700; font-size: 15px; color: var(--text); }

.xs-loading { padding: 44px 26px; display: flex; flex-direction: column; align-items: center; gap: 14px; }
.xs-spinner {
  width: 32px; height: 32px; border: 3px solid var(--brand-a16); border-top-color: var(--xero);
  border-radius: 50%; animation: xs-spin .7s linear infinite;
}
@keyframes xs-spin { to { transform: rotate(360deg); } }
.xs-loading__text { font-size: 13.5px; color: var(--muted); }

.xs-body { padding: 18px 26px 4px; overflow-y: auto; flex: 1 1 auto; }
.xs-assign { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.xs-assign label { font-size: 13px; font-weight: 600; color: var(--label); white-space: nowrap; }
.xs-assign select { flex: 1; min-width: 160px; }
/* Narrows 727 org-wide sales items to one agency's by code prefix, or finds them by name. */
.xs-filter { margin-top: 14px; }
.xs-filter input {
  width: 100%; font-family: inherit; font-size: 13px; padding: 9px 11px;
  border: 1px solid var(--input-line); border-radius: var(--radius-sm);
  background: var(--input-bg); color: var(--text);
}
.xs-count-row { display: flex; align-items: center; justify-content: space-between; margin: 10px 0 10px; }
.xs-count { font-size: 12px; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); font-weight: 700; }
.xs-toggle-all {
  font-size: 12.5px; font-weight: 600; color: var(--link); cursor: pointer;
  background: none; border: none; padding: 0; font-family: inherit;
}
.xs-toggle-all:hover { color: var(--link-hover); }

.xs-items { display: flex; flex-direction: column; gap: 8px; max-height: 320px; overflow-y: auto; padding-bottom: 4px; }
.xs-item {
  display: flex; align-items: center; gap: 11px; padding: 10px 12px;
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel); cursor: pointer;
}
.xs-item--selected { border-color: var(--active-line); background: var(--brand-a06); }
.xs-item__mark {
  display: flex; align-items: center; justify-content: center; width: 19px; height: 19px; flex-shrink: 0;
  border-radius: 5px; font-size: 12px; font-weight: 700; color: var(--on-solid); background: var(--panel); border: 1px solid var(--line);
}
.xs-item--selected .xs-item__mark { background: var(--xero); border-color: var(--xero); }
.xs-item__name { flex: 1; min-width: 0; font-size: 14px; font-weight: 600; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.xs-item__price { font-size: 13.5px; font-weight: 700; color: var(--ok); flex-shrink: 0; white-space: nowrap; }

.xs-footer {
  display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
  padding: 16px 26px; border-top: 1px solid var(--line); flex-shrink: 0;
}
.xs-footer__actions { display: flex; gap: 10px; }
.xs-synced { font-size: 12.5px; color: var(--muted); }

/* Import button greys out (rather than just fading) when nothing's selected, matching the
   prototype's disabled Xero-import affordance. */
.btn--xero:disabled { background: var(--line); border-color: var(--line); color: var(--on-solid); opacity: 1; cursor: default; }

/* ---------- Import price list modal ---------- */
.imp-drop {
  border: 1px dashed var(--card-line); border-radius: var(--radius-sm); padding: 22px;
  text-align: center; font-size: 13px; color: var(--muted); margin-bottom: 14px; transition: border-color .12s, background .12s;
}
.imp-drop--over { border-color: var(--xero); background: var(--brand-a06); color: var(--text); }
.imp-errors { margin: 4px 0 16px; padding: 10px 14px; border-radius: var(--radius-sm); background: var(--danger-bg); border: 1px solid var(--danger-line); }
.imp-errors__head { font-size: 12.5px; font-weight: 700; color: var(--danger); margin: 0 0 4px; }
.imp-errors li { font-size: 12.5px; color: var(--danger); }
.imp-plan { display: flex; flex-direction: column; gap: 8px; max-height: 320px; overflow-y: auto; margin: 4px 0 16px; }
.imp-row {
  display: flex; align-items: flex-start; gap: 11px; padding: 10px 12px;
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel);
}
.imp-row--warn { border-color: var(--warn-line); background: var(--warn-bg); }
.imp-row__tick { flex-shrink: 0; padding-top: 2px; }
.imp-row__main { flex: 1; min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; }
.imp-row__name { font-size: 13.5px; font-weight: 600; color: var(--text); }
.imp-row__action { font-size: 12px; color: var(--muted); }
.imp-row__warn { flex-basis: 100%; font-size: 12px; color: var(--warn); }
.imp-row__note { flex-basis: 100%; font-size: 12px; color: var(--muted); }
/* The sales account each row will post to. Per row because a price list mixes service types. */
.imp-row__account {
  flex-basis: 100%; display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 6px; font-size: 12px; color: var(--muted);
}
.imp-row__account select, .imp-row__account input {
  font-family: inherit; font-size: 12.5px; padding: 5px 8px;
  border: 1px solid var(--input-line); border-radius: var(--radius-sm);
  background: var(--input-bg); color: var(--text);
}
.imp-row__account em { font-style: normal; color: var(--warn); }
.imp-row__account--missing select, .imp-row__account--missing input { border-color: var(--warn); }
.imp-row__price { font-size: 13.5px; font-weight: 700; color: var(--ok); flex-shrink: 0; white-space: nowrap; }
.imp-result { margin-top: 4px; }
.imp-result--ok { color: var(--ok); font-size: 13px; }

/* ---------- Toasts ---------- */
#toast-container {
  position: fixed; right: 20px; bottom: 20px; z-index: 200;
  display: flex; flex-direction: column; gap: 10px; max-width: min(360px, calc(100vw - 40px));
}
.toast {
  padding: 12px 16px; border-radius: var(--radius-sm); font-size: 13px; line-height: 1.4;
  border: 1px solid var(--line); background: var(--panel); color: var(--text);
  box-shadow: 0 12px 30px -14px var(--scrim-a40);
}
.toast--success { border-color: var(--ok-line); background: var(--ok-bg); color: var(--ok); }
.toast--error { border-color: var(--danger-line); background: var(--danger-bg); color: var(--danger); }

/* ---------- Invoicing ---------- */
.xero-banner {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  border-radius: var(--radius); padding: 16px 20px; margin: 20px 0 24px; border: 1px solid var(--line);
}
.xero-banner--ok { background: var(--ok-bg); border-color: var(--ok-line); }
.xero-banner--warn { background: var(--warn-bg); border-color: var(--warn-line); }
.xero-banner__dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.xero-banner__dot--ok { background: var(--ok); }
.xero-banner__dot--warn { background: var(--warn); }
.xero-banner__icon {
  width: 34px; height: 34px; border-radius: 8px; background: var(--xero); color: var(--on-solid);
  display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 15px; flex-shrink: 0;
}
.xero-banner__icon--warn { background: var(--neutral-bg); color: var(--muted); }
.xero-banner__text { display: flex; flex-direction: column; gap: 2px; font-size: 13.5px; color: var(--text); }
.xero-banner__title { display: flex; align-items: center; gap: 8px; font-weight: 600; }
.xero-banner__sub { font-size: 12.5px; color: var(--muted); font-weight: 400; }

/* Generic metric tile (label / big number / sub) — reusable across screens. */
/* Agency filter on Invoicing. Sits above the tiles because it governs them too, not just the
   lists — see visibleSummary() in public/invoicing.js.
   It IS a .field, so it inherits the label, control, border and focus ring every other form
   control in the portal uses. All this adds is a width: a full-bleed select above three tiles
   would read as a page header rather than a filter. */
.inv-filter { max-width: 320px; }
/* Agency and date range on one row above the tiles, wrapping rather than shrinking: a date input
   squeezed below its native picker width loses the picker button on some platforms, which is the
   whole reason for using the native control. align-items: end so the two controls line up on
   their boxes when the agency label wraps and the date labels do not. */
.inv-filters { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px 16px; margin-bottom: 16px; }
.inv-filter--date { max-width: 170px; flex: 0 0 auto; }
/* Sits on the row's own baseline with the controls, not with their labels. */
.inv-filters__clear { align-self: flex-end; margin-bottom: 1px; }
/* An inverted range matches nothing, so it takes the whole row and says why rather than leaving
   a page that looks like it has lost its data. */
.inv-filters__error { flex-basis: 100%; margin: 0; }
/* Says whose money the tiles are showing while a filter is on. Reading a filtered figure as the
   business total is a wrong number about money, so this sits directly above them. */
.inv-scope { font-size: 13px; color: var(--muted); margin: 0 0 10px; }
/* The scope note and the tiles are one unit — keeps the note from drifting away from the numbers
   it qualifies if anything is ever inserted between them. */
.tile-block { display: block; }
.tile-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 28px; }
.tile { background: var(--panel-2); border-radius: var(--radius); padding: 18px 20px; }
.tile__label { font-size: 13px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); font-weight: 600; }
.tile__value { font-size: 24px; font-weight: 600; margin-top: 8px; letter-spacing: -.01em; }
.tile__sub { font-size: 12.5px; color: var(--muted); margin-top: 4px; }

/* Single standalone metric tile, sized to its content (agent Invoices' "Outstanding" tile). */
.tile--solo { display: inline-block; margin: 20px 0 4px; }

/* White empty-state card (Invoicing + agent Invoices) */
.inv-empty-card {
  background: var(--panel); border: 1px solid var(--card-line); border-radius: var(--radius);
  box-shadow: 0 14px 34px -28px var(--scrim-a30);
  padding: 56px 24px; text-align: center; color: var(--muted); font-size: 14px;
  display: flex; flex-direction: column; align-items: center; gap: 16px;
}

.inv-section { margin-bottom: 32px; }
.inv-section__head { font-size: 13px; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); margin: 0 0 14px; font-weight: 600; }

.inv-list { display: flex; flex-direction: column; gap: 10px; }
.inv-row {
  display: flex; align-items: center; gap: 16px; width: 100%; text-align: left;
  background: var(--panel); border: 1px solid var(--card-line); border-radius: var(--radius-sm);
  padding: 14px 16px; cursor: pointer; font: inherit; color: inherit; transition: border-color .15s;
}
.inv-row:hover { border-color: var(--btn-hover-line); }
.inv-row--static { cursor: default; }
.inv-row--static:hover { border-color: var(--card-line); }
.inv-row__num { font-family: var(--font-mono); font-size: 12.5px; font-weight: 700; color: var(--active-text); white-space: nowrap; flex-shrink: 0; }
.inv-row__main { flex: 1; min-width: 0; }
.inv-row__number { font-size: 13.5px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.inv-row__meta { display: flex; gap: 10px; flex-wrap: wrap; font-size: 12px; color: var(--muted); margin-top: 4px; }
.inv-row__amount { font-family: var(--font-mono); font-size: 13px; font-weight: 600; white-space: nowrap; }

/* The agent invoice row's due date and its two actions, added 2026-08-28. .inv-row is a flex
   row, so these need explicit sizing or the buttons squash the reference text. */
.inv-row__due { font-size: 12.5px; color: var(--muted); white-space: nowrap; flex-shrink: 0; }
.inv-row__actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }

.pill--inv-draft { color: var(--neutral); border-color: var(--neutral-line); background: var(--neutral-bg); }
.pill--inv-submitted { color: var(--warn); border-color: var(--warn-line); background: var(--warn-bg); }
.pill--inv-authorised { color: var(--active-text); border-color: var(--active-line); background: var(--active-bg); }
.pill--inv-paid { color: var(--ok); border-color: var(--ok-line); background: var(--ok-bg); }
.pill--inv-voided { color: var(--danger); border-color: var(--danger-line); background: var(--danger-bg); }

.inv-uninvoiced-list { display: flex; flex-direction: column; gap: 10px; }
.inv-uninvoiced-row {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  background: var(--panel); border: 1px solid var(--card-line); border-radius: var(--radius-sm); padding: 12px 16px;
}
.inv-uninvoiced-row__main { flex: 1; min-width: 0; }
.inv-uninvoiced-row__addr { font-size: 13.5px; font-weight: 600; }
.inv-uninvoiced-row__meta { display: flex; gap: 10px; flex-wrap: wrap; font-size: 12px; color: var(--muted); margin-top: 4px; }
.inv-uninvoiced-row__price { font-family: var(--font-mono); font-size: 13px; color: var(--muted); white-space: nowrap; }

/* Invoice detail modal */
.inv-detail-meta { display: flex; gap: 24px; flex-wrap: wrap; margin-bottom: 18px; }
.inv-detail-meta__label { display: block; font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); margin-bottom: 4px; }
.inv-detail-meta__value { font-size: 13.5px; color: var(--text); }
.inv-lines { border-top: 1px solid var(--line); padding-top: 14px; }
.inv-line-row { display: grid; grid-template-columns: 1fr 60px 90px 90px; gap: 10px; padding: 8px 0; font-size: 13px; align-items: center; }
.inv-line-row--head { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); font-weight: 600; border-bottom: 1px solid var(--line); padding-bottom: 8px; }
.inv-line-row__qty, .inv-line-row__unit, .inv-line-row__total { font-family: var(--font-mono); text-align: right; }
/* A draft with more than one line gets a per-line Remove, which needs a FIFTH column. The rule
   above is a fixed 4-column grid, so the button would otherwise land in an implicit column and put
   every body row out of step with its own headings. Scoped to the modifier so a non-amendable
   invoice keeps the original four and gains no dead gutter. */
.inv-lines--amendable .inv-line-row { grid-template-columns: 1fr 60px 90px 90px 72px; }
.inv-line-row__remove { justify-self: end; font-size: 12px; }
/* The "add a job" picker inside the invoice detail modal: this client's other open jobs, one row
   each. A button rather than a list item because pressing one bills it, and a <button> gets the
   keyboard and the focus ring for free. The columns mirror the line rows above it so the price
   sits under the price. */
.inv-add-jobs { border: 1px solid var(--line); border-radius: var(--radius-sm); margin-top: 12px; overflow: hidden; }
.inv-add-job {
  display: grid; grid-template-columns: 1fr auto 90px; gap: 10px; align-items: center;
  width: 100%; text-align: left; font: inherit; font-size: 13px; color: var(--text);
  background: none; border: 0; border-bottom: 1px solid var(--line); padding: 9px 12px; cursor: pointer;
}
.inv-add-job:last-child { border-bottom: 0; }
.inv-add-job:hover { background: var(--panel-2); }
.inv-add-job:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.inv-add-job__pkg { color: var(--muted); font-size: 12px; }
.inv-add-job__price { font-family: var(--font-mono); text-align: right; }
.inv-add-job:disabled { color: var(--muted-2); cursor: not-allowed; }
.inv-detail-total { display: flex; justify-content: flex-end; align-items: baseline; gap: 8px; font-size: 14px; padding-top: 12px; border-top: 1px solid var(--line); margin-top: 6px; }

/* Batch invoice modal */
.bm-select-all { margin: 2px 0 12px; }
.bm-jobs { display: flex; flex-direction: column; gap: 8px; max-height: 320px; overflow-y: auto; margin-bottom: 14px; }
.bm-job {
  display: flex; align-items: center; gap: 10px; padding: 9px 10px;
  border: 1px solid var(--line); border-radius: var(--radius-sm); background: var(--panel-2); cursor: pointer;
}
/* A row is one JOB carrying its billable lines, so it stacks: the address and date on top, then
   each line beneath. It used to be one row per LINE, which drew the same address three times for
   a property shot twice and read as a duplicate (Dave, 2026-08-21). align-items moves to the top
   because a two-line job is now taller than its checkbox. */
.bm-job { align-items: flex-start; }
.bm-job .bm-job-check { margin-top: 2px; }
.bm-job__main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
.bm-job__addr { min-width: 0; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The date is what tells two shoots at one address apart, so it sits with the address rather than
   down among the lines — and it comes FIRST, both because the month is what you scan when
   reconciling a client and because this element clips. .bm-job__addr is nowrap + ellipsis, so
   while the date trailed the address a long enough address truncated its own date away. Leading
   puts it before the clip point. margin-RIGHT for the same reason: the gap now belongs after it. */
.bm-job__date { color: var(--muted); font-weight: 400; margin-right: 8px; }
.bm-job__lines { display: flex; flex-direction: column; gap: 2px; }
.bm-job__line {
  display: flex; justify-content: space-between; gap: 12px;
  font-size: 12px; color: var(--muted); min-width: 0;
}
.bm-job__line-price { white-space: nowrap; }
.bm-job__pkg { font-size: 12px; color: var(--muted); white-space: nowrap; }
/* The job's combined total, which is what ticking the row actually bills. */
.bm-job__price { font-size: 12.5px; font-weight: 600; white-space: nowrap; }

/* Booking-modal Invoice action */
.btn--invoice { color: var(--active-text); border-color: var(--active-line); }
.btn--invoice:hover { background: var(--active-bg); }

@media (max-width: 820px) {
  .sidebar {
    position: fixed; top: 0; left: 0; z-index: 50; height: 100vh;
    transform: translateX(-100%); transition: transform .2s ease;
    box-shadow: 0 0 40px -10px var(--scrim-a35);
  }
  .sidebar.open { transform: translateX(0); }

  .sidebar-backdrop {
    display: block; position: fixed; inset: 0; z-index: 45;
    background: var(--scrim-a35); opacity: 0; pointer-events: none;
    transition: opacity .2s ease;
  }
  .sidebar-backdrop.open { opacity: 1; pointer-events: auto; }

  .mobile-topbar {
    display: flex; align-items: center; gap: 14px;
    padding: 12px 16px; border-bottom: 1px solid var(--line);
    background: var(--bg); position: sticky; top: 0; z-index: 30;
  }
  .mobile-topbar__logo { height: 26px; }

  .main__inner { padding: 24px 18px; }
}

/* ==========================================================================
   Bookings calendar re-skin (Task R2) — overrides/extends the base calendar
   rules in styles.css (loaded before this file, so same-specificity
   selectors here win) to match the prototype: title badge, pill week-nav,
   trailing legend actions, a full-width pending-jobs panel above the grid,
   and richer booking cards (time range, package, crew avatars, unschedule x).
   ========================================================================== */

/* Toolbar: Today/prev/next pill on the left, primary action on the right. */
.cal-toolbar { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 20px 0 18px; }
.cal-toolbar .spacer { flex: 1; }

/* Booked value for the visible week and its month, beside the week arrows. Two quiet stacked
   pairs: the label small and muted, the figure the thing your eye lands on. */
.cal-income { display: inline-flex; align-items: center; gap: 18px; margin-left: 6px; }
.cal-income__item { display: inline-flex; flex-direction: column; line-height: 1.15; }
.cal-income__label {
  font-size: 10.5px; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted);
}
.cal-income__value { font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--text); }
@media (max-width: 700px) { .cal-income { gap: 12px; margin-left: 0; } }

/* Find a job. The calendar shows one week, so this is the only way to reach a job whose date you
   cannot name — see calendar/search.js. The panel is absolutely positioned so opening it never
   reflows the toolbar and pushes the grid down. */
.cal-search { position: relative; flex: 0 1 240px; min-width: 150px; }
.cal-search__input {
  width: 100%; box-sizing: border-box;
  padding: 7px 11px; font-size: 13px; line-height: 1.3;
  color: var(--text); background: var(--panel);
  border: 1px solid var(--card-line); border-radius: 999px;
}
.cal-search__input::placeholder { color: var(--muted); }
.cal-search__input:focus-visible, .cal-search__input:focus {
  outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px var(--focus-ring);
}
.cal-search__panel {
  position: absolute; z-index: 40; top: calc(100% + 6px); left: 0;
  /* Wider than the box it hangs off: a result line carries an address, a date and an agency, and
     wrapping every row to two lines to fit a 240px input would make the list harder to scan. */
  min-width: 100%; width: 340px; max-width: 90vw;
  max-height: 340px; overflow-y: auto;
  background: var(--panel); border: 1px solid var(--card-line); border-radius: 10px;
  box-shadow: 0 10px 28px rgba(0, 0, 0, .16);
  padding: 4px;
}
.cal-search__panel[hidden] { display: none; }
.cal-search__hit {
  display: flex; flex-direction: column; gap: 1px; width: 100%; text-align: left;
  padding: 7px 9px; border: 0; border-radius: 7px; background: transparent; cursor: pointer;
}
.cal-search__hit:hover, .cal-search__hit--active { background: var(--brand-a10); }
.cal-search__hit-main { font-size: 13px; font-weight: 600; color: var(--text); }
/* The property, under a job NAME only. Same weight as the meta line but the body colour, because
   it is what you probably typed to get here. */
.cal-search__hit-addr { font-size: 12px; color: var(--text); }
.cal-search__hit-meta { font-size: 11.5px; color: var(--muted); }
.cal-search__empty { margin: 0; padding: 9px; font-size: 12.5px; color: var(--muted); }

/* The job the search took you to. One shot, so the mark fades on its own rather than needing a
   second render to clear it. */
@keyframes cal-found-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(31, 143, 201, 0); }
  15%, 60% { box-shadow: 0 0 0 3px var(--brand-a90); }
}
.cal-card--found, .tray-card--found { animation: cal-found-pulse 2.4s ease-in-out 1; }
@media (prefers-reduced-motion: reduce) {
  .cal-card--found, .tray-card--found { animation: none; box-shadow: 0 0 0 3px var(--brand-a90); }
}

@media (max-width: 700px) { .cal-search { flex: 1 1 100%; } }

.cal-nav {
  display: inline-flex; align-items: center; gap: 2px;
  background: var(--panel); border: 1px solid var(--card-line); border-radius: 999px; padding: 4px;
}
/* Segmented tab (Full week / Weekdays) — matches the pill look of .cal-nav. */
.seg-toggle {
  display: inline-flex; align-items: center; gap: 2px; margin-right: 10px;
  background: var(--panel); border: 1px solid var(--card-line); border-radius: 999px; padding: 3px;
}
.seg-toggle__opt {
  border: none; background: none; cursor: pointer; font-family: inherit; font-size: 12.5px;
  font-weight: 600; color: var(--muted); padding: 5px 12px; border-radius: 999px; line-height: 1;
}
.seg-toggle__opt:hover:not(.seg-toggle__opt--active) { color: var(--text); }
.seg-toggle__opt--active { background: var(--active-bg); color: var(--active-text); }
/* Zoom control: −  Nh  +  in the same pill style. */
.zoom-ctl__btn { font-size: 15px; padding: 3px 11px; }
.zoom-ctl__btn:disabled { opacity: .3; cursor: default; }
.zoom-ctl__btn:hover:not(:disabled) { color: var(--text); background: var(--active-bg); }
.zoom-ctl__val {
  min-width: 26px; text-align: center; font-size: 11.5px; font-weight: 600; color: var(--muted);
  padding: 0 2px; user-select: none;
}
.cal-nav__arrow {
  width: 30px; height: 30px; border-radius: 50%; border: none; background: none; cursor: pointer;
  font-size: 16px; color: var(--muted); display: inline-flex; align-items: center; justify-content: center;
  font-family: inherit; line-height: 1; padding: 0;
}
.cal-nav__arrow:hover { background: var(--panel-2); color: var(--text); }
/* Highlighted while a card is dragged over the arrow, about to flip the week. */
.cal-nav__arrow--flip { background: var(--brand-strong); color: var(--brand-ink); box-shadow: 0 0 0 3px var(--brand-a30); }

/* Week-flip drop zones at the grid's left/right edges. Hidden until a card is being dragged
   (root gets .cal-dragging); hovering one for ~0.5s flips to the previous/next week. */
.week-flip {
  position: absolute; top: 0; bottom: 0; width: 56px; z-index: 8;
  display: none; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  pointer-events: none; color: var(--on-solid); font-size: 11px; font-weight: 700; text-align: center;
}
.cal-dragging .week-flip { display: flex; pointer-events: auto; }
/* A GRADIENT IS STILL A FILL UNDER TEXT. This washed from --brand-a90 to --brand-a10, and the
   label is centred in the 56px strip, so it rode the MIDDLE of the wash over a near-white calendar
   panel by day. Even at the strong end it only reached 3.14:1 in light and 3.12:1 in dark, and it
   fell away from there. Now --brand-strong at full strength across the 80% the label covers, with
   the fade kept for the inner edge so the strip still blends into the grid. That reads 4.75:1 in
   dark and 4.50:1 in light -- light is EXACTLY on the AA floor, no headroom. If --brand-strong is
   ever lightened for the light palette, this label goes under before anything else does. */
.week-flip--prev { left: 0; background: linear-gradient(to right, var(--brand-strong) 80%, transparent 100%); }
.week-flip--next { right: 0; background: linear-gradient(to left, var(--brand-strong) 80%, transparent 100%); }
.week-flip__arrow { font-size: 28px; line-height: 1; text-shadow: 0 1px 3px rgba(0,0,0,.35); }
.week-flip__txt { letter-spacing: .02em; text-shadow: 0 1px 2px rgba(0,0,0,.4); }
/* The ring says "drop here", so it follows the fill rather than the page. --ink-a55 inverts with
   the theme (white by day, black by night) and --brand-strong stopped inverting, which left a 55%
   black ring on a dark blue at 2.65:1 in dark and 2.43:1 in light: under WCAG 1.4.11's 3:1 for a
   non-text state indicator, and it was failing in LIGHT before any of this. --on-solid is pinned
   white in both palettes and is already the label's colour, so the zone now draws one ink. */
.week-flip--hot { background: var(--brand-strong); box-shadow: inset 0 0 0 3px var(--on-solid); }
/* While a card is being dragged anywhere, the arrows pulse to advertise "drop here to change
   week". The whole nav pill also gets a subtle ring so the target reads at a glance. */
.cal-dragging .cal-nav { box-shadow: 0 0 0 2px var(--brand-a30); }
.cal-dragging .cal-nav__arrow { color: var(--brand); animation: navArrowPulse 1s ease-in-out infinite; }
@keyframes navArrowPulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.22); } }
.cal-nav__today {
  border: none; background: none; cursor: pointer; font-family: inherit; font-weight: 600;
  font-size: 13px; color: var(--text); padding: 6px 14px; border-radius: 999px;
}
.cal-nav__today:hover { background: var(--panel-2); }

/* Legend: colour-coded photographer chips, wrapping with "Manage photographers" /
   "Add time off" trailing at the end of the row (or flowing to their own line). */
.cal-legend { row-gap: 10px; }
.cal-legend__manage { margin-left: 4px; }
.cal-legend__addoff { margin-left: auto; }

/* Day headers: bold date next to the muted day-of-week label. */
.cal-day-head { padding: 12px 8px 10px; }
.cal-day-head__date { font-weight: 700; color: var(--text); }
.cal-day-head--today .cal-day-head__date { color: var(--active-text); }

/* Booking cards: room for the unschedule "x", bold time-range header, and a
   small crew-avatar row under the address. */
.cal-card { padding: 4px 20px 4px 8px; }
.cal-card__x {
  position: absolute; top: 2px; right: 2px; width: 16px; height: 16px; border-radius: 50%;
  /* --on-light, NOT --text. The disc is a fixed white alpha because the card underneath it is a
     saturated crew colour in both themes; --text inverts, so in dark this button was near-white
     ink on a near-white disc at 1.17:1 and simply was not there. */
  border: none; background: rgba(255,255,255,.7); color: var(--on-light); cursor: pointer;
  font-size: 12px; line-height: 1; padding: 0; display: inline-flex; align-items: center; justify-content: center;
  font-family: inherit; z-index: 4;
}
.cal-card__x:hover { background: var(--on-solid); }
.cal-card__time { font-weight: 700; font-size: 10.5px; letter-spacing: .01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The job's price, riding the time row so it costs the card no height. Slightly transparent
   against the crew-colour fill rather than a new colour: the card's background is a gradient of
   whoever is on the shoot, so any fixed ink would clash on some of them. It is the LAST thing
   that should survive a narrow lane, hence the shrink. */
.cal-card__price { float: right; margin-left: 8px; font-weight: 600; flex-shrink: 1; }
/* The invoiced tick (Dave, 2026-10-12). Solid once the invoice is out, outlined while it is a
   draft. It sits in the 20px right-hand gutter the × reserves (.cal-card padding-right), just
   under the ×, so it costs no layout at any lane width: inline beside the price it pushed the
   price onto a line of its own on a 95px lane. Both states are built from the card's own ink
   rather than a new colour, for the same reason as the price: the fill is whoever is on the
   shoot. The solid disc uses the fixed --on-light/--on-solid pair the × uses, so it holds in
   dark too. A card under ~32px clips it; the card's hover text still says it is invoiced. */
.cal-card__invoiced {
  position: absolute; top: 20px; right: 3.5px; z-index: 3;
  display: inline-flex; align-items: center; justify-content: center;
  width: 13px; height: 13px; box-sizing: border-box; border-radius: 50%;
  font-size: 9px; font-weight: 800; line-height: 1;
  /* On a card about 33-40px tall it overlaps the bottom resize handle; let the drag through. The
     card's own hover text still says it is invoiced. */
  pointer-events: none;
}
.cal-card__invoiced--issued { background: var(--on-solid); color: var(--on-light); }
.cal-card__invoiced--draft { border: 1.5px solid currentColor; background: none; color: inherit; }
/* Copy the shoot's date and time (see interactions.js). Floated like the price, and
   `flex-shrink: 0` because .cal-card__time clips with an ellipsis — the price is deliberately the
   first thing to give way on a narrow lane, and a control that disappears is worse than one that
   is tight. It sits left of the price so the × in the corner keeps its own space.

   SIZED FOR A FINGER, not for the 10.5px type it sits beside. At 10px it was a speck on a
   coloured card and you had to hunt for it (Dave, 2026-08-25: "make the copy button bigger").
   16px of glyph in an 18px box is roughly the × in the corner, which is the other thing on this
   card you are meant to be able to hit.

   `line-height: 1` with a fixed height rather than padding: .cal-card__time has overflow:hidden,
   so it establishes a formatting context and GROWS to contain this float. Pinning the height caps
   how much the time row can gain over the 10.5px text beside it — a few pixels on every card in a
   dense week is a real cost, and the row is above the address and the crew on the shortest cards. */
.cal-card__copy {
  float: right; margin-left: 6px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; margin-top: -3px;
  border: none; background: none; padding: 0; cursor: pointer; border-radius: 4px;
  font-size: 16px; line-height: 1; opacity: .85; font-family: inherit;
}
.cal-card__copy:hover { opacity: 1; background: rgba(255,255,255,.25); }
.cal-card__copy:focus-visible { outline: 2px solid var(--on-solid); outline-offset: 1px; border-radius: 4px; }

/* A booking being HELD rather than confirmed (migration 0049). Drawn as an outline rather than a
   solid: the card's fill is the crew's colours, so dropping to a dashed border and a translucent
   body says "not settled" without inventing a colour that would clash on some crew. Same visual
   language as .cal-card--ghost, which already means "staged, not saved". */
.cal-card--tentative {
  border: 1px dashed var(--panel); opacity: .82;
  background-blend-mode: normal;
}
/* Rides the time row beside the price, so a held job costs the card no extra height. */
.cal-card__tentative {
  float: right; margin-left: 8px; font-size: 9.5px; font-weight: 700;
  /* Dimming this cost 0.5 of a contrast ratio to say something the uppercase weight already
     says. It reads as held either way. */
  letter-spacing: .04em; text-transform: uppercase;
}
/* Confirming is the one press on this card that sends email, so it is a real button rather than
   another chip — and it sits after the label it acts on. */
.cal-card__confirm {
  float: right; margin-left: 6px; font: inherit; font-size: 9.5px; font-weight: 700;
  letter-spacing: .03em; text-transform: uppercase; cursor: pointer;
  padding: 1px 6px; border-radius: 999px; border: 1px solid currentColor;
  background: none; color: inherit;
}
.cal-card__confirm:hover { background: rgba(255, 255, 255, .18); }
/* The time line now carries the drive-time badge, so it must clip like siblings (.cal-card__addr, .cal-card__service)
   rather than wrap and push content past the card's hidden overflow on narrow lanes. */
.cal-card__crew-row { display: flex; align-items: center; gap: 3px; margin-top: 2px; flex-wrap: wrap; }
/* --on-solid, not --muted: this sits on the card FILL (the unassigned grey, taken down by
   readableFill), not on a panel, and --muted on it measured 2.6:1. Later rule than the one
   above, so this is the one that wins — both say the same thing now. */
.cal-card__crew-row--empty { font-size: 10px; color: var(--on-solid); }
.cal-card__avatar {
  /* 16px/9px, up from 15px/8px. Two-letter initials at 8px bold were the other half of why
     Dave could not read one on a phone; contrast alone was not going to fix a glyph that
     small. */
  width: 16px; height: 16px; border-radius: 50%; color: var(--on-solid); font-size: 9px; font-weight: 700;
  display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0;
  box-shadow: 0 0 0 1px rgba(255,255,255,.7);
}
/* Whether this crew member has answered their calendar invite, as a ring around the avatar that
   already identifies them. A 15px circle has no room for a word, so the ring carries it here while
   the avatar's title says the same thing in text — the booking modal spells it out in full. Ring
   colours are chosen to read against the crew-colour gradient the card is filled with. */
.cal-card__avatar--accepted { box-shadow: 0 0 0 2px var(--ok); }
.cal-card__avatar--declined { box-shadow: 0 0 0 2px var(--danger); }
.cal-card__avatar--tentative { box-shadow: 0 0 0 2px var(--warn); }
/* Awaiting a reply keeps the plain white ring: it is the common state, and colouring it would turn
   an ordinary week of unanswered invites into a wall of alerts. */
.cal-card__avatar--none { box-shadow: 0 0 0 1px rgba(255,255,255,.7); }
/* Nobody was asked (invite_crew off). Faded rather than coloured — there is no reply outstanding. */
/* Nobody was asked (invite_crew off). The FILL is muted by grid.js (discFill "faded") and the
   ring is fainter; it used to set opacity: .72 on the whole avatar, which dimmed the initials
   along with the disc and took a 4.92:1 letter down to 3.06:1. */
.cal-card__avatar--uninvited { box-shadow: 0 0 0 1px rgba(255,255,255,.45); }

/* Pending bookings: fixed-width rail on the RIGHT of the calendar. The grid
   takes the remaining width on the left; cards stack vertically in the rail.
   The tray comes first in the DOM, so `order` flips it to the right visually. */
.cal-layout { flex-direction: row; align-items: flex-start; flex-wrap: nowrap; }
.cal-layout .cal-grid-wrap { flex: 1 1 auto; min-width: 0; order: 1; }
.tray-panel { flex: 0 0 260px; width: 260px; order: 2; align-self: stretch; }
.tray-panel__head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 4px; }
.tray-panel__hint { font-size: 12px; color: var(--muted); margin: 0; order: 3; flex-basis: 100%; }

/* Collapsed to a slim strip so the grid can reclaim the ~220px this rail otherwise costs (Task:
   collapsible bookings panels — this is the one that matters most on a narrow portrait monitor).
   Selector carries the #view-bookings prefix on purpose: the >=1200px density block further down
   this file sets `#view-bookings .tray-panel { flex: 0 0 220px }`, and an ID selector always
   outranks a two-class one no matter how the rest of the specificity compares, so without the
   matching prefix here a wide monitor would silently keep the rail at 220px while calling itself
   collapsed. It is still a `.tray-panel` underneath either way, so it stays the exact same
   drag-and-drop target interactions.js already wires up (dragover/dragleave/drop) — the strip IS
   the drop zone, not a decoy in front of one that vanished. */
#view-bookings .tray-panel.tray-panel--collapsed {
  flex: 0 0 40px; width: 40px; padding: 12px 4px;
  display: flex; flex-direction: column; align-items: center; gap: 8px;
}
.tray-panel__count {
  font-size: 11px; font-weight: 700; color: var(--active-text); background: var(--active-bg);
  border-radius: 999px; min-width: 18px; height: 18px; padding: 0 4px;
  display: inline-flex; align-items: center; justify-content: center;
}
.tray-list { flex-direction: column; flex-wrap: nowrap; }

@media (max-width: 640px) {
  .cal-legend__addoff { margin-left: 0; }
}

/* ---------- Branding & watermark panel (admin agency detail — Task 10) ---------- */
.brand-section { margin-top: 30px; }
/* Agency picker on the dedicated Branding & watermark page. */
.brand-pick { display: flex; align-items: center; gap: 10px; margin: 4px 0 18px; flex-wrap: wrap; }
.brand-pick label { font-size: 12.5px; font-weight: 600; color: var(--label); }
.brand-pick__select {
  padding: 9px 12px; border: 1px solid var(--line); border-radius: 8px; font: inherit; font-size: 14px;
  background: var(--panel); color: var(--text); min-width: 300px; max-width: 100%; cursor: pointer;
}
.brand-panel {
  background: var(--panel); border: 1px solid var(--card-line); border-radius: var(--radius);
  padding: 20px 22px; display: flex; flex-direction: column; gap: 20px;
}

/* Share-page logo panel: its own card under the watermark editor, gap tightened because the
   label and note sit directly above the drop target rather than beside sliders. */
.brand-panel--share { margin-top: 20px; gap: 10px; }
.brand-panel--share .brand-field-label { margin-bottom: 0; }
.brand-section-note { font-size: 13px; line-height: 1.5; color: var(--muted); margin: 0 0 6px; max-width: 62ch; }

.brand-logo { display: flex; align-items: flex-start; gap: 16px; flex-wrap: wrap; }
.brand-logo__drop {
  width: 92px; height: 92px; flex-shrink: 0; border-radius: var(--radius-sm);
  border: 2px dashed var(--line); background: var(--panel-2);
  display: flex; align-items: center; justify-content: center; overflow: hidden;
  cursor: pointer; transition: border-color .15s, background .15s;
}
.brand-logo__drop:hover, .brand-logo__drop--drag { border-color: var(--brand); background: var(--active-bg); }
.brand-logo__img { max-width: 100%; max-height: 100%; object-fit: contain; }
.brand-logo__empty { font-size: 11px; color: var(--muted); text-align: center; padding: 0 6px; }
.brand-logo__meta { flex: 1; min-width: 220px; display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }
.brand-logo__btns { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.brand-logo__hint { font-size: 12px; color: var(--muted); margin: 0; }
.brand-logo__status { font-size: 12px; color: var(--ok); margin: 0; }
.brand-logo__status--error { color: var(--danger); }

.brand-row { display: grid; grid-template-columns: auto 1fr auto; gap: 28px; align-items: start; }
.brand-col--sliders { display: flex; flex-direction: column; gap: 14px; min-width: 200px; }
.brand-field-label {
  font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted);
  margin-bottom: 8px; font-weight: 600;
}

/* 9-cell position grid — a single-select radio-style control (buttons + aria-pressed). */
.wm-grid {
  display: grid; grid-template-columns: repeat(3, 34px); grid-template-rows: repeat(3, 34px); gap: 6px;
}
.wm-grid__cell {
  width: 34px; height: 34px; border-radius: 6px; border: 1px solid var(--line); background: var(--panel-2);
  cursor: pointer; display: flex; align-items: center; justify-content: center; padding: 0;
}
.wm-grid__cell:hover { border-color: var(--brand); }
.wm-grid__cell--selected { border-color: var(--brand); background: var(--active-bg); }
.wm-grid__dot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted-2); }
.wm-grid__cell--selected .wm-grid__dot { background: var(--brand); }

.brand-slider label { display: block; font-size: 12.5px; font-weight: 600; color: var(--label); margin-bottom: 4px; }
.brand-slider__row { display: flex; align-items: center; gap: 10px; }
.brand-slider__row input[type="range"] {
  flex: 1; -webkit-appearance: none; appearance: none; height: 4px; border-radius: 2px;
  background: var(--card-line); outline: none;
}
.brand-slider__row input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; width: 16px; height: 16px; border-radius: 50%;
  background: var(--brand); cursor: pointer; border: 2px solid var(--panel); box-shadow: 0 0 0 1px var(--brand);
}
.brand-slider__row input[type="range"]::-moz-range-thumb {
  width: 16px; height: 16px; border-radius: 50%; background: var(--brand); cursor: pointer;
  border: 2px solid var(--panel); box-shadow: 0 0 0 1px var(--brand);
}
.brand-slider__value { font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; width: 34px; text-align: right; flex-shrink: 0; }

/* Live preview: a neutral gradient "sample photo" backdrop with the logo overlaid absolutely,
   positioned/sized/faded by wmOverlayStyle() in agencies.js. Not tied to any real listing photo. */
.brand-col--preview { min-width: 220px; }
.brand-preview__frame {
  position: relative; width: 100%; max-width: 260px; aspect-ratio: 4 / 3; border-radius: var(--radius-sm);
  overflow: hidden; border: 1px solid var(--card-line);
  background: linear-gradient(135deg, #cfd9c9 0%, #aebfa4 45%, #8fa085 100%);
}
.brand-preview__logo { display: block; max-width: none; }
.brand-preview__caption { font-size: 11.5px; color: var(--muted); margin: 8px 0 0; max-width: 260px; }

@media (max-width: 860px) {
  .brand-row { grid-template-columns: 1fr; gap: 20px; }
}

/* ── Annotation canvas (annotator.js) ─────────────────────────────────── */
/* .annot shrink-wraps the image; the SVG overlay is absolutely positioned to
   cover it exactly. All geometry is normalized (0..1) and denormalized against
   the image's live rendered box at draw time. */
.annot {
  position: relative;
  display: inline-block;
  max-width: 100%;
  line-height: 0;
}
.annot__img {
  display: block;
  max-width: 100%;
  height: auto;
  user-select: none;
  -webkit-user-drag: none;
}
.annot--drawing .annot__img { cursor: crosshair; }
.annot__overlay {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;   /* clicks pass through to the image */
}

/* Optional built-in toolbar classes (consumers may render their own) */
.annot__toolbar {
  display: flex; gap: 8px; margin-bottom: 12px; flex-wrap: wrap;
}
.annot__tool {
  padding: 8px 14px; border-radius: var(--radius-sm); cursor: pointer;
  border: 1px solid var(--line); background: var(--panel); color: var(--text);
  font: inherit; font-size: 13px;
}
.annot__tool:hover { border-color: var(--brand); }
.annot__tool--active {
  background: var(--brand-strong); border-color: var(--brand-strong); color: var(--brand-ink);
}

/* Overlay marks. Every colour here is one of the palette's invariant tokens, and annotator.js
   flattens the PNG from the same four — a marker sits on the agent's photograph, where neither the
   page background nor the panel exists, so it must not change with the theme the agent happened to
   be using. tests/ui/annotator.test.ts holds both ends of that together. */
.annot__pin {
  fill: var(--annot-pin);
  stroke: var(--on-solid);
  stroke-width: 2;
}
.annot__num {
  fill: var(--on-solid);
  font: 600 12px/1 system-ui, -apple-system, "Segoe UI", sans-serif;
  text-anchor: middle;
  dominant-baseline: central;
  pointer-events: none;
}
.annot__arrow {
  stroke: var(--annot);
  stroke-width: 3;
  fill: none;
  stroke-linecap: round;
}
.annot__arrowhead { fill: var(--annot); }
.annot__box {
  stroke: var(--annot);
  stroke-width: 3;
  fill: var(--annot-fill);
}
.annot__preview { opacity: 0.75; stroke-dasharray: 5 4; }

/* Note popover */
.annot__note-pop {
  position: absolute;
  z-index: 30;
  width: 230px;
  background: var(--panel);
  border: 1px solid var(--card-line);
  border-radius: var(--radius-sm);
  box-shadow: 0 8px 24px var(--scrim-a16);
  padding: 10px;
  line-height: normal;
}
.annot__note-input {
  width: 100%;
  box-sizing: border-box;
  resize: vertical;
  font: inherit;
  font-size: 13px;
  border: 1px solid var(--input-line);
  border-radius: var(--radius-sm);
  padding: 8px 9px;
  color: var(--text);
  background: var(--input-bg);
}
.annot__note-input:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-a16);
}
.annot__note-actions {
  display: flex; justify-content: space-between; gap: 8px; margin-top: 8px;
}
.annot__note-actions .btn { padding: 6px 12px; font-size: 12.5px; }

/* ---------- Change requests: agent capture UI (Task 6, gallery-view.js) ---------- */
.cr-banner {
  display: inline-flex; align-items: center; gap: 6px; margin: 10px 0 0;
  padding: 6px 12px; border-radius: 999px; font-size: 12.5px; font-weight: 600;
  color: var(--warn); background: var(--warn-bg); border: 1px solid var(--warn-line);
}

/* Request-mode content (annotator + thumbnails + panel) is taller than a single photo — let the
   overlay scroll and stack from the top instead of forcing it all into one viewport-height block. */
.lightbox-overlay--cr { align-items: flex-start; overflow-y: auto; padding: 64px 24px 40px; }
.cr-stage { width: 100%; max-width: 900px; margin: 0 auto; display: flex; flex-direction: column; gap: 16px; }
.cr-annot-mount {
  display: flex; justify-content: center; background: rgba(0,0,0,.2);
  border-radius: var(--radius); padding: 12px;
}
.cr-annot-mount .annot { max-width: 100%; }
.cr-annot-mount .annot__img { max-height: 62vh; width: auto; }

.cr-toolbar { justify-content: center; }

.cr-thumbs { display: flex; gap: 8px; overflow-x: auto; padding: 4px 2px; }
.cr-thumb {
  position: relative; flex: none; width: 64px; height: 64px; padding: 0;
  border-radius: var(--radius-sm); border: 2px solid transparent; overflow: hidden;
  cursor: pointer; background: var(--panel-2);
}
.cr-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.cr-thumb__placeholder { display: block; width: 100%; height: 100%; background: var(--card-line); }
.cr-thumb--active { border-color: var(--brand); }
.cr-thumb__badge {
  position: absolute; top: 3px; right: 3px; min-width: 16px; height: 16px; padding: 0 4px;
  border-radius: 999px; background: var(--brand-strong); color: var(--brand-ink);
  font-size: 10.5px; font-weight: 700; line-height: 16px; text-align: center;
}

.cr-panel {
  background: var(--panel); border: 1px solid var(--card-line); border-radius: var(--radius);
  padding: 18px 20px; display: flex; flex-direction: column; gap: 14px;
}
.cr-panel__field { display: flex; flex-direction: column; gap: 6px; }
.cr-panel__field label {
  font-size: 11.5px; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); font-weight: 600;
}
.cr-textarea {
  width: 100%; box-sizing: border-box; min-height: 80px; resize: vertical; font: inherit; font-size: 13.5px;
  border: 1px solid var(--input-line); border-radius: var(--radius-sm); padding: 10px 12px;
  color: var(--text); background: var(--input-bg);
}
.cr-textarea:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-a16); }
.cr-attach-input { font-size: 13px; }
.cr-files { list-style: none; margin: 8px 0 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.cr-file {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  font-size: 12.5px; color: var(--text); background: var(--panel-2, var(--panel-2));
  border-radius: var(--radius-sm); padding: 6px 10px;
}
.cr-file__remove {
  background: none; border: none; cursor: pointer; color: var(--muted);
  font-size: 15px; line-height: 1; padding: 0 2px;
}
.cr-file__remove:hover { color: var(--text); }
.cr-panel__actions { display: flex; justify-content: flex-end; gap: 10px; }

/* ---------- Change requests: admin queue + detail (Task 7, changeRequests.js) ---------- */
/* `cr-admin*` is a distinct prefix from the agent-capture `cr-*` classes above — this is the
   admin-facing queue/detail screen (own nav item), not the gallery-view capture panel. */
.cr-admin .tbl__row[data-cr-id] { cursor: pointer; }

.cr-admin-head { margin-bottom: 14px; }

.cr-admin-status-bar {
  display: flex; align-items: center; gap: 10px; margin: 4px 0 22px;
}
.cr-admin-status-bar label {
  font-size: 12px; letter-spacing: .02em; font-weight: 500; color: var(--label);
}
.cr-admin-status-bar select {
  padding: 8px 12px; border: 1px solid var(--input-line); border-radius: var(--radius-sm);
  font-size: 13.5px; font-family: inherit; color: var(--text); background: var(--input-bg);
}
.cr-admin-status-bar select:focus {
  outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-a16);
}

.cr-admin-general-note, .cr-admin-attachments, .cr-admin-photos { margin-bottom: 26px; }
.cr-admin-general-note h3, .cr-admin-attachments h3, .cr-admin-photos h3 {
  font-size: 13px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted);
  margin: 0 0 10px;
}
.cr-admin-general-note p {
  font-size: 14px; line-height: 1.5; color: var(--text); margin: 0;
  background: var(--panel-2, var(--panel-2)); border-radius: var(--radius-sm); padding: 12px 14px;
  white-space: pre-wrap;
}

.cr-admin-attachments__list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
.cr-admin-attachment {
  font-size: 13px; font-family: inherit; color: var(--active-text); background: var(--active-bg);
  border: 1px solid var(--active-line); border-radius: 999px; padding: 6px 14px; cursor: pointer;
}
.cr-admin-attachment:hover { background: var(--active-bg); }

.cr-admin-photos { display: block; }
.cr-admin-photo-card {
  background: var(--panel); border: 1px solid var(--card-line); border-radius: var(--radius);
  padding: 16px; margin-bottom: 16px;
}
.cr-admin-photo-card__canvas {
  display: flex; justify-content: center; background: rgba(0,0,0,.2);
  border-radius: var(--radius-sm); padding: 12px; margin-bottom: 12px;
}
.cr-admin-photo-card__canvas .annot { max-width: 100%; }
.cr-admin-photo-card__canvas .annot__img { max-height: 480px; width: auto; }
.cr-admin-photo-card__notes {
  margin: 0 0 12px; padding-left: 20px; font-size: 13.5px; line-height: 1.6; color: var(--text);
}
.cr-admin-photo-card__notes li { margin-bottom: 2px; }

/* Working-days editor on the Photographers page. A row per weekday: tick to roster them on,
   then set the hours. Unticked days stay visible but disabled so the block does not reflow
   as you click through it. */
.crew-row__avail { font-size: 12px; color: var(--muted); margin-top: 4px; }
.avail-editor { border-top: 1px solid var(--line); margin-top: 12px; padding-top: 12px; }
.avail-editor__note { font-size: 12px; color: var(--muted); margin: 0 0 10px; max-width: 56ch; }
.avail-row { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; }
.avail-row .check { min-width: 92px; }
.avail-row__to { font-size: 12px; color: var(--muted); }
.avail-row input[type="time"] { width: 116px; }
.avail-row input[type="time"]:disabled { opacity: .45; }

/* Availability dots in each calendar day header. Grey wins over the crew colour deliberately:
   "not available" must read the same for everyone, and three faded colours at 16px are not
   distinguishable. */
/* WRAPS, and that is load-bearing. Each disc is a fixed 17px and this row is measured at its
   widest, so without wrapping the row IS the day column's minimum width: 9 discs came to
   9x17 + 8x3 gaps + 16 padding = 193px, five of those plus the 52px time gutter needed 1017px,
   and a portrait monitor sliced Friday off. Twenty pixels per column per photographer, a hundred
   across the week. Wrapping spends header HEIGHT on a bigger roster instead of column width.
   Measured after, same rig: the column sits at 141px flat from 5 crew to 12, and the header grows
   79px -> 99px at the seventh, one step per extra line of discs. */
.cal-avail { display: flex; flex-wrap: wrap; gap: 3px; justify-content: center; margin-top: 3px; }
.cal-avail__dot {
  width: 17px; height: 17px; border-radius: 50%; font-size: 8.5px; font-weight: 700;
  color: var(--on-solid); display: flex; align-items: center; justify-content: center; letter-spacing: -.02em;
}
/* Partly booked. The FILL is blended toward the page by grid.js (discFill "faded"), NOT dimmed
   with opacity — at .42 the initials landed at 1.77:1 against their own disc. */
.cal-avail__dot--faded { }
/* --label, not --muted-2. This dot is the only thing in the day header that says a photographer
   is not working, so it has to be readable even though it is meant to recede — --muted-2 on
   --line measured 3.37:1 in light and 3.2:1 in dark. --label still reads as secondary. */
.cal-avail__dot--grey { background: var(--line) !important; color: var(--label); }

/* Google Places suggestions under an address input. .skyaddr is inserted as the input's next
   sibling and is position:absolute, so it anchors to the nearest positioned ancestor — each
   input's own .field wrapper. None of the three .field wrappers used for address inputs
   (bk-address, nl-address, af-address) are positioned by any generic .field rule, so each is
   opted in individually here rather than making every .field relative. */
.field:has(> #bk-address),
.field:has(> #nl-address),
.field:has(> #af-address),
/* The Xero contact suggestions reuse .skyaddr and anchor to the agency NAME field the same way. */
.field:has(> #af-name) {
  position: relative;
}
.skyaddr {
  position: absolute; z-index: 60; left: 0; right: 0; margin-top: 2px;
  background: var(--panel); border: 1px solid var(--line);
  border-radius: 8px; box-shadow: 0 6px 20px rgba(0,0,0,.12); overflow: hidden;
}
.skyaddr__item {
  display: block; width: 100%; text-align: left; font: inherit; font-size: 13.5px;
  padding: 9px 12px; border: none; background: none; cursor: pointer; color: inherit;
}
.skyaddr__item:hover, .skyaddr__item:focus { background: var(--brand-a10); }

/* Shown to an agent in place of the photo grid while a shoot is undelivered. Deliberately calm:
   nothing is wrong, the work just isn't finished. */
.gallery-pending {
  margin: 18px 0; padding: 18px 20px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--panel-2); color: var(--muted); font-size: 14px; line-height: 1.55;
}
.gallery-pending strong { color: var(--text); }

/* Xero contact link state under the agency name field. The unlinked case is a warning, not a
   neutral "optional field": unlinked is what makes invoicing create a duplicate contact. */
.xero-link { margin-top: -4px; }
.xero-link__ok { font-size: 13px; color: var(--ok); display: flex; align-items: center; gap: 8px; }
.xero-link__warn { font-size: 13px; color: var(--warn); }
.pill--warn { color: var(--warn); border-color: var(--warn-line); background: var(--warn-bg); }

/* Agent invite preview panel (booking modal) */
.bk-preview__box { margin-top: 16px; border: 1px solid var(--card-line); border-radius: 10px; overflow: hidden; }
.bk-preview__to, .bk-preview__subj { padding: 8px 14px; font-size: 13px; color: var(--text);
  border-bottom: 1px solid var(--panel-2); }
/* White, not the cream page tint: both tabs now render a document inside the frame, and a tinted
   backdrop behind a white-bodied document showed as a border the content did not have. */
.bk-preview__body iframe { display: block; width: 100%; height: 460px; border: 0; background: var(--panel); }
.bk-preview__ics { padding: 10px 14px; font-size: 12px; color: var(--muted); }
.bk-preview__ics pre { max-height: 220px; overflow: auto; white-space: pre-wrap;
  word-break: break-all; margin: 8px 0 0; }
.bk-preview__loading, .bk-preview__error, .bk-preview__note { margin-top: 16px; padding: 12px 14px;
  font-size: 13px; color: var(--muted); }
.bk-preview__error { color: var(--danger); }

/* Agent / Photographer tabs on the invite preview panel */
.bk-tabs { display: flex; gap: 2px; margin-top: 16px; }
.bk-tab { padding: 8px 16px; font-size: 13px; font-family: inherit; cursor: pointer;
  border: 1px solid var(--card-line); border-bottom: none; background: var(--panel-2); color: var(--muted);
  border-radius: 8px 8px 0 0; }
.bk-tab--on { background: var(--panel); color: var(--text); font-weight: 600; }
.bk-tabs + .bk-preview__box { margin-top: 0; border-top-left-radius: 0; }
/* When/Where and the no-notes line moved INSIDE the photographer frame, so their rules live in
   PHOTOGRAPHER_BODY_CSS (booking-modal/index.js) — the frame is a separate document and cannot see
   this stylesheet. .bk-preview__meta*, .bk-preview__empty were removed here rather than left dead. */
.bk-preview__foot { padding: 10px 14px; font-size: 12px; color: var(--muted-2);
  border-top: 1px solid var(--panel-2); }

/* A booking that names an agent while invite_agent is off: the agent gets nothing, and without
   this the card is indistinguishable from one that invited them. */
.cal-card__flag, .tray-card__flag {
  display: inline-block; margin-top: 4px; padding: 1px 6px; border-radius: 4px;
  font-size: 10px; line-height: 1.5; letter-spacing: .02em; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.cal-card__flag { background: rgba(0,0,0,.28); color: var(--on-solid); }
.tray-card__flag { background: var(--warn-bg); color: var(--warn); }

/* Closed days (public holidays). The banner carries its own warn fill so it reads the same on
   today's coloured header as on any other. A closed day warns, it never blocks: the column is
   tinted, not disabled. #view-bookings beats the even-column and today tints. */
.cal-closed-label, .cal-closed-suggest {
  display: block; width: 100%; margin-top: 4px; padding: 2px 6px; border-radius: 4px;
  border: 1px solid var(--warn-line); background: var(--warn-bg); color: var(--warn);
  font: inherit; font-size: 10.5px; font-weight: 600; line-height: 1.4;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cal-closed-label { cursor: pointer; }
.cal-closed-label:hover { border-color: var(--warn); }
.cal-closed-suggest { white-space: normal; border-style: dashed; }
.cal-closed-suggest__name { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cal-closed-suggest__acts { display: flex; flex-wrap: wrap; justify-content: center; gap: 2px 6px; margin-top: 2px; }
.cal-closed-suggest__btn {
  border: 0; background: none; padding: 0; color: inherit; font: inherit; font-weight: 600;
  text-decoration: underline; cursor: pointer;
}
.cal-closed-suggest__btn:disabled { opacity: .5; cursor: default; }
#view-bookings .cal-day-col--closed { background-color: var(--warn-bg); }
.cal-card__flag--closed { background: var(--warn-bg); color: var(--warn); }
/* The closed-day form: bookings already on the day, and the public-holiday check under it. */
.cd-bookings {
  margin: 12px 0; padding: 8px 12px; border-radius: 6px;
  border: 1px solid var(--warn-line); background: var(--warn-bg); color: var(--warn);
}
.cd-bookings p { margin: 0 0 4px; font-weight: 600; }
.cd-bookings ul { margin: 0; padding-left: 18px; }
.cd-refresh { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; margin-top: 14px; }

/* ---------- Bookings calendar density (desktop only) ---------- */
/* On a 2K/4K screen the grid was ~1150px tall inside a 1500px viewport: four stacked bands of
   chrome (page heading, toolbar, legend, action row) ate 280px above it and the shell's 36/60px
   vertical padding took another 96. Everything here reclaims that space for the grid itself.
   Scoped to #view-bookings so the listings, invoicing and gallery pages keep their roomier
   spacing, and gated at 1200px so nothing below a laptop is touched — the phone layout in
   particular is left exactly as it is. */
@media (min-width: 1200px) {
  .main__inner--wide:has(#view-bookings:not(.hidden)) { padding: 18px 24px 16px; }
  /* The "Bookings" heading costs ~45px and says what the highlighted nav item already says.
     Outlook's calendar has no page title either. The date range stays — that one earns its
     space — and moves up into the heading's slot. */
  #view-bookings .page-head { margin-bottom: 0; }
  #view-bookings .page-head h1.page { display: none; }
  #view-bookings .page-head__sub { margin-top: 0; font-size: 15px; font-weight: 600; }
  #view-bookings .cal-toolbar { margin: 10px 0 10px; }
  #view-bookings .cal-legend { margin-bottom: 10px; }
  #view-bookings .cal-grid-wrap { padding-bottom: 0; }
  /* The rail only ever holds a short stack of pending cards; 260px of it was width the grid
     could use. */
  #view-bookings .tray-panel { flex: 0 0 220px; width: 220px; }
  #view-bookings .cal-layout { gap: 12px; }
}

/* ---------- Desktop density (>=1200px) ---------- */
/* Every control in the app was spaced for a 1080p laptop: 16px between fields, 12px of padding
   inside every input, 84px-tall textareas. On a 2K/4K screen that reads as a form floating in
   whitespace, and the booking modal in particular could not be seen in one go. These trim the
   shared primitives rather than any one screen, so the modal, the listings table, the agency
   pages and the invoicing forms all tighten together and stay consistent with each other.
   Gated at 1200px: laptops and phones keep the roomier spacing, which is what touch needs. */
@media (min-width: 1200px) {
  .field { margin-bottom: 11px; }
  .field label { margin-bottom: 4px; }
  .field input, .field select { padding: 8px 11px; }
  .field textarea { padding: 8px 11px; min-height: 60px; }
  .field-hint { margin-top: 4px; }
  .field-error, .field-notice { margin-top: 5px; }
  .btn { padding: 8px 15px; }
  .btn--sm { padding: 5px 10px; }

  .modal { padding: 18px 20px 20px; }
  .modal__head { margin-bottom: 12px; }
  /* The sticky action bar bleeds sideways to meet the modal's edge, so its negative margin has
     to track the padding above. Left at -24px against a 20px padding it overhangs by 4px each
     side and the modal grows a horizontal scrollbar. */
  .modal--wide .sec-actions { margin: 8px -20px 0; padding: 10px 20px; }
  /* The two scrolling pickers set their own heights and ignore the rules above. Both sit beside
     a column that is now shorter than they are, so they were what decided the modal's height. */
  .modal--wide .bk-cc-groups { max-height: 132px; }
  .bk-pkg-results { max-height: 150px; }
}

/* ---------- Phone layout (<=820px) ---------- */
/* Deliberately at the END of the file. Several calendar rules are declared at top level with no
   media query (.cal-layout, .tray-panel), so anything gated has to come after them to win on
   equal specificity. An earlier block is silently dead, which is exactly what happened to the
   980px stacking rules this replaces.
   820px reuses the breakpoint the sidebar drawer already uses, so the app has one definition of
   "narrow" rather than two that can drift. */

/* Desktop default, declared BEFORE the query so the query can override it. Putting it after
   would silently win on equal specificity and the phone wording would never appear. */
.tray-panel__hint--narrow { display: none; }

@media (max-width: 820px) {
  /* Pending bookings drops below the calendar instead of taking a 260px rail beside it. In a
     column, `flex: 0 0 260px` would set a 260px HEIGHT, so the basis has to be reset too. */
  /* nowrap is load-bearing. A wrapping COLUMN flex container is allowed to grow sideways into a
     second column, and it did: the grid measured 760px inside a 387px frame. */
  .cal-layout { flex-direction: column; flex-wrap: nowrap; align-items: stretch; }
  /* align-items must be stretch, not the desktop flex-start. In a column, flex-start sizes each
     child to its own content WIDTH, and the seven-day grid's content is ~760px, so the wrap grew
     past the 336px container and the page scrolled sideways. Stretch pins it to the container and
     the grid scrolls inside .cal-grid-wrap's own overflow-x instead. */
  .cal-layout .cal-grid-wrap { order: 1; width: 100%; max-width: 100%; }
  .tray-panel { order: 2; flex: 0 0 auto; width: 100%; align-self: auto; }

  /* Dragging does not work on touch, so the phone wording tells you what does. */
  .tray-panel__hint--wide { display: none; }
  .tray-panel__hint--narrow { display: inline; }
}
@media (max-width: 820px) {
  /* Folded legend + management buttons. Only rendered on a phone (renderLegend returns the bare
     legend on the desktop), so these rules have nothing to undo above the breakpoint. */
  .cal-fold { margin-bottom: 10px; border: 1px solid var(--line); border-radius: var(--radius-sm);
    background: var(--panel); }
  .cal-fold__summary { cursor: pointer; list-style: none; padding: 10px 14px;
    font-size: 13px; font-weight: 600; color: var(--text); }
  .cal-fold__summary::-webkit-details-marker { display: none; }
  .cal-fold__summary::after { content: "▾"; float: right; color: var(--muted); }
  .cal-fold[open] .cal-fold__summary::after { content: "▴"; }
  .cal-fold[open] .cal-fold__summary { border-bottom: 1px solid var(--line); }
  .cal-fold .cal-legend { margin: 0; padding: 12px 14px; }

  /* The VIEW AS pill's phone treatment is one block, near the bottom of this file with the
     dropdown anchoring it depends on. It used to be split across two media queries that
     disagreed about `position`, and the later one silently won. */
}
@media (max-width: 820px) {
  /* .field-row is a non-wrapping flex row whose children carry flex-basis 0, so three fields
     share whatever width there is. Duration holds two number inputs plus their h/m units and
     needs ~164px, so on a phone it was squeezed under that and the minutes box was clipped off
     the right edge of the modal.
     A 140px basis with wrapping lets two fields share a line at 390px and pushes the third onto
     its own full-width line, which is where Duration lands. Below ~320px they all stack, which
     is the right thing to happen rather than a cliff. */
  .field-row { flex-wrap: wrap; }
  .field-row .field { flex: 1 1 140px; }
  /* Belt and braces: if a narrower phone ever squeezes this, the units wrap instead of clipping. */
  .bk-dur { flex-wrap: wrap; }
}
@media (max-width: 820px) {
  /* Agency and Agent are both long: an agency name, and a search box whose placeholder is a
     sentence. Sharing a line at 390px gave each ~140px, so the select read "— Choose an agen"
     and the input "Search agents by r". They take a line each instead. */
  .field-row--orderer .field { flex: 1 1 100%; }

  /* Agent search rows put the name and the agency side by side. At 390px neither fits: every row
     read "Abbey ..." against "Biggin &...". Stacked, the name gets the full width and the agency
     sits under it, so both are legible and neither has to win. */
  .bk-agent-result { flex-direction: column; align-items: flex-start; gap: 1px; }
  .bk-agent-result__name, .bk-agent-result__agency { max-width: 100%; white-space: normal; }
}
@media (max-width: 820px) {
  /* .cal-grid carries min-width: 760px, a floor sized so seven day columns stay usable and the
     wrapper scrolls sideways on a narrow screen. The phone renders ONE day, so that floor is
     just an off-screen grid: the column was ~760px inside a 390px viewport and had to be
     scrolled to. Dropping it lets the single day fit the screen, which was the entire point of
     rendering one day. The desktop floor is untouched. */
  .cal-grid { min-width: 0; }
}

/* A hidden gallery folder, admin view only: the agent never receives it, so this styling can
   only ever be seen by someone who is allowed to know it exists. Muted rather than removed,
   because the admin still works in it. */
.gtab--hidden .gtab__select { opacity: .55; text-decoration: line-through; }
.gtab__hide {
  border: 0; background: transparent; cursor: pointer; padding: 0 8px 0 2px;
  font-size: 13px; line-height: 1; opacity: .7;
}
.gtab__hide:hover { opacity: 1; }

/* The full address a Google pick captured, under the street input. Empty until something is
   picked, so it collapses rather than leaving a gap on a hand-typed address. */
.bk-address-parts:empty { display: none; }
.bk-address-parts { color: var(--muted); }

/* ── Staged (unconfirmed) booking move ────────────────────────────────────────
   A drag or resize no longer writes straight through. The card shows where the booking is
   GOING, ghosted, and nothing is sent until Confirm — because /schedule re-syncs the Graph
   event and Graph emails the attendees, so move-then-resize used to invite the crew twice.

   Appended after the phone block on purpose: these selectors are new and overlap nothing in
   it, so the documented "phone rules last" ordering for .cal-layout still holds. */
.cal-card--ghost {
  opacity: 0.62;
  outline: 2px dashed rgba(255,255,255,0.85);
  outline-offset: -3px;
  box-shadow: 0 6px 18px rgba(0,0,0,0.28);
  /* Above its neighbours so a ghost overlapping another booking stays readable while you
     decide, rather than being half-hidden by the card it may be about to clash with. */
  z-index: 6;
}

/* Fixed, not anchored to the card: a staged move can sit on a week you have flipped away from,
   and the bar has to stay reachable and legible either way. */
.cal-pending {
  position: fixed;
  left: 50%;
  bottom: 22px;
  transform: translateX(-50%);
  z-index: 60;
  display: flex;
  align-items: center;
  gap: 18px;
  max-width: min(720px, calc(100vw - 24px));
  padding: 12px 16px;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: var(--panel);
  box-shadow: 0 10px 30px rgba(0,0,0,0.18);
}
.cal-pending__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.cal-pending__text strong {
  font-size: 14px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cal-pending__detail { font-size: 13px; color: var(--muted); }
.cal-pending__note { font-size: 12px; color: var(--muted); opacity: 0.85; }
/* "This job didn't happen" on the unschedule bar, shown only for a shoot whose time has passed.
   Full opacity and the normal text colour, unlike the note above it: this one is a control that
   changes what Confirm does, not a caption. */
.cal-pending__opt {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
  font-size: 12px;
  color: var(--text);
  cursor: pointer;
}
.cal-pending__opt input { cursor: pointer; }
.cal-pending__actions { display: flex; gap: 8px; flex: none; }

@media (max-width: 820px) {
  /* Full-width on a phone, sat above the bottom edge. The single-day grid leaves no room for a
     centred pill, and the actions wrap under the text rather than squeezing it out. */
  .cal-pending {
    left: 10px;
    right: 10px;
    bottom: 10px;
    transform: none;
    max-width: none;
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
  }
  .cal-pending__actions { justify-content: flex-end; }
}

/* Delete a job straight out of the Pending rail. Top-right of the card, out of the way of the
   address and packages, and only inked on hover/focus so a rail of ten jobs is not a row of ten
   bins competing with the content. */
.tray-card { position: relative; }
.tray-card__del {
  position: absolute;
  top: 6px;
  right: 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--muted);
  opacity: 0;
  cursor: pointer;
  transition: opacity .12s ease, background .12s ease, color .12s ease;
}
.tray-card:hover .tray-card__del,
.tray-card:focus-within .tray-card__del { opacity: .75; }
.tray-card__del:hover { opacity: 1; background: var(--danger-a12); color: var(--danger); }
.tray-card__del:focus-visible {
  opacity: 1;
  outline: 2px solid var(--danger);
  outline-offset: 1px;
}
/* An agent-request card already carries a badge in that corner; nudge the bin clear of it. */
.tray-card--agent .tray-card__del { top: 4px; right: 4px; }

@media (hover: none) {
  /* No hover on touch, so the bin has to be permanently visible or it is unreachable. Slightly
     larger target, in line with the rest of the phone layout. */
  .tray-card__del { opacity: .7; width: 30px; height: 30px; }
}

/* ---------- Phone density on the bookings calendar ----------
   The grid sizes itself to the space left over (computePxPerMin in calendar/grid.js), so every
   pixel of chrome above it comes straight out of the day. On a 390x844 phone the calendar had
   about a fifth of the screen while four control rows sat above it.

   This mirrors the >=1200px density block, which already does the same thing for the desktop.
   The auto-fit does the rest: reclaiming this space grows the grid rather than leaving a gap. */
@media (max-width: 820px) {
  /* "Bookings" costs 28px plus its margins and repeats the highlighted nav item. The date below
     it is the part that earns its place, so it moves up into the heading's slot. */
  #view-bookings .page-head h1.page { display: none; }
  #view-bookings .page-head { margin-bottom: 0; }
  #view-bookings .page-head__sub { margin-top: 0; font-size: 15px; font-weight: 600; }

  #view-bookings .cal-toolbar { margin: 10px 0 10px; }
  #view-bookings .cal-fold { margin-bottom: 8px; }
  #view-bookings .cal-grid-wrap { padding-bottom: 0; }

  /* The tray sits BELOW the grid here and is reached by scrolling, so the page no longer has to
     fit both. Trimming the bottom padding stops it padding out a fold nobody is looking at. */
  .main__inner:has(#view-bookings:not(.hidden)) { padding-top: 14px; padding-bottom: 16px; }
}

/* ---------- Phone calendar: scroll the day instead of squashing it ----------
   The desktop fits the whole visible window into the viewport so nothing scrolls, and .cal-grid
   clips to enforce that. On a phone that meant 12 hours in ~370px — 31px an hour, a 45-minute job
   23px tall and unreadable. computePxPerMin now floors the phone at 60px an hour (close to
   Outlook's mobile calendar), which makes the grid taller than the screen, so it has to scroll
   rather than clip.

   Both containers go overflow:visible. overflow-x:auto on the wrapper computes overflow-y to auto
   as well, and either one would turn the grid into its own scroll container — trapping the sticky
   day header inside it and clipping the taller grid. Horizontal scroll is not needed here anyway:
   the phone renders ONE day and .cal-grid already drops its 760px floor at this width. */
@media (max-width: 820px) {
  .cal-grid-wrap { overflow: visible; }
  .cal-grid { overflow: visible; }

  /* Sticky offsets clear the mobile topbar (40px control + 12px padding top and bottom + border),
     which is itself sticky at top:0 with a higher z-index. Without this the day header slides
     underneath it and the date is hidden exactly when scrolling makes it most useful. */
  .cal-grid__headers, .cal-grid__corner { top: 65px; }
}

/* A finger on the day column scrolls the day rather than drawing a new booking. `pan-x` above
   exists so pointermove can drive drag-to-create without the browser stealing the gesture, but
   that also means a vertical swipe does nothing — and since the phone grid became taller than
   the screen, a vertical swipe is the only way to reach the rest of the day.
   The pointerdown handler ignores touch at this width to match (see interactions.js). */
@media (max-width: 820px) {
  .cal-day-col { touch-action: auto; }

  /* Tap-and-hold to book was dying on Android: the OS read the long press as a TEXT SELECTION,
     grabbed "PM" out of the hour gutter and raised Chrome's Touch-to-Search bar. Starting a
     selection fires touchcancel, and interactions.js cancels the press timer on touchcancel, so
     the booking window never opened. It survived in a Chrome tab and failed in the installed app,
     which is what made it look like a PWA bug.
     There is nothing on the grid chrome worth selecting on a phone — hour labels and empty
     columns — so selection is turned off there and left ON inside a card, where the address is
     the one thing anyone would ever want to copy. */
  .cal-grid__time-col, .cal-grid__body, .cal-day-col, .cal-hour-label {
    -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;
  }
  .cal-card, .cal-timeoff, .cal-busy {
    -webkit-user-select: text; user-select: text; -webkit-touch-callout: default;
  }
}

/* ---------- Agency list on a phone ----------
   .agency-block__head is a non-wrapping flex row, and .agency-block__actions carries
   flex-shrink: 0. At 390px the actions refused to shrink and ran off the right edge, the name
   column collapsed to a sliver so the agency name wrapped a word per line, and the "No Xero
   contact" pill inside it wrapped to three lines inside a 999px radius — rendering as a circle
   sitting on top of the name.

   Letting the head wrap fixes all three at once: avatar and name take the first line with the
   width they need, the actions get a line of their own, and the show/hide toggle gets another. */
@media (max-width: 820px) {
  .agency-block__head { flex-wrap: wrap; gap: 10px 12px; padding: 16px; }
  .agency-block__head > .avatar { order: 0; }
  .agency-block__info { order: 1; flex: 1 1 auto; }
  /* 100% basis is what forces the break, so these can no longer overflow the card. */
  .agency-block__actions { order: 2; flex: 1 1 100%; padding-top: 0; gap: 8px 14px; }
  .agency-block__toggle { order: 3; flex: 1 1 100%; align-self: stretch; text-align: left; }
  .agency-block__body { padding: 4px 16px 14px; }

  /* A pill is a one-line label. Wrapping turns its 999px radius into a blob, so on a narrow
     screen it keeps its own line rather than being squeezed beside the name. */
  .agency-block__name { gap: 6px 10px; }
  .agency-block__name .pill { white-space: nowrap; }

  /* Agent rows have the same shape and the same squeeze: the action cluster is what overflows. */
  .agency-row { flex-wrap: wrap; }
  .agency-row__info { flex: 1 1 100%; }
  .agency-row__actions { flex: 1 1 100%; justify-content: flex-start; gap: 8px 12px; }
}

/* Held-finger feedback while a long press is counting down on a phone. Without it the 500ms
   before the modal appears reads as the tap having done nothing. */
.cal-day-col--pressing { background-color: var(--brand-a12); }

/* A <select> is as wide as its longest option, and these read "Melinda Xiberras — Leading Real
   Estate Sunbury Pty Ltd". The strip already wraps, which moved the select onto its own line but
   could not make it any narrower, so it ran off the right edge of the card. Capping it is what
   lets the wrap actually work. */
.gallery-admin-strip select { max-width: 100%; min-width: 0; }

@media (max-width: 820px) {
  /* Its own full-width line, so the long agent/agency label has somewhere to go. */
  .gallery-admin-strip select { flex: 1 1 100%; }

  /* Same shape as the agency block: the action cluster wraps INTERNALLY but carries
     flex-shrink: 0, so its four buttons set a max-content width it then refuses to come below.
     A full-width basis lets them wrap onto as many lines as they need instead of running off the
     card and giving the page a sideways scroll. */
  .gallery-head__actions { flex: 1 1 100%; }
  /* margin-left:auto pins Delete to the far right, which on a wrapped column leaves it stranded
     opposite Manage folders with a gap between them. */
  .gallery-admin-strip__delete { margin-left: 0; }
}

/* The VIEW AS menu is anchored right:0 against its trigger, which sits partway across the pill.
   On a phone that put a ~342px menu's right edge about 220px in, so it extended off the LEFT of
   the screen and the agency names were cut in half.
   Anchoring it to the pill instead makes it span the pill's width, which is the page width minus
   its padding — so it cannot run off either edge whatever the trigger's position. */
/* ---------- VIEW AS on a phone ----------
   It used to be a full-width dark bar at the top of the page content, which cost a whole row
   above the heading and — carrying z-index 60 against the drawer's 50 — painted straight over the
   open navigation menu. Now it is a compact chip pinned into the mobile top bar, which is space
   the app already spends.

   z-index 31 puts it one above the top bar it sits in (30) and BELOW both the drawer (50) and its
   backdrop (45), so opening the menu covers it instead of the other way round. */
@media (max-width: 820px) {
  .viewas-pill {
    position: fixed; top: 13px; right: 14px; bottom: auto; left: auto; z-index: 31;
    margin: 0; padding: 0;
    /* The hamburger and logo take a fixed ~109px on the left whatever the screen is, so the cap
       has to be viewport MINUS that, not a share of it. An earlier 40vw cap on the label alone
       overlapped the logo by 5px at 320px once an agent had a long name. */
    max-width: calc(100vw - 135px);
    background: none; box-shadow: none; border-radius: 999px; gap: 6px; flex-wrap: nowrap;
  }
  /* "VIEW AS" in caps costs more width than it explains next to a name. */
  .viewas-pill__label { display: none; }

  /* The chip IS the trigger here, so it carries the pill's own dark background. */
  .viewas-dd__trigger {
    background: var(--dark); border-color: transparent; color: var(--dark-ink);
    padding: 6px 10px; font-size: 12px; max-width: 100%;
    box-shadow: 0 6px 18px -8px rgba(0,0,0,.5);
  }
  .viewas-dd__current { display: none; }
  /* min-width: 0 is what lets it ellipsis. A flex item defaults to min-width: auto and refuses to
     shrink below its text, so overflow/text-overflow alone do nothing and the chip just grows. */
  .viewas-dd__short {
    display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }

  /* Viewing as someone else is the state that can mislead you about whose listings these are, so
     it keeps the agent's name AND takes the brand colour rather than staying quiet. */
  .viewas-pill--active .viewas-dd__trigger { background: var(--brand-strong); color: var(--brand-ink); }
  .viewas-pill__btn--icon { width: 28px; height: 28px; font-size: 14px; background: var(--dark); }

  /* The menu opens DOWNWARD from a trigger at the top of the screen, and spans the viewport
     rather than the chip — anchored to the chip it would inherit its ~100px width. */
  .viewas-dd { position: static; }
  .viewas-dd__menu {
    position: fixed; top: 52px; left: 12px; right: 12px; bottom: auto;
    width: auto; max-width: none; max-height: calc(100vh - 80px);
  }
}

/* "or drop files here" (files.js) describes a desktop drag-and-drop gesture that does not exist
   on a phone — tapping the label already opens the native file picker, which is the right
   behaviour there, so only the second line is meaningless and hidden. The input and its drop
   handlers are untouched; this is a label change only. */
@media (max-width: 820px) {
  .bk-file-add__or { display: none; }
}

/* Un-deliver dialog options. Both start unticked, so each row has to read as a thing the admin
   is choosing to do rather than a summary of what is about to happen anyway. */
.ud-opts { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
.ud-opt { display: flex; align-items: flex-start; gap: 8px; font-size: 14px; cursor: pointer; }
.ud-opt input { margin-top: 2px; }
.ud-warn { color: var(--muted); margin-top: 10px; }
