/* Seahorse Admin - Main Stylesheet */

/* Keep first paint independent of a third-party font stylesheet/connection.
   The same files are already embedded for PDF export, so the UI and exported
   artifacts continue to use identical Quicksand metrics. */
@font-face {
    font-family: 'Quicksand';
    src: url('/fonts/Quicksand-Regular.woff2?v=1') format('woff2');
    font-style: normal;
    font-weight: 400 500;
    font-display: swap;
}

@font-face {
    font-family: 'Quicksand';
    src: url('/fonts/Quicksand-Bold.woff2?v=1') format('woff2');
    font-style: normal;
    font-weight: 600 700;
    font-display: swap;
}

/* ═══════════════════════════════════════════════════════════════════════════
   Typography scale — ~1.2 ratio modular scale anchored to existing usage.
   Tokens below are the source of truth; bare h1..h6 and body pick them up,
   but existing module-specific overrides (e.g. children h4=23px, educators
   h2=18px) keep winning by CSS specificity. That means this scale fills
   gaps without disrupting what modules already style locally — safe to
   merge upstream, easy to revert from this one file if rejected.
   ═══════════════════════════════════════════════════════════════════════════ */
:root {
    --text-xs:   11px;  /* meta, timestamps, badges */
    --text-sm:   13px;  /* secondary body, table cells */
    --text-base: 15px;  /* default body text */
    --text-lg:   17px;  /* lead paragraphs */
    --text-xl:   20px;  /* h4, small section headers */
    --text-2xl:  24px;  /* h3, module headers */
    --text-3xl:  28px;  /* h2, page titles */
    --text-4xl:  32px;  /* h1, hero titles */
}

/* Global heading defaults. Module-specific rules (e.g. .children-module h4)
   have higher specificity and will override these. */
h1 { font-size: var(--text-4xl); }
h2 { font-size: var(--text-3xl); }
h3 { font-size: var(--text-2xl); }
h4 { font-size: var(--text-xl);  }
h5 { font-size: var(--text-lg);  }
h6 { font-size: var(--text-base); }

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Quicksand', sans-serif;
    font-size: var(--text-base); /* was unset — now 15px by default */
    background: #e0e0e0;
    margin: 0;
    padding: 0;
    /* dvh, not vh: `vh` is the LARGE viewport — the height the page would have
       with the browser's URL bar retracted. On a fleet tablet in Chrome that is
       744px while only 648px is visible, so the shell was sized 96px taller than
       the screen and `overflow: hidden` below made that strip unreachable by
       touch. Measured on the fleet AVD (1920x1200 @ 240dpi, Chrome 134):
       innerHeight 648, 100vh 744, 100dvh 648, 100svh 648, 100lvh 744.
       Desktop is unaffected — with no retractable chrome, dvh == vh. */
    height: 100dvh;
    color: #444;
    overflow: hidden;
}

.container {
    width: 100vw;
    /* Same reason as body above — this is the other half of the shell. */
    height: 100dvh;
    padding: 15px;
    display: flex;
    flex-direction: column;
    background: #e0e0e0;
    box-sizing: border-box;
}

/* Header Styles */
.header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 15px;
    background: rgba(255, 255, 255, 0.3);
    padding: 12px 20px;
    border-radius: 25px;
    box-shadow:
        0 4px 20px rgba(0, 0, 0, 0.12),
        0 1px 4px rgba(0, 0, 0, 0.06),
        inset 1.5px 1.5px 1px 0 rgba(255, 255, 255, 0.45),
        inset -1px -1px 1px 0.5px rgba(255, 255, 255, 0.35);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border: 1px solid rgba(255, 255, 255, 0.3);
    height: 70px;
    position: relative;
    /* Sits above all modal scrims (photos modal-overlay 9999,
       docs-modal-overlay 2000, lightbox 3000) so the flex bar +
       module name + notification icons stay clear/visible while
       the rest of the page (incl. side nav) gets frosted. */
    z-index: 10002;
    overflow: visible;
}

.educator-profile {
    display: flex;
    align-items: center;
    gap: 15px;
}

/* Name badge (not a letter-circle): color matches the dropdown's
   getEducatorColor(id) swatch. Sized for first name + last name stack. */
.profile-avatar {
    position: relative;
    cursor: pointer;
    min-width: 64px;
    max-width: 148px;
    min-height: 48px;
    padding: 7px 12px;
    background: #95a5a6; /* Admin gray until Alpine binds real color */
    border-radius: 14px;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    color: white;
    line-height: 1.15;
    text-align: center;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
    box-sizing: border-box;
}
.profile-avatar .avatar-first {
    font-size: 14px;
    font-weight: 700;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.profile-avatar .avatar-last {
    font-size: 11px;
    font-weight: 500;
    opacity: 0.92;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    margin-top: 1px;
}

/* App-shell educator picker: illustrated face instead of the name pill.
   Outer trigger stays overflow:visible so the dropdown is not clipped. */
.profile-avatar--face {
    width: 48px;
    height: 48px;
    min-width: 48px;
    max-width: 48px;
    padding: 0;
    background: transparent;
    border-radius: 50%;
    box-shadow: none;
}
.profile-avatar-face {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    overflow: hidden;
    background: #95a5a6;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
}
.profile-avatar-face .child-face {
    width: 100%;
    height: 100%;
}

.profile-info h2 {
    color: #21304d;
    font-size: 16px;
    margin-bottom: 3px;
}

.profile-info p {
    color: #7f8c8d;
    font-size: 13px;
}

/* ── Flex Bar ──────────────────────────────────────────────────────────────── */
/* Sizing rule: the bar grows to fit its content (module label, step
   title, or notification + buttons) but never exceeds 2/3 of the
   header width. That way long confirmation prompts like "Are you
   sure? Stay / Log Out" don't get clamped to "Ar...", but the bar
   also never eats the profile / notification slots on either side. */
.flex-bar-container {
    position: absolute;
    /* header-flex-bar-fit.js sets --flex-bar-centre and --flex-bar-cap from
       the measured profile card and icon cluster, so the bar is centred
       between them and never wider than the space between them. Unset
       (tablet, or before the script runs) these fall back to the header's
       midpoint and the fixed reserve below. */
    left: var(--flex-bar-centre, 50%);
    top: 50%;
    transform: translate(-50%, -50%);
    display: flex;
    justify-content: center;
    padding: 0 20px;
    min-width: 0;
    /* Absolutely positioned + centered on .header's midpoint, so it does
       NOT participate in the profile/notifications flex layout — it has
       no idea how much space either sibling needs. A flat 66.67% cap is
       not enough on its own: because it's CENTERED (not flexed), and the
       two siblings are unequal widths (profile ~186px, notification
       cluster ~285px), the box can drift into the wider (right) sibling
       even when its total width looks "reserved" for both combined.
       For a box centered at the midpoint to clear BOTH sides, its width
       must leave room for the WIDER sibling on both sides (the box only
       "sees" half its own width on each side) — i.e. reserve
       2 × max(profile, notifications), not their sum. Notifications is
       the larger at ~285px; 2×285=570, +80px safety margin = 650px. */
    max-width: min(66.67%, calc(100% - 650px), var(--flex-bar-cap, 100%));
    width: clamp(320px, 45vw, 720px);
}

/* Dashboard Operate mode carries the title, three top-level tabs, and the
   Drop-off / Pick-up switch in one row. Give that control-heavy state a
   wider desktop budget so "Attendance Logging" stays readable instead of
   ellipsizing. The existing max-width reservation still keeps the bar clear
   of the profile and header-action clusters; tablet mode overrides this
   later with its device-specific 46vw sizing. */
.flex-bar-container:has(.flex-bar-mode-toggle) {
    width: clamp(720px, 50vw, 840px);
}

/* Care actions and child navigation share the capsule's width budget. */
html:not([data-ui-mode="tablet"]) .flex-bar-container:has(.flex-bar-care-context) {
    width: clamp(720px, 50vw, 960px);
}
.flex-bar-container:has(.flex-bar-care-context) {
    container-type: inline-size;
    container-name: care-toolbar;
}
.flex-bar:has(.flex-bar-care-context) .flex-bar-back-controls {
    left: 0;
    flex-shrink: 0;
}

/* Belt-and-suspenders for the reservation above: at moderate header
   widths the box can still end up wide enough to visually reach the
   notification icon cluster (nested flex-basis:100% percentages 3
   levels down don't reliably propagate a hard pixel constraint to the
   actual button content, so the reservation above narrows the box but
   doesn't guarantee its content never overflows it). .notifications is
   a later DOM sibling with no z-index, so on overlap it paints ON TOP
   by default — exactly backwards for a state whose entire purpose is
   "click this button". Force the controls above the icons whenever
   they've taken over the bar, so Send/Cancel are ALWAYS the clickable,
   visible layer regardless of how the width math resolves. */
.flex-bar-container:has(.topbar-controls--centered) {
    z-index: 1;
}

/* Spacer not needed — label uses flex:1 to fill all space left of controls */
.flex-bar-spacer-left {
    display: none;
}

/* Label fills all space left of controls; text-align:center centers text within that space */
.label-centered {
    flex: 1;
    text-align: center;
    font-size: 20px;
    font-weight: 700;
    color: white;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}

/* Teleported controls fill their container and right-align content */
.children-top-bar-content,
.curriculum-top-bar-content,
.educators-top-bar-controls {
    width: 100%;
    min-width: 0;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 10px;
}

/* Controls take their natural width — do NOT grow into label's space.
   flex-start (not flex-end): flex-end + an oversized controls cluster
   overflows LEFT and paints over the module title ("Childre|" bug). */
#topBarControls {
    flex: 0 1 auto;
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 10px;
    min-width: 0;
}

/* Anonymous teleported wrappers (children/planner) and classed ones
   (docs/photos) must be allowed to shrink; otherwise they overflow
   leftward over .flex-bar-content. */
#topBarControls > div {
    min-width: 0;
}

/* Children landing flanks — one in-flow row on desktop. Tablet Operate
   lifts Search / Status+Rooms onto the candy row via display:contents
   (F0C2XM6088G). No inline display: the tablet rule must be able to
   win. */
.children-flexbar-flanks {
    display: flex;
    gap: 8px;
    align-items: center;
}
.children-flexbar-filters {
    display: flex;
    gap: 8px;
    align-items: center;
    flex-shrink: 0;
}

/* Photos/docs used to teleport into #flexBar with flex:1 / width:100%.
   Inside #topBarControls those claims crush the title — size to content. */
#topBarControls .gallery-topbar-filters,
#topBarControls .docs-top-bar-content {
    flex: 0 1 auto;
    width: auto;
    max-width: 100%;
    padding: 0;
    justify-content: flex-start;
    gap: 10px;
}

/* When empty, collapse entirely.

   `:empty` alone is not enough: a module that teleports a control and then
   hides it with x-show leaves a real (but `display: none`) child behind, so
   the container stops being `:empty` while still rendering nothing. It then
   contributes a 14px `gap` to the bar's flex row with no visible content —
   an unbalanced trailing slot that pushes the centred title left. Messaging
   sat 14px off centre for exactly this reason. Collapse on VISIBLE children
   instead, matching the predicate used by the .flex-bar-content rules. */
#topBarControls:empty,
#topBarControls:not(:has(> *:not(template):not([style*="display: none"]))) {
    display: none;
}

/* When label is hidden (ASQ modal flow), controls take full width and center.
   flex-wrap is a best-effort safety net for long content; the reliable
   guarantee that these controls stay clickable is the z-index rule on
   .flex-bar-container below (this element sits 3 levels of nested flex
   deep, and that chain does not reliably propagate a width constraint
   down to here — see the .flex-bar-container comment for why the fix
   is paint-order, not width). */
#topBarControls.topbar-controls--centered {
    flex: 1 1 100%;
    justify-content: center;
    flex-wrap: wrap;
}

/* ASQ nav fills full bar and centers the version picker */
.asq-topbar-nav {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
}

/* Top bar select styling (Children, Activity Planner) */
.topbar-select {
    font-size: 13px !important;
    font-family: 'Quicksand', sans-serif !important;
    font-weight: 500 !important;
    color: #444 !important;
    border-radius: 16px !important;
    border: 1px solid rgba(255, 255, 255, 0.4) !important;
    background: rgba(255, 255, 255, 0.85) !important;
    padding: 0 10px !important;
    height: 100% !important;
    outline: none !important;
    cursor: pointer !important;
    text-align: left !important;
    min-width: 80px !important;
}
.topbar-select:hover {
    background: rgba(255, 255, 255, 0.95) !important;
}


.flex-bar-container.curriculum-mode {
    /* width handled by flex */
}

.flex-bar {
    padding: 10px 28px;
    border-radius: 20px;
    font-weight: 700;
    font-size: 20px;
    cursor: default;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 14px;
    /* Fixed height so the bar is consistent across every module regardless of
       its controls (was content-driven min-height, ranging 47–60px per module).
       Content is centred; tallest control measured is ~38px, well under 56px. */
    box-sizing: border-box;
    height: 56px;
    width: 100%;
    min-width: 350px;
    white-space: nowrap;
    background: linear-gradient(-45deg,
        #4a6b8a, #5583a0, #4f9aa0, #579c94, #6e9280, #a08070,
        #b07a78, #b08465, #a89060, #8fa060, #6ea070, #5fa085,
        #4a9a98, #429aa8, #3ea0a0, #50a090, #6ca085, #8a9c7a,
        #a09480, #b07e85, #b07080, #a56a78, #9c5f6e, #8f5f6a,
        #8a5f78, #7a608a, #60709a, #5082a0, #4a6b8a);
    background-size: 600% 600%;
    animation: gradientFlow 180s ease infinite;
    backdrop-filter: blur(5px);
    -webkit-backdrop-filter: blur(5px);
    border: 1px solid rgba(255, 255, 255, 0.35);
    color: white;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
    overflow: visible;
    position: relative;
    isolation: isolate;

    transition: background 0.4s ease, border-color 0.3s ease;
    box-shadow:
        0 4px 10px rgba(0, 0, 0, 0.2),
        inset 0 1px 1px rgba(255, 255, 255, 0.35);
}

.flex-bar::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: linear-gradient(180deg, rgba(0, 0, 0, 0.08) 0%, rgba(0, 0, 0, 0.18) 100%);
    pointer-events: none;
    z-index: 0;
}

/* Left-side back-button slot. Absolutely pinned to the LEFT edge of
   the flex bar so Back never sits flush against the centred title —
   the title stays truly centred; Back is a separate leftmost control.
   Hidden when empty so the bar's padding / layout doesn't shift. */
.flex-bar-back-controls {
    display: inline-flex;
    align-items: center;
    z-index: 2;
    /* NOT absolute, despite how this rule used to read. `.flex-bar > *`
       below sets `position: relative` on every direct child and has the
       same specificity (0,1,0), so it wins on source order — the
       `absolute` never took effect. The `top: 50%` + `translateY(-50%)`
       centring idiom that came with it DID still apply, but relative
       offsets are measured from the in-flow position the bar has already
       centred via `align-items: center`, so together they pushed Back
       1.75px below centre (top 17px, translate −15.25px). Dropping both
       lets the flex container do the centring it was already doing.
       `left` is kept: under `relative` it is the nudge that insets Back
       from the bar's padding edge. */
    position: relative;
    left: 14px;
    margin-right: 0;
    pointer-events: auto;
}
.flex-bar-back-controls:empty {
    display: none;
}

/* Right-side counterpart: forward/next actions pinned to the far end of the
   bar. margin-left:auto is what pins it — it eats the slack the bar's other
   children leave, so Next sits hard right whatever the context content is.
   Contributes nothing when empty, same as the back slot. */
.flex-bar-next-controls {
    display: inline-flex;
    align-items: center;
    z-index: 1;
    position: relative;
    margin-left: auto;
}
.flex-bar-next-controls:empty {
    margin-left: 0;
}
/* :empty above means "no child NODES", which is not the same as "nothing
   drawn": a teleported control hidden by x-show is still a node, so the slot
   went on claiming the bar's free space with nothing visible in it and
   left-shifted its own module's title by 46px while every other module sat
   at 7. The owning template sets data-slot-vacant whenever its x-show is
   false (Alpine drops a bound attribute when the expression is falsy), so
   this reads the author's own intent rather than sniffing a serialised
   inline style. */
.flex-bar-next-controls:has(> [data-slot-vacant]) {
    margin-left: 0;
}
/* Children read view: the Previous / Next pair floats to the bar's edge
   instead of sitting in flow. In flow, the slot's width is subtracted from
   the space the title centres in (Next alone already sat the title ~19px
   left of the bar's centre; two labelled buttons would make that ~85px).
   The bar's backdrop-filter makes it the containing block, and right:28px
   is its own padding, so the buttons do not move from where Next sits
   today. The owning template sets data-child-nav-float only when neither
   the care-log nor the billing context bar is up — those keep the pair in
   flow, to the right of their own actions. Desktop only: the tablet bar's
   title wrapper stretches under out-of-flow controls (its capsule ran
   under Prev), and its compact pills do not need the centring. */
html:not([data-ui-mode="tablet"]) .flex-bar-next-controls:has(> [data-child-nav-float]) {
    position: absolute;
    top: 0;
    bottom: 0;
    right: 28px;
    margin-left: 0;
    display: inline-flex;
    align-items: center;
}
/* Same for Back in that view, or the title centres between an in-flow Back
   and nothing (≈43px right of centre). left:42px keeps Back exactly where
   its relative nudge put it (28px padding + 14px). Specificity (0,2,0)
   beats the `.flex-bar > *` relative rule this time. */
html:not([data-ui-mode="tablet"]) .flex-bar-back-controls:has(> [data-child-nav-float]) {
    /* Above the later full-width title layer, which otherwise intercepts clicks. */
    z-index: 3;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 42px;
    margin-right: 0;
    display: inline-flex;
    align-items: center;
}

/* ── Children-module billing-detail context bar ─────────────────────
   Hosted in the global flex bar via shared store state
   (see app.js childrenBillingContext + children.js syncBilling-
   Context). Replaces the in-module billing-detail-head actions row
   so the per-child detail view doesn't have a competing horizontal
   strip. Buttons here dispatch CustomEvents back to children.js. */
.flex-bar-billing-context {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    z-index: 1;
    position: relative;
    /* Allow the action group to shrink and the label to truncate
       on narrow viewports rather than overflowing the flex bar. */
    min-width: 0;
    max-width: 100%;
}
.flex-bar-billing-label {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    color: white;
    font-weight: 700;
    font-size: 16px;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
}
.flex-bar-billing-sep {
    opacity: 0.55;
    font-weight: 400;
}
.flex-bar-billing-actions {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

/* Daily-care context: the inherited module title leads at the left, the
   actions ride the right edge. The child itself is named by the log surface's
   own identity strip below, so the title here is the module.
   Right-aligning also pins Delete: the auto-save pill on its left comes and
   goes with every write, and a left-packed row would have slid the button
   sideways under the educator's finger each time. Anchored to the right edge,
   the pill grows leftward into the gap instead. */
.flex-bar-care-context {
    display: flex;
    width: 100%;
    align-items: center;
    justify-content: space-between;
    /* Roomier than the 8px .flex-bar-content default: this bar packs Back,
       title, Delete and Next Child into one strip, and at tablet tap sizes
       they read as one run-on cluster without it. padding-left holds the title
       off the teleported Back button, which sits outside this box. No
       padding-right: Delete is meant to land beside Next Child, and the bar's
       own 14px gaps either side of the empty #topBarControls already put ~28px
       between them. */
    gap: 18px;
    padding-left: 8px;
}
.flex-bar-care-title {
    /* Match every other flex-bar title (20px, inherited from .flex-bar) —
       .flex-bar-billing-label, which this shares for colour/weight/shadow,
       drops to 16px for the billing bar's longer child-name label. */
    font-size: inherit;
    /* Truncate rather than push the actions off the bar on a narrow tablet. */
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* space-between already pins this group to the right edge; flex-shrink:0 keeps
   Delete at full size when a long title competes for the row. */
.flex-bar-care-context .flex-bar-billing-actions {
    flex-shrink: 0;
    gap: 10px;
}
/* space-between only reaches the right edge if the container actually spans
   the row, and two rules shrink-wrap .flex-bar-content out from under it:
   the `:has(#topBarControls > *)` rule below (the children module teleports
   an inert <template> into that slot even in detail view, so it matches and
   its id makes it win) and the tablet width:auto override. Out-specify both
   by carrying an id in the :has() — the same trick, one level up. Safe to
   span here: in care mode #topBarControls holds nothing visible, which is
   the collision that rule exists to prevent. Offline-queue mode is excluded:
   it collapses the content deliberately to make room for the queue UI. */
.flex-bar:not(.flex-bar--offline-queue):has(#topBarControls) .flex-bar-content:has(.flex-bar-care-context) {
    width: 100%;
    flex: 1 1 auto;
    min-width: 0;
    justify-content: flex-start;
}
/* Dead 14px-gap item between the care actions and Next Child — the centred
   label it spaces is display:none whenever .flex-bar-content renders. */
.flex-bar:has(.flex-bar-care-context) .top-bar-spacer-left {
    display: none;
}

/* Same 8px hold-off for every OTHER detail bar (child read view, child
   billing, educator detail). #topBarBackControls is absolutely positioned,
   so it contributes no width and the title lands flush against the Back
   button — the daily-care bar was the only one holding itself off, which
   made the two read as differently padded when you toggle between them.
   Excludes the care bar so its own padding-left isn't doubled, and matches
   the slot on a VISIBLE child: the children module keeps its Back button
   teleported (x-show'd off) while the grid is up, so `> *` alone would
   shift the grid title too. */
.flex-bar:has(#topBarBackControls > *:not(template):not([style*="display: none"]))
    .flex-bar-content:not(:has(.flex-bar-care-context)) {
    padding-left: 8px;
}

/* Payer picker — child-name label as a clickable dropdown trigger
   when the billing surface has >1 contributor on the plan. Slice C
   of the 2026-05-19 consolidation. */
.flex-bar-payer-picker {
    position: relative;
}
.flex-bar-billing-label--clickable {
    font-family: inherit;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    background: transparent;
    border: none;
    color: white;
    cursor: pointer;
    /* Vertical padding only — horizontal stays 0 so the highlight
       doesn't bleed into the parent .flex-bar-billing-context's gap
       and crowd the Send button on the right. */
    padding: 4px 0;
    border-radius: 10px;
    transition: background 0.15s ease;
    text-shadow: inherit;
    line-height: inherit;
}
.flex-bar-billing-label--clickable:hover,
.flex-bar-billing-label--clickable.is-open {
    /* Background only paints within the button bounds (no negative
       margin); breathing room comes from the parent gap. */
    background: rgba(255, 255, 255, 0.18);
    padding-left: 8px;
    padding-right: 8px;
    margin-left: -4px;
    margin-right: -4px;
}
.flex-bar-billing-label-caret {
    opacity: 0.75;
    transition: transform 0.15s ease;
    margin-left: 2px;
    margin-right: 2px;
}
.flex-bar-billing-label--clickable.is-open .flex-bar-billing-label-caret {
    transform: rotate(180deg);
}
.flex-bar-billing-payer-pill {
    margin-left: 10px;
    padding: 3px 10px;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.22);
    border: 1px solid rgba(255, 255, 255, 0.45);
    font-size: 12px;
    font-weight: 600;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
.flex-bar-payer-menu {
    position: absolute;
    top: calc(100% + 10px);
    left: -8px;
    z-index: 100;
    min-width: 220px;
    padding: 4px;
    background: rgba(255, 255, 255, 0.97);
    border: 1px solid rgba(33, 48, 77, 0.08);
    border-radius: 14px;
    box-shadow:
        2px 5px 4px rgba(33, 48, 77, 0.08),
        3px 14px 22px rgba(33, 48, 77, 0.14);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
}
.flex-bar-payer-item {
    font-family: inherit;
    width: 100%;
    padding: 8px 12px;
    border-radius: 10px;
    border: none;
    cursor: pointer;
    color: #21304d;
    background: transparent;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    transition: background 0.12s ease;
    text-shadow: none;
}
.flex-bar-payer-item:hover {
    background: rgba(116, 185, 255, 0.14);
}
.flex-bar-payer-item.is-active {
    background: rgba(116, 185, 255, 0.22);
    color: #1e3a5f;
    font-weight: 600;
}
.flex-bar-payer-item-name {
    font-size: 13px;
    text-align: left;
}
.flex-bar-payer-item-share {
    font-size: 11px;
    color: #64748b;
    font-weight: 500;
    font-variant-numeric: tabular-nums;
}
.flex-bar-payer-item.is-active .flex-bar-payer-item-share {
    color: #2c5282;
}
.flex-bar-billing-btn {
    font-family: inherit;
    font-size: 12px;
    font-weight: 600;
    padding: 6px 10px;
    border-radius: 8px;
    border: 1px solid rgba(255, 255, 255, 0.45);
    cursor: pointer;
    color: white;
    background: rgba(255, 255, 255, 0.18);
    display: inline-flex;
    align-items: center;
    gap: 6px;
    backdrop-filter: blur(10px) saturate(1.1);
    -webkit-backdrop-filter: blur(10px) saturate(1.1);
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
    transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease;
}
.flex-bar-billing-btn:hover {
    background: rgba(255, 255, 255, 0.32);
    border-color: rgba(255, 255, 255, 0.65);
    transform: translateY(-1px);
}
.flex-bar-billing-btn--accent {
    /* Tax statement primary slot during the surge window. Filled
       accent so it pops against the muted Edit Plan + overflow. */
    background: rgba(116, 185, 255, 0.45);
    border-color: rgba(186, 215, 244, 0.75);
}
.flex-bar-billing-btn--accent:hover {
    background: rgba(116, 185, 255, 0.6);
    border-color: rgba(186, 215, 244, 0.85);
}
.flex-bar-billing-btn--icon {
    padding: 6px 8px;
}

.flex-bar-billing-overflow {
    position: relative;
}
.flex-bar-billing-btn--icon.is-open {
    background: rgba(255, 255, 255, 0.42);
    border-color: rgba(255, 255, 255, 0.75);
}
.flex-bar-billing-menu {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    z-index: 10;
    min-width: 180px;
    padding: 4px;
    background: rgba(255, 255, 255, 0.96);
    border: 1px solid rgba(33, 48, 77, 0.08);
    border-radius: 12px;
    box-shadow:
        2px 5px 4px rgba(33, 48, 77, 0.10),
        3px 14px 22px rgba(33, 48, 77, 0.16);
    display: flex;
    flex-direction: column;
    gap: 2px;
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
}
.flex-bar-billing-menu-item {
    font-family: inherit;
    font-size: 12px;
    font-weight: 600;
    padding: 8px 12px;
    border-radius: 8px;
    border: none;
    cursor: pointer;
    color: #21304d;
    background: transparent;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    text-align: left;
    width: 100%;
    transition: background 0.12s ease;
    text-shadow: none;
}
.flex-bar-billing-menu-item:hover {
    background: rgba(116, 185, 255, 0.14);
}
.flex-bar-billing-menu-item iconify-icon {
    color: #4a5670;
}

.flex-bar > * { position: relative; z-index: 1; }

/* White text treatment for controls teleported into the flex bar */
.flex-bar .asq-nav-label,
.flex-bar .asq-nav-caret { color: white; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); }
.flex-bar .asq-nav-arrow { color: white !important; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); }
.flex-bar input[type="text"] {
    color: white !important;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}
.flex-bar input[type="text"]::placeholder { color: rgba(255, 255, 255, 0.75); }

/* ═══════════════════════════════════════════════════════════════════════════
   Flex-bar search — gold standard (liquid glass)
   Use the `.docs-search` + `.docs-search-input` pattern for ANY search
   control hoisted into the flex bar, across all modules.
   ═══════════════════════════════════════════════════════════════════════════ */
.flex-bar .docs-search {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 14px;
    border-radius: 12px;
    background: rgba(255, 255, 255, 0.2);
    border: 1px solid rgba(255, 255, 255, 0.35);
    box-shadow:
        inset 1px 1px 0.5px 0 rgba(255, 255, 255, 0.35),
        inset -0.5px -0.5px 0.5px 0.5px rgba(255, 255, 255, 0.15);
    backdrop-filter: blur(8px) saturate(1.2);
    -webkit-backdrop-filter: blur(8px) saturate(1.2);
    transition: background 0.15s ease, border-color 0.15s ease;
}

.flex-bar .docs-search:focus-within {
    background: rgba(255, 255, 255, 0.3);
    border-color: rgba(255, 255, 255, 0.55);
}

.flex-bar .docs-search iconify-icon {
    color: rgba(255, 255, 255, 0.85) !important;
    flex-shrink: 0;
}

.flex-bar .docs-search-input {
    border: none;
    outline: none;
    background: none;
    font-family: 'Quicksand', sans-serif;
    font-size: 14px;
    font-weight: 500;
    width: 100%;
    color: white !important;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
    /* ellipsize instead of clipping mid-glyph when the box is capped
       (tablet caps the search width on control-heavy bars) */
    text-overflow: ellipsis;
}
.flex-bar .docs-search-input::placeholder { color: rgba(255, 255, 255, 0.75); }

@keyframes gradientFlow {
    0% {
        background-position: 0% 50%;
    }
    50% {
        background-position: 100% 50%;
    }
    100% {
        background-position: 0% 50%;
    }
}

/* Onboarding Child/Educator toggle inside the flex bar — compact pill
   pair styled to match the gradient header. Replaces the page-level
   "Select one of the following entities" prompt + tabs. */
.flex-bar-entity-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0;
    padding: 4px;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.18);
    border: 1px solid rgba(255, 255, 255, 0.25);
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.15);
    margin-left: 14px;
    flex-shrink: 0;
}
.flex-bar-entity-btn {
    appearance: none;
    -webkit-appearance: none;
    background: transparent;
    border: none;
    color: rgba(255, 255, 255, 0.85);
    font-size: 13px;
    font-weight: 700;
    padding: 6px 16px;
    border-radius: 999px;
    cursor: pointer;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
    transition: background 0.18s ease, color 0.18s ease;
}
.flex-bar-entity-btn:hover {
    background: rgba(255, 255, 255, 0.12);
}

/* Leave-without-saving control, left slot. Mirrors .flex-bar-entity-btn's
   sizing so the bar stays balanced around the centred label, but is
   deliberately lower-contrast: exiting is the recoverable action and must not
   compete with the commit button on the opposite side. */
.flex-bar-back-btn {
    appearance: none;
    -webkit-appearance: none;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    background: transparent;
    border: none;
    color: rgba(255, 255, 255, 0.7);
    font-size: 13px;
    font-weight: 600;
    padding: 6px 14px;
    border-radius: 999px;
    cursor: pointer;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
    transition: background 0.18s ease, color 0.18s ease;
}
.flex-bar-back-btn:hover {
    background: rgba(255, 255, 255, 0.12);
    color: rgba(255, 255, 255, 0.95);
}
.flex-bar-entity-btn.is-active {
    background: rgba(255, 255, 255, 0.95);
    color: #21304d;
    text-shadow: none;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
}

/* Action button in flex bar (Add Child, Save and Exit) - matches gallery Send button style */
.flex-bar-entity-btn.action-btn {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 8px 14px;
    background: #10b981;
    color: white;
    border: none;
    border-radius: 8px;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.2s ease;
    white-space: nowrap;
    text-shadow: none;
    margin-left: 10px;
}

.flex-bar-entity-btn.action-btn:hover {
    background: #059669;
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
}

.flex-bar-entity-btn.action-btn.btn-outline {
    background: transparent;
    border: 2px solid rgba(255, 255, 255, 0.6);
    color: rgba(255, 255, 255, 0.85);
}

.flex-bar-entity-btn.action-btn.btn-outline:hover {
    background: rgba(255, 255, 255, 0.12);
    transform: none;
    box-shadow: none;
}

.flex-bar-entity-btn.action-btn.btn-disabled {
    cursor: not-allowed;
    background: rgba(255, 255, 255, 0.15) !important;
    border: 2px solid rgba(255, 255, 255, 0.4) !important;
    color: rgba(255, 255, 255, 0.65) !important;
    box-shadow: none !important;
}

.flex-bar-entity-btn.action-btn.btn-disabled:hover {
    transform: none !important;
    box-shadow: none !important;
    background: rgba(255, 255, 255, 0.15) !important;
}

.flex-bar-entity-btn.action-btn.btn-disabled iconify-icon {
    opacity: 0.65;
}

/* ── Flex Bar – module content wrapper ──────────────────────────────────── */
.flex-bar-content {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    width: 100%;
    animation: flexBarFadeIn 0.3s ease both;
    min-width: 0;
}
/* When teleported controls sit beside the title (room/week, filters),
   don't force the title wrapper to width:100% — that collides with
   #topBarControls and paints the title under the pickers. Keep a
   readable ellipsis floor so long titles yield space to controls.

   The predicate must mean "a control is VISIBLE beside the title", not
   "an element exists". A bare `> *` also matched Alpine's inert `<template>`
   hosts, which never render: at one click into Children the only child of
   #topBarControls is such a template, so the title wrapper collapsed from
   100% to its intrinsic width and the spacer vanished, leaving the bar
   `justify-content: center` with nothing to fill it — Back and the title
   clustered in the middle of the bar. Excluding templates and x-show'd
   (`style="display: none"`) children matches what the tablet rules below
   already do with `> div:not([style*="display: none"])`. */
.flex-bar:has(#topBarControls > *:not(template):not([style*="display: none"])) .flex-bar-content {
    width: auto;
    flex: 0 1 auto;
    min-width: 0;
    justify-content: flex-start;
}
.flex-bar:has(#topBarControls > *:not(template):not([style*="display: none"])) .flex-bar-content > span {
    min-width: 72px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Empty spacer between title and controls must not steal gap budget. */
.flex-bar:has(#topBarControls > *:not(template):not([style*="display: none"])) .top-bar-spacer-left {
    display: none;
}
/* The spacer is the LEFT action slot, and its job is to BALANCE a leading
   item so the centred title stays centred: Back on the left contributes one
   14px gap, the spacer contributes the matching one on the right, and they
   cancel. With no Back rendered there is nothing to balance, so the spacer
   becomes a one-sided 14px trailing gap that drags the title left — the
   other half of Messaging's off-centre label. Only carry the counterweight
   when there is a weight. */
.flex-bar:not(:has(.flex-bar-back-controls:not(:empty))) .top-bar-spacer-left {
    display: none;
}

@keyframes flexBarFadeIn {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* ── Flex Bar – notification ─────────────────────────────────────────────── */
/* Notification used to be `position: absolute; inset: 0` so it
   overlaid the bar perfectly. Side effect: it didn't contribute to
   the bar's intrinsic size, so "Are you sure? Stay / Log Out" got
   crushed into the inherited 350px min-width. Now in-flow so the
   bar grows to fit its own content (and gets clamped only by
   .flex-bar-container's 2/3 max-width). */
.flex-bar-notif {
    /* Flow naturally so the parent flex-bar grows vertically with
       wrapped multi-line warnings. Siblings (spacers / nav controls)
       are pulled out of flow below so they don't reserve height. */
    position: relative;
    display: flex;
    align-items: center;
    gap: 15px;
    padding: 10px 25px;
    animation: flexBarSlideIn 0.25s ease;
    justify-content: center;
    /* Same radius as the outer .flex-bar (set below when a notif is
       showing). Stretch to full bar height so we never leave a shorter
       inner pill floating inside a taller outer shell — that double
       radius was the corner overflow/rim users saw on error toasts. */
    border-radius: inherit;
    align-self: stretch;
    width: 100%;
    box-sizing: border-box;
    white-space: nowrap;
    /* Parent already paints the type gradient; keep this layer clear so
       only one rounded surface is visible. */
    background: transparent !important;
}

.flex-bar:has(.flex-bar-notif) {
    /* Bar collapses to the notification's natural width — the parent
       container caps it at ~92vw of the header when a notif is up
       (see .flex-bar-container:has(.flex-bar-notif) below). Floor stays
       at the regular .flex-bar min-width so a one-word toast still
       looks like a bar, not a button. */
    width: max-content;
    min-width: 350px;
    max-width: 100%;
    padding: 0;
    /* Drop the fixed 56px height so multi-line errors aren't clipped,
       and use a rounder radius (matches header/side-nav 25px language)
       shared with the inner notif via border-radius: inherit. */
    height: auto;
    min-height: 56px;
    border-radius: 25px;
    overflow: hidden;
    box-shadow: none;
    border-color: transparent;
}

/* When a notification is showing, widen the container cap so the bar
   grows HORIZONTALLY first — prose like "Email invoice X to Y (no email
   on file — Stripe will use the address from their associate record)
   via Stripe?" fits on one or two lines instead of three. Plain
   navigation labels are still bounded by the original 720 px clamp
   defined on the base .flex-bar-container rule.

   Cap: header width minus ~640 px reserved for the left logo/profile
   cluster and the right notifications + AI bubble. Container is
   centered (left:50% + translateX(-50%)) so the symmetric reserve is
   what keeps wide confirms from sliding under the side icons. The
   right cluster (context-add + AI bubble + chat + 2-3 notification
   icons + container padding) needs ~310 px clearance; mirroring that
   on the left keeps the centered bar symmetric. */
.flex-bar-container:has(.flex-bar-notif) {
    width: max-content;
    max-width: min(calc(100% - 640px), var(--flex-bar-cap, 100%));
}

/* Offline queue chip + module controls (e.g. Activity Planner room/week)
   need more horizontal room than the default 45vw/720px clamp. Grow to
   content so nothing clips; keep the same side clearance as notifications
   so the bar never slides under profile / header icons. */
.flex-bar-container--offline-queue {
    width: max-content;
    max-width: min(92%, calc(100% - 480px));
}
.flex-bar--offline-queue {
    width: max-content;
    max-width: 100%;
    min-width: 350px;
    gap: 12px;
    padding-left: 14px;
    padding-right: 18px;
    height: auto;
    min-height: 56px;
    /* Single row: chip | title | controls — no full-width children
       stacking/overlapping each other. */
    justify-content: center;
    flex-wrap: nowrap;
    overflow: visible;
}
/* .flex-bar-content defaults to width:100%, which forces the title
   strip to eat the whole bar and paint over room/week controls once
   we switch to content-sized width. Collapse to intrinsic width. */
.flex-bar--offline-queue .flex-bar-content {
    width: auto;
    flex: 0 0 auto;
    justify-content: flex-start;
    min-width: 0;
}
/* label-centered duplicates the title already rendered inside
   .flex-bar-content for non-dashboard modules (tablet already hid
   this; desktop only blew up once offline expanded the bar). */
.flex-bar:has(.flex-bar-content) .label-centered {
    display: none !important;
}
.flex-bar--offline-queue #topBarControls,
.flex-bar--offline-queue .flex-bar-mode-toggle,
.flex-bar--offline-queue .flex-bar-back-controls,
.flex-bar--offline-queue .flex-bar-next-controls,
.flex-bar--offline-queue .offline-queue-wrap {
    flex: 0 0 auto;
    min-width: 0;
}
.flex-bar--offline-queue .offline-queue-wrap {
    margin-right: 0;
}
/* Empty left spacer must not steal space in the content-sized row. */
.flex-bar--offline-queue .top-bar-spacer-left {
    display: none;
}

/* Activity Planner command bar — one layout for every crowded state.
   No draft, a saved draft, editing, an empty week, the SSO Expires
   chip, the chip plus a draft, a long room name, and the loading or
   retry icons all use this rule. There is no per-state width.

   The quiet floor stays the 45vw/720px clamp. The ceiling is
   --planner-bar-cap: a length set from the left edge of the header's
   right-hand button group, minus a 16px gap. The bar is centered, and
   this container's 20px padding sits outside the gradient, so the
   length is 2 * (groupLeft - center - 16px + 20px). It is not a
   percentage. Until that measure runs, the cap is the 720px floor,
   which stays clear of the button group.

   Space gives way in order, via data-planner-fit on #flexBar:
     full       nothing yields
     title      "Activity Planner" ellipsizes
     chip-day   the Expires line shortens to "Expires Fri"
     chip-icon  the Expires line is the clock icon only
     room       the room name ellipsizes
   Week dates and the week arrows stay on --planner-week-pin and are
   never cut short. Icon buttons do not shrink. Inline flex:none and
   fixed chip widths are overridden with !important. */
html:not([data-ui-mode="tablet"]) .flex-bar-container:has(#topBarControls .asq-nav-arrow) {
    --planner-week-pin: 148px;
    width: max-content;
    min-width: min(var(--planner-bar-cap, 720px), clamp(320px, 45vw, 720px));
    max-width: var(--planner-bar-cap, 720px);
}
html:not([data-ui-mode="tablet"]) .flex-bar:has(#topBarControls .asq-nav-arrow) {
    width: max-content;
    min-width: 100%;
    max-width: 100%;
    flex-wrap: nowrap;
}
html:not([data-ui-mode="tablet"]) .flex-bar:has(#topBarControls .asq-nav-arrow) .flex-bar-content {
    width: auto;
    flex: 0 1 auto;
    min-width: 0;
    justify-content: flex-start;
}
/* Title yields first. The full stage keeps the words intact. */
html:not([data-ui-mode="tablet"]) .flex-bar[data-planner-fit="full"]:has(#topBarControls .asq-nav-arrow) .flex-bar-content > span {
    flex: 0 0 auto;
    min-width: auto;
    overflow: visible;
    text-overflow: clip;
    white-space: nowrap;
}
html:not([data-ui-mode="tablet"]) .flex-bar:not([data-planner-fit="full"]):has(#topBarControls .asq-nav-arrow) .flex-bar-content > span {
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Once the title has yielded, keep it yielded. Shortening the chip or
   the room must not hand that space back and paint the full title. */
html:not([data-ui-mode="tablet"]) .flex-bar:is([data-planner-fit="chip-day"], [data-planner-fit="chip-icon"], [data-planner-fit="room"]):has(#topBarControls .asq-nav-arrow) .flex-bar-content > span {
    flex: 0 1 72px;
    max-width: 72px;
}
html:not([data-ui-mode="tablet"]) .flex-bar:has(#topBarControls .asq-nav-arrow) #flex-bar-session-expiry {
    flex: 0 0 auto;
}
html:not([data-ui-mode="tablet"]) .flex-bar:has(#topBarControls .asq-nav-arrow) #topBarControls {
    flex: 0 1 auto;
    min-width: 0;
}
html:not([data-ui-mode="tablet"]) .flex-bar:has(#topBarControls .asq-nav-arrow) #topBarControls > div {
    min-width: 0;
    max-width: 100%;
}
/* Week pack is a fixed pin so stepping weeks does not resize the bar,
   and the date range is not ellipsized. */
html:not([data-ui-mode="tablet"]) .flex-bar:has(#topBarControls .asq-nav-arrow) #topBarControls > div > :nth-child(2) {
    flex: 0 0 auto !important;
}
html:not([data-ui-mode="tablet"]) .flex-bar:has(#topBarControls .asq-nav-arrow) #topBarControls > div > :nth-child(2) .asq-nav-label-wrap {
    flex: 0 0 auto !important;
    width: var(--planner-week-pin) !important;
    min-width: var(--planner-week-pin) !important;
    max-width: var(--planner-week-pin) !important;
}
html:not([data-ui-mode="tablet"]) .flex-bar:has(#topBarControls .asq-nav-arrow) #topBarControls > div > :nth-child(2) .asq-nav-label {
    display: block;
    overflow: visible;
    text-overflow: clip;
    white-space: nowrap;
}
/* Room name yields last. Until that stage it keeps its full text. */
html:not([data-ui-mode="tablet"]) .flex-bar:not([data-planner-fit="room"]):has(#topBarControls .asq-nav-arrow) #topBarControls > div > :first-child {
    flex: 0 0 auto !important;
}
html:not([data-ui-mode="tablet"]) .flex-bar:not([data-planner-fit="room"]):has(#topBarControls .asq-nav-arrow) #topBarControls > div > :first-child .asq-nav-label-wrap {
    flex: 0 0 auto !important;
    width: auto !important;
    min-width: max-content !important;
    max-width: none !important;
}
html:not([data-ui-mode="tablet"]) .flex-bar[data-planner-fit="room"]:has(#topBarControls .asq-nav-arrow) #topBarControls > div > :first-child {
    flex: 0 1 auto !important;
    min-width: 0 !important;
    max-width: 200px;
}
html:not([data-ui-mode="tablet"]) .flex-bar[data-planner-fit="room"]:has(#topBarControls .asq-nav-arrow) #topBarControls > div > :first-child .asq-nav-label-wrap {
    flex: 1 1 auto !important;
    width: auto !important;
    min-width: 0 !important;
    max-width: 100% !important;
}
html:not([data-ui-mode="tablet"]) .flex-bar[data-planner-fit="room"]:has(#topBarControls .asq-nav-arrow) #topBarControls > div > :first-child .asq-nav-label {
    display: block;
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
html:not([data-ui-mode="tablet"]) .flex-bar:has(#topBarControls .asq-nav-arrow) #topBarControls .asq-nav-arrow,
html:not([data-ui-mode="tablet"]) .flex-bar:has(#topBarControls .asq-nav-arrow) #topBarControls .ap-draft-actions {
    flex: 0 0 auto !important;
}
html:not([data-ui-mode="tablet"]) .flex-bar:has(#topBarControls .asq-nav-arrow) .top-bar-spacer-left {
    display: none;
}
/* Expires chip stages. The full string stays in the label for the
   screen reader. chip-day paints "Expires Fri" (data-expiry-short).
   chip-icon and room hide the words and leave the clock icon. */
.flex-bar[data-planner-fit="chip-day"] #flex-bar-session-expiry [data-session-expiry-label] {
    font-size: 0;
    line-height: 0;
}
.flex-bar[data-planner-fit="chip-day"] #flex-bar-session-expiry [data-session-expiry-label]::before {
    content: attr(data-expiry-short);
    font-size: 12px;
    line-height: 16px;
    font-weight: 600;
}
.flex-bar[data-planner-fit="chip-icon"] #flex-bar-session-expiry [data-session-expiry-label],
.flex-bar[data-planner-fit="room"] #flex-bar-session-expiry [data-session-expiry-label] {
    display: none;
}

/* Hide the regular bar children outright when a notification is
   present — `visibility: hidden` preserved their layout space, which
   prevented the notif from sizing the bar. `display: none` lets the
   notif drive the width. */
/* The notification markup moved into a <seahorse-flex-bar-notif> custom
   element (js/shell-chrome.js) so the shells stop duplicating it. That added a
   wrapper the selector below did not know about: `:has()` matches DESCENDANTS
   so the rule still fired, but `>` matches DIRECT CHILDREN, and the direct
   child is now the wrapper — which is not `.flex-bar-notif`. So this rule was
   hiding the notification's own container along with the siblings, and the bar
   went completely blank on every notify()/confirm(). `flexBarSlideIn` then
   froze at its `from` state, which is why the element also measured opacity 0.

   Cost while it was broken: flexBar.confirm() renders the Confirm/Cancel
   buttons, so any flow gated on one was unreachable — deleting a child avatar
   among them, which read as "there is no way to delete a photo".

   `display: contents` puts .flex-bar-notif back in the bar's own flex layout,
   as it was before the wrapper existed. It does NOT affect selector matching,
   so the :not() exclusion is still required. */
seahorse-flex-bar-notif {
    display: contents;
}

.flex-bar:has(.flex-bar-notif) > *:not(.flex-bar-notif):not(seahorse-flex-bar-notif) {
    display: none !important;
}

@keyframes flexBarSlideIn {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: translateY(0);    }
}

.flex-bar-notif__icon {
    font-size: 16px;
    font-weight: 800;
    flex-shrink: 0;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(255,255,255,0.25);
}

.flex-bar-notif__track {
    flex: 0 0 88px;
    height: 6px;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.35);
    overflow: hidden;
}
.flex-bar-notif__fill {
    height: 100%;
    background: #fff;
    border-radius: inherit;
    transition: width 0.2s ease;
}

.flex-bar-notif__msg {
    /*
      Use flex:1 with min-width:0 so the message is allowed to shrink
      inside the (centered, width-capped) flex-bar. Wrap on long copy so
      the full warning is readable — bar grows vertically instead of
      truncating with "…".
    */
    flex: 1 1 auto;
    min-width: 0;
    max-width: 100%;
    font-size: 16px;
    font-weight: 700;
    text-shadow: 0 1px 2px rgba(0,0,0,0.15);
    line-height: 1.3;
    /* pre-line: honour explicit \n as a visual break while still
       wrapping long copy. Lets callers control sentence splits. */
    white-space: pre-line;
    text-align: center;
}

/* When a notification is active, relax the parent flex-bar's nowrap so
   the inner message can lay out within the bar rather than forcing the
   whole bar to its intrinsic width. */
.flex-bar:has(.flex-bar-notif) {
    white-space: normal;
}

.flex-bar-notif__actions {
    display: flex;
    align-items: center;
    gap: 12px;
    flex-shrink: 0;
}

/* Notification type — paint the OUTER flex-bar only. Inner .flex-bar-notif
   stays transparent so outer and inner share one radius (no double pill). */
.flex-bar-notif--success,
.flex-bar-notif--error,
.flex-bar-notif--warning,
.flex-bar-notif--info,
.flex-bar-notif--confirm {
    background: transparent !important;
    animation: flexBarSlideIn 0.25s ease;
}

.flex-bar:has(.flex-bar-notif--success) { background: linear-gradient(135deg, #00b894, #00cec9) !important; animation: none; border-color: rgba(0,184,148,0.5); }
.flex-bar:has(.flex-bar-notif--error)   { background: linear-gradient(135deg, #d63031, #e17055) !important; animation: none; border-color: rgba(214,48,49,0.5); }
.flex-bar:has(.flex-bar-notif--warning) { background: linear-gradient(135deg, #e17055, #fdcb6e) !important; animation: none; border-color: rgba(253,203,110,0.5); }
.flex-bar:has(.flex-bar-notif--info)    { background: linear-gradient(135deg, #74b9ff, #a29bfe) !important; animation: none; border-color: rgba(116,185,255,0.5); }
.flex-bar:has(.flex-bar-notif--confirm) { background: linear-gradient(135deg, #6c5ce7, #a29bfe) !important; animation: none; border-color: rgba(108,92,231,0.5); }

/* Notification buttons */
.flex-bar-notif__btn {
    padding: 8px 18px;
    border-radius: 12px;
    border: 1px solid rgba(255, 255, 255, 0.3);
    font-size: 14px;
    font-weight: 700;
    font-family: 'Quicksand', sans-serif;
    cursor: pointer;
    transition: all 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.4);
    white-space: nowrap;
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
    box-shadow:
        inset 1px 1px 0.5px 0 rgba(255, 255, 255, 0.3),
        inset -0.5px -0.5px 0.5px 0.5px rgba(255, 255, 255, 0.2);
}
.flex-bar-notif__btn:hover { transform: scale(1.05); }

.flex-bar-notif__btn--primary   { background: rgba(255, 255, 255, 0.85); color: #6c5ce7; }
.flex-bar-notif__btn--secondary { background: rgba(255,255,255,0.2); color: white; border: 1px solid rgba(255,255,255,0.4); }
.flex-bar-notif__btn--danger    { background: rgba(214, 48, 49, 0.85); color: white; }

.flex-bar-notif__close {
    width: 22px;
    height: 22px;
    border-radius: 50%;
    border: none;
    background: rgba(255,255,255,0.2);
    color: white;
    font-size: 11px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.2s ease;
    flex-shrink: 0;
    font-family: 'Quicksand', sans-serif;
}
.flex-bar-notif__close:hover { background: rgba(255,255,255,0.35); }

.message-notification-dot {
    width: 8px;
    height: 8px;
    background-color: #f1c40f;
    border-radius: 50%;
}

.messages-dropdown {
    display: none;
    position: absolute;
    top: 100%;
    left: 50%;
    transform: translateX(-50%);
    background: rgba(255, 255, 255, 0.75);
    border-radius: 16px;
    box-shadow:
        0 8px 28px rgba(0, 0, 0, 0.12),
        0 2px 8px rgba(0, 0, 0, 0.06),
        inset 1.5px 1.5px 1px 0 rgba(255, 255, 255, 0.5),
        inset -1px -1px 1px 0.5px rgba(255, 255, 255, 0.4);
    padding: 20px;
    z-index: 110;
    margin-top: 10px;
    width: 450px;
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    border: 1px solid rgba(255, 255, 255, 0.4);
}

.messages-dropdown.show {
    display: block;
}

.message {
    padding-bottom: 10px;
    margin-bottom: 10px;
    border-bottom: 1px solid #ecf0f1;
}

.message:last-child {
    border-bottom: none;
    margin-bottom: 0;
}

.message p {
    font-size: 14px;
    color: #34495e;
    margin-bottom: 5px;
}

.message span {
    font-size: 11px;
    color: #7f8c8d;
}

/* Trailing header cluster: (+) disc + notification icons. Desktop keeps
   them together on the right (same visual as when (+) lived inside
   .notifications). Tablet Operate uses display:contents so (+) becomes
   its own in-flow disc beside the candy house flex-bar. */
.header-trailing {
    --header-icon-gap: 15px;
    display: flex;
    align-items: center;
    gap: var(--header-icon-gap);
    margin-left: auto;
    flex: 0 0 auto;
}

/* Notifications */
.notifications {
    /* Read back by .header-icon-collapsible to cancel its own gap share
       when it collapses — keep the two in step by changing only this. */
    --header-icon-gap: 15px;
    display: flex;
    align-items: center;
    gap: var(--header-icon-gap);
}

.notification-item {
    position: relative;
    cursor: pointer;
}

.notification-icon {
    width: 45px;
    height: 45px;
    background: rgba(255, 255, 255, 0.25);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border: 1px solid rgba(255, 255, 255, 0.3);
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #21304d;
    font-size: 18px;
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15),
                inset 0 1px 1px rgba(255, 255, 255, 0.5),
                inset 0 -1px 1px rgba(0, 0, 0, 0.1);
    transition: all 0.3s ease;
}

button.notification-icon {
    padding: 0;
    cursor: pointer;
}

.notification-icon:focus-visible,
.header-print-item:focus-visible {
    outline: 3px solid #215ca0;
    outline-offset: 3px;
}

.notification-icon:hover {
    transform: scale(1.1);
    box-shadow: 0 12px 25px rgba(0, 0, 0, 0.2),
                inset 0 1px 1px rgba(255, 255, 255, 0.6),
                inset 0 -1px 1px rgba(0, 0, 0, 0.15);
}

/* .context-add-btn--filled removed 2026-08-05 — it only ever marked the
   child daily-care log as "engaged", and (+) is now hidden on that
   surface rather than highlighted. */

/* Contextual header icons — (+) and Edit — come and go as you move between
   the child grid, the read view and the daily-care log. They used x-show,
   which flips `display`, so the icons to their right jumped sideways the
   instant the state changed; the fade x-transition added never touched that
   reflow, and the reflow is the part that read as abrupt. These stay in
   flow and collapse their own box instead, so the row closes over the gap
   in the same beat as the fade. .notification-icon's `transition: all 0.3s
   ease` already carries width / margin / opacity / transform, so there is
   no separate transition declaration to keep in sync here.

   Deliberately NO `overflow: hidden`: it would clip the .has-tip ::after
   tooltip, which renders above the disc. The glyph rides the shrinking
   box's centre instead and is fully faded well before the box is small
   enough for it to spill. */
.header-icon-collapsible.is-collapsed {
    width: 0;
    /* A zero-width flex item still earns its share of the .notifications
       gap — cancel it so the row really does close up. Tablet slims that
       gap to 4px, hence the variable rather than a literal. */
    margin-left: calc(-1 * var(--header-icon-gap, 15px));
    padding: 0;
    border-width: 0;
    box-shadow: none;
    opacity: 0;
    transform: scale(0.55);
    pointer-events: none;
}

/* ── View-mode toggle (Operate / Analyze) ───────────────────────
   Single iconify glyph (pencil for Operate, telescope for Analyze)
   on a standard .notification-icon disc. Active state (.is-analyze)
   shifts the background to teal so the toggle's current mode is
   visible at a glance without reading the icon. 2026-05-26 —
   simpler than the earlier crescent-SVG approach; icons are bigger
   and more legible. Modules opt in via `supportsModes: true`. */
.header-mode-toggle.is-analyze {
    background: rgba(70, 207, 192, 0.45);
    border-color: rgba(70, 207, 192, 0.7);
    box-shadow: 0 8px 20px rgba(70, 207, 192, 0.25),
                inset 0 1px 1px rgba(255, 255, 255, 0.5),
                inset 0 -1px 1px rgba(0, 0, 0, 0.08);
}
.header-mode-toggle.is-analyze:hover {
    box-shadow: 0 12px 25px rgba(70, 207, 192, 0.32),
                inset 0 1px 1px rgba(255, 255, 255, 0.6),
                inset 0 -1px 1px rgba(0, 0, 0, 0.12);
}

/* Header print button — context-aware dropdown. Drops DOWN from the
   button. Reads $store.shared.printContext to populate items. */
.header-print {
    position: relative;
}
.header-print.is-open > .notification-icon {
    background: rgba(255, 255, 255, 0.7);
    color: #21304d;
}
.header-print-menu {
    position: absolute;
    top: calc(100% + 10px);
    right: 0;
    z-index: 1000;
    min-width: 240px;
    padding: 6px;
    background: rgba(255, 255, 255, 0.97);
    border: 1px solid rgba(33, 48, 77, 0.08);
    border-radius: 14px;
    box-shadow:
        2px 5px 4px rgba(33, 48, 77, 0.08),
        3px 14px 22px rgba(33, 48, 77, 0.14);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    text-align: left;
    cursor: default;
}
.header-print-item {
    font-family: inherit;
    width: 100%;
    padding: 8px 10px;
    border-radius: 10px;
    border: none;
    cursor: pointer;
    color: #21304d;
    background: transparent;
    display: flex;
    align-items: center;
    gap: 10px;
    transition: background 0.12s ease;
}
.header-print-item:hover {
    background: rgba(116, 185, 255, 0.14);
}
.header-print-item iconify-icon {
    color: #4a5670;
    flex-shrink: 0;
}
.header-print-item-label {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 1px;
    line-height: 1.3;
}
.header-print-item-title {
    font-size: 13px;
    font-weight: 600;
}
.header-print-item-sub {
    font-size: 11px;
    color: #64748b;
    font-weight: 500;
}
.header-print-empty {
    padding: 12px;
    color: #94a3b8;
    font-size: 12px;
    text-align: center;
}

/* ── Priority Tasks panel (header bell dropdown) ────────────────────
   Same glass treatment as .header-print-menu, but a taller, scrollable
   list of open items. Each row jumps to the record; a footer CTA starts
   the guided walkthrough. */
.priority-panel {
    position: absolute;
    top: calc(100% + 10px);
    right: 0;
    z-index: 1000;
    width: 320px;
    padding: 6px;
    background: rgba(255, 255, 255, 0.97);
    border: 1px solid rgba(33, 48, 77, 0.08);
    border-radius: 14px;
    box-shadow:
        2px 5px 4px rgba(33, 48, 77, 0.08),
        3px 14px 22px rgba(33, 48, 77, 0.14);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    text-align: left;
    cursor: default;
}
.priority-panel-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    padding: 6px 8px 8px;
    border-bottom: 1px solid rgba(33, 48, 77, 0.07);
    margin-bottom: 4px;
}
.priority-panel-title {
    font-size: 13px;
    font-weight: 700;
    color: #21304d;
}
.priority-panel-count {
    font-size: 11px;
    font-weight: 600;
    color: #e17055;
}
.priority-panel-body {
    max-height: 320px;
    overflow-y: auto;
}
.priority-item {
    display: flex;
    align-items: center;
}
/* Main clickable area — jumps to the record. */
.priority-item-go {
    font-family: inherit;
    flex: 1;
    min-width: 0;
    padding: 8px 10px;
    border-radius: 10px;
    border: none;
    cursor: pointer;
    color: #21304d;
    background: transparent;
    display: flex;
    align-items: center;
    gap: 10px;
    text-align: left;
    transition: background 0.12s ease;
}
.priority-item-go:hover {
    background: rgba(116, 185, 255, 0.14);
}
/* ✨ Radar → Co-pilot action — pre-fills the AI on this task. */
.priority-item-spark {
    flex-shrink: 0;
    margin-left: 2px;
    padding: 6px;
    border: none;
    border-radius: 8px;
    background: transparent;
    color: #b8c0cf;
    cursor: pointer;
    display: flex;
    align-items: center;
    transition: background 0.12s ease, color 0.12s ease;
}
.priority-item-spark:hover {
    background: rgba(9, 132, 227, 0.12);
    color: #0984e3;
}
.priority-item-dot {
    flex-shrink: 0;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #94a3b8;
}
.priority-item-dot.is-late {
    background: #e17055;
}
.priority-item-dot.is-due {
    background: #f39c12;
}
.priority-item-label {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 1px;
    line-height: 1.3;
    flex: 1;
    min-width: 0;
}
.priority-item-title {
    font-size: 13px;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
}
.priority-item-sub {
    font-size: 11px;
    color: #64748b;
    font-weight: 500;
}
.priority-item-chev {
    color: #b8c0cf;
    flex-shrink: 0;
    font-size: 18px;
}
.priority-panel-empty {
    padding: 22px 12px;
    color: #94a3b8;
    font-size: 12px;
    text-align: center;
}
.priority-panel-cta {
    font-family: inherit;
    width: 100%;
    margin-top: 4px;
    padding: 9px 10px;
    border-radius: 10px;
    border: none;
    cursor: pointer;
    color: #fff;
    background: linear-gradient(135deg, #0984e3, #74b9ff);
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    font-size: 12.5px;
    font-weight: 600;
    transition: filter 0.12s ease;
}
.priority-panel-cta:hover {
    filter: brightness(1.06);
}
.notification-icon--active {
    background: rgba(99, 102, 241, 0.15);
    border-color: rgba(99, 102, 241, 0.35);
    color: #6366f1;
}

/* Ambient pending-task indicators */
.nav-pending-dot {
    position: absolute;
    top: 2px;
    right: 2px;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: #e17055;
    border: 2px solid rgba(255, 255, 255, 0.7);
    pointer-events: none;
}


.notification-badge {
    position: absolute;
    top: -3px;
    right: -3px;
    background: #e74c3c;
    color: white;
    border-radius: 50%;
    width: 18px;
    height: 18px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    font-weight: bold;
}

/* Main Content */
.main-content {
    display: flex;
    flex: 1;
    position: relative;
}

/* Logs Sidebar */
.logs-sidebar {
    background: rgba(255, 255, 255, 0.4);
    border-radius: 25px;
    padding: 10px;
    margin-right: 15px;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
    backdrop-filter: blur(15px);
    -webkit-backdrop-filter: blur(15px);
    border: 1px solid rgba(255, 255, 255, 0.2);
    display: flex;
    flex-direction: column;
    gap: 10px;
    width: 110px;
    transition: all 0.4s ease;
    position: relative;
    overflow: visible;
    z-index: 100;
}

.expand-toggle {
    position: absolute;
    bottom: 10px;
    left: 50%;
    transform: translateX(-50%);
    width: 40px;
    height: 25px;
    border: none;
    border-radius: 50%;
    background: linear-gradient(135deg, #74b9ff 0%, #0984e3 100%);
    color: white;
    cursor: pointer;
    font-size: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.3s ease;
    z-index: 101;
}

.expand-toggle:hover {
    transform: translateX(-50%) scale(1.1);
}

/* Collapsed View */
.logs-collapsed {
    display: flex;
    flex-direction: column;
    gap: 5px;
    align-items: center;
}

.logs-sidebar.expanded .logs-collapsed {
    display: none;
}

.log-icon-small {
    width: 80px;
    height: 70px;
    border-radius: 12px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 30px;
    color: white;
    cursor: pointer;
    transition: all 0.3s ease;
}

.log-icon-small.attendance { background: linear-gradient(135deg, #a095e8 0%, #c3bdf2 100%); }
.log-icon-small.bathroom { background: linear-gradient(135deg, #89c4ff 0%, #b9d9ff 100%); }
.log-icon-small.observations { background: linear-gradient(135deg, #50c9b3 0%, #8ee0d1 100%); }
.log-icon-small.snacks { background: linear-gradient(135deg, #ffd58a 0%, #ffb396 100%); }
.log-icon-small.nap { background: linear-gradient(135deg, #ffb3c1 0%, #ffd5a1 100%); }
.log-icon-small.incident { background: linear-gradient(135deg, #ff6b6b 0%, #ff8e8e 100%); }
.log-icon-small.health { background: linear-gradient(135deg, #48dbfb 0%, #7ed6df 100%); }

.log-icon-small:hover {
    transform: scale(1.05);
}

/* Expanded Grid View */
.logs-expanded {
    display: none;
    width: 100%;
    height: 100%;
}

.logs-sidebar.expanded .logs-expanded {
    display: block;
    position: absolute;
    top: 0;
    left: 0;
    width: 1125px;
    height: 100%;
    background: rgba(255, 255, 255, 0.4);
    backdrop-filter: blur(15px);
    -webkit-backdrop-filter: blur(15px);
    border: 1px solid rgba(255, 255, 255, 0.2);
    padding: 10px;
    border-radius: 12px;
    box-shadow: 0 4px 15px rgba(0,0,0,0.1);
    z-index: 100;
}

.grid-main {
    display: grid;
    grid-template-columns: 90px 1fr;
    gap: 15px;
    flex: 1;
}

.grid-logs {
    display: flex;
    flex-direction: column;
    width: 90px;
    align-items: center;
}

.log-icon {
    width: 80px;
    height: 70px;
    border-radius: 12px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 30px;
    color: white;
    margin-bottom: 5px;
    cursor: pointer;
    transition: all 0.3s ease;
}

.log-icon.attendance { background: linear-gradient(135deg, #a095e8 0%, #c3bdf2 100%); }
.log-icon.bathroom { background: linear-gradient(135deg, #89c4ff 0%, #b9d9ff 100%); }
.log-icon.observations { background: linear-gradient(135deg, #50c9b3 0%, #8ee0d1 100%); }
.log-icon.snacks { background: linear-gradient(135deg, #ffd58a 0%, #ffb396 100%); }
.log-icon.nap { background: linear-gradient(135deg, #ffb3c1 0%, #ffd5a1 100%); }
.log-icon.incident { background: linear-gradient(135deg, #ff6b6b 0%, #ff8e8e 100%); }
.log-icon.health { background: linear-gradient(135deg, #48dbfb 0%, #7ed6df 100%); }

.log-icon:hover {
    transform: scale(1.05);
}

.grid-cells {
    flex: 1;
    display: flex;
    flex-direction: column;
}

.grid-row {
    display: flex;
    margin-bottom: 5px;
    height: 70px;
    align-items: center;
    gap: 10px;
}

.grid-cell {
    width: 80px;
    height: 70px;
    border: 2px solid #ecf0f1;
    border-radius: 8px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: all 0.3s ease;
    background: white;
    font-size: 12px;
    font-weight: bold;
    text-align: center;
    padding: 2px;
}

.grid-cell.logged {
    background: linear-gradient(135deg, #00b894 0%, #55efc4 100%);
    color: white;
    border-color: #00b894;
}

.grid-cell.away-cell {
    background: #f8f9fa;
    color: #bbb;
    border-style: dashed;
}

.grid-cell:hover:not(.away-cell) {
    transform: scale(1.05);
    border-color: #74b9ff;
}

.grid-cell.no-nap-cell {
    background: linear-gradient(135deg, #e17055 0%, #fab1a0 100%);
    color: white;
    border-color: #e17055;
    font-size: 20px;
}

/* Center Activity Area */
.center-area {
    flex: 1;
    display: flex;
    align-items: stretch;
    justify-content: center;
    transition: opacity 0.4s ease 0.2s;
    margin-right: 15px;
}

.logs-sidebar.expanded + .center-area {
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease;
}

.activity-display {
    position: relative;
    z-index: 1;
    border-radius: 25px;
    padding: 40px;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
    text-align: center;
    width: 100%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    transition: all 0.4s ease;
    background: rgba(255, 255, 255, 0.25);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border: 1px solid rgba(255, 255, 255, 0.2);
}

.activity-display::before {
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: var(--activity-background, linear-gradient(135deg, #a8edea 0%, #fed6e3 100%));
    opacity: 0.25;
    border-radius: 15px;
    z-index: -1;
}

.activity-icon {
    font-size: 60px;
    margin-bottom: 20px;
}

.activity-name {
    font-size: 28px;
    color: #21304d;
    font-weight: bold;
    margin-bottom: 10px;
}

.activity-time {
    font-size: 18px;
    color: #7f8c8d;
    margin-bottom: 15px;
}

.activity-description {
    font-size: 16px;
    color: #34495e;
    line-height: 1.5;
}

/* Log Entry Display */
.log-entry-display {
    display: none;
    background: white;
    border-radius: 15px;
    padding: 20px;
    box-shadow: 0 4px 15px rgba(0,0,0,0.1);
    width: 100%;
    height: 100%;
    flex-direction: column;
}

.log-entry-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 15px;
}

.log-entry-header h3 {
    font-size: 22px;
    color: #21304d;
}

.close-btn {
    font-size: 24px;
    cursor: pointer;
    color: #7f8c8d;
}

.selected-children-container {
    margin-bottom: 20px;
    padding: 10px;
    background: #f8f9fa;
    border-radius: 8px;
    min-height: 50px;
}

.selected-children-list {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    align-items: center;
    justify-content: center;
}

.selected-children-placeholder {
    color: #7f8c8d;
    font-style: italic;
    font-size: 14px;
}

.selected-child-tag {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    background: #74b9ff;
    color: white;
    padding: 5px 10px;
    border-radius: 15px;
    font-size: 14px;
    cursor: pointer;
}

.log-entry-content {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 8px;
    overflow-y: auto;
}

.log-form-group {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.log-form-group label {
    font-weight: bold;
    color: #34495e;
}

.log-form-group input,
.log-form-group textarea,
.log-form-group select {
    width: 100%;
    padding: 10px;
    border: 1px solid #ccc;
    border-radius: 8px;
    font-size: 14px;
    font-family: 'Quicksand', sans-serif;
}

.log-form-group textarea {
    resize: vertical;
    min-height: 100px;
}

.log-button-group {
    display: flex;
    gap: 10px;
}

.log-button {
    flex: 1;
    padding: 12px;
    border: none;
    border-radius: 8px;
    background: #ecf0f1;
    color: #21304d;
    cursor: pointer;
    font-size: 14px;
    transition: background 0.3s;
    font-family: 'Quicksand', sans-serif;
}

.log-button.active {
    background: #74b9ff;
    color: white;
}

.log-submit-btn {
    padding: 15px;
    border: none;
    border-radius: 8px;
    background: #0984e3;
    color: white;
    font-size: 16px;
    cursor: pointer;
    margin-top: auto;
    transition: background-color 0.3s ease;
    font-family: 'Quicksand', sans-serif;
    font-weight: 500;
}

.log-submit-btn.saved {
    background-color: #27ae60;
}

.log-submit-btn:disabled {
    background-color: #a4b0be;
    opacity: 0.6;
    cursor: not-allowed;
}

/* Child Profile Display */
.child-profile-display {
    display: none;
    flex-direction: column;
    gap: 20px;
    width: 100%;
    height: 100%;
    padding: 25px;
    background: rgba(255, 255, 255, 0.4);
    border-radius: 25px;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
    backdrop-filter: blur(15px);
    -webkit-backdrop-filter: blur(15px);
    border: 1px solid rgba(255, 255, 255, 0.2);
}

.profile-header {
    display: flex;
    align-items: center;
    gap: 20px;
}

.profile-photo {
    width: 100px;
    height: 100px;
    border-radius: 50%;
    background-color: #ccc;
    background-size: cover;
    background-position: center;
    border: 3px solid white;
    box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}

.profile-details {
    flex: 1;
}

.profile-details h2 {
    font-size: 24px;
    color: #21304d;
    margin-bottom: 5px;
}

.profile-details p {
    font-size: 14px;
    color: #7f8c8d;
}

.profile-status {
    font-size: 14px;
    font-weight: 500;
    padding: 4px 10px;
    border-radius: 12px;
    display: inline-block;
    margin-top: 8px;
}

.profile-status.absent {
    color: #7f8c8d;
    background-color: #dfe6e9;
}

.profile-status.present {
    color: #00b894;
    background-color: #e8f5e8;
}

.profile-status.picked-up {
    color: #2d3436;
    background-color: #b2bec3;
}

.profile-actions {
    display: flex;
    gap: 10px;
    flex-direction: column;
}

.profile-actions button {
    padding: 10px 15px;
    border-radius: 20px;
    border: 1px solid #bdc3c7;
    background: white;
    cursor: pointer;
    font-size: 14px;
    font-weight: 500;
    transition: all 0.3s ease;
    font-family: 'Quicksand', sans-serif;
}

.profile-actions button:hover {
    background: #f0f0f0;
    border-color: #7f8c8d;
}

.profile-actions button.check-in-btn {
    background-color: #0984e3;
    color: white;
    border-color: #0984e3;
}

.profile-actions button.check-out-btn {
    background-color: #d63031;
    color: white;
    border-color: #d63031;
}

.profile-summary {
    background: rgba(255, 255, 255, 0.5);
    border-radius: 15px;
    padding: 20px;
}

.profile-summary h3 {
    margin-bottom: 10px;
    color: #34495e;
}

.profile-cards {
    display: flex;
    gap: 20px;
    flex: 1;
}

.profile-card {
    flex: 1;
    background: rgba(255, 255, 255, 0.5);
    border-radius: 15px;
    padding: 20px;
    display: flex;
    flex-direction: column;
}

.profile-card h3 {
    margin-bottom: 15px;
    color: #34495e;
}

.profile-card ul {
    list-style-type: none;
    padding-left: 5px;
}

.profile-card ul li {
    margin-bottom: 10px;
    padding-left: 20px;
    position: relative;
}

.profile-card ul li::before {
    content: '✓';
    position: absolute;
    left: 0;
    color: #27ae60;
}

/* Right Panel - Timeline */
.right-panel {
    width: 110px;
    background: rgba(255, 255, 255, 0.4);
    border-radius: 25px;
    padding: 20px 10px;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
    backdrop-filter: blur(15px);
    -webkit-backdrop-filter: blur(15px);
    border: 1px solid rgba(255, 255, 255, 0.2);
    display: flex;
    justify-content: center;
    user-select: none;
    position: relative;
    z-index: 2001;
}

.timeline {
    position: relative;
    height: 100%;
    width: 2px;
    background-color: #ccc;
}

.timeline-progress {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    background-color: #888;
    z-index: 1;
}

.timeline-marker {
    position: absolute;
    left: 10px;
    transform: translateY(-50%) rotate(45deg);
    width: 40px;
    height: 40px;
    border-radius: 50% 50% 50% 0;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 18px;
    background-color: #f0f0f0;
    border: 1px solid #ccc;
    z-index: 2;
    transition: all 0.3s ease;
}

.current-time-marker {
    position: absolute;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 90px;
    height: 35px;
    background: rgba(255, 255, 255, 0.65);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border-radius: 25px;
    border: 1px solid rgba(255, 255, 255, 0.4);
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15),
                inset 0 1px 1px rgba(255, 255, 255, 0.7);
    cursor: grab;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #21304d;
    font-size: 12px;
    font-weight: bold;
    z-index: 3;
    text-shadow: 0 1px 1px rgba(0,0,0,0.1);
    transition: background-color 0.1s ease;
}

.timeline-time {
    position: absolute;
    right: 10px;
    transform: translateY(-50%);
    font-size: 12px;
    color: #888;
    transition: all 0.3s ease;
}

/* Children Status Row */
.children-row {
    display: flex;
    background: rgba(255, 255, 255, 0.4);
    border-radius: 25px;
    padding: 15px 20px 25px 20px;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
    backdrop-filter: blur(15px);
    -webkit-backdrop-filter: blur(15px);
    border: 1px solid rgba(255, 255, 255, 0.2);
    margin-top: 15px;
    position: relative;
}

.children-spacer {
    width: 105px;
    flex-shrink: 0;
    transition: all 0.4s ease;
}

/* Attendance tray only — do not use bare .children-grid (collides with
   the Children module card grid in children-module.css). */
.attendance-modal .children-grid,
.children-row .children-grid {
    flex: 1;
    display: flex;
    gap: 10px;
}

.child-status {
    width: 80px;
    height: 80px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: bold;
    font-size: 14px;
    cursor: pointer;
    transition: all 0.3s ease;
    position: relative;
    backdrop-filter: blur(5px);
    -webkit-backdrop-filter: blur(5px);
    box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2),
                inset 0 2px 2px rgba(255, 255, 255, 0.6),
                inset 0 -2px 2px rgba(0, 0, 0, 0.1);
    text-shadow: 0 1px 1px rgba(0,0,0,0.2);
}

.child-status.present {
    background: linear-gradient(135deg, #55efc4 0%, #00b894 100%);
    border: 1px solid #00b894;
    color: white;
    box-shadow: 0 10px 20px rgba(0, 184, 148, 0.25),
                inset 0 2px 2px rgba(255, 255, 255, 0.5),
                inset 0 -2px 2px rgba(0, 0, 0, 0.15);
}

.child-status.picked-up {
    background: #b2bec3;
    border: 1px solid #95a5a6;
    color: #2d3436;
    box-shadow: 0 5px 10px rgba(0, 0, 0, 0.15),
                inset 0 2px 2px rgba(255, 255, 255, 0.5),
                inset 0 -2px 2px rgba(0, 0, 0, 0.05);
}

.child-status.away {
    background: transparent;
    border: 3px dashed #b2bec3;
    color: #a4b0be;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    box-shadow: none;
    text-shadow: none;
}

.child-status.neutral {
    background: radial-gradient(circle at 50% 0%, rgba(255, 255, 255, 0.5) 0%, rgba(224, 224, 224, 0.5) 100%);
    border: 1px solid rgba(255, 255, 255, 0.6);
    color: #7f8c8d;
}

.child-status:hover {
    transform: scale(1.1);
}

.child-status.disabled,
.child-status.inactive-for-log {
    opacity: 0.4;
}

.child-status.disabled {
    cursor: not-allowed;
}

.child-status.selected {
    transform: scale(1.1);
    box-shadow: 0 0 15px rgba(9, 132, 227, 0.7);
    border-color: #74b9ff;
}

.tooltip {
    position: absolute;
    bottom: -30px;
    left: 50%;
    transform: translateX(-50%);
    background: #21304d;
    color: white;
    padding: 4px 8px;
    border-radius: 4px;
    font-size: 10px;
    white-space: nowrap;
    opacity: 0;
    transition: opacity 0.3s ease;
    z-index: 1000;
}

.child-status:hover .tooltip {
    opacity: 1;
}

/* Legend */
.legend {
    display: flex;
    gap: 20px;
    justify-content: center;
    font-size: 12px;
    margin-top: 10px;
    position: absolute;
    bottom: 8px;
    left: 50%;
    transform: translateX(-50%);
}

.legend-item {
    display: flex;
    align-items: center;
    gap: 5px;
    color: #7f8c8d;
}

.legend-dot {
    width: 14px;
    height: 14px;
    border-radius: 50%;
}

.legend-dot.present { background: linear-gradient(135deg, #00b894 0%, #55efc4 100%); }
.legend-dot.picked-up { background: #bdc3c7; }
.legend-dot.away { border: 2px dashed #bdc3c7; background: transparent; }

/* Modals — heavy frosted scrim that matches .lightbox-overlay so
   modals everywhere (Send Photos, Send Document, asset picker, etc.)
   read like the photo lightbox: one element clear and in focus, the
   rest washed into a uniform slate-blue blur. The .header
   (z-index: 10002) sits above this scrim so the flex bar and module
   pill stay sharp. */
.modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(208, 212, 222, 0.78);
    backdrop-filter: blur(28px) saturate(1.3);
    -webkit-backdrop-filter: blur(28px) saturate(1.3);
    display: flex;
    justify-content: center;
    align-items: flex-start;
    padding: 5vh 0;
    overflow-y: auto;
    z-index: 2000;
    border: none;
}

.modal-content {
    background: rgba(255, 255, 255, 0.35);
    padding: 25px;
    border-radius: 20px;
    box-shadow:
        0 20px 60px rgba(0, 0, 0, 0.1),
        0 4px 16px rgba(0, 0, 0, 0.05),
        inset 2px 2px 1px 0 rgba(255, 255, 255, 0.7),
        inset -1px -1px 1px 1px rgba(255, 255, 255, 0.5);
    width: 80%;
    max-width: 900px;
    height: 80%;
    display: flex;
    flex-direction: column;
    border: 1px solid rgba(255, 255, 255, 0.5);
    backdrop-filter: blur(10px) saturate(1.1);
    -webkit-backdrop-filter: blur(10px) saturate(1.1);
}

.modal-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    border-bottom: 1px solid rgba(0, 0, 0, 0.06);
    padding-bottom: 15px;
    margin-bottom: 15px;
}

.modal-header h2 {
    color: #21304d;
}

.close-modal {
    font-size: 28px;
    cursor: pointer;
    color: #7f8c8d;
}

.close-modal:hover {
    color: #34495e;
}

.modal-body {
    flex: 1;
    overflow-y: auto;
}

.record-item {
    background: #f8f9fa;
    border: 1px solid #ecf0f1;
    border-radius: 8px;
    padding: 15px;
    margin-bottom: 10px;
}

.record-item h4 {
    color: #34495e;
    margin-bottom: 10px;
}

.record-item pre {
    background: #e9ecef;
    padding: 10px;
    border-radius: 5px;
    white-space: pre-wrap;
    word-wrap: break-word;
    font-size: 13px;
    font-family: monospace;
}

/* Confetti Canvas */
#confetti-canvas {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 9999;
    display: none;
}

/* Attendance Log Specific Styles */
.attendance-log-container {
    display: flex;
    flex-direction: column;
    gap: 15px;
    height: 100%;
    flex: 1;
}

.attendance-box {
    flex: 1;
    border: 3px dashed #dfe6e9;
    border-radius: 12px;
    padding: 15px;
    display: flex;
    flex-direction: column;
    transition: all 0.3s ease;
    cursor: pointer;
}

.attendance-box.active {
    border-color: #74b9ff;
    border-style: solid;
    background-color: #f0f7ff;
}

/* Educators Attendance: smoother drag */
.attendance-chip-draggable {
    will-change: transform;
}
.attendance-chip-draggable:active {
    cursor: grabbing;
}
.attendance-chip-draggable.attendance-chip-dragging {
    opacity: 0.5;
    transform: scale(0.9);
}
.attendance-chips .attendance-chip-wrap {
    transition: transform 0.2s ease-out, opacity 0.2s ease-out;
}

/* Educators Attendance: drag-over feedback for Present/Home zones */
.attendance-zone.attendance-zone-dragover {
    border-color: #0984e3 !important;
    background: rgba(9, 132, 227, 0.12) !important;
    box-shadow: inset 0 0 0 3px rgba(9, 132, 227, 0.3);
}

.attendance-box h4 {
    font-size: 16px;
    color: #21304d;
    margin-bottom: 15px;
    font-weight: 600;
}

.attendance-children-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(80px, 1fr));
    gap: 10px;
    flex: 1;
}

.attendance-child-slot {
    width: 80px;
    height: 80px;
    position: relative;
}

.attendance-child-icon {
    width: 70px;
    height: 70px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    color: white;
    font-weight: bold;
    font-size: 16px;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    cursor: pointer;
}

.attendance-child-icon.present {
    background: linear-gradient(135deg, #00b894 0%, #55efc4 100%);
    border: 3px solid #00b894;
}

.attendance-child-icon.picked-up {
    background: #bdc3c7;
    border: 3px solid #95a5a6;
}

.attendance-child-icon.present.completed {
    background: #bdc3c7;
    border-color: #95a5a6;
    opacity: 0.4;
}

.attendance-child-icon .timestamp {
    position: absolute;
    bottom: -18px;
    font-size: 11px;
    color: #7f8c8d;
    font-weight: 500;
    background: #f0f0f0;
    padding: 1px 4px;
    border-radius: 4px;
}

.line-connector {
    position: absolute;
    top: -100%;
    left: 50%;
    transform: translateX(-50%);
    width: 2px;
    height: calc(100% + 15px);
    background-color: #bdc3c7;
    z-index: -1;
}

/* Observation Styles */
.observation-item {
    padding-bottom: 15px;
    margin-bottom: 0;
    border-bottom: 1px solid #ecf0f1;
}

.observation-item:last-of-type {
    border-bottom: none;
    margin-bottom: 0;
}

.observation-question {
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
}

.observation-question-details {
    display: flex;
    align-items: center;
    gap: 10px;
}

.developmental-area-tag {
    font-size: 11px;
    font-weight: normal;
    background-color: #e8f5e8;
    color: #006400;
    padding: 3px 8px;
    border-radius: 10px;
    flex-shrink: 0;
}

.observation-actions {
    display: flex;
    align-items: center;
    gap: 10px;
}

.comment-btn {
    background: #ecf0f1;
    border: none;
    cursor: pointer;
    border-radius: 8px;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.3s;
    width: 44px;
    height: 44px;
    padding: 0;
}

.comment-btn:hover {
    background-color: #e0e0e0;
}

.comment-btn.active,
.comment-btn.has-comment {
    background-color: #74b9ff;
    border: none;
}

.comment-btn svg {
    width: 24px;
    height: 24px;
    fill: #888;
    transition: fill 0.3s;
}

.comment-btn.active svg,
.comment-btn.has-comment svg {
    fill: white;
}

.comment-box-container {
    display: none;
    margin-top: 15px;
    padding: 15px;
    border: 2px solid #0984e3;
    border-radius: 12px;
    background-color: #f8f9fa;
}

.comment-box-container textarea {
    width: 100%;
    min-height: 80px;
    border: 1px solid #ccc;
    border-radius: 8px;
    padding: 10px;
    resize: vertical;
    font-size: 14px;
    font-family: 'Quicksand', sans-serif;
}

/* Snack/Food Items */
.food-item-tags {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 10px;
}

.food-item-tag {
    background: #e0e0e0;
    padding: 5px 10px;
    border-radius: 15px;
    cursor: pointer;
    transition: background 0.3s;
}

.food-item-tag:hover {
    background: #c7c7c7;
}

.snack-items-container {
    display: flex;
    flex-direction: column;
    gap: 15px;
    margin-top: 15px;
    max-height: 280px;
    overflow-y: auto;
    padding-right: 10px;
}

.snack-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 15px;
    background: #f8f9fa;
    border-radius: 8px;
    border: 1px solid #ecf0f1;
}

.snack-item span {
    font-weight: 500;
    color: #34495e;
    flex: 1;
}

.snack-item .progress-bar-container {
    width: 220px;
    flex-shrink: 0;
    margin-left: 15px;
}

.progress-bar-container {
    display: flex;
    gap: 5px;
    cursor: pointer;
}

.progress-bar-block {
    flex: 1;
    height: 20px;
    background-color: #e0e0e0;
    border-radius: 4px;
    transition: background-color 0.3s ease;
}

.progress-bar-block.active {
    background-color: darksalmon;
}

/* Nap Slider */
.nap-log-container {
    display: flex;
    flex-direction: column;
    gap: 20px;
    height: 100%;
    flex: 1;
    justify-content: space-between;
}

#napVisualizer {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    flex: 1;
    gap: 20px;
    width: 80%;
    margin: 0 auto;
}

.nap-timeline-container {
    width: 100%;
}

.nap-timeline-labels {
    display: flex;
    justify-content: space-between;
    font-size: 12px;
    color: #7f8c8d;
    margin-bottom: 5px;
    padding: 0 10px;
}

.nap-timeline {
    width: 100%;
    height: 20px;
    background-color: #ecf0f1;
    border-radius: 10px;
    position: relative;
}

.nap-duration-bar {
    position: absolute;
    height: 100%;
    background: linear-gradient(135deg, #74b9ff 0%, #0984e3 100%);
    border-radius: 10px;
    cursor: grab;
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.nap-duration-bar:active {
    cursor: grabbing;
}

.handle {
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background-color: white;
    border: 2px solid #0984e3;
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    cursor: ew-resize;
    z-index: 10;
}

.handle.left-handle {
    left: -12px;
}

.handle.right-handle {
    right: -12px;
}

.nap-time-display {
    display: flex;
    gap: 40px;
    font-size: 16px;
    font-weight: 500;
    color: #21304d;
}

/* Health Log Styles */
.symptom-icons {
    display: flex;
    flex-wrap: wrap;
    gap: 15px;
    justify-content: center;
    padding: 10px 0;
}

.symptom-icon {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 5px;
    cursor: pointer;
    padding: 10px;
    border-radius: 8px;
    transition: background 0.3s;
    width: 80px;
    text-align: center;
}

.symptom-icon:hover {
    background: #f0f0f0;
}

.symptom-icon.selected {
    background: #74b9ff;
    color: white;
}

.symptom-icon .icon {
    font-size: 30px;
}

.symptom-icon .label {
    font-size: 12px;
}

/* Profile Messages View */
.profile-messages-view {
    display: none;
    flex-direction: column;
    gap: 20px;
    flex: 1;
}

.message-history-card {
    background: rgba(255, 255, 255, 0.5);
    border-radius: 15px;
    padding: 20px;
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 10px;
    max-height: 350px;
    overflow-y: auto;
}

.message-item {
    border: 1px solid #dfe6e9;
    border-radius: 12px;
    padding: 15px;
    background: white;
    display: flex;
    align-items: center;
    gap: 15px;
    transition: all 0.3s ease;
    cursor: pointer;
}

.message-item:hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 12px rgba(0,0,0,0.08);
    border-color: #74b9ff;
}

.message-item.urgent {
    border-left: 4px solid #d63031;
}

.message-icon {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.message-icon.urgent-icon {
    background-color: #ffe8e8;
}

.message-icon.normal-icon {
    background-color: #eef6ff;
}

.message-icon svg {
    width: 20px;
    height: 20px;
}

.message-content {
    flex-grow: 1;
}

.message-sender {
    font-weight: 600;
    color: #21304d;
    font-size: 14px;
    margin-bottom: 3px;
}

.message-sender .relationship {
    font-weight: 400;
    color: #7f8c8d;
    font-size: 12px;
}

.message-date {
    font-size: 12px;
    color: #7f8c8d;
}

.message-text {
    font-size: 13px;
    color: #34495e;
    margin-top: 4px;
}

.message-indicator {
    width: 24px;
    height: 24px;
    flex-shrink: 0;
}

/* Observation View */
.observation-view {
    display: none;
    flex-direction: column;
    gap: 15px;
    padding: 20px;
    background: rgba(255, 255, 255, 0.5);
    border-radius: 15px;
    flex: 1;
    overflow-y: auto;
}

.observation-question-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding-bottom: 15px;
    border-bottom: 1px solid #ecf0f1;
}

.observation-question-row:last-child {
    border-bottom: none;
}

.observation-question-row .log-button-group {
    width: auto;
}

.observation-question-row .log-button {
    padding: 8px 18px;
}

.observation-question-row .comment-btn {
    width: 38px;
    height: 38px;
}

.observation-question-row .comment-btn svg {
    width: 20px;
    height: 20px;
}

/* Print-specific styles */
@media print {
    * {
        -webkit-print-color-adjust: exact !important;
        color-adjust: exact !important;
    }

    body.printing {
        overflow: visible !important;
    }

    .print-container {
        page-break-inside: avoid;
    }

    .curriculum-box {
        page-break-inside: avoid;
        margin-bottom: 10px !important;
    }

    .add-note-btn,
    .tag-input-wrapper,
    .tag-add-btn,
    input,
    button:not(.print-button),
    .close-note-modal {
        display: none !important;
    }
}

/* Print button hover effect */
.notification-icon[title="Export PDF"]:hover {
    background: rgba(116, 185, 255, 0.2) !important;
}

/* Photo Gallery Modal Styles */
.photo-gallery-overlay {
    background: rgba(255, 255, 255, 0.8);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    padding-top: 100px;
    align-items: flex-start;
    z-index: 3000;
}

.photo-gallery-modal {
    max-width: 800px !important;
    min-width: 700px !important;
    width: 800px !important;
    height: 80vh !important;
    max-height: 700px !important;
    display: flex;
    flex-direction: column;
    padding: 30px !important;
    position: relative;
}

.gallery-close-btn {
    position: absolute;
    top: 20px;
    right: 20px;
    z-index: 10;
    font-size: 32px;
    background: rgba(255, 255, 255, 0.6);
    width: 40px;
    height: 40px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.2s ease;
}

.gallery-close-btn:hover {
    background: rgba(255, 255, 255, 0.9);
    transform: scale(1.1);
}

/* Top Bar Filter Styles */
.gallery-topbar-filters {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 0 20px;
    flex: 1;
    justify-content: center;
}

.gallery-title {
    font-size: 18px;
    font-weight: 700;
    margin-right: 15px;
    color: white;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}

.gallery-date-filter,
.gallery-select-filter {
    padding: 8px 14px;
    border: 1px solid rgba(255, 255, 255, 0.5);
    border-radius: 20px;
    font-size: 14px;
    font-family: 'Quicksand', sans-serif;
    font-weight: 600;
    background: rgba(255, 255, 255, 0.25);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    color: white;
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
    transition: all 0.2s ease;
    cursor: pointer;
}

.gallery-date-filter {
    min-width: 150px;
}

.gallery-date-presets {
    display: inline-flex;
    align-items: stretch;
    border: 1px solid rgba(255, 255, 255, 0.45);
    border-radius: 999px;
    overflow: hidden;
    background: rgba(255, 255, 255, 0.18);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    flex-shrink: 0;
}

.gallery-date-preset {
    border: 0;
    background: transparent;
    color: rgba(255, 255, 255, 0.88);
    font: 600 13px/1 Quicksand, system-ui, sans-serif;
    padding: 8px 14px;
    cursor: pointer;
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
    transition: background 0.15s ease, color 0.15s ease;
    white-space: nowrap;
}

.gallery-date-preset + .gallery-date-preset {
    border-left: 1px solid rgba(255, 255, 255, 0.28);
}

.gallery-date-preset:hover:not(.active) {
    background: rgba(255, 255, 255, 0.18);
}

.gallery-date-preset.active {
    background: rgba(255, 255, 255, 0.92);
    color: #1a365d;
    text-shadow: none;
}

.gallery-viewing-month {
    margin-left: 10px;
    font: 600 13px/1.2 Quicksand, system-ui, sans-serif;
    color: rgba(255, 255, 255, 0.95);
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25);
    white-space: nowrap;
    letter-spacing: 0.01em;
    flex-shrink: 0;
}

/* Month cue only appears on Last 30 after scroll. Hide the redundant
   "Photo Gallery" flex-bar title (already in the left subtitle) so the
   cue isn't clipped against the pill edge. */
.flex-bar:has(.gallery-viewing-month:not([style*="display: none"])) .flex-bar-content > span {
    display: none;
}

/* "+" button in the gallery top bar for quick photo upload */
.gallery-add-photo-btn {
    height: 34px;
    width: 34px;
    min-width: 34px;
    padding: 0;
    border-radius: 10px;
    border: 1px solid rgba(255, 255, 255, 0.3);
    background: rgba(255, 255, 255, 0.25);
    color: #ffffff;
    font-size: 22px;
    font-weight: 600;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: 'Quicksand', sans-serif;
    flex-shrink: 0;
    transition: background 0.15s ease, transform 0.1s ease;
}

.gallery-add-photo-btn:hover {
    background: rgba(255, 255, 255, 0.4);
    transform: translateY(-1px);
}

.gallery-add-photo-btn:active {
    transform: translateY(0);
}

.gallery-add-photo-btn span {
    display: block;
    margin-top: -2px;
}

/* Upload progress banner shown above the grid while files are uploading */
.gallery-upload-progress {
    padding: 12px 16px;
    margin: 0 0 16px 0;
    border-radius: 12px;
    background: rgba(255, 255, 255, 0.6);
    border: 1px solid rgba(0, 0, 0, 0.05);
    font-size: 13px;
    color: #34495e;
}

.gallery-upload-progress .upload-progress-text {
    margin-bottom: 8px;
    font-weight: 600;
}

.gallery-select-filter {
    min-width: 140px;
}

.gallery-date-filter::-webkit-calendar-picker-indicator {
    filter: brightness(0) invert(1);
    cursor: pointer;
}

.gallery-date-filter::placeholder,
.gallery-select-filter option {
    color: rgba(255, 255, 255, 0.8);
}

.gallery-select-filter option {
    background: #21304d;
    color: white;
}

.gallery-date-filter:hover,
.gallery-select-filter:hover {
    background: rgba(255, 255, 255, 0.35);
    border-color: rgba(255, 255, 255, 0.7);
    transform: translateY(-1px);
}

.gallery-date-filter:focus,
.gallery-select-filter:focus {
    outline: none;
    background: rgba(255, 255, 255, 0.4);
    border-color: rgba(255, 255, 255, 0.8);
    box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.2);
}

.photo-gallery-body {
    flex: 1;
    overflow: hidden;
}

.photo-grid-container {
    height: 100%;
    overflow-y: auto;
    padding-right: 10px;
}

.photo-grid-container::-webkit-scrollbar {
    width: 8px;
}

.photo-grid-container::-webkit-scrollbar-track {
    background: #f1f3f4;
    border-radius: 4px;
}

.photo-grid-container::-webkit-scrollbar-thumb {
    background: #c1c8cd;
    border-radius: 4px;
}

.photo-grid-container::-webkit-scrollbar-thumb:hover {
    background: #a4acb3;
}

.photos-module .photo-grid, .photo-gallery-modal .photo-grid {
    display: grid;
    grid-template-columns: repeat(5, 1fr) !important;
    gap: 16px !important;
    transition: gap 0.3s ease;
}
@media (max-width: 1280px) {
    .photos-module .photo-grid, .photo-gallery-modal .photo-grid {
        grid-template-columns: repeat(4, 1fr) !important;
    }
}
@media (max-width: 980px) {
    .photos-module .photo-grid, .photo-gallery-modal .photo-grid {
        grid-template-columns: repeat(3, 1fr) !important;
    }
}
@media (max-width: 720px) {
    .photos-module .photo-grid, .photo-gallery-modal .photo-grid {
        grid-template-columns: repeat(2, 1fr) !important;
    }
}

.photos-module .photo-grid.selection-mode, .photo-gallery-modal .photo-grid.selection-mode {
    gap: 10px !important;
}

.photo-tile {
    position: relative;
    aspect-ratio: 1;
    cursor: pointer;
    overflow: hidden;

    /* Hairline frame — drop the padding entirely; the image sits
       edge-to-edge with a 1px border. Inset glass halos previously
       added another ~2px of visible white frame, removed. */
    padding: 0;
    background: rgba(255, 255, 255, 0.35);
    border-radius: 12px;
    border: 1px solid rgba(255, 255, 255, 0.5);
    box-shadow: 0 4px 14px 0 rgba(31, 38, 135, 0.08);

    transition: all 0.3s ease;
}

.photo-tile:hover {
    transform: translateY(-4px) scale(1.02);
    box-shadow:
        0 12px 40px 0 rgba(31, 38, 135, 0.2),
        inset 0 0 0 1px rgba(255, 255, 255, 0.7),
        inset 0 0 20px rgba(255, 255, 255, 0.4);
}

.photo-tile-image {
    width: 100%;
    height: 100%;
    background-size: cover;
    background-position: center;
    background-color: #e0e0e0;
    border-radius: 12px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

.photo-tile-caption {
    position: absolute;
    bottom: 12px;
    left: 12px;
    right: 12px;
    padding: 8px 12px;
    background: rgba(33, 48, 77, 0.85);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    color: white;
    font-size: 12px;
    border-radius: 8px;
    font-weight: 500;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Photo Selection Styles */
.photo-tile.selected {
    border: 3px solid #46cfc0;
    box-shadow:
        0 0 0 2px rgba(70, 207, 192, 0.2),
        0 12px 40px 0 rgba(31, 38, 135, 0.2),
        inset 0 0 0 1px rgba(255, 255, 255, 0.7),
        inset 0 0 20px rgba(255, 255, 255, 0.4);
}

.photo-tile.selected:hover {
    transform: translateY(-2px) scale(1.01);
}

/* Per-tile tag chip — bottom-left overlay on photo grid tiles.
   Shows tagged children's first names ("Carter, Lucas +2"). Compact +
   semi-transparent so it floats over the photo content without
   competing. Hover-only on idle tiles to keep the grid quiet; always
   visible in selection mode so admin can scan tags fast while
   choosing recipients. Empty when photo has no child tags — chip is
   hidden in markup via x-if so the rule below only paints when the
   chip exists. */
.photo-tile-tag-chip {
    position: absolute;
    bottom: 8px;
    left: 8px;
    max-width: calc(100% - 56px);
    padding: 3px 9px;
    border-radius: 999px;
    /* Translucent frosted-glass — drop alpha further (0.32 → 0.18)
       so the photo content reads clearly through the chip. Stronger
       blur + saturate compensates so text remains legible against
       busy photo backgrounds; heavier text-shadow as a safety net. */
    background: rgba(15, 23, 42, 0.18);
    color: #fff;
    font-size: 11px;
    font-weight: 600;
    line-height: 1.4;
    letter-spacing: 0.01em;
    backdrop-filter: blur(14px) saturate(1.5);
    -webkit-backdrop-filter: blur(14px) saturate(1.5);
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6), 0 0 4px rgba(0, 0, 0, 0.4);
    pointer-events: none;
    z-index: 5;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    opacity: 0;
    transition: opacity 0.18s ease;
}
.photo-tile:hover .photo-tile-tag-chip,
.photos-module .photo-grid.selection-mode .photo-tile-tag-chip, .photo-gallery-modal .photo-grid.selection-mode .photo-tile-tag-chip,
.photo-tile.selected .photo-tile-tag-chip {
    opacity: 1;
}

/* Photo Tile Checkbox */
.photo-tile-checkbox {
    position: absolute;
    top: 16px;
    left: 16px;
    z-index: 10;
    opacity: 0;
    transition: opacity 0.2s ease;
    background: white;
    border-radius: 50%;
    width: 28px;
    height: 28px;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}

/* Only show checkbox in selection mode or when actually selected */
.photos-module .photo-grid.selection-mode .photo-tile-checkbox, .photo-gallery-modal .photo-grid.selection-mode .photo-tile-checkbox,
.photo-tile-checkbox.visible {
    opacity: 1;
}

/* Photo Tile Select Icon */
.photo-tile-select-icon {
    position: absolute;
    top: 16px;
    right: 16px;
    z-index: 10;
    opacity: 0;
    transition: all 0.2s ease;
    background: rgba(150, 150, 150, 0.6);
    backdrop-filter: blur(5px);
    -webkit-backdrop-filter: blur(5px);
    border-radius: 50%;
    width: 34px;
    height: 34px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
}
/* Reveal-on-hover, mirrors .docs-card-select-icon. Selected tiles
   keep the chip visible regardless of hover so the selection state
   is always legible. */
.photo-tile:hover .photo-tile-select-icon,
.photo-tile.selected .photo-tile-select-icon {
    opacity: 1;
}
/* Invisible hit-area extension — keeps the visible bubble at 34×34
   but expands the clickable region to ~62×62 so taps near the
   corner don't slip past the bubble and trigger the lightbox. */
.photo-tile-select-icon::before {
    content: '';
    position: absolute;
    inset: -14px;
    border-radius: 50%;
}

.photo-tile-select-icon iconify-icon {
    color: white;
    opacity: 0.8;
}

.photo-tile-select-icon:hover {
    background: rgba(150, 150, 150, 0.8);
    transform: scale(1.1);
}

/* Selected photo icon - bright turquoise */
.photo-tile.selected .photo-tile-select-icon {
    opacity: 1;
    background: rgba(70, 207, 192, 1);
}

.photo-tile.selected .photo-tile-select-icon iconify-icon {
    opacity: 1;
}

.photo-tile.selected .photo-tile-select-icon:hover {
    background: rgba(70, 207, 192, 1);
    transform: scale(1.1);
}

/* Gallery Selection Controls */
.gallery-selection-controls,
.gallery-filter-controls {
    display: flex;
    align-items: center;
    gap: 12px;
}

/* Tablet selection chips live with the flex-bar rules further down.
   Desktop paint (padding, radius, frost) stays in the rules below. */

.gallery-back-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 8px 16px;
    border: 1px solid rgba(255, 255, 255, 0.5);
    border-radius: 20px;
    font-size: 14px;
    font-family: 'Quicksand', sans-serif;
    font-weight: 600;
    background: rgba(255, 255, 255, 0.25);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    color: white;
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
    transition: all 0.2s ease;
    cursor: pointer;
}

.gallery-back-btn:hover {
    background: rgba(255, 255, 255, 0.35);
    border-color: rgba(255, 255, 255, 0.7);
    transform: translateY(-1px);
}

.gallery-select-all-btn {
    padding: 8px 16px;
    border: 1px solid rgba(255, 255, 255, 0.5);
    border-radius: 20px;
    font-size: 14px;
    font-family: 'Quicksand', sans-serif;
    font-weight: 600;
    background: rgba(255, 255, 255, 0.25);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    color: white;
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
    transition: all 0.2s ease;
    cursor: pointer;
}

.gallery-select-all-btn:hover:not(:disabled) {
    background: rgba(255, 255, 255, 0.35);
    border-color: rgba(255, 255, 255, 0.7);
    transform: translateY(-1px);
}

.gallery-select-all-btn:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

.gallery-selected-count {
    padding: 8px 16px;
    border-radius: 20px;
    font-size: 14px;
    font-weight: 600;
    background: rgba(70, 207, 192, 0.9);
    color: white;
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
}

.gallery-actions-dropdown-wrapper {
    position: relative;
}

.gallery-actions-btn {
    padding: 8px 16px;
    border: 1px solid rgba(255, 255, 255, 0.5);
    border-radius: 20px;
    font-size: 14px;
    font-family: 'Quicksand', sans-serif;
    font-weight: 600;
    background: rgba(255, 255, 255, 0.25);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    color: white;
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
    transition: all 0.2s ease;
    cursor: pointer;
}

.gallery-actions-btn:hover {
    background: rgba(255, 255, 255, 0.35);
    border-color: rgba(255, 255, 255, 0.7);
    transform: translateY(-1px);
}

.gallery-actions-dropdown {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    background: white;
    border-radius: 12px;
    box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
    padding: 8px;
    z-index: 1001;
    min-width: 160px;
    border: 1px solid rgba(0, 0, 0, 0.05);
}

.gallery-action-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 14px;
    border-radius: 8px;
    cursor: pointer;
    transition: all 0.2s ease;
    color: #21304d;
    font-size: 14px;
    font-weight: 500;
    background: transparent;
    border: none;
    width: 100%;
    text-align: left;
    font-family: 'Quicksand', sans-serif;
}

.gallery-action-item:hover {
    background: #f8f9fa;
}

.gallery-action-item.delete:hover {
    background: #ffe8e8;
    color: #d63031;
}

.gallery-action-item.delete:hover iconify-icon {
    color: #d63031;
}

.gallery-clear-selection-btn {
    padding: 8px 16px;
    border: 1px solid rgba(255, 255, 255, 0.5);
    border-radius: 20px;
    font-size: 14px;
    font-family: 'Quicksand', sans-serif;
    font-weight: 600;
    background: rgba(255, 255, 255, 0.25);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    color: white;
    text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
    transition: all 0.2s ease;
    cursor: pointer;
}

.gallery-clear-selection-btn:hover {
    background: rgba(255, 255, 255, 0.35);
    border-color: rgba(255, 255, 255, 0.7);
    transform: translateY(-1px);
}

/* Gallery States */
.gallery-loading-state,
.gallery-empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 100%;
    color: #7f8c8d;
}

.gallery-empty-state h3 {
    margin: 15px 0 10px 0;
    color: #21304d;
    font-size: 20px;
}

.gallery-empty-state p {
    color: #7f8c8d;
    font-size: 14px;
    max-width: 400px;
    text-align: center;
    line-height: 1.6;
}

.loading-spinner {
    width: 50px;
    height: 50px;
    border: 4px solid #f3f3f3;
    border-top: 4px solid #0984e3;
    border-radius: 50%;
    animation: spin 1s linear infinite;
}

@keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}

.gallery-loading-more {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 20px;
    color: #7f8c8d;
    font-size: 14px;
}

.loading-spinner-small {
    width: 20px;
    height: 20px;
    border: 3px solid #f3f3f3;
    border-top: 3px solid #0984e3;
    border-radius: 50%;
    animation: spin 0.8s linear infinite;
}

.gallery-end-message {
    text-align: center;
    padding: 20px;
    color: #7f8c8d;
    font-size: 14px;
    font-style: italic;
}

/* ── Generic CSS-managed tooltip (.has-tip + data-tip="...") ───────────
   Replaces native `title=""` everywhere we want a predictable show-
   on-hover / hide-on-leave bubble. Native tooltips can stick when an
   element repaints or hides under a modal mid-hover — this :hover-
   driven recipe vanishes the moment the cursor leaves the host.
   Was scoped to messaging.css; moved here so it's available on every
   page, not just after the messaging module first mounts. */
.has-tip {
    cursor: default;
    /* Anchor for the ::after tooltip — without this, the absolute-
       positioned tooltip resolves up to the nearest positioned
       ancestor, which on header icons is the body and renders
       wherever the cursor last was. */
    position: relative;
}
.has-tip::after {
    content: attr(data-tip);
    position: absolute;
    bottom: calc(100% + 8px);
    right: 0;
    transform: translateY(2px);
    background: #1e293b;
    color: #fff;
    font-size: 12px;
    font-weight: 500;
    line-height: 1.3;
    padding: 6px 10px;
    border-radius: 6px;
    white-space: nowrap;
    max-width: 320px;
    overflow: hidden;
    text-overflow: ellipsis;
    pointer-events: none;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.12s ease, transform 0.12s ease;
    z-index: 100;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
}
.has-tip:hover::after {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
}

/* Tooltip position-below modifier — for hosts where above-the-anchor
   would clip against the viewport top (notification icons in the
   header sit ~12px from the top edge; the default upward tooltip
   gets cut off by the browser chrome). Apply via:
       <div class="has-tip has-tip--below" data-tip="...">
   or scope by parent (see `.notifications .has-tip` below). */
.has-tip.has-tip--below::after,
.notifications .has-tip::after {
    bottom: auto;
    top: calc(100% + 8px);
    transform: translateY(-2px);
}

/* Wide / multi-line tooltip modifier — for explanatory tips longer
   than the ~30-char default fits on one line. Lets text wrap, bumps
   max-width so the popover has room to read. Apply via:
       <div class="has-tip has-tip--wide" data-tip="long explanation…"> */
.has-tip.has-tip--wide::after {
    white-space: normal;
    max-width: 360px;
    width: max-content;
    text-overflow: clip;
    overflow: visible;
    line-height: 1.4;
    padding: 8px 12px;
}
.has-tip.has-tip--below:hover::after,
.notifications .has-tip:hover::after {
    transform: translateY(0);
}

/* Left-anchored variant — tooltip extends rightward from the anchor.
   Use when the host sits near the left edge of an overflow:hidden container
   and the default right-anchored tooltip would be clipped. */
.has-tip.has-tip--start::after {
    right: auto;
    left: 0;
}
.has-tip.has-tip--below.has-tip--start::after {
    right: auto;
    left: 0;
}

/* Photo Lightbox / editor workspace
   Full-viewport: photo canvas on the left, tagging rail docked flush
   to the right edge. Stage shrink-wraps the image so face circles share
   the same coordinate origin as the painted photo. */
.lightbox-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(208, 212, 222, 0.78);
    backdrop-filter: blur(28px) saturate(1.3);
    -webkit-backdrop-filter: blur(28px) saturate(1.3);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 3000;
    padding: 50px 40px 30px 40px;
}

.lightbox-overlay--editor {
    align-items: stretch;
    justify-content: stretch;
    /* Clear the fixed header (container pad + header row) so the
       filmstrip / photo / tagging rail start on the same upper line
       as normal module content below the flex bar — not under it. */
    padding: 96px 20px 20px 16px;
    background: rgba(45, 55, 72, 0.55);
    backdrop-filter: blur(22px) saturate(1.2);
    -webkit-backdrop-filter: blur(22px) saturate(1.2);
    box-sizing: border-box;
}

.lightbox-workspace {
    display: flex;
    flex: 1 1 auto;
    width: 100%;
    height: 100%;
    min-height: 0;
    min-width: 0;
    gap: 14px;
}

/* Left markup panel — mirrors right tagging rail (~300px) so the
   photo stays centered. Tools: text / arrow / box / pen. */
.lightbox-markup-panel {
    flex: 0 0 300px;
    width: 300px;
    height: 100%;
    max-height: 100%;
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding: 18px 16px 14px;
    background: rgba(255, 255, 255, 0.94);
    backdrop-filter: blur(18px) saturate(1.15);
    -webkit-backdrop-filter: blur(18px) saturate(1.15);
    border: 1px solid rgba(255, 255, 255, 0.55);
    border-radius: 16px;
    box-shadow: 0 10px 32px rgba(0, 0, 0, 0.14);
    color: #2c3e50;
    box-sizing: border-box;
    min-height: 0;
    overflow-y: auto;
}
.lightbox-markup-panel__title {
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #4a5568;
}
.lightbox-markup-panel__hint {
    margin: 0;
    font-size: 12.5px;
    line-height: 1.4;
    color: #5b6b7b;
}
.lightbox-markup-panel__section {
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #718096;
    margin-top: 4px;
}
.lightbox-markup-tools {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 8px;
}
.lightbox-markup-tool {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    padding: 10px 6px;
    border: 1.5px solid #cbd5e0;
    border-radius: 10px;
    background: #fff;
    color: #2d3748;
    font-size: 11px;
    font-weight: 700;
    font-family: inherit;
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.lightbox-markup-tool:hover {
    border-color: #2d9cdb;
    background: rgba(45, 156, 219, 0.08);
}
.lightbox-markup-tool.active {
    border-color: #1a365d;
    background: #1a365d;
    color: #fff;
}
.lightbox-markup-swatches {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}
.lightbox-markup-swatch {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    border: 2px solid rgba(0, 0, 0, 0.15);
    cursor: pointer;
    padding: 0;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.35);
}
.lightbox-markup-swatch.active {
    border-color: #1a365d;
    box-shadow: 0 0 0 2px rgba(26, 54, 93, 0.35);
}
.lightbox-markup-stroke {
    display: flex;
    border: 1.5px solid #2d3748;
    border-radius: 8px;
    overflow: hidden;
}
.lightbox-markup-stroke-btn {
    flex: 1;
    border: none;
    background: #f7fafc;
    padding: 7px 4px;
    font-size: 12px;
    font-weight: 700;
    font-family: inherit;
    color: #1a202c;
    cursor: pointer;
}
.lightbox-markup-stroke-btn + .lightbox-markup-stroke-btn {
    border-left: 1.5px solid #2d3748;
}
.lightbox-markup-stroke-btn.active {
    background: #1a365d;
    color: #fff;
}
.lightbox-markup-rotate {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
    margin-bottom: 4px;
}
.lightbox-markup-panel__actions {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: auto;
    padding-top: 8px;
}
.lightbox-markup-action {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 8px 10px;
    border: 1px solid #cbd5e0;
    border-radius: 8px;
    background: #fff;
    color: #2d3748;
    font-size: 13px;
    font-weight: 600;
    font-family: inherit;
    cursor: pointer;
}
.lightbox-markup-action:hover:not(:disabled) {
    border-color: #c0392b;
    color: #c0392b;
}
.lightbox-markup-action:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}
.lightbox-markup-panel__count {
    margin: 0;
    font-size: 12px;
    color: #718096;
    text-align: center;
}

/* Flex bar undo/redo while the photo editor is open */
.flex-bar-markup-history {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex-shrink: 0;
    margin-left: 8px;
}
/* Lightbox editor: drop the date filter (not an editor action) so
   Send / Undo stay inside the current pill. Do not replace the bar. */
.flex-bar--photo-editor .gallery-filter-controls {
    display: none;
}
.flex-bar-markup-history-btn {
    font-family: inherit;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 6px 10px;
    border-radius: 10px;
    border: 1px solid rgba(255, 255, 255, 0.45);
    background: rgba(255, 255, 255, 0.14);
    color: #fff;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.15s ease, opacity 0.15s ease;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
}
.flex-bar-markup-history-btn:hover:not(:disabled) {
    background: rgba(255, 255, 255, 0.24);
}
.flex-bar-markup-history-btn:disabled {
    opacity: 0.42;
    cursor: not-allowed;
}

/* Text label popover — reuses .center-dropdown glass from app-modules.css */
.markup-text-popover {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 12px !important;
}
.markup-text-popover__label {
    font-size: 12px;
    font-weight: 700;
    color: #4a5568;
    letter-spacing: 0.02em;
    text-transform: uppercase;
}
.markup-text-popover__input {
    width: 100%;
    box-sizing: border-box;
    padding: 9px 11px;
    border: 1px solid rgba(33, 48, 77, 0.12);
    border-radius: 12px;
    font-size: 14px;
    font-family: inherit;
    color: #21304d;
    background: rgba(255, 255, 255, 0.92);
}
.markup-text-popover__input:focus {
    outline: none;
    border-color: rgba(116, 185, 255, 0.85);
    box-shadow: 0 0 0 3px rgba(116, 185, 255, 0.18);
}
.markup-text-popover__actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 2px;
}
.markup-text-popover__btn {
    font-family: inherit;
    padding: 7px 12px;
    border-radius: 10px;
    border: none;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    background: rgba(255, 255, 255, 0.45);
    color: #4a5568;
    transition: background 0.15s ease;
}
.markup-text-popover__btn:hover {
    background: rgba(255, 255, 255, 0.7);
}
.markup-text-popover__btn--primary {
    background: linear-gradient(135deg, rgba(116, 185, 255, 0.92) 0%, rgba(9, 132, 227, 0.92) 100%);
    color: #fff;
    box-shadow: 0 2px 6px rgba(9, 132, 227, 0.25);
}
.markup-text-popover__btn--primary:hover {
    background: linear-gradient(135deg, rgba(116, 185, 255, 1) 0%, rgba(9, 132, 227, 1) 100%);
}

.photo-markup-layer {
    position: absolute;
    display: block;
    pointer-events: none; /* Tag mode: clicks pass through to face layer */
    z-index: 5;
    touch-action: none;
}
.photo-markup-layer.active {
    pointer-events: auto;
    cursor: crosshair;
}
.face-tag-layer--behind {
    z-index: 2;
    pointer-events: none !important; /* don't steal markup drags */
}
.lightbox-stage.markup-mode {
    cursor: crosshair;
}

.lightbox-main {
    position: relative;
    flex: 1 1 auto;
    min-width: 0;
    min-height: 0;
    display: flex;
    align-items: stretch;
    justify-content: center;
    padding: 8px 4px;
    box-sizing: border-box;
}
/* Empty gutters beside the photo are clickable prev/next zones */
.lightbox-main--nav-prev,
.lightbox-main--nav-next {
    cursor: pointer;
}
.lightbox-main .lightbox-stage,
.lightbox-main .lightbox-close {
    cursor: default;
}
.lightbox-main .lightbox-stage.tag-mode {
    cursor: crosshair;
}
.lightbox-main .lightbox-caption-bar,
.lightbox-main .lightbox-caption-bar__input {
    cursor: text;
}

/* Full width of the center column so the caption bar doesn't shrink
   with portrait photos or grow with landscape ones. */
.lightbox-stage-wrap {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
    width: 100%;
    max-width: 100%;
    height: 100%;
    max-height: 100%;
    min-width: 0;
    min-height: 0;
}
/* Centers the shrink-wrapped photo stage in the space above the caption. */
.lightbox-stage-center {
    flex: 1 1 auto;
    min-height: 0;
    min-width: 0;
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
}

.lightbox-caption-bar {
    flex: 0 0 auto;
    display: flex;
    flex-direction: column;
    gap: 6px;
    width: 100%;
    padding: 10px 12px;
    border-radius: 12px;
    background: rgba(255, 255, 255, 0.82);
    border: 1px solid rgba(255, 255, 255, 0.5);
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
    box-sizing: border-box;
}
.lightbox-caption-bar__meta {
    display: flex;
    flex-wrap: wrap;
    gap: 8px 14px;
    font-size: 12.5px;
    font-weight: 600;
    color: #4a5568;
}
.lightbox-caption-bar__input {
    width: 100%;
    box-sizing: border-box;
    resize: vertical;
    min-height: 88px;
    max-height: 192px;
    padding: 8px 10px;
    border: 1px solid #e2e8f0;
    border-radius: 8px;
    font-size: 13.5px;
    font-family: inherit;
    background: #fff;
    color: #2c3e50;
}
.lightbox-caption-bar__input:focus {
    outline: none;
    border-color: #74b9ff;
    box-shadow: 0 0 0 3px rgba(116, 185, 255, 0.12);
}

/* Legacy centred card (kept for any non-editor lightbox callers) */
.lightbox-content {
    position: relative;
    max-width: calc(90vw - 80px);
    max-height: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 7.5px;
}

/* Tablet: stack rail under the photo; keep usable height budgets. */
html[data-ui-mode="tablet"] .lightbox-overlay:not(.lightbox-overlay--editor) {
    align-items: flex-start;
    overflow-y: auto;
    padding: 88px 24px 20px;
}
html[data-ui-mode="tablet"] .lightbox-overlay--editor {
    overflow-y: auto;
    align-items: flex-start;
    /* 10px container + 60px header + slack. 5vh is only ~38–40px on
       the 1024×768 / 1280×800 tablet-mode repros and let the bar
       sit on the workspace. */
    padding: 96px 14px 14px;
}
html[data-ui-mode="tablet"] .flex-bar--photo-editor {
    overflow: hidden;
}
html[data-ui-mode="tablet"] .flex-bar--photo-editor .flex-bar-markup-history {
    margin-left: 0;
    gap: 4px;
}
html[data-ui-mode="tablet"] .flex-bar--photo-editor .flex-bar-markup-history-btn {
    padding: 5px 8px;
}
html[data-ui-mode="tablet"] .lightbox-workspace {
    flex-direction: column;
    height: auto;
    min-height: calc(100vh - 110px);
}
html[data-ui-mode="tablet"] .lightbox-markup-panel {
    flex: 0 0 auto;
    width: 100%;
    max-width: none;
    height: auto;
    max-height: none;
}
html[data-ui-mode="tablet"] .lightbox-main {
    padding: 4px 0;
    min-height: 280px;
}
html[data-ui-mode="tablet"] .lightbox-image {
    max-height: 42vh;
}
html[data-ui-mode="tablet"] .lightbox-tag-rail {
    flex: 0 0 auto;
    width: 100%;
    max-width: none;
    height: auto;
    max-height: none;
}

.lightbox-close {
    position: absolute;
    top: 16px;
    right: 16px;
    width: 40px;
    height: 40px;
    background: rgba(255, 255, 255, 0.55);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border: 1px solid rgba(0, 0, 0, 0.12);
    border-radius: 50%;
    color: #2c3e50;
    font-size: 28px;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.2s ease, transform 0.2s ease;
    line-height: 1;
    padding: 0;
    z-index: 2;
}

/* Done + landed note (Rob's notes 1 + 2), left of the ×. Same glass as the
   close button so the pair reads as one control group. */
.lightbox-landed {
    position: absolute;
    top: 16px;
    right: 68px;
    display: flex;
    align-items: center;
    gap: 10px;
    z-index: 2;
}
.lightbox-landed__note {
    padding: 0 14px;
    height: 40px;
    display: flex;
    align-items: center;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.72);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border: 1px solid rgba(0, 0, 0, 0.08);
    color: #1f7a4d;
    font-size: 14px;
    font-weight: 700;
    white-space: nowrap;
}
.lightbox-done {
    height: 40px;
    padding: 0 20px;
    border: 0;
    border-radius: 999px;
    background: #0984e3;
    color: #fff;
    font: inherit;
    font-size: 15px;
    font-weight: 700;
    cursor: pointer;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
    transition: background 0.2s ease, transform 0.2s ease;
}
.lightbox-done:hover { background: #0770c4; transform: translateY(-1px); }
.lightbox-close:hover {
    background: rgba(255, 255, 255, 0.85);
    transform: scale(1.06);
}

.lightbox-image {
    display: block;
    max-width: 100%;
    /* Header clearance + overlay bottom pad + taller caption bar + gaps */
    max-height: calc(100vh - 340px);
    width: auto;
    height: auto;
    border-radius: 10px;
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.28);
    object-fit: contain;
    border: 1px solid rgba(255, 255, 255, 0.25);
    flex-shrink: 0;
    user-select: none;
}

/* Stage shrink-wraps the image — face-tag-layer must share this box so
   circle left/top map onto painted pixels (not a taller stretched parent). */
.lightbox-stage {
    position: relative;
    display: block;
    width: fit-content;
    max-width: 100%;
    max-height: 100%;
    margin: 0;
    line-height: 0;
    flex: 0 1 auto;
    min-height: 0;
}
.lightbox-stage.tag-mode { cursor: crosshair; }

.face-tag-layer {
    position: absolute;
    inset: 0;
    touch-action: none;   /* pointer drag draws, doesn't scroll/zoom */
}
.face-tag-layer--drop {
    /* HTML5 drop target — show the stage is ready for a child name */
    outline: 2px dashed rgba(45, 156, 219, 0.55);
    outline-offset: -4px;
    border-radius: 10px;
    background: rgba(45, 156, 219, 0.06);
}

.face-tag-circle {
    position: absolute;
    box-sizing: border-box;
    border: 2px solid rgba(45, 156, 219, 0.95);   /* confirmed/manual = solid blue */
    border-radius: 50%;
    background: rgba(45, 156, 219, 0.08);
    cursor: grab;
    transition: border-color 0.15s, background 0.15s;
}
/* Hard hide — :style geometry binding replaces the style attr and would
   otherwise defeat Alpine x-show's display:none. */
.face-tag-circle.face-tag-circle--hidden {
    display: none !important;
    visibility: hidden !important;
    pointer-events: none !important;
}
.face-tag-circle.suggested {                       /* AI-suggested = dashed amber */
    border-style: dashed;
    border-color: rgba(243, 156, 18, 0.95);
    background: rgba(243, 156, 18, 0.10);
}
.face-tag-circle.active {
    border-width: 3px;
    box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.7), 0 2px 10px rgba(0, 0, 0, 0.25);
    cursor: grabbing;
    z-index: 3;
}
.face-tag-circle.drop-target {
    border-color: rgba(39, 174, 96, 0.95);
    background: rgba(39, 174, 96, 0.18);
    box-shadow: 0 0 0 3px rgba(39, 174, 96, 0.35);
    z-index: 4;
}
/* Proximity-revealed circles fade in under a dragged name */
.face-tag-layer--drop .face-tag-circle {
    animation: face-tag-reveal 0.16s ease-out;
}
@keyframes face-tag-reveal {
    from { opacity: 0; transform: scale(0.85); }
    to { opacity: 1; transform: scale(1); }
}
.face-tag-circle.forced {                          /* force-blur = solid slate + hatch */
    border-color: rgba(71, 85, 105, 0.95);
    background: repeating-linear-gradient(
        45deg,
        rgba(71, 85, 105, 0.18) 0 6px,
        rgba(71, 85, 105, 0.06) 6px 12px);
}

.face-tag-pin {
    position: absolute;
    left: 50%;
    bottom: -22px;
    transform: translateX(-50%);
    white-space: nowrap;
    font-size: 11px;
    font-weight: 600;
    color: #fff;
    background: rgba(45, 156, 219, 0.95);
    padding: 1px 7px;
    border-radius: 8px;
    pointer-events: none;
}
.face-tag-pin--suggested { background: rgba(243, 156, 18, 0.95); }

.face-tag-handle {
    position: absolute;
    right: -7px;
    bottom: -7px;
    width: 14px;
    height: 14px;
    border-radius: 50%;
    background: #fff;
    border: 2px solid rgba(45, 156, 219, 0.95);
    cursor: nwse-resize;
}

/* Right tagging rail — inset card, mirrors the left filmstrip width balance */
.lightbox-tag-rail {
    flex: 0 0 300px;
    width: 300px;
    height: 100%;
    max-height: 100%;
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 18px 16px 14px;
    background: rgba(255, 255, 255, 0.94);
    backdrop-filter: blur(18px) saturate(1.15);
    -webkit-backdrop-filter: blur(18px) saturate(1.15);
    border: 1px solid rgba(255, 255, 255, 0.55);
    border-radius: 16px;
    box-shadow: 0 10px 32px rgba(0, 0, 0, 0.14);
    color: #2c3e50;
    box-sizing: border-box;
    min-height: 0;
    z-index: 1;
}
.lightbox-tag-rail__section-label {
    font-size: 12px;
    font-weight: 700;
    color: #4a5568;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    flex-shrink: 0;
}
.lightbox-tag-rail__hint {
    margin: 0;
    font-size: 12.5px;
    line-height: 1.4;
    color: #5b6b7b;
    flex-shrink: 0;
}
.lightbox-tag-rail__list {
    flex: 1 1 auto;
    min-height: 120px;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 2px;
    margin: 0 -4px;
    border-radius: 8px;
    background: rgba(247, 250, 252, 0.7);
}
.lightbox-tag-rail .tag-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 8px;
    border-radius: 8px;
    font-size: 13.5px;
    color: #2d3748;
    user-select: none;
}
.lightbox-tag-rail .tag-item:hover {
    background: rgba(116, 185, 255, 0.14);
}
.lightbox-tag-rail .tag-item--pending {
    background: rgba(45, 156, 219, 0.16);
    box-shadow: inset 0 0 0 1.5px rgba(45, 156, 219, 0.55);
}
.lightbox-tag-rail .tag-item--previewing {
    background: rgba(26, 54, 93, 0.08);
    box-shadow: inset 0 0 0 1.5px rgba(26, 54, 93, 0.45);
}
.lightbox-tag-rail__check {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 1 1 auto;
    min-width: 0;
    cursor: pointer;
}
.lightbox-tag-rail .tag-item input[type="checkbox"] {
    flex-shrink: 0;
    width: 16px;
    height: 16px;
    accent-color: #2d9cdb;
    cursor: pointer;
}
.lightbox-tag-rail__name {
    flex: 1;
    min-width: 0;
    cursor: grab;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.lightbox-tag-rail__name:active {
    cursor: grabbing;
}
.lightbox-tag-rail .tag-item--dragging {
    opacity: 0.45;
}
.lightbox-tag-rail .tag-item--opt-out {
    background: rgba(192, 57, 43, 0.06);
}
.lightbox-tag-rail .tag-item--opt-out.tag-item--pending {
    background: rgba(45, 156, 219, 0.16);
}

/* Per-child Family / Others preview (tagged + opted-out only) */
.lightbox-tag-rail__preview {
    flex-shrink: 0;
    display: inline-flex;
    border: 1.5px solid #2d3748;
    border-radius: 7px;
    overflow: hidden;
    background: #f7fafc;
}
.lightbox-tag-rail__preview-btn {
    border: none;
    background: #f7fafc;
    padding: 4px 7px;
    font-size: 11px;
    font-weight: 700;
    font-family: inherit;
    color: #1a202c;
    cursor: pointer;
    line-height: 1.2;
}
.lightbox-tag-rail__preview-btn + .lightbox-tag-rail__preview-btn {
    border-left: 1.5px solid #2d3748;
}
.lightbox-tag-rail__preview-btn:hover:not(:disabled):not(.active) {
    background: #edf2f7;
}
.lightbox-tag-rail__preview-btn.active {
    background: #1a365d;
    color: #ffffff;
}
.lightbox-tag-rail__preview-btn:disabled {
    cursor: wait;
}
.lightbox-image--preview {
    box-shadow: 0 0 0 3px rgba(26, 54, 93, 0.5), 0 12px 40px rgba(0, 0, 0, 0.28);
}
.lightbox-tag-rail__active {
    flex-shrink: 0;
    padding-top: 10px;
    border-top: 1px solid rgba(0, 0, 0, 0.08);
}
/* "Who sees what" — the per-child sharing summary rendered by app.js
   photoSharing(), sitting at the foot of the tag rail.

   Colour carries EMPHASIS ONLY. Every state is also spelled out in words,
   because the distinction is a consent fact rather than a severity, and
   colour alone survives neither a colour-blind reader nor a screenshot
   pasted into a compliance thread.

   Sizing: the rail is a fixed-height flex column whose __list is
   `flex: 1 1 auto; min-height: 120px`. Left unbounded this panel would
   push the list to that 120px floor and then overflow the rail, so it is
   capped and scrolls internally instead. min-height:0 is required or a
   flex child refuses to shrink below its content. */
.lightbox-sharing {
    /* 0 0 auto, NOT 0 1 auto. The sibling __list is `flex: 1 1 auto`, so if
       this panel is allowed to shrink the list wins every time: with a full
       roster above it (85 children on the verification org) it collapsed to
       43px, which still measures as "inside the viewport" because the BOX
       fits -- the content was clipped inside the overflow. Let the list
       shrink toward its 120px floor instead; it already scrolls. */
    flex: 0 0 auto;
    min-height: 0;
    max-height: 38%;
    overflow-y: auto;
    padding-top: 10px;
    margin-bottom: 2px;
    border-top: 1px solid rgba(0, 0, 0, 0.06);
}

.lightbox-sharing-title {
    font-weight: 600;
    margin-bottom: 4px;
}

.lightbox-sharing-row {
    display: flex;
    align-items: baseline;
    gap: 8px;
    flex-wrap: wrap;
    font-size: 14px;
    line-height: 1.5;
}

.lightbox-sharing-name {
    font-weight: 500;
}

.lightbox-sharing-state--shared {
    color: #4a7c59;
}

.lightbox-sharing-state--others,
.lightbox-sharing-state--everyone {
    color: #a05a2c;
}

.lightbox-sharing-summary {
    margin-top: 6px;
    font-size: 13px;
    line-height: 1.5;
    opacity: 0.75;
}

.lightbox-tag-rail__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    flex-shrink: 0;
    padding-top: 10px;
    border-top: 1px solid rgba(0, 0, 0, 0.06);
    background: transparent;
}
.lightbox-tag-rail__actions .lightbox-edit-btn {
    flex: 1 1 auto;
    justify-content: center;
}

.lightbox-tag-assign__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 8px;
}
.lightbox-tag-assign__label { font-weight: 600; font-size: 13px; }
.lightbox-tag-del {
    background: none; border: none; color: #c0392b;
    cursor: pointer; padding: 4px; border-radius: 6px;
}
.lightbox-tag-del:hover { background: rgba(192, 57, 43, 0.1); }
.lightbox-tag-clear {
    margin-top: 8px;
    background: none; border: none;
    color: #5b6b7b; font-size: 13px;
    cursor: pointer; text-decoration: underline;
}
.lightbox-tag-consent-hint {
    margin: 6px 0 8px;
    font-size: 12.5px;
    line-height: 1.35;
    color: #8a6d1a;
}
.lightbox-tag-consent-hint--ok {
    color: #1e8449;
}
.lightbox-tag-forceblur {
    display: block;
    width: 100%;
    margin-top: 10px;
    padding: 7px 10px;
    border: 1px solid rgba(71, 85, 105, 0.4);
    border-radius: 8px;
    background: rgba(71, 85, 105, 0.06);
    color: #334155;
    font-size: 13px;
    cursor: pointer;
}
.lightbox-tag-forceblur.active {
    background: rgba(71, 85, 105, 0.85);
    border-color: rgba(71, 85, 105, 0.85);
    color: #ffffff;
}
.lightbox-edit-btn--done {
    background: rgba(39, 174, 96, 0.12);
    border-color: rgba(39, 174, 96, 0.5);
    color: #1e8449;
}
.lightbox-edit-btn--done:hover {
    background: rgba(39, 174, 96, 0.2);
    border-color: rgba(39, 174, 96, 0.7);
}

.lightbox-info {
    margin-top: 20px;
    padding: 20px 30px;
    background: rgba(255, 255, 255, 0.35);
    backdrop-filter: blur(12px) saturate(1.2);
    -webkit-backdrop-filter: blur(12px) saturate(1.2);
    border: 1px solid rgba(255, 255, 255, 0.5);
    border-radius: 12px;
    color: #2c3e50;
    width: 100%;
    max-width: 600px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
    
    /* Add scrolling instead of clipping */
    max-height: 250px;
    overflow-y: auto;
    flex-shrink: 1;
}

/* Custom scrollbar styling for lightbox info */
.lightbox-info::-webkit-scrollbar {
    width: 6px;
}

.lightbox-info::-webkit-scrollbar-track {
    background: rgba(0, 0, 0, 0.05);
    border-radius: 3px;
}

.lightbox-info::-webkit-scrollbar-thumb {
    background: rgba(0, 0, 0, 0.2);
    border-radius: 3px;
}

.lightbox-info::-webkit-scrollbar-thumb:hover {
    background: rgba(0, 0, 0, 0.3);
}

.lightbox-info-row {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    margin-bottom: 12px;
    font-size: 15px;
    line-height: 1.6;
}

.lightbox-info-row:last-child {
    margin-bottom: 0;
}

/* Legacy lightbox action row (kept for any remaining callers). */
.lightbox-info-actions {
    margin-top: 4px;
    padding-top: 12px;
    border-top: 1px solid rgba(0, 0, 0, 0.08);
    display: flex;
    justify-content: center;
    gap: 10px;
}
.lightbox-edit-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 7px 14px;
    border-radius: 16px;
    border: 1px solid rgba(99, 102, 241, 0.4);
    background: rgba(99, 102, 241, 0.06);
    color: #4f46e5;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
}
.lightbox-edit-btn:hover {
    background: rgba(99, 102, 241, 0.14);
    border-color: rgba(99, 102, 241, 0.6);
}

.lightbox-info-icon {
    flex-shrink: 0;
    color: #7f8c8d;
    margin-top: 2px;
}

.lightbox-info-content {
    flex: 1;
    color: #2c3e50;
    word-wrap: break-word;
    word-break: break-word;
    overflow-wrap: break-word;
    min-width: 0;
}

.lightbox-date {
    font-weight: 600;
    color: #34495e;
}

.lightbox-caption {
    font-style: italic;
    color: #555;
    word-wrap: break-word;
    word-break: break-word;
    overflow-wrap: break-word;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    text-overflow: ellipsis;
    cursor: pointer;
    transition: all 0.2s ease;
}

.lightbox-caption.expanded {
    -webkit-line-clamp: unset;
    overflow: visible;
}

.lightbox-caption:hover {
    color: #2c3e50;
}

/* Photo Tagging Modal */
.photo-tagging-overlay {
    z-index: 3100;
}

.photo-tagging-modal {
    max-width: 500px !important;
    width: 500px !important;
    height: auto !important;
    max-height: 80vh !important;
    padding: 0 !important;
}

.tagging-modal-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 20px 25px;
    border-bottom: 1px solid #ecf0f1;
}

.tagging-modal-header h3 {
    font-size: 18px;
    font-weight: 700;
    color: #21304d;
    margin: 0;
}

.tagging-modal-body {
    padding: 20px 25px;
    max-height: calc(80vh - 140px);
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 20px;
}

.tag-section {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.tag-section-header {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 5px;
}

.tag-section-header h4 {
    font-size: 16px;
    font-weight: 600;
    color: #21304d;
    margin: 0;
}

.tag-search-input {
    width: 100%;
    padding: 10px 14px;
    border: 1px solid #dfe6e9;
    border-radius: 8px;
    font-size: 14px;
    font-family: 'Quicksand', sans-serif;
    transition: all 0.2s ease;
}

.tag-search-input:focus {
    outline: none;
    border-color: #46cfc0;
    box-shadow: 0 0 0 3px rgba(70, 207, 192, 0.1);
}

.tag-items-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
    max-height: 200px;
    overflow-y: auto;
    padding: 5px;
    border: 1px solid #ecf0f1;
    border-radius: 8px;
    background: #f8f9fa;
}

.tag-items-list::-webkit-scrollbar {
    width: 6px;
}

.tag-items-list::-webkit-scrollbar-track {
    background: #f1f3f4;
    border-radius: 3px;
}

.tag-items-list::-webkit-scrollbar-thumb {
    background: #c1c8cd;
    border-radius: 3px;
}

.tag-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 12px;
    background: white;
    border-radius: 6px;
    cursor: pointer;
    transition: all 0.2s ease;
    font-size: 14px;
    color: #21304d;
}

.tag-item:hover {
    background: #e8f5f5;
}

.tag-item input[type="checkbox"] {
    width: 18px;
    height: 18px;
    cursor: pointer;
    accent-color: #46cfc0;
}

.tag-empty-message {
    text-align: center;
    padding: 20px;
    color: #7f8c8d;
    font-size: 14px;
    font-style: italic;
}

.tagging-modal-footer {
    display: flex;
    justify-content: flex-end;
    gap: 12px;
    padding: 20px 25px;
    border-top: 1px solid #ecf0f1;
}

.tagging-cancel-btn,
.tagging-save-btn {
    padding: 10px 24px;
    border-radius: 8px;
    font-size: 14px;
    font-family: 'Quicksand', sans-serif;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.2s ease;
    border: none;
}

.tagging-cancel-btn {
    background: #ecf0f1;
    color: #21304d;
}

.tagging-cancel-btn:hover {
    background: #dfe6e9;
}

.tagging-save-btn {
    background: #46cfc0;
    color: white;
}

.tagging-save-btn:hover {
    background: #3db8a8;
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(70, 207, 192, 0.3);
}

/* Lightbox Tags Display */
.lightbox-tags {
    display: flex;
    align-items: center;
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid rgba(255, 255, 255, 0.2);
    font-size: 14px;
    opacity: 0.9;
}

/* Utility Classes */
.time-input-active {
    border-color: #0984e3 !important;
    box-shadow: 0 0 8px rgba(9, 132, 227, 0.5);
}

@keyframes shake {
    0%, 100% { transform: translateX(0); }
    25% { transform: translateX(-5px); }
    75% { transform: translateX(5px); }
}

/* ── Skeleton shimmer loading animation ────────────────────────── */
@keyframes shimmer {
    0%   { background-position: -400px 0; }
    100% { background-position: 400px 0; }
}

.skeleton {
    background: linear-gradient(90deg, #f0efee 25%, #e4e3e1 37%, #f0efee 63%);
    background-size: 800px 100%;
    animation: shimmer 1.6s ease-in-out infinite;
    border-radius: 12px;
}

.skeleton-line {
    background: linear-gradient(90deg, #f0efee 25%, #e4e3e1 37%, #f0efee 63%);
    background-size: 800px 100%;
    animation: shimmer 1.6s ease-in-out infinite;
    border-radius: 6px;
    height: 14px;
}

/* ── Card fade-in animation ────────────────────────────────────── */
/* 🔴 Animates the independent `translate` property, NOT `transform`.
   `.card-appear` runs with `animation-fill-mode: both`, which holds the
   end-state after the animation finishes — and a filled animation value
   outranks every normal author declaration in the cascade. While this
   animated `transform`, the held `translateY(0)` permanently defeated the
   `:hover` lift on every card that carried the class: `.child-card`,
   `.docs-card`, `.room-card` and `.att-room-donut-card` all had dead hover
   transforms. `translate` is a separate property that composes with
   `transform`, so the held entrance value leaves a hover `transform` free.
   Same fix, and the same reasoning, as `eduCascadeRise` in
   educators-unified.css — which sidestepped this trap deliberately. */
@keyframes cardFadeIn {
    from {
        opacity: 0;
        translate: 0 12px;
    }
    to {
        opacity: 1;
        translate: 0 0;
    }
}

.card-appear {
    animation: cardFadeIn 0.35s cubic-bezier(0.4, 0, 0.2, 1) both;
}

@media (prefers-reduced-motion: reduce) {
    .card-appear {
        animation: none;
    }
    .flex-bar {
        animation: none;
    }
}

/* Generated / photo child face — fills any circular avatar host.
   Absolute fill so a landscape file cannot shrink to a letterboxed
   strip (flex + intrinsic ratio) or stretch to the circle (width and
   height 100% without a definite box). Cover keeps the native ratio. */
.child-face-wrap,
.card-avatar-circle,
.child-avatar-large,
.care-child-avatar,
.large-avatar-circle,
.contact-avatar,
.parent-child-avatar,
.participant-avatar,
.attendance-child-icon,
.child-status-named,
.profile-avatar-face,
.educator-icon,
.edu-avatar,
.edu-avatar-large,
.floor-ed-avatar {
    position: relative;
    overflow: hidden;
}
.child-face {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    display: block;
    border-radius: inherit;
    overflow: hidden;
}
.child-face-img,
.child-face-svg {
    width: 100%;
    height: 100%;
    display: block;
    object-fit: cover;
    object-position: center;
    border-radius: inherit;
}
.attendance-child-icon .child-face,
.child-status-named .child-face {
    position: absolute;
    inset: 0;
}
.child-avatar-large.clickable-avatar,
.large-avatar-circle.clickable-avatar,
.edu-avatar-large.clickable-avatar {
    cursor: pointer;
}

/* Photo editor — crop / replace / remove a square profile photo.
   House modal chrome (.modal-content.glass-content). The vestibule cut
   stays (open on take / upload / tagged; frame only after pick).
   Crop-well contain-fit is from the later 1.3 ratio fix — keep it. */
.photo-editor-modal {
    max-width: 560px;
    width: 92%;
    height: auto;
    max-height: calc(100dvh - 128px);
}
.photo-editor-body {
    padding: 20px 24px 28px;
}
.photo-editor-error {
    margin-bottom: 12px;
    padding: 10px 14px;
    border-radius: 10px;
    background: rgba(255, 107, 107, 0.12);
    color: #a33;
    font-size: 14px;
}
.photo-editor-current {
    display: flex;
    justify-content: center;
    margin-bottom: 16px;
}
.photo-editor-current__img {
    width: 160px;
    height: 160px;
    border-radius: 50%;
    object-fit: cover;      /* the avatar surfaces are circular; preview must agree */
    box-shadow: 0 4px 18px rgba(0, 0, 0, 0.18);
}
.photo-editor-hint {
    margin: 0 0 14px;
    text-align: center;
    font-size: 13px;
    opacity: 0.7;
}
/* Cropper.js replaces this <img> with its own container and sizes to the
   element's box, so the height has to be explicit or the canvas collapses.
   Do not set the img to width+height 100% — that stretches the source
   before Cropper takes over. Contain + auto keeps the native ratio. */
.photo-editor-crop {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 340px;
    background: #1b1b1b;
    border-radius: 12px;
    overflow: hidden;
    margin-bottom: 12px;
}
.photo-editor-crop > img {
    display: block;
    width: auto;
    height: auto;
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}
/* Round the crop box so the framing matches where the photo actually lands. */
.photo-editor-crop .cropper-view-box,
.photo-editor-crop .cropper-face {
    border-radius: 50%;
}
.photo-editor-crop .cropper-line,
.photo-editor-crop .cropper-point {
    display: none;
}
.photo-editor-actions {
    display: flex;
    justify-content: center;
    gap: 12px;
    flex-wrap: wrap;
}
.photo-editor-actions--footer {
    justify-content: flex-end;
}

.media-picker-modal {
    max-width: 480px;
    width: 90%;
    height: auto;
    max-height: calc(100dvh - 128px);
}
.media-picker-modal--gallery {
    max-width: 720px;
    max-height: 85vh;
    display: flex;
    flex-direction: column;
}
.media-picker-body {
    padding: 20px 24px 28px;
}
.media-picker-modal--gallery .media-picker-body {
    overflow: hidden;
    display: flex;
    flex-direction: column;
    min-height: 0;
}
.media-picker-choices {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
}
.media-picker-choice {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    min-height: 140px;
    padding: 18px 12px;
    border: 1px solid rgba(33, 48, 77, 0.12);
    border-radius: 16px;
    background: rgba(255, 255, 255, 0.72);
    color: #21304d;
    font-family: 'Quicksand', sans-serif;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
    transition: border-color 0.15s ease, box-shadow 0.15s ease, transform 0.12s ease;
}
.media-picker-modal--gallery .media-picker-choice {
    min-height: 88px;
    padding: 14px 12px;
}
.media-picker-choice:hover {
    border-color: #74b9ff;
    box-shadow: 0 6px 16px rgba(9, 132, 227, 0.12);
    transform: translateY(-1px);
}
.media-picker-choice iconify-icon {
    font-size: 32px;
    color: #21304d;
}
.media-picker-gallery {
    margin-top: 16px;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
.media-picker-gallery-tabs {
    display: flex;
    gap: 8px;
    margin-bottom: 10px;
}
.media-picker-gallery-tab {
    border: 1px solid rgba(33, 48, 77, 0.12);
    background: rgba(255, 255, 255, 0.65);
    color: #21304d;
    font-family: 'Quicksand', sans-serif;
    font-size: 13px;
    font-weight: 600;
    border-radius: 999px;
    padding: 6px 12px;
    cursor: pointer;
}
.media-picker-gallery-tab.is-active {
    border-color: #74b9ff;
    background: rgba(116, 185, 255, 0.18);
}
.media-picker-gallery-status {
    margin: 8px 0 0;
    color: #7f8c8d;
    font-size: 13px;
}
.media-picker-gallery-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(92px, 1fr));
    gap: 8px;
    overflow: auto;
    max-height: 360px;
    padding: 2px;
}
.media-picker-gallery-thumb {
    aspect-ratio: 1 / 1;
    border: 2px solid rgba(255, 255, 255, 0.5);
    border-radius: 10px;
    padding: 0;
    overflow: hidden;
    cursor: pointer;
    background: #eef2f7;
}
.media-picker-gallery-thumb:hover {
    border-color: rgba(59, 130, 246, 0.6);
}
.media-picker-gallery-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.media-picker-gallery-thumb.is-picking {
    opacity: 0.55;
}
.media-picker-video {
    width: 100%;
    border-radius: 14px;
    background: #000;
    max-height: 320px;
    object-fit: cover;
}
.media-picker-error {
    background: #ff7675;
    color: #fff;
    padding: 12px 14px;
    border-radius: 10px;
    margin-bottom: 12px;
    font-size: 14px;
}
.media-picker-loading {
    text-align: center;
    padding: 36px 12px;
    color: #7f8c8d;
}
.media-picker-camera-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    justify-content: center;
    margin-top: 14px;
}

.photo-tile,
.child-card {
    content-visibility: auto;
    contain-intrinsic-size: 0 220px;
}

/* Responsive adjustments for smaller screens */
@media (max-width: 1366px) {
    .container {
        width: 95%;
        max-width: 1280px;
    }
}

/* ── Tablet UI mode (Galaxy Tab A 8.4 / tablet-class devices) ────────
   Gated on html[data-ui-mode="tablet"], set at boot by the inline
   detector in app.html/admin.html. Auto = touch + (Android|iPad) +
   tablet geometry (short≥500 & long≥900 CSS px); override with
   ?ui=tablet|desktop|auto, persisted in sessionStorage (per-tab only).
   Deliberately NOT pure-width-based: a half-snapped desktop Chrome
   window without a tablet-class device keeps the stock desktop UI.
   Tuned for the fleet Tab A ~1024×532 CSS. parent.html never sets the
   attribute, so the parent portal is untouched by design. */
html[data-ui-mode="tablet"] .container {
    width: 100vw;
    max-width: none;
    /* Vertical economy: the measured device gives Chrome only ~532px of
       height — desktop chrome (15px shell padding + 70px header + 15px
       margin) leaves ~417px for content. Trimmed paddings below buy
       back ~25px. */
    padding: 10px;
}
html[data-ui-mode="tablet"] .header {
    /* Thinner chrome for short fleet tablets (~532 CSS-px).
       Name left; candy soft-distributes the middle track; plus sits
       beside the shell; icons stay on the right (#141). */
    height: 44px;
    margin-bottom: 6px;
    padding: 4px 10px;
    justify-content: flex-start;
    gap: 8px;
}
/* ── Tablet flex-bar control height ───────────────────────────────────
   ONE token for every control that sits in the bar. The bar is 28px with
   2px vertical padding, so its inner box is 24px and controls sit at 22px.
   22px is not a new value: it is what the gallery filters, the add-photo
   button, the mode toggle and the topbar selects were each already set to
   by hand, one rule at a time.

   Three controls were never given a tablet height and kept whatever their
   content forced. Measured on `dbbe7623` at 1280x800, and identical at
   980x612 / 960x600 / 1024x532 — this is a control-vs-bar mismatch, not a
   viewport one:

     .docs-search (Resources)              38px — exceeded the inner box by 14
     .docs-search (Children, Questionnaires) 28px — exceeded it by 4
     .docs-mode-toggle (inside the above)  28px — exceeded it by 4

   Resources overflowed the bar outright, visible since `dbbe7623` replaced
   the bar's `overflow: hidden` with `visible` and stopped hiding it.

   Desktop is the control and needs none of this: its bar is 56px and every
   control already clears it with 12-33px to spare. Only the tablet tier
   halves the bar without rescaling what goes in it.

   The bar's own height is deliberately unchanged — this constrains content
   to the bar, it does not take page height. */
html[data-ui-mode="tablet"] {
    --flexbar-control-h: 22px;
}

html[data-ui-mode="tablet"] .flex-bar {
    height: 28px;
    border-radius: 999px;
    /* Visible: planner/ASQ/Children room menus sit just below the chip.
       Clipping this 28px pill (Tab A fit, 2026-09-08) hid the menu, so
       tablets could not leave Infants. Title/chip ellipsis still clips
       on the inner spans. */
    overflow: visible;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}

/* Soft-distribute: candy shell fills the middle track between the
   educator name and the plus disc (#141 / F0C2XM6088G). Shrink-wrap
   (flex: 0 1 auto) left 370–626px dead outside the pill — the gap
   now lives as empty olive *inside* the shell, between flanks and
   title. Plus stays its own disc beside the bar, not inside it. */
html[data-ui-mode="tablet"] .flex-bar-container {
    position: relative;
    left: auto;
    top: auto;
    transform: none;
    flex: 1 1 0%;
    width: auto;
    max-width: 100%;
    min-width: 0;
    padding: 0;
    /* Square overflow clip + backdrop-filter painted a grey
       rectangle behind the pill's curve. Clip on the pill itself. */
    overflow: visible;
    background: transparent;
}
html[data-ui-mode="tablet"] .educator-profile {
    flex: 0 1 auto;
    min-width: 0;
}
/* RESERVED TRAILING TRACK — this is what stops the flex bar's controls
   jerking after a module switch.

   The bar is `flex: 1 1 0%` above, i.e. it is literally whatever the rest
   of the header row does not use. This cluster used to be `display:
   contents`, which dissolved it: the (+) disc and every notification icon
   became header flex items in their own right, so the bar's width was the
   leftover of a row whose membership changes asynchronously for about a
   third of a second after every module switch. Measured on a cold
   dashboard -> Activity Planner switch at 1280x800, the bar took ten
   separate widths over 360ms, overshooting its final size by 48px before
   falling back, and three unrelated things drove it:

     - the Operate/Analyze toggle leaving with the dashboard (56px, ~19ms),
     - the (+) disc's own 0.3s .header-icon-collapsible WIDTH transition,
       which is a layout animation and therefore dragged the bar with it
       for the whole 300ms,
     - the print icon arriving once the planner's data landed (56px,
       ~164ms) — the overshoot, and the "comes back" educators described.

   Hiding those icons earlier cannot work, and makes it worse: hidden icons
   mean a WIDER bar, so pre-hiding everything starts the bar at its maximum
   and shrinks it as each icon resolves. The bar has to stop depending on
   the icon set at all.

   So: give the cluster a real box of its own with a reserved width, and
   right-align the icons inside it. Icons may now appear, disappear or
   animate freely — it all happens inside this box and the bar never
   notices. Visual order and spacing are unchanged ([name] [candy] [plus]
   [icons], 3px between the discs); the only difference is that the pill's
   right edge is now anchored instead of tracking the icons.

   --header-trailing-reserve is the one number to tune. 308px is the widest
   this cluster was measured at across all fifteen top-level modules
   (Dashboard: (+) plus five 48px icons and their gaps). It is a min-width,
   not a hard width, so the rare state that needs more — a drill-in showing
   Back, edit, delete and print together — still grows and shrinks the bar
   rather than painting over it.

   COST, and it is a deliberate one: the pill no longer widens on
   icon-light modules. At 1280x800 it is a constant 770px everywhere,
   where today it ranges 770-913px. Documents, Messaging, Questionnaires,
   Data and the analytics screens lose about 143px of pill, which shows
   as olive between the pill's right edge and the first icon. That is a
   design change and Pete should be asked to confirm it; the alternative
   is that the controls keep moving after every switch. To hand the
   width back and accept the movement again, set the reserve to 0 — the
   box then shrink-wraps its icons and the bar is the leftover once more,
   exactly as before. No other rule needs touching.

   margin-left:0 stays deliberately: Chrome transfers an auto margin on a
   display:contents box to its last child, which parked the icon cluster
   on the right and starved the candy track. The box is real now, but the
   desktop rule above still sets margin-left:auto and we do not want it. */
/* Was 308px at 28×28 plus + 3px gap. #298 grows plus to the same 48×34
   paint / 48×48 tap as sibling header icons and opens an 8dp gap to the
   mode-toggle cluster (+25px).
   389px = 333px + one more 48px icon and its 8px gap: the Help & Training "?"
   is shown on the tablet as well (floor staff are its first audience).
   Keep the token the first line of its block: the #141 test reads it there. */
html[data-ui-mode="tablet"] {
    --header-trailing-reserve: 389px;
}
html[data-ui-mode="tablet"] .header-trailing {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    margin-left: 0;
    /* 8px between plus and .notifications (mode toggle is the first neighbour).
       Was 3px — remasured as 0px effective gap / overlapping 48dp hits (#298). */
    --header-icon-gap: 8px;
    gap: var(--header-icon-gap);
    flex: 0 1 auto;
    min-width: var(--header-trailing-reserve);
}
html[data-ui-mode="tablet"] .flex-bar-plus-disc:not(.is-collapsed) {
    /* Own control beside the candy pill — not inside it, not kit frost.
       Match sibling .notification-icon paint (48×34 pill) so the inherited
       rectangular ::before is a true 48×48 tap (#298 / #142). Do not keep a
       28×28 disc: round hit-testing misses the corners. */
    flex: 0 0 auto;
    width: 48px;
    height: 34px;
    min-width: 48px;
    margin-left: 0;
}
html[data-ui-mode="tablet"] .notifications {
    /* No margin-left:auto — that ate the free space the candy
       shell needs to fill the track. Icons stay a trailing cluster
       immediately after the plus disc. */
    flex: 0 0 auto;
    margin-left: 0;
}
html[data-ui-mode="tablet"] .educator-profile {
    gap: 8px;
}
/* A long centre name must not starve the flex bar.

   The bar is the leftover of the header row (`flex: 1 1 0%`), and the
   profile card above is `flex: 0 1 auto` with no cap, so a 46-character
   centre name took its full 364px and left the bar 213px at 1024x532
   (433px with a short name). Every control then ran out of room at once:
   Attendance clipped "Spaces", Children's filters and the Planner week
   picker slid behind the (+) disc, and the Questionnaires title read
   "Questior".

   Measured on the local seed at 1024x532, sweeping every landing view:
   427px is the narrowest bar at which none of them escapes or clips;
   397px already fails Planner and Children. So the card yields only when
   keeping it whole would take the bar below that floor. At 1280 the card
   has room to spare and nothing changes. The name ellipsizes; the centre
   switcher menu still lists the full name.

   150px is the smallest the card is allowed to get (avatar plus a stub of
   the name). 100% is the header's content box (982px at 1024: 1004 less
   2x10px padding less 2x1px border); the 16px is its two 8px gaps. */
html[data-ui-mode="tablet"] {
    --flexbar-floor: 427px;
}
/* App shell only. The reserve term is .header-trailing's min-width, and
   only app.html has that box: admin.html shares the profile card and
   data-ui-mode but keeps its icons in a bare .notifications, so there the
   term would be phantom and the org name would be cut to 150px at 1024
   whether or not its row needed it. The shells are identical at the html
   and body level (only x-data differs), so the cap keys off the box it
   subtracts: a card followed by .header-trailing in its header row. The
   admin header is left exactly as it was. */
html[data-ui-mode="tablet"] .educator-profile:has(~ .header-trailing) {
    max-width: max(150px, calc(100% - var(--header-trailing-reserve) - var(--flexbar-floor) - 16px));
}
html[data-ui-mode="tablet"] .educator-profile:has(~ .header-trailing) .profile-info,
html[data-ui-mode="tablet"] .educator-profile:has(~ .header-trailing) .center-switcher {
    min-width: 0;
}
/* The name's h2 is inline-flex, and an inline-flex box never shrinks below
   its items' min-content (the whole nowrap name), so without this it kept
   its full width, overflowed the card and slid under the bar instead of
   ellipsizing. Clamped to the card, the span inside shrinks and ellipsizes
   and the chevron keeps its place. */
html[data-ui-mode="tablet"] .educator-profile:has(~ .header-trailing) .center-switcher h2 {
    max-width: 100%;
}
/* Control-heavy module bars (Children: search + two dropdowns) need the
   pill's side padding as content room; 4px vertical keeps the tallest
   control inside the 40px bar (border-box). 18px sides: at 12px
   the title sat uncomfortably close to the pill edge on full bars (jj,
   round 17) — the shrink-first search absorbs the lost width. */
html[data-ui-mode="tablet"] .flex-bar {
    padding: 2px 10px;
    box-sizing: border-box;
}
/* Desktop-sized chrome (36px Drop-off, 34px gallery +, 8px-pad
   date field) is taller than the 28px candy pill and reads as
   overflowing the header. Cap every common control to the inner
   track (~24px). */
html[data-ui-mode="tablet"] .flex-bar .gallery-date-filter,
html[data-ui-mode="tablet"] .flex-bar .gallery-select-filter {
    height: var(--flexbar-control-h);
    min-height: var(--flexbar-control-h);
    max-height: var(--flexbar-control-h);
    min-width: 0;
    padding: 0 8px;
    font-size: 11px;
    line-height: 20px;
    border-radius: 11px;
    box-sizing: border-box;
}
html[data-ui-mode="tablet"] .flex-bar .gallery-add-photo-btn {
    height: var(--flexbar-control-h);
    width: var(--flexbar-control-h);
    min-width: var(--flexbar-control-h);
    font-size: 16px;
    border-radius: 8px;
}
html[data-ui-mode="tablet"] .flex-bar-mode-toggle {
    margin-left: 6px;
}
html[data-ui-mode="tablet"] .flex-bar-mode-toggle .mode-toggle {
    height: var(--flexbar-control-h);
    min-height: var(--flexbar-control-h);
    max-height: var(--flexbar-control-h);
    min-width: 96px;
    /* Padding and the 1px border ate the token: the track measured 22px
       while the knob and "Drop-off" label sat at 18px / 14px and a lower
       top. The hairline stays as a shadow so the content box is the token. */
    padding: 0;
    border-width: 0;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.55);
    border-radius: 11px;
    margin-left: 6px;
    box-sizing: border-box;
}
html[data-ui-mode="tablet"] .flex-bar-mode-toggle .toggle-switch {
    width: var(--flexbar-control-h);
    height: var(--flexbar-control-h);
    top: 0;
}
html[data-ui-mode="tablet"] .flex-bar-mode-toggle .mode-toggle.pickup .toggle-switch {
    left: calc(100% - var(--flexbar-control-h));
}
html[data-ui-mode="tablet"] .flex-bar-mode-toggle .toggle-label {
    font-size: 11px;
    height: var(--flexbar-control-h);
    line-height: var(--flexbar-control-h);
    box-sizing: border-box;
}
html[data-ui-mode="tablet"] .dashboard-tab-bar {
    /* The shell and each segment share --flexbar-control-h. A 1px pad
       plus the desktop 1px border left the buttons at 18px (top 23)
       inside a 22px shell (top 21) — #224. Hairline is a shadow so it
       does not shrink the content box. */
    height: var(--flexbar-control-h);
    min-height: var(--flexbar-control-h);
    max-height: var(--flexbar-control-h);
    padding: 0;
    border-width: 0;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.3);
    box-sizing: border-box;
    border-radius: 12px;
    overflow: hidden;
    align-items: stretch;
}
html[data-ui-mode="tablet"] .dashboard-tab-btn {
    height: var(--flexbar-control-h);
    min-height: var(--flexbar-control-h);
    max-height: var(--flexbar-control-h);
    padding: 0 8px;
    font-size: 11px;
    line-height: 1;
    border-radius: 10px;
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
html[data-ui-mode="tablet"] .flex-bar .topbar-select {
    height: var(--flexbar-control-h) !important;
    font-size: 11px !important;
}
/* The last two bar controls off the token (#144). Both were left sizing
   themselves from their content: `.flex-bar-entity-btn` (Add Child, Save and
   Exit) painted 38px against the bar's 22.7px inner box, and the Next Child
   button teleported into `#topBarNextControls` painted 31.3px -- measured on
   the fleet AVD at 1280x648, tablet tier. Neither was clipped and neither
   collided with anything: the bar's overflow has been visible since
   `dbbe7623` and both still sat inside the 44px page header. They were left
   out of #139 deliberately, because they are also the bar's two LARGEST touch
   targets and the token is 22px -- 46% of Android's 48dp minimum -- so
   shrinking them mechanically would have moved the two best controls in the
   bar in the wrong direction.

   Resolved the way #142 resolved the header icons: paint on the token, tap at
   48. The ::before carries the touch target, paints nothing and takes no
   layout room, so the bar keeps one control height and the finger still gets
   a compliant square. Verified on the live bar with a forced-then-restored
   control: the four corners of the intended 48x48 return the button 0/4
   before, 4/4 with the ::before, 0/4 again after, and the button's own height
   never moves. Absolutely positioned, so it is not a flex item -- these
   buttons are flex containers and an in-flow pseudo-element would lay out.

   The technique works here and NOT for the rest of the bar: ::before and
   ::after do not render on replaced elements, and `.docs-search` /
   `.topbar-select` are an input and a select. These two are buttons.

   `!important` because the Next Child button carries its styling inline from
   the teleported markup in children-module.html, and a normal declaration
   loses to an inline one. `.flex-bar-entity-btn` also has two bordered states
   (`btn-disabled` and `btn-outline` add 2px) which made its painted height
   depend on whether the form was valid; border-box plus a fixed height ends
   that. */
html[data-ui-mode="tablet"] #topBarNextControls {
    flex: 0 0 auto;
    flex-shrink: 0;
    min-width: 0;
}
html[data-ui-mode="tablet"] .flex-bar-next-child-btn,
html[data-ui-mode="tablet"] .flex-bar-prev-child-btn {
    padding-left: 10px !important;
    padding-right: 10px !important;
}
html[data-ui-mode="tablet"] .flex-bar-entity-btn,
html[data-ui-mode="tablet"] #topBarNextControls button {
    height: var(--flexbar-control-h) !important;
    min-height: var(--flexbar-control-h) !important;
    max-height: var(--flexbar-control-h) !important;
    box-sizing: border-box !important;
    padding-top: 0 !important;
    padding-bottom: 0 !important;
    font-size: 13px !important;
    line-height: 1 !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    position: relative;
}
html[data-ui-mode="tablet"] .flex-bar-entity-btn.action-btn iconify-icon {
    font-size: 14px !important;
}
html[data-ui-mode="tablet"] .flex-bar-entity-btn::before,
html[data-ui-mode="tablet"] #topBarNextControls button::before {
    /* Touch target only -- paints nothing, takes no layout room. */
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    width: 48px;
    height: 48px;
    transform: translate(-50%, -50%);
}
/* Right icon cluster. The desktop tier's 45px discs at a 15px gap crush the
   candy pill to its 350px floor on-device, so this tier sizes them itself.

   Shape is a 48x34 pill, not a 22px disc (#142). Two measurements drove that,
   both taken on the fleet AVD (1280x800 CSS, dpr 1.5, so 1 CSS px = 1 dp):

   1. The header's inner content track is 34.7px — 44px less 4px padding top
      and bottom and a 1px border, box-sizing: border-box. A 34px-tall control
      therefore fits the chrome that already exists. `.main-content` still
      starts at y=60, exactly as it did at 22px, so this costs no page height.
   2. Hit-testing follows border-radius, so a round 48px disc is a 48px
      CIRCLE — its corners return the element behind it, and its area is 79%
      of the 48x48 square Android's 48dp minimum asks for. The pill carries a
      rectangular ::before instead, which makes the touch target a true 48x48
      while the painted pill stays inside the header.

   ::before, not ::after — `.notifications .has-tip::after` is the tooltip. */
html[data-ui-mode="tablet"] .notifications {
    /* 8px is Android's minimum separation between adjacent touch targets. */
    --header-icon-gap: 8px;
}
html[data-ui-mode="tablet"] .notification-icon {
    width: 48px;
    height: 34px;
    border-radius: 17px;
    position: relative;
}
html[data-ui-mode="tablet"] .notification-icon::before {
    /* Touch target only — paints nothing and takes no layout room, so it can
       exceed the 34px pill without touching the header's height. */
    content: '';
    position: absolute;
    top: 50%;
    left: 50%;
    width: 48px;
    height: 48px;
    transform: translate(-50%, -50%);
}
html[data-ui-mode="tablet"] .notification-icon.is-collapsed {
    /* Tablet `.notification-icon { width: 48px }` is more specific than
       the shared `.header-icon-collapsible.is-collapsed { width: 0 }`,
       so Edit/Delete still occupied two 48px slots on Children and
       starved the candy leftover (38px "hildre" pill). Collapse must
       actually collapse or #141 cannot fill the name→plus track. */
    width: 0;
    min-width: 0;
    height: 0;
    margin-left: calc(-1 * var(--header-icon-gap, 8px));
    padding: 0;
    border-width: 0;
}
html[data-ui-mode="tablet"] .notification-icon.is-collapsed::before {
    /* A collapsed context disc is invisible and non-interactive; it must not
       acquire a live touch target. */
    display: none;
}
/* Vacant print/QR hosts are otherwise anonymous flex items. Lift the
   inner disc so x-show:none leaves no slot in the icon cluster. */
html[data-ui-mode="tablet"] .notifications seahorse-header-print,
html[data-ui-mode="tablet"] .notifications seahorse-header-qr {
    display: contents;
}
/* Educator/admin face in the header — 48px desktop disc overflows the
   44px tablet header. Keep it inside the chrome. */
html[data-ui-mode="tablet"] .profile-avatar--face,
html[data-ui-mode="tablet"] .profile-avatar-face {
    width: 28px;
    height: 28px;
    min-width: 28px;
    max-width: 28px;
}
html[data-ui-mode="tablet"] .label-centered {
    font-size: 14px;
}
html[data-ui-mode="tablet"] .flex-bar .docs-search {
    /* Adaptive, not fixed: a hard 72px cap crushed the search to "Se…"
       while the bar sat on visible slack. Natural width comes from each
       module's authored size (children 170, ASQ 220); under pressure the
       high shrink factor makes the search yield toward its 72px floor
       BEFORE the module title starts ellipsizing. */
    min-width: 60px;
    max-width: 170px;
    flex-shrink: 3;
}
/* The input's intrinsic minimum (~170px) otherwise props up every
   ancestor's min-content, so the search box never actually shrinks —
   the wrapper's 60px floor above is the real minimum. */
html[data-ui-mode="tablet"] .flex-bar .docs-search .docs-search-input {
    min-width: 0;
    height: var(--flexbar-control-h);
    min-height: var(--flexbar-control-h);
    max-height: var(--flexbar-control-h);
    padding: 0;
    margin: 0;
    line-height: 1;
    box-sizing: border-box;
}
html[data-ui-mode="tablet"] .flex-bar .docs-search iconify-icon {
    width: var(--flexbar-control-h);
    height: var(--flexbar-control-h);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
}
/* Bars whose only control is the search box (Resources) have the whole
   pill to themselves — let the placeholder breathe instead of clipping
   at "Filter by I". */
html[data-ui-mode="tablet"] .flex-bar:not(:has(.asq-nav-label-wrap)) .docs-search {
    max-width: 230px;
}
html[data-ui-mode="tablet"] .profile-info h2,
html[data-ui-mode="tablet"] .profile-info p {
    max-width: none;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
html[data-ui-mode="tablet"] .profile-info h2 {
    font-size: 13px;
    margin-bottom: 1px;
}
html[data-ui-mode="tablet"] .profile-info p {
    font-size: 11px;
}
/* admin.html's centre-switcher h2 is inline-flex (inline style), so
   text-overflow on the h2 itself clips MID-GLYPH — the ellipsis has
   to live on the text span inside it. */
html[data-ui-mode="tablet"] .profile-info h2 > span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}
/* Bars that carry teleported controls keep their module title (jj:
   hidden labels read as broken — the Messaging bar even rendered fully
   blank because its teleport mounts an invisible wrapper). The title
   shrinks and ellipsizes instead, controls keep their full labels. */
html[data-ui-mode="tablet"] .flex-bar .flex-bar-content,
html[data-ui-mode="tablet"] .flex-bar .label-centered {
    font-size: 12px;
}
/* flex-bar-content keeps its base flex layout (several modules put
   controls INSIDE it — photos' date picker, resources' search — and
   display:block scattered them). It may shrink; the ellipsis lives on
   the plain-text title span so composite content is untouched.
   min-content (NOT 0): with an explicit 0 the wrapper collapsed
   entirely under pressure and the title span spilled out of the bar
   leftward — the floor is its child's own minimum. */
html[data-ui-mode="tablet"] .flex-bar-content {
    flex: 0 1 auto;
    width: auto;
    min-width: 0;
    overflow: hidden;
}
/* #topBarControls carries flex-shrink 0 at desktop — under tablet
   pressure that pushed the ENTIRE deficit onto the title wrapper.
   Let the controls cluster shrink; min-width 0 (NOT min-content —
   Chrome resolves min-content through this nested flex chain at the
   PREFERRED size, which blocks shrinking entirely). The real floors
   live on the leaves: the search stops at its 60px min-width and the
   fixed pills never shrink, so the cluster bottoms out at their sum. */
html[data-ui-mode="tablet"] .flex-bar #topBarControls {
    flex: 0 1 auto;
    min-width: 0;
    overflow: visible;
    justify-content: flex-start;
    gap: 10px;
}
/* The teleported control wrappers are anonymous inline-styled divs
   whose automatic minimum resolves at the search's inline width:170
   basis (not its 60px floor) — the wrapper OVERFLOWED #topBarControls
   LEFTWARD, painting over the module title while every scrollWidth
   check read clean (leftward paint doesn't extend scrollWidth; jj's
   "Childre|" screenshot, round 15). */
html[data-ui-mode="tablet"] .flex-bar #topBarControls > div {
    min-width: 0;
    flex-shrink: 1 !important;
}
html[data-ui-mode="tablet"] .flex-bar-content > span {
    flex: 0 1 auto;
    min-width: 64px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Title-only bars stay centred. When teleported controls sit beside
   the title, centering the cluster parks empty pill to the LEFT of an
   ellipsized "Activity Pla…" — the title must occupy that space.
   width:100%: the candy shell fills the soft-distributed container
   (#141); controls stay intrinsic (no Search flex-grow). */
html[data-ui-mode="tablet"] .flex-bar {
    justify-content: flex-start;
    gap: 10px;
    width: 100%;
    max-width: 100%;
    min-width: 0;
    overflow: visible;
}
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls > *:not(template):not([style*="display: none"])) {
    justify-content: flex-start;
}
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls > *:not(template):not([style*="display: none"])) .flex-bar-content {
    flex: 0 1 auto;
    width: auto;
    min-width: 0;
    overflow: hidden;
}
/* Nano pack (Tab A 1024×532): the educator subtitle already names the
   module. A second in-candy title plus pills plus the plus disc is what
   Falco filed as collide/clip on Attendance / Planner / Photo. Drop the
   duplicate title when controls are present.

   Exceptions that KEEP a title:
   - Children landing plain title (#149) — more specific :has below
   - Drill-in bars with a visible Back — title sits between Back and Next
   - Resources search and Questionnaires grid search/chip (Arbutus QA #7)
     — the following rules; a search is not a title replacement

   Pete scale lock (2026-09-16, #174 carry — not a MacBook recut):
   MacBook 15″/16″ is the desktop baseline for dynamic scaling. Different
   aspect ratios get minor flex (mainly wider large components), never a
   squished-desktop pack. These nano rules stay html[data-ui-mode=tablet].
   Desktop (no attribute / data-ui-mode=desktop) keeps the candy titles. */
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls > *:not(template):not([style*="display: none"])):not(:has(.children-flexbar-search)):not(:has(#topBarBackControls > *:not(template):not([style*="display: none"]))) .flex-bar-content > span {
    display: none;
}
/* Resources and Questionnaires only teleport a search (or the
   selected-child chip). The hide above treats that node as a
   replacement for the candy title — the same rule that drops the
   duplicate label beside Attendance tabs, Photo chips, and Planner
   arrows. A search does not fill the bar, so the bar paints as an
   empty gradient (Arbutus QA #7). Put the title back as plain text and
   centre it with the search. Bubbles are for buttons. Do not set
   width / max-width here: candy width stays on the tablet flex-bar
   rules above (#193 / #181 HOLD). */
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls > *:not(template):not([style*="display: none"])):not(:has(.children-flexbar-search)):not(:has(#topBarBackControls > *:not(template):not([style*="display: none"]))):has(#topBarControls .docs-top-bar-content .docs-search),
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls > *:not(template):not([style*="display: none"])):not(:has(.children-flexbar-search)):not(:has(#topBarBackControls > *:not(template):not([style*="display: none"]))):has(#topBarControls .q-grid-search),
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls > *:not(template):not([style*="display: none"])):not(:has(.children-flexbar-search)):not(:has(#topBarBackControls > *:not(template):not([style*="display: none"]))):has(#topBarControls .q-grid-chip) {
    justify-content: center;
}
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls > *:not(template):not([style*="display: none"])):not(:has(.children-flexbar-search)):not(:has(#topBarBackControls > *:not(template):not([style*="display: none"]))):has(#topBarControls .docs-top-bar-content .docs-search) .flex-bar-content > span,
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls > *:not(template):not([style*="display: none"])):not(:has(.children-flexbar-search)):not(:has(#topBarBackControls > *:not(template):not([style*="display: none"]))):has(#topBarControls .q-grid-search) .flex-bar-content > span,
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls > *:not(template):not([style*="display: none"])):not(:has(.children-flexbar-search)):not(:has(#topBarBackControls > *:not(template):not([style*="display: none"]))):has(#topBarControls .q-grid-chip) .flex-bar-content > span {
    flex: 0 0 auto;
    width: auto;
    max-width: none;
    min-width: max-content;
    height: auto;
    min-height: 0;
    max-height: none;
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    line-height: 1;
    background: transparent;
    border: 0;
    border-radius: 0;
    box-shadow: none;
}
html[data-ui-mode="tablet"] .flex-bar .docs-top-bar-content .docs-search {
    margin-right: 0;
}
html[data-ui-mode="tablet"] .flex-bar:has(#topBarBackControls > *:not(template):not([style*="display: none"])) .flex-bar-content > span {
    flex: 1 1 auto;
    min-width: 4ch;
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
/* Module control wrappers that claim the full row on desktop
   (flex-basis 100% / width:100%, right-aligned) crush the title to its
   ellipsis floor no matter how much space the bar has. Content-sized
   and centred in tablet mode. NB children/curriculum/educators teleport
   ANONYMOUS inline-styled divs (no wrapper class exists for them) —
   only docs and photos ship a classed full-row wrapper. */
html[data-ui-mode="tablet"] .flex-bar .docs-top-bar-content,
html[data-ui-mode="tablet"] .flex-bar .gallery-topbar-filters {
    /* !important: docs' wrapper carries these as inline styles */
    flex: 0 1 auto !important;
    width: auto !important;
    justify-content: center !important;
}
/* Photos' wrapper also carries side padding that would offset the
   centred [title | date-picker] group. */
html[data-ui-mode="tablet"] .flex-bar .gallery-topbar-filters {
    padding: 0;
}
/* Photo Gallery selection (Back / Select All / Send / Delete / Clear).
   Desktop buttons stay content-sized (36px frost, 32px Send/Delete).
   On the 28px tablet candy they were never given --flexbar-control-h,
   and #topBarControls > div { flex-shrink:1; min-width:0 } plus
   justify-content:center on .gallery-topbar-filters centred a 451px
   row in a ~360–430px track. At 1024 Back crossed the centre name and
   Clear left the capsule; the ::after shade read through the 0.25
   frost and around the taller pills. This case only:
   one track, 22px chips, shared padding and radius, 13px inset,
   no shrink, no centred spill. Date presets are a different :has. */
html[data-ui-mode="tablet"] .flex-bar:has(.gallery-selection-controls) {
    padding-left: 13px;
    padding-right: 13px;
    align-items: center;
}
html[data-ui-mode="tablet"] .flex-bar:has(.gallery-selection-controls) #topBarControls {
    flex: 1 1 auto;
    align-items: center;
    justify-content: center;
    min-width: 0;
    max-width: 100%;
    overflow: visible;
}
html[data-ui-mode="tablet"] .flex-bar #topBarControls > .gallery-topbar-filters:has(.gallery-selection-controls) {
    flex: 0 0 auto !important;
    flex-shrink: 0 !important;
    align-items: center;
    justify-content: center !important;
    min-width: max-content;
    width: max-content !important;
    max-width: 100%;
    padding: 0;
    box-sizing: border-box;
}
html[data-ui-mode="tablet"] .flex-bar .gallery-selection-controls {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    height: var(--flexbar-control-h);
    max-height: var(--flexbar-control-h);
    min-width: max-content;
    max-width: 100%;
}
html[data-ui-mode="tablet"] .flex-bar .gallery-selection-controls > button {
    flex: 0 0 auto;
    align-self: center;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 4px;
    height: var(--flexbar-control-h);
    min-height: var(--flexbar-control-h);
    max-height: var(--flexbar-control-h);
    box-sizing: border-box;
    margin: 0;
    padding: 0 8px;
    font-size: 11px;
    font-weight: 600;
    line-height: 1;
    border-radius: 999px;
    border-width: 0;
    overflow: hidden;
    white-space: nowrap;
    min-width: max-content;
    transform: none;
    text-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}
html[data-ui-mode="tablet"] .flex-bar .gallery-selection-controls > button:hover {
    transform: none;
}
html[data-ui-mode="tablet"] .flex-bar .gallery-back-btn,
html[data-ui-mode="tablet"] .flex-bar .gallery-select-all-btn,
html[data-ui-mode="tablet"] .flex-bar .gallery-clear-selection-btn {
    /* Opaque frost. rgba(255,255,255,0.25) plus backdrop-filter let
       .flex-bar::after tint the chip and paint a square behind the radius. */
    background-color: #f4f7fb;
    background-image: linear-gradient(180deg, rgba(255, 255, 255, 0.65), rgba(255, 255, 255, 0));
    color: #334155;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.95);
}
html[data-ui-mode="tablet"] .flex-bar .gallery-send-btn {
    background-color: #10b981;
    background-image: none;
    color: #fff;
    box-shadow: none;
}
html[data-ui-mode="tablet"] .flex-bar .gallery-delete-btn {
    background-color: #ef4444;
    background-image: none;
    color: #fff;
    box-shadow: none;
}
html[data-ui-mode="tablet"] .flex-bar .gallery-selection-controls iconify-icon {
    width: 12px;
    font-size: 12px !important;
    flex: 0 0 auto;
}
/* Resources selection (Back / Select All / count / Send / Delete / Clear)
   and the detail pair (Send / Download). Both teleport through
   .docs-top-bar-content, which is inline-styled flex:1 and then forced
   to flex 0 1 auto + justify-content:center. That centres a content-sized
   row wider than the track, so at 1024 Back crossed the centre name and
   Clear left the capsule. This case only: 13px inset, no shrink, no
   centred spill. Search keeps the frost title rules above. */
html[data-ui-mode="tablet"] .flex-bar:has(.docs-selection-controls),
html[data-ui-mode="tablet"] .flex-bar:has(.docs-detail-actions) {
    padding-left: 13px;
    padding-right: 13px;
    align-items: center;
}
html[data-ui-mode="tablet"] .flex-bar:has(.docs-selection-controls) #topBarControls,
html[data-ui-mode="tablet"] .flex-bar:has(.docs-detail-actions) #topBarControls {
    flex: 1 1 auto;
    align-items: center;
    justify-content: center;
    min-width: 0;
    max-width: 100%;
    overflow: visible;
}
html[data-ui-mode="tablet"] .flex-bar #topBarControls > .docs-top-bar-content:has(.docs-selection-controls),
html[data-ui-mode="tablet"] .flex-bar #topBarControls > .docs-top-bar-content:has(.docs-detail-actions) {
    flex: 0 0 auto !important;
    flex-shrink: 0 !important;
    align-items: center;
    justify-content: center !important;
    min-width: max-content;
    width: max-content !important;
    max-width: 100%;
    padding: 0;
    box-sizing: border-box;
}
/* Back button pins to the pill's left edge on drill-in bars (it was
   getting swept into the centred group — jj: keep it consistently
   left, title centred). No :has() padding companion: the compliance
   drill-in's back teleport is PERMANENTLY mounted with x-show
   (display:none at rest), so any child-existence test fires on every
   bar — it stole 280px from dashboard/children/educators and crushed
   their titles. Visible backs only occur on title-only drill-in bars
   where the centred title never reaches the pill edge, so no reserve
   is needed. */
html[data-ui-mode="tablet"] .flex-bar #topBarBackControls {
    position: absolute;
    left: 10px;
    top: 50%;
    transform: translateY(-50%);
    /* Above .flex-bar > * siblings (z-index 1): long drill-in titles
       slide under the pinned button, and on a z tie the later sibling
       paints on top and STEALS ITS CLICKS (jj, round 22: back button
       overlapped and unclickable on Attendance·Compliance·Infants). */
    z-index: 3;
}
/* Cap the back label: the attendance-report back button renders the
   CENTRE NAME ("← Sunshine Valley Early Learning…") — unbounded, it
   blows past the 150px reserve below and shoves the centred title.
   Ellipsize inside a hard cap instead (audit 2026-07-16). */
html[data-ui-mode="tablet"] .flex-bar #topBarBackControls,
html[data-ui-mode="tablet"] .flex-bar #topBarNextControls {
    /* Slot itself cannot outgrow the token — the teleported wrapper
       is an inline-styled flex row that would otherwise size to the
       unclipped 13px/6px-pad button and spill the 28px candy.
       Do NOT overflow:hidden here: Next's 48×48 ::before tap target
       (#144) must paint/hit outside the 22px chip.
       font/line-height: .flex-bar is 20px; an inline-flex button on
       that baseline sits ~4.5px low and paints past the candy floor
       (F0C2DJJ0X9A). */
    height: var(--flexbar-control-h);
    max-height: var(--flexbar-control-h);
    display: flex;
    align-items: center;
    line-height: 1;
    font-size: 13px;
}
html[data-ui-mode="tablet"] .flex-bar #topBarBackControls > div,
html[data-ui-mode="tablet"] .flex-bar #topBarNextControls > div {
    /* No !important: x-show=false uses inline display:none and must
       win, or Dashboard Back paints on Attendance. x-show=true clears
       the inline display; this stylesheet flex then beats block. */
    display: flex;
    align-items: center;
    height: var(--flexbar-control-h);
    max-height: var(--flexbar-control-h);
    line-height: 1;
}
html[data-ui-mode="tablet"] .flex-bar #topBarBackControls button {
    /* Depth-1 sub-pages (Suppression Audit, and anything else that teleports a
       back control here) render this button at 30px, which overhangs the 28px
       bar by 1px top and bottom. It is not a direct .flex-bar child, so the
       sweep over the nine landing views never saw it -- none of them has a back
       control. Same token as every other control in the bar.
       JJ 2026-09-16 F0C2DJJ0X9A: Children frost was the right height;
       Back/Next were undersized AND spilled the candy floor. Lock to the
       token, beat inline padding/font, clip inside the pill. */
    height: var(--flexbar-control-h) !important;
    min-height: var(--flexbar-control-h) !important;
    max-height: var(--flexbar-control-h) !important;
    box-sizing: border-box !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    overflow: hidden !important;
    font-size: 13px !important;
    line-height: 1 !important;
    border-radius: 999px !important;
    /* Zero the VERTICAL padding, or the token clips what it is sizing.
       .flex-bar-back-btn carries `padding: 6px 14px` from the desktop rule,
       and with box-sizing: border-box a 22px button spends 12 of those 22px
       on padding — leaving an 8-10px content box for an 18px icon and a
       15-16px text line, which the `overflow: hidden` below (added by the
       2026-07-16 label cap) then clips rather than overflows. Measured on
       both back bars: Onboarding clipped its icon by 4px and "Cancel" by
       2.5px, Attendance Report clipped "Dashboard" by 8px. Dropping the
       vertical padding changes NO painted dimension — the height is already
       fixed at the token and the width is unchanged at 92.6 / 116px — it
       only stops the content box being smaller than its own contents.
       Horizontal padding is kept: it is what insets the hover pill.

       !important because SEVEN of these buttons carry `padding:6px 14px`
       as an INLINE style in their component markup (attendance-report,
       children x2, dashboard, onboarding, educators, suppression-audit),
       and an inline declaration beats any selector. Same reason the mode
       toggle above needs it. The alternative — lifting those inline styles
       into a class so the tiers can layer normally — is the better shape
       but a much wider diff that also moves desktop, so it is deliberately
       not bundled into this fix. */
    padding-top: 0 !important;
    padding-bottom: 0 !important;
    max-width: 140px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
html[data-ui-mode="tablet"] .flex-bar #topBarBackControls button span {
    display: inline-block;
    max-width: 110px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    vertical-align: bottom;
}
/* Reserve the back button's zone only when a back child is actually
   VISIBLE. The mount is permanent with x-show, so :has(> *) would fire
   on every bar (the round-11 trap) — but x-show hides via inline
   display:none, which IS selectable. Also skip Alpine <template>
   hosts: #topBarBackControls always contains the onboarding x-if
   template, which has no inline display:none, so treating it as a
   visible Back parked 110px of empty maroon left of "Activity Pla…"
   on every module.
   Tutorial mode hides the same children with a stylesheet
   (display:none !important), which does not set the style attribute.
   :not(.tutorial-mode) keeps that hidden Back from applying the
   reserve. The tutorial bar keeps its own 12px side padding. */
html:not(.tutorial-mode)[data-ui-mode="tablet"] .flex-bar:not(:has(.flex-bar-notif)):has(#topBarBackControls > *:not(template):not([style*="display: none"])) {
    /* 150px: the widest back label ('← Dashboard') measures ~117px +
       10px pin offset — 128px left it pixel-flush, which still reads
       as overlap (jj, round 23). flex-start (not center): centring
       the group inside the reserved space pushed composite bars'
       right-side controls past the pill edge (jj, round 25: billing's
       '…' button spilled). Content flows from the reserve edge and
       the title span absorbs the squeeze. */
    padding-left: 150px;
    justify-content: flex-start;
}
/* COMPOSITE drill bars (back + visible controls) run tight: their back
   label is the short '← Back' (~86px), so the Dashboard-sized reserve
   wastes 40px, and rigid control buttons ('Send to Jason' + '…') can
   spill the pill edge (jj, round 29: Harper Anderson billing). Smaller
   reserve + let the title wrapper shrink (its span keeps the 64px
   ellipsis floor) so buttons always stay inside. */
html:not(.tutorial-mode)[data-ui-mode="tablet"] .flex-bar:not(:has(.flex-bar-notif)):has(#topBarBackControls > *:not(template):not([style*="display: none"])):has(#topBarControls > div:not([style*="display: none"])),
html:not(.tutorial-mode)[data-ui-mode="tablet"] .flex-bar:not(:has(.flex-bar-notif)):has(#topBarBackControls > *:not(template):not([style*="display: none"])):has(.flex-bar-billing-context:not([style*="display: none"])) {
    padding-left: 110px;
}
html[data-ui-mode="tablet"] .flex-bar:not(:has(.flex-bar-notif)):has(#topBarBackControls > *:not(template):not([style*="display: none"])):has(#topBarControls > div:not([style*="display: none"])) .flex-bar-content,
html[data-ui-mode="tablet"] .flex-bar:not(:has(.flex-bar-notif)):has(#topBarBackControls > *:not(template):not([style*="display: none"])):has(.flex-bar-billing-context:not([style*="display: none"])) .flex-bar-content {
    min-width: 0;
}
/* Back-visible bars stay in the same in-flow slot as every other
   tablet bar — the old 430px reservation was for the centered
   absolute pill and would shrink this one off the name again. */
html[data-ui-mode="tablet"] .flex-bar-container:not(:has(.flex-bar-notif)):has(#topBarBackControls > *:not(template):not([style*="display: none"])) {
    width: auto;
    max-width: none;
}
/* TITLE-ONLY drill bars — Desktop SoR F0C27S658KT / F0C1ZMKTPT5 /
   F0C1QHE4A87: [← Back]  leftover  Title  leftover  [Next].
   In-flow flanks + space-between; leftover is the middle track, not
   a 150px padding hack around an absolute Back. */
html:not(.tutorial-mode)[data-ui-mode="tablet"] .flex-bar:not(:has(.flex-bar-notif)):has(#topBarBackControls > *:not(template):not([style*="display: none"])):not(:has(#topBarControls > div:not([style*="display: none"]))):not(:has(.flex-bar-billing-context:not([style*="display: none"]))) {
    /* Fill the soft-distributed track — leftover is olive BETWEEN
       Back and Next, matching desktop F0C27S658KT / F0C1ZMKTPT5. */
    width: 100%;
    justify-content: space-between;
    /* 18px clears the 14px end cap. 10px sat inside the curve, so Back
       read as flush with the left edge while the right flank kept the
       same numeric inset and looked roomier. Both sides move together
       so the title stays centred in the leftover. */
    padding-left: 18px;
    padding-right: 18px;
}
html[data-ui-mode="tablet"] .flex-bar:not(:has(.flex-bar-notif)):has(#topBarBackControls > *:not(template):not([style*="display: none"])):not(:has(#topBarControls > div:not([style*="display: none"]))):not(:has(.flex-bar-billing-context:not([style*="display: none"]))) #topBarBackControls {
    position: relative;
    left: 0;
    top: auto;
    transform: none;
}
html[data-ui-mode="tablet"] .flex-bar:not(:has(.flex-bar-notif)):has(#topBarBackControls > *:not(template):not([style*="display: none"])):not(:has(#topBarControls > div:not([style*="display: none"]))):not(:has(.flex-bar-billing-context:not([style*="display: none"]))) .flex-bar-content {
    flex: 1 1 0%;
    justify-content: center;
    min-width: 0;
    overflow: hidden;
}
html[data-ui-mode="tablet"] .flex-bar:not(:has(.flex-bar-notif)):has(#topBarBackControls > *:not(template):not([style*="display: none"])):not(:has(#topBarControls > div:not([style*="display: none"]))):not(:has(.flex-bar-billing-context:not([style*="display: none"]))) #topBarNextControls,
html[data-ui-mode="tablet"] .flex-bar:not(:has(.flex-bar-notif)):has(#topBarBackControls > *:not(template):not([style*="display: none"])):not(:has(#topBarControls > div:not([style*="display: none"]))):not(:has(.flex-bar-billing-context:not([style*="display: none"]))) .flex-bar-next-controls {
    margin-left: 0;
}
/* Title is plain text in the leftover, centred, same as desktop.
   A 50% frost pill clipped long labels ("ttendance Rep") and put a
   bubble on text. Bubbles stay on Back / Previous / Next below.
   Content width so the word is whole; the middle track still clips
   only if the label is wider than the space between the buttons. */
html[data-ui-mode="tablet"] .flex-bar:not(:has(.flex-bar-notif)):has(#topBarBackControls > *:not(template):not([style*="display: none"])):not(:has(#topBarControls > div:not([style*="display: none"]))):not(:has(.flex-bar-billing-context:not([style*="display: none"]))) .label-centered,
html[data-ui-mode="tablet"] .flex-bar:not(:has(.flex-bar-notif)):has(#topBarBackControls > *:not(template):not([style*="display: none"])):not(:has(#topBarControls > div:not([style*="display: none"]))):not(:has(.flex-bar-billing-context:not([style*="display: none"]))) .flex-bar-content > span {
    box-sizing: border-box;
    flex: 0 1 auto;
    width: auto;
    max-width: 100%;
    min-width: 0;
    height: auto;
    max-height: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    background: transparent;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}
/* JJ: Back/Next still read taller than Children frost at the same
   22px token (13px glyph + 0.15 fill, no inset). Same frost chrome
   and 12px title type so sibling height is optical, not just bbox. */
html[data-ui-mode="tablet"] .flex-bar:not(:has(.flex-bar-notif)):has(#topBarBackControls > *:not(template):not([style*="display: none"])):not(:has(#topBarControls > div:not([style*="display: none"]))):not(:has(.flex-bar-billing-context:not([style*="display: none"]))) #topBarBackControls button,
html[data-ui-mode="tablet"] .flex-bar:not(:has(.flex-bar-notif)):has(#topBarBackControls > *:not(template):not([style*="display: none"])):not(:has(#topBarControls > div:not([style*="display: none"]))):not(:has(.flex-bar-billing-context:not([style*="display: none"]))) #topBarNextControls button {
    height: var(--flexbar-control-h) !important;
    min-height: var(--flexbar-control-h) !important;
    max-height: var(--flexbar-control-h) !important;
    box-sizing: border-box !important;
    font-size: 12px !important;
    font-weight: 700 !important;
    line-height: 1 !important;
    background: rgba(255, 255, 255, 0.22) !important;
    border: 1px solid rgba(255, 255, 255, 0.35) !important;
    border-radius: 999px !important;
    box-shadow:
        inset 1px 1px 0.5px 0 rgba(255, 255, 255, 0.35),
        inset -0.5px -0.5px 0.5px 0.5px rgba(255, 255, 255, 0.15);
    backdrop-filter: blur(8px) saturate(1.2);
    -webkit-backdrop-filter: blur(8px) saturate(1.2);
}
html[data-ui-mode="tablet"] .flex-bar:has(.flex-bar-content) .label-centered {
    display: none;
}
/* Dashboard has no .flex-bar-content (gated off so a ghost box cannot
   crush .label-centered). Tabs already say Attendance / Groups / Spaces,
   so the duplicate "Attendance" label is the collide on Tab A.

   Locked: F0C1ZQG9YM9 vibe + F0C2419M014 chrome + F0C218SNUGM spacing.
   A9+ CSS ≈1280×800 / Chrome sit ≈1280×648 is the packing canvas.
   Tab A 1024×532 is stress + UI-PR stills, not the optimize-only sit.

   Attendance is TWO packs with bounded breath BETWEEN them.
   Reject F0C29M8F0UU (space-between slim-jim / over-breath) and
   F0C25U82YM8 (left-shove into maroon void). F0C27U6AFTK is NOT
   locked Desktop SoR. Outer 1fr columns share aurora; inner track
   is capped so Drop-off cannot pin to the far edge. Plus beside. */
html[data-ui-mode="tablet"] .flex-bar:has(.flex-bar-mode-toggle) .label-centered {
    display: none;
}
html[data-ui-mode="tablet"] .flex-bar:has(.flex-bar-mode-toggle) {
    width: 100%;
    max-width: 100%;
    min-width: 0;
    justify-content: flex-start;
}
html[data-ui-mode="tablet"] .flex-bar-mode-toggle {
    flex: 1 1 0%;
    min-width: 0;
    width: auto;
    display: grid;
    /* max-content tracks, not auto. `auto` shrinks to a collapsed
       min-content once .dashboard-tab-bar is overflow:hidden, so the
       152px breath hits its cap and Groups/Spaces clip into Drop-off
       (Arbutus Tab A, F0C3UEPRM9B). max-content will not shrink.
       The breath still yields first (52px floor, 152px cap). Outer
       columns are minmax(0, 1fr) so they can go to zero before the
       chips do. Bar width is unchanged — this is packing only. */
    grid-template-columns: minmax(0, 1fr) max-content minmax(52px, 152px) max-content minmax(0, 1fr);
    align-items: center;
    justify-content: stretch;
    gap: 0;
    margin-left: 0;
}
.coverage-notice-chip {
    margin: 0 0 0 10px;
    padding: 0 12px;
    height: 32px;
    display: flex;
    align-items: center;
    border-radius: 999px;
    background: linear-gradient(135deg, #e17055, #fdcb6e);
    color: #fff;
    font-size: 13px;
    font-weight: 700;
    line-height: 1;
    white-space: nowrap;
    flex: 0 0 auto;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.15);
}
html[data-ui-mode="tablet"] .flex-bar-mode-toggle:has(.coverage-notice-chip) {
    grid-template-columns: minmax(0, 1fr) max-content minmax(0, max-content) max-content minmax(0, 1fr);
}
html[data-ui-mode="tablet"] .flex-bar-mode-toggle .coverage-notice-chip {
    grid-column: 3;
    justify-self: center;
    height: var(--flexbar-control-h);
    max-height: var(--flexbar-control-h);
    margin: 0;
    padding: 0 8px;
    font-size: 11px;
}
html[data-ui-mode="tablet"] .flex-bar-mode-toggle .dashboard-tab-bar {
    grid-column: 2;
    flex: 0 0 auto;
    width: max-content;
    min-width: max-content;
    max-width: none;
}
html[data-ui-mode="tablet"] .flex-bar-mode-toggle .dashboard-tab-btn {
    flex: 0 0 auto;
}
html[data-ui-mode="tablet"] .flex-bar-mode-toggle .mode-toggle {
    grid-column: 4;
    flex: 0 0 auto;
    margin-left: 0;
    /* The knob is position:absolute and the label is flex:1 / basis 0,
       so the toggle's max-content collapses to the padding and the
       grid track clips "Drop-off". Size the label to its text and
       keep a floor wide enough for the knob plus the word. */
    width: max-content;
    min-width: 96px;
    max-width: none;
    overflow: visible;
}
html[data-ui-mode="tablet"] .flex-bar-mode-toggle .toggle-label {
    flex: 0 0 auto;
    white-space: nowrap;
}
html[data-ui-mode="tablet"] .flex-bar-mode-toggle .toggle-label.left {
    /* Knob sits at left:5px and is --flexbar-control-h wide. Padding
       must clear that edge or the D of Drop-off paints under the disc. */
    padding-left: calc(5px + var(--flexbar-control-h) + 6px);
    padding-right: 8px;
}
html[data-ui-mode="tablet"] .flex-bar-mode-toggle .toggle-label.right {
    padding-right: calc(var(--flexbar-control-h) + 6px);
    padding-left: 8px;
}

/* Photo candy — F0C2419M014 still 5: candy fills; joined Today |
   This week | Last 30 days sit as a CENTRED intrinsic pack (A9+
   canvas first). The ID-bearing tablet flex-start rule
   (`.flex-bar:has(#topBarControls > …)`) beat a class-only :has
   and left-shoved the pack into empty maroon — double :has so
   center wins. Vestibule stays Take / Upload (house law). */
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls > *:not(template):not([style*="display: none"])):has(.gallery-date-presets) {
    width: 100%;
    max-width: 100%;
    min-width: 0;
    justify-content: center;
}
html[data-ui-mode="tablet"] .flex-bar:has(.gallery-date-presets) .flex-bar-content,
html[data-ui-mode="tablet"] .flex-bar:has(.gallery-date-presets) .label-centered,
html[data-ui-mode="tablet"] .flex-bar:has(.gallery-date-presets) #topBarBackControls:not(:has(> *:not(template):not([style*="display: none"]))),
html[data-ui-mode="tablet"] .flex-bar:has(.gallery-date-presets) #topBarNextControls:not(:has(> *:not(template):not([style*="display: none"]))) {
    display: none;
}
html[data-ui-mode="tablet"] .flex-bar:has(.gallery-date-presets) #topBarControls,
html[data-ui-mode="tablet"] .flex-bar:has(.gallery-date-presets) .gallery-topbar-filters,
html[data-ui-mode="tablet"] .flex-bar:has(.gallery-date-presets) .gallery-filter-controls {
    display: contents;
}
html[data-ui-mode="tablet"] .flex-bar .gallery-date-presets {
    flex: 0 0 auto;
    min-width: 0;
    width: auto;
    height: var(--flexbar-control-h);
    min-height: var(--flexbar-control-h);
    max-height: var(--flexbar-control-h);
    box-sizing: border-box;
    display: inline-flex;
    align-items: stretch;
    gap: 2px;
    /* No vertical pad: 1px pad + 1px border measured the Today /
       This week / Last 30 days leaves at 18px inside a 22px shell (#224). */
    padding: 0;
    border-width: 0;
    margin-left: auto;
    margin-right: auto;
    overflow: hidden;
    /* Attendance / Groups / Spaces language: padded shell, rounded
       inner tabs, no vertical-line section borders (desktop
       .gallery-date-preset + .gallery-date-preset { border-left }). */
    border-radius: 12px;
    background: rgba(255, 255, 255, 0.12);
    border: 0;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.3);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}
html[data-ui-mode="tablet"] .flex-bar .gallery-date-preset {
    flex: 0 0 auto;
    height: var(--flexbar-control-h);
    min-height: var(--flexbar-control-h);
    max-height: var(--flexbar-control-h);
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    padding: 0 10px;
    font-size: 11px;
    text-align: center;
    justify-content: center;
    overflow: hidden;
    border: 0;
    border-radius: 10px;
    background: transparent;
}
html[data-ui-mode="tablet"] .flex-bar .gallery-date-preset + .gallery-date-preset {
    border-left: 0;
}
html[data-ui-mode="tablet"] .flex-bar .gallery-date-preset:hover:not(.active) {
    background: rgba(255, 255, 255, 0.15);
}
html[data-ui-mode="tablet"] .flex-bar .gallery-date-preset.active {
    /* Same selected language as Attendance / Groups / Spaces
       (.dashboard-tab-btn.active). The tablet transparent default
       beat desktop .active (0,3,1 vs 0,2,0) so "This week" washed out. */
    background: rgba(255, 255, 255, 0.92);
    color: #334155;
    text-shadow: none;
}

/* Planner candy — Attendance soft-distribute *feel* (JJ 2026-09-16).
   Room and week are two packs with equal outer aurora and a CAPPED
   breath between (same 152px ceiling as Attendance). Rejects the
   left-cluster `auto 1fr` (F0C23MZ9TEF) and the slim-jim
   space-between (F0C29M8F0UU). Horizontal ~⅓/⅔ is aesthetic, not
   hardline pixels. Skip Back-visible drill-in. */
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .asq-nav-arrow):not(:has(.children-flexbar-search)):not(:has(#topBarBackControls > *:not(template):not([style*="display: none"]))) {
    width: 100%;
    max-width: 100%;
    min-width: 0;
    justify-content: flex-start;
}
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .asq-nav-arrow):not(:has(.children-flexbar-search)):not(:has(#topBarBackControls > *:not(template):not([style*="display: none"]))) .flex-bar-content {
    display: none;
}
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .asq-nav-arrow):not(:has(.children-flexbar-search)):not(:has(#topBarBackControls > *:not(template):not([style*="display: none"]))) #topBarControls {
    flex: 1 1 auto;
    /* Prefer the controls' content size, and never wider than the bar.
       min(100%, max-content) is dropped by browsers (a percentage and
       max-content in min() is cyclic), so the floor and the ceiling
       are two properties. When both apply, max-width wins. */
    min-width: max-content;
    max-width: 100%;
    width: auto;
}
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .asq-nav-arrow):not(:has(.children-flexbar-search)):not(:has(#topBarBackControls > *:not(template):not([style*="display: none"]))) #topBarControls > div {
    display: grid !important;
    grid-template-columns: 1fr auto minmax(36px, 152px) auto auto auto 1fr;
    align-items: center;
    justify-content: unset;
    min-width: 0;
    width: 100%;
    gap: 0 !important;
}
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .asq-nav-arrow):not(:has(.children-flexbar-search)):not(:has(#topBarBackControls > *:not(template):not([style*="display: none"]))) #topBarControls > div > :first-child {
    grid-column: 2;
    justify-self: center;
}
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .asq-nav-arrow):not(:has(.children-flexbar-search)):not(:has(#topBarBackControls > *:not(template):not([style*="display: none"]))) #topBarControls > div > :nth-child(2) {
    grid-column: 4;
    justify-self: center;
}
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .asq-nav-arrow):not(:has(.children-flexbar-search)):not(:has(#topBarBackControls > *:not(template):not([style*="display: none"]))) #topBarControls > div > :nth-child(n+3) {
    grid-row: 1;
    justify-self: end;
}
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .asq-nav-arrow):not(:has(.children-flexbar-search)):not(:has(#topBarBackControls > *:not(template):not([style*="display: none"]))) #topBarControls > div > :nth-child(3) { grid-column: 5; }
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .asq-nav-arrow):not(:has(.children-flexbar-search)):not(:has(#topBarBackControls > *:not(template):not([style*="display: none"]))) #topBarControls > div > :nth-child(4) { grid-column: 6; }
/* Trailing icons (saved-draft pair, and anything added after the
   loading and retry slots) share the right-hand track on row 1.
   Without a column, the draft pair auto-placed into the left side.
   Without an explicit row, a later child can wrap onto an implicit
   second row. Icon buttons stay content-sized. */
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .asq-nav-arrow):not(:has(.children-flexbar-search)):not(:has(#topBarBackControls > *:not(template):not([style*="display: none"]))) #topBarControls > div > :nth-child(n+5) {
    grid-column: 7;
    grid-row: 1;
    justify-self: start;
    min-width: max-content;
}
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .asq-nav-arrow) {
    --planner-week-pin: 148px;
}
/* Room chip stays the 148px pin. The label itself must not be a flex
   container: asq-module.css sets display:inline-flex on
   .flex-bar .asq-nav-label, and text-overflow does not paint on a
   flex container, so the name hard-clipped ("PRE · Buttercup P"). */
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .asq-nav-arrow) #topBarControls > div > :first-child .asq-nav-label-wrap {
    min-width: 0 !important;
    width: 148px !important;
    max-width: 148px !important;
}
/* Once the title and the Expires chip have yielded, the 148px room pin
   may shrink so the week dates and the icon buttons stay inside. */
/* The quiet grid keeps its column string. Under the room stage the
   room track and the breath between the packs may shrink, and the
   draft track may not shrink below the buttons. */
html[data-ui-mode="tablet"] .flex-bar[data-planner-fit="room"]:has(#topBarControls .asq-nav-arrow):not(:has(.children-flexbar-search)):not(:has(#topBarBackControls > *:not(template):not([style*="display: none"]))) #topBarControls > div {
    grid-template-columns: minmax(0, 1fr) minmax(0, 148px) minmax(0, 36px) auto auto auto minmax(max-content, 1fr);
}
html[data-ui-mode="tablet"] .flex-bar[data-planner-fit="room"]:has(#topBarControls .asq-nav-arrow) #topBarControls > div > :first-child {
    min-width: 0 !important;
    max-width: 148px;
    justify-self: stretch;
}
html[data-ui-mode="tablet"] .flex-bar[data-planner-fit="room"]:has(#topBarControls .asq-nav-arrow) #topBarControls > div > :first-child .asq-nav-label-wrap {
    width: auto !important;
    min-width: 0 !important;
    max-width: 100% !important;
}
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .asq-nav-arrow) #topBarControls > div > :first-child .asq-nav-label {
    display: block;
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    line-height: var(--flexbar-control-h);
}
/* Week dates stay on the same pin as desktop and are not cut short. */
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .asq-nav-arrow) #topBarControls > div > :nth-child(2) .asq-nav-label-wrap {
    width: var(--planner-week-pin) !important;
    min-width: var(--planner-week-pin) !important;
    max-width: var(--planner-week-pin) !important;
}
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .asq-nav-arrow) #topBarControls > div > :nth-child(2) .asq-nav-label {
    display: block;
    overflow: visible;
    text-overflow: clip;
    white-space: nowrap;
    line-height: var(--flexbar-control-h);
}
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .asq-nav-arrow) #flex-bar-session-expiry {
    flex: 0 0 auto;
    min-width: auto;
    max-width: none;
}
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .asq-nav-arrow) #topBarControls .asq-nav-arrow,
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .asq-nav-arrow) #topBarControls .ap-draft-actions {
    flex: 0 0 auto !important;
}

/* ── Children landing: Search | plain title | Status+Rooms ──
   Locked vibe F0C1ZQG9YM9 (A9+ canvas first). Closes #141 + #149.
   The title used to be a 50% frost pill. Bubbles are for buttons
   (Search, Status, Rooms). The word is plain text, centred in the
   leftover, same as desktop.

   display:contents on the teleported wrapper lifts Search and the
   Status+Rooms flank onto the candy row. Search / Status / Rooms
   stay intrinsic. Plus beside. No absolute overlay.

   Rejected: overlap F0C2XCG4P1N, Rooms-only stretch, 34% pill,
   Search flex-grow, Tab-A-only 110px Search crush. */
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .children-flexbar-search) #topBarControls,
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .children-flexbar-search) .children-flexbar-flanks {
    display: contents;
}
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .children-flexbar-search) .children-flexbar-search {
    /* F0C1ZQG9YM9: Search is an intrinsic compact pill, not the
       authored 170px desktop box. A wide Search shrinks leftover so
       a 50% frost reads as a small lozenge (~25% of candy). Compact
       Search grows leftover; frost can actually be half of it.
       Beat the inline width:170px. Not the old 110px collide crush. */
    order: 1;
    flex: 0 0 auto;
    flex-grow: 0;
    flex-shrink: 0;
    width: auto !important;
    max-width: 128px;
    min-width: 88px;
    justify-content: flex-start;
    text-align: start;
}
/* Double :has so this beats the ID-bearing general tablet title rule
   that sets flex: 0 0 auto on .flex-bar-content. */
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls > *:not(template):not([style*="display: none"])):has(.children-flexbar-search) .flex-bar-content {
    order: 2;
    flex: 1 1 0%;
    width: auto;
    /* The span's max-content floor has to be this track's floor too.
       min-width: 0 let a 73px "Children" paint over Search while the
       track stayed ~26px. */
    min-width: max-content;
    justify-content: center;
    overflow: visible;
}
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .children-flexbar-search) .children-flexbar-filters {
    order: 3;
    /* Shrink only when the bar is short (Tab A 1024×532). flex-basis auto
       keeps the 302px Status+Rooms pair at 1280, where the frost is half
       the leftover. min-width: 0 lets that pair give the title its word
       back instead of painting past the pill. */
    flex: 0 1 auto;
    min-width: 0;
    gap: 6px;
}
/* Status and Rooms are pinned at 148px by the shared tablet ASQ wrap
   (asq-module.css). That pin is why the pair stayed 302px and shoved
   the title. Prefer 148px — so a 1280 bar still holds the half pill —
   and let each chip shrink only to its label when the bar is short.
   !important beats the inline flex-shrink:0 / flex:none and the 148px
   ASQ floor. */
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .children-flexbar-search) .children-flexbar-filters > div {
    flex: 0 1 148px !important;
    width: 148px !important;
    min-width: max-content !important;
    max-width: 148px !important;
    box-sizing: border-box !important;
}
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .children-flexbar-search) .children-flexbar-filters .asq-nav-label-wrap {
    width: 100% !important;
    min-width: 0 !important;
    max-width: 100% !important;
    flex: 1 1 auto !important;
    box-sizing: border-box !important;
}
/* Empty Next slot is a 0px flex item, so the bar's 10px gap still
   sits in front of Search. On Tab A that gap is what leaves All Rooms
   7px past the pill once "Children" keeps its word. 1280 still fits
   the 148px chips, so leave the slot there. */
@media (max-width: 1100px) {
    html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .children-flexbar-search) .flex-bar-next-controls:not(:has(> *:not(template):not([style*="display: none"]))) {
        display: none;
    }
}
/* Intrinsic Status/Rooms on the A9+ canvas (F0C1ZQG9YM9). The shared
   tablet ASQ wrap cap (148px) still ellipsizes on Tab A stress;
   do not add a tighter 108px cap that starves A9+ labels. */
/* Same :has(#topBarControls > …) as the general tablet title rule
   (that selector carries an ID, so a class-only :has loses). */
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls > *:not(template):not([style*="display: none"])):has(.children-flexbar-search) .flex-bar-content > span {
    box-sizing: border-box;
    /* Content-sized plain text, centred in the leftover by the parent.
       A 50% frost pill clipped "Children" on a short bar. */
    flex: 0 1 auto;
    width: auto;
    max-width: 100%;
    min-width: max-content;
    height: auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    background: transparent;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .children-flexbar-search) .flex-bar-back-controls:not(:has(> *:not(template):not([style*="display: none"]))),
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .children-flexbar-search) .top-bar-spacer-left,
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .children-flexbar-search) .label-centered {
    display: none;
}
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls .children-flexbar-search) .flex-bar-next-controls {
    margin-left: 0;
}
/* Controls sized to the 48px bar: the search box and picker pills sat
   nearly edge-to-edge vertically. */
html[data-ui-mode="tablet"] .flex-bar .docs-search {
    /* Was padding-only, so the height came from whatever sat inside: 38px in
       Resources (a 28px .docs-mode-toggle), 28px elsewhere (an 18px input on
       4px padding). Both exceeded the bar's inner box. Vertical padding is
       0 so the input and icon — the measured leaves — are the token, not
       the token minus padding (#224). The 1px border is a shadow so it
       does not shrink that box. */
    height: var(--flexbar-control-h);
    min-height: var(--flexbar-control-h);
    max-height: var(--flexbar-control-h);
    box-sizing: border-box;
    padding: 0 10px;
    border-width: 0;
    box-shadow:
        inset 0 0 0 1px rgba(255, 255, 255, 0.35),
        inset 1px 1px 0.5px 0 rgba(255, 255, 255, 0.35);
}
html[data-ui-mode="tablet"] .flex-bar .docs-search .docs-mode-toggle {
    height: var(--flexbar-control-h);
    min-height: var(--flexbar-control-h);
    max-height: var(--flexbar-control-h);
    width: var(--flexbar-control-h);
    padding: 0;
    border-width: 0;
    box-sizing: border-box;
}
/* Notification state — POPUP CARD, not a header pill (jj, round 25:
   long toasts/confirms never fit the crowded 48px tablet header; "show
   it as a pop up window"). The whole container pops out of the header
   as a fixed, centred card below the header line; the base rules
   already hide the bar's regular children while a notif shows, so the
   card IS the notification. Same store, timeouts, and confirm buttons
   — presentation only. */
html[data-ui-mode="tablet"] .flex-bar-container:has(.flex-bar-notif):not(body[data-module="dashboard"] .flex-bar-container:has(.flex-bar-notif--message)) {
    position: fixed;
    left: 50%;
    top: 84px;
    transform: translateX(-50%);
    /* FIXED card width (not max-content): the bar/notif/message chain
       sizes circularly under intrinsic sizing, and messages with
       embedded newlines (pre-line) collapsed the inner card into a
       crunched column inside a wide outer pill (jj, round 28). */
    width: min(80vw, 560px);
    max-width: min(80vw, 560px);
    z-index: 10050;
}
html[data-ui-mode="tablet"] .flex-bar:has(.flex-bar-notif):not(body[data-module="dashboard"] .flex-bar:has(.flex-bar-notif--message)) {
    height: auto;
    min-height: 0;
    min-width: 0;
    width: 100%;
    max-width: 100%;
    box-shadow: 0 10px 32px rgba(0, 0, 0, 0.3);
}
html[data-ui-mode="tablet"] .flex-bar:has(.flex-bar-notif) .flex-bar-notif {
    width: 100%;
    max-width: none;
}
html[data-ui-mode="tablet"] .flex-bar:has(.flex-bar-notif) .flex-bar-notif__msg {
    /* Popup flows text at card width; source strings' embedded
       newlines were tuned for the old in-bar layout. */
    white-space: normal;
    max-width: none;
}

/* On Attendance Logging a message with no buttons stays in the flex bar:
   the popup covered the room titles. Every other module keeps the popup,
   and confirms keep it everywhere (their buttons do not fit the bar).
   Scoped by the body's data-module (set from currentModule in app.html);
   widening it to the whole shell is Ives's call, not an attendance PR's. */
html[data-ui-mode="tablet"] body[data-module="dashboard"] .flex-bar:has(.flex-bar-notif--message) {
    min-height: 0;
    width: 100%;
    max-width: 100%;
    overflow: hidden;
    border-radius: 999px;
}
html[data-ui-mode="tablet"] body[data-module="dashboard"] .flex-bar:has(.flex-bar-notif--message) .flex-bar-notif {
    width: 100%;
    max-width: none;
    padding: 0 14px;
    gap: 8px;
    min-height: 0;
}
html[data-ui-mode="tablet"] body[data-module="dashboard"] .flex-bar:has(.flex-bar-notif--message) .flex-bar-notif__msg {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    font-size: 13px;
    line-height: 1.15;
}

/* Messaging's queue button carried desktop padding (6px) and a 13px line,
   so the button painted 32px and spilled the 28px candy. Same token as
   every other flex-bar control. The icon's inline font-size does not
   set the box. */
html[data-ui-mode="tablet"] .flex-bar .messaging-queue-flexbar-btn {
    height: var(--flexbar-control-h);
    min-height: var(--flexbar-control-h);
    max-height: var(--flexbar-control-h);
    box-sizing: border-box;
    padding: 0 10px;
    font-size: 12px;
    line-height: 1;
    border-width: 0;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.45);
}
html[data-ui-mode="tablet"] .flex-bar .messaging-queue-flexbar-btn iconify-icon,
html[data-ui-mode="tablet"] .flex-bar .messaging-queue-flexbar-btn > span {
    height: var(--flexbar-control-h);
    max-height: var(--flexbar-control-h);
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    line-height: 1;
}

/* Daily Care editing cluster — #174 leaf height + Attendance breath.
   Saved and Delete entry are .care-autosave-pill / .flex-bar-billing-btn:
   desktop padding 6px 10px, 8px radius, content-sized. Inside the 28px
   candy (overflow: visible) that paints a taller rounded rectangle, so
   the green/red fill spills the capsule and the pills miss the nav
   midline. Colour stays ON the pill (radius 999, overflow hidden,
   backdrop-filter off — the blur layer is a square). The actions
   wrapper stays transparent so it cannot paint a rectangular block.
   Packing is the Attendance grid (outer 1fr, breath 8–152). The floor
   stays above zero so Tab A cannot glue Children onto Saved. Horizontal
   padding on this bar's pills is one step tighter than the 10px leaf so
   the full labels still clear the capsule curve at 1024:
   [Back | Children] breath [Saved Delete | Previous Next]. Rejects the
   space-between void after Children (actions crushed right) and a
   left-shove. Back is in flow here; the 110px absolute reserve is the
   composite-drill hack for billing. */
html:not(.tutorial-mode)[data-ui-mode="tablet"] .flex-bar:not(:has(.flex-bar-notif)):has(#topBarBackControls > *:not(template):not([style*="display: none"])):has(.flex-bar-billing-context:not([style*="display: none"])):has(.flex-bar-care-context) {
    display: grid;
    width: 100%;
    max-width: 100%;
    min-width: 0;
    grid-template-columns: minmax(0, 1fr) max-content max-content minmax(8px, 152px) max-content max-content minmax(0, 1fr);
    align-items: center;
    justify-content: stretch;
    column-gap: 0;
    row-gap: 0;
    gap: 0;
    padding-left: 10px;
    padding-right: 10px;
}
html[data-ui-mode="tablet"] .flex-bar:has(.flex-bar-care-context) #topBarBackControls {
    position: relative;
    left: 0;
    top: auto;
    transform: none;
    grid-column: 2;
    justify-self: start;
    align-self: center;
    flex: 0 0 auto;
    margin: 0;
    height: var(--flexbar-control-h);
    max-height: var(--flexbar-control-h);
}
html[data-ui-mode="tablet"] .flex-bar:has(#topBarControls) .flex-bar-content:has(.flex-bar-care-context) {
    display: contents;
}
html[data-ui-mode="tablet"] .flex-bar-care-context {
    display: contents;
}
html[data-ui-mode="tablet"] .flex-bar:has(.flex-bar-care-context) .flex-bar-care-title {
    grid-column: 3;
    justify-self: start;
    align-self: center;
    flex: 0 0 auto;
    margin: 0 0 0 6px;
    height: var(--flexbar-control-h);
    min-height: var(--flexbar-control-h);
    max-height: var(--flexbar-control-h);
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    padding: 0 2px;
    font-size: 12px;
    font-weight: 700;
    line-height: 1;
    background: transparent;
    overflow: visible;
    text-overflow: clip;
    min-width: max-content;
}
html[data-ui-mode="tablet"] .flex-bar:has(.flex-bar-care-context) .flex-bar-billing-actions {
    grid-column: 5;
    justify-self: start;
    align-self: center;
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin: 0;
    height: var(--flexbar-control-h);
    min-height: var(--flexbar-control-h);
    max-height: var(--flexbar-control-h);
    background: transparent;
    box-shadow: none;
    border: 0;
    overflow: visible;
    min-width: max-content;
}
html[data-ui-mode="tablet"] .flex-bar-care-context .care-autosave-pill,
html[data-ui-mode="tablet"] .flex-bar-care-context .care-delete-flex-btn {
    flex: 0 0 auto;
    align-self: center;
    height: var(--flexbar-control-h);
    min-height: var(--flexbar-control-h);
    max-height: var(--flexbar-control-h);
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 0 6px;
    margin: 0;
    font-size: 12px;
    font-weight: 700;
    line-height: 1;
    border-radius: 999px;
    overflow: hidden;
    white-space: nowrap;
    min-width: max-content;
    transform: none;
    /* Desktop backdrop-filter paints a square layer behind the radius. */
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}
html[data-ui-mode="tablet"] .flex-bar-care-context .care-delete-flex-btn:hover,
html[data-ui-mode="tablet"] .flex-bar-care-context .care-autosave-pill:hover {
    transform: none;
}
html[data-ui-mode="tablet"] .flex-bar:has(.flex-bar-care-context) #topBarBackControls button,
html[data-ui-mode="tablet"] .flex-bar:has(.flex-bar-care-context) #topBarNextControls button {
    font-size: 12px !important;
    font-weight: 700 !important;
    line-height: 1 !important;
    /* Beat inline 14px / the 10px prev-next rule. Full words stay;
       the saved pixels are what keep an 8px breath at 1024. */
    padding-left: 8px !important;
    padding-right: 8px !important;
}
html[data-ui-mode="tablet"] .flex-bar:has(.flex-bar-care-context) #topBarNextControls,
html[data-ui-mode="tablet"] .flex-bar:has(.flex-bar-care-context) .flex-bar-next-controls {
    grid-column: 6;
    justify-self: start;
    align-self: center;
    margin-left: 6px;
    margin-right: 0;
    flex: 0 0 auto;
}

/* Max-content grid tracks could exceed the available capsule. Let the
   title yield space, while every action stays inside the existing bar. */
html[data-ui-mode="tablet"] .flex-bar:not(:has(.flex-bar-notif)):has(.flex-bar-care-context) {
    display: flex;
    justify-content: space-between;
    gap: 8px;
}
html[data-ui-mode="tablet"] .flex-bar:has(.flex-bar-care-context) .flex-bar-care-title {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}
@container care-toolbar (max-width: 700px) {
    .flex-bar:has(.flex-bar-care-context) { gap: 8px; padding-inline: 12px; min-width: 0; }
    .flex-bar:has(.flex-bar-care-context) .flex-bar-care-context { gap: 8px; padding-left: 0; }
    .flex-bar:has(.flex-bar-care-context) .flex-bar-care-title { font-size: 14px; }
    .flex-bar:has(.flex-bar-care-context) .flex-bar-prev-child-btn span,
    .flex-bar:has(.flex-bar-care-context) .flex-bar-next-child-btn span { display: none; }
    .flex-bar:has(.flex-bar-care-context) .flex-bar-prev-child-btn::after { content: 'Previous'; }
    .flex-bar:has(.flex-bar-care-context) .flex-bar-next-child-btn::after { content: 'Next'; }
}
@container care-toolbar (max-width: 480px) {
    .flex-bar:has(.flex-bar-care-context) .care-autosave-pill span { display: none; }
    .flex-bar:has(.flex-bar-care-context) .care-delete-flex-btn span { display: none; }
    .flex-bar:has(.flex-bar-care-context) .care-delete-flex-btn::after { content: 'Delete'; }
    .flex-bar:has(.flex-bar-care-context) .care-correct-flex-btn span { display: none; }
    .flex-bar:has(.flex-bar-care-context) .care-correct-flex-btn::after { content: 'Correct'; }
    .flex-bar:has(.flex-bar-care-context) .flex-bar-billing-actions { gap: 4px; }
}
@container care-toolbar (max-width: 360px) {
    .flex-bar:has(.flex-bar-care-context) { gap: 6px; }
    .flex-bar:has(.flex-bar-care-context) #topBarBackControls button { font-size: 0 !important; padding-inline: 8px !important; }
    .flex-bar:has(.flex-bar-care-context) #topBarBackControls button::before { content: '←'; font-size: 14px; }
    .flex-bar:has(.flex-bar-care-context) .flex-bar-prev-child-btn,
    .flex-bar:has(.flex-bar-care-context) .flex-bar-next-child-btn { padding-inline: 8px !important; }
    .flex-bar:has(.flex-bar-care-context) .flex-bar-prev-child-btn::after { content: '←'; }
    .flex-bar:has(.flex-bar-care-context) .flex-bar-next-child-btn::after { content: '→'; }
    .flex-bar:has(.flex-bar-care-context) .care-delete-flex-btn::after { content: none; }
    .flex-bar:has(.flex-bar-care-context) .care-correct-flex-btn::after { content: none; }
}

/* Title-only bars (Educators, Reports, Onboarding, admin module names,
   and any module with no teleported controls). Plain centred text.
   Desktop does not put a bubble on these labels. Bar width is
   unchanged — this only paints the label. */
html[data-ui-mode="tablet"] .flex-bar:not(:has(.flex-bar-notif)):not(:has(#topBarControls > *:not(template):not([style*="display: none"]))):not(:has(#topBarBackControls > *:not(template):not([style*="display: none"]))) .flex-bar-content {
    flex: 1 1 auto;
    width: auto;
    min-width: 0;
    justify-content: center;
}
html[data-ui-mode="tablet"] .flex-bar:not(:has(.flex-bar-notif)):not(:has(#topBarControls > *:not(template):not([style*="display: none"]))):not(:has(#topBarBackControls > *:not(template):not([style*="display: none"]))) .flex-bar-content > span {
    flex: 0 0 auto;
    width: auto;
    max-width: 100%;
    min-width: max-content;
    height: auto;
    min-height: 0;
    max-height: none;
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    line-height: 1;
    background: transparent;
    border: 0;
    border-radius: 0;
    box-shadow: none;
}

/* Module-content zoom (jj round 16: "75% zoom — smaller but clear is
   better than bigger and suffocating"). Renders every module's
   desktop-proportioned content at 75%, giving the 1024px glass an
   effective ~1365px content viewport — the layout register jj approved
   on the (fictional) 1340 preset, now on the real device. The header,
   flex bar, and nav rail are OUTSIDE the display area and keep their
   tablet tuning. The dashboard is exempt: it is the finger-first
   check-in surface and keeps its approved 52px (9.2 mm) circles. */
html[data-ui-mode="tablet"] .app-display-area > * {
    zoom: 0.75;
}
html[data-ui-mode="tablet"] .app-display-area > .dashboard-container-wrapper {
    zoom: 1;
}

/* Quiet scrollbars for admin shells only (html[data-ui-mode] is set
   by app.html/admin.html for both tablet and desktop). parent.html
   never sets the attribute, so the parent portal keeps platform
   default scrollbars. Thin thumbs on a transparent track; the four
   JS-recipe surfaces keep their fade behaviour on top of this. */
html[data-ui-mode] * {
    scrollbar-width: thin;
    scrollbar-color: rgba(33, 48, 77, 0.18) transparent;
}
html[data-ui-mode] ::-webkit-scrollbar {
    width: 5px;
    height: 5px;
}
html[data-ui-mode] ::-webkit-scrollbar-track {
    background: transparent;
}
html[data-ui-mode] ::-webkit-scrollbar-thumb {
    background: rgba(33, 48, 77, 0.18);
    border-radius: 4px;
}

/* ── Liquid Glass modal base — shared by onboarding, messaging
   (topic compose) and the app-shell asset picker. Moved from
   onboarding.css: that file loads lazily on module entry, so these
   modals rendered unfrosted until Onboarding had been visited. ── */
/* Liquid Glass Modal Effect.
   Overlay blur bumped 5px → 12px to match the stronger blur on
   .glass-content below it — 5px was imperceptible on top of the already-
   translucent background, so the overlay read as a flat tint rather
   than frosted glass. 12px keeps the visual hierarchy (content > overlay)
   while making the frost effect visible. */
.glass-overlay {
    background: rgba(255, 255, 255, 0.15);
    backdrop-filter: blur(28px) saturate(1.4);
    -webkit-backdrop-filter: blur(28px) saturate(1.4);
    border: none;
}

.glass-content {
    background: rgba(255, 255, 255, 0.85);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    border: 1px solid rgba(255, 255, 255, 0.5);
    box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.15);
    max-width: 700px;
}

.glass-content .modal-header {
    border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.context-delete-btn {
    color: #b83b32;
}

.context-delete-btn:hover {
    color: #8f241d;
    background: rgba(184, 59, 50, 0.1);
}

/* ── Child name reveal — the house pattern for compact child chips ─────
   Matches the Attendance child circles (Pete, #seahorse, 2026-09-24): the
   chip shows the first name; on hover, keyboard focus or a tap it shows the
   first name with the LAST NAME ON THE LINE UNDER. Two children who share a
   first name get a last initial at rest, as the circles do.

   The full name is laid OVER the chip (like the circle's ::before chip), so
   nothing around it moves. Markup:

     <span class="name-reveal" :aria-label="first + ' ' + last">
       <span class="name-reveal__idle" aria-hidden="true">
         <span x-text="first"></span><span class="name-reveal__initial" x-show="dup" x-text="' ' + last[0] + '.'"></span>
       </span>
       <span class="name-reveal__full" aria-hidden="true">
         <span class="name-reveal__first" x-text="first"></span>
         <span class="name-reveal__last" x-text="last"></span>
       </span>
     </span>

   Put .name-reveal on the chip itself so the whole chip is the hover target.
   A chip that is already SELECTED adds .name-reveal--open: both lines show in
   place, with no overlay. A host can tint the overlay with --name-reveal-bg. */
.name-reveal {
    position: relative;
}
.name-reveal__idle {
    white-space: nowrap;
}
.name-reveal__full {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    display: none;
    flex-direction: column;
    align-items: center;
    min-width: 100%;
    box-sizing: border-box;
    /* No vertical padding and a tight line height: the two lines must fit
       inside the chip's own height, or a one-row scroll container around
       the chip (the Groups roster list) turns scrollable on hover. */
    padding: 0 12px;
    border-radius: 14px;
    background: var(--name-reveal-bg, rgb(255 255 255 / 97%));
    border: 1px solid rgb(148 163 184 / 35%);
    box-shadow: 0 6px 18px rgb(0 0 0 / 16%);
    white-space: nowrap;
    line-height: 1.05;
    pointer-events: none;
    z-index: 30;
}
.name-reveal__first {
    font-weight: 700;
}
.name-reveal__last {
    font-weight: 600;
    font-size: 0.86em;
    opacity: 0.8;
}
.name-reveal:hover,
.name-reveal:focus-within {
    z-index: 30;
}
.name-reveal:hover .name-reveal__full,
.name-reveal:focus-within .name-reveal__full {
    display: flex;
}
/* Hide the resting text under the overlay (visibility keeps its footprint)
   so it does not ghost through. */
.name-reveal:hover .name-reveal__idle,
.name-reveal:focus-within .name-reveal__idle {
    visibility: hidden;
}
/* Already selected: both lines in place, no overlay. */
.name-reveal--open .name-reveal__idle {
    display: none;
}
.name-reveal--open .name-reveal__full {
    position: static;
    transform: none;
    display: flex;
    min-width: 0;
    padding: 0;
    border: 0;
    background: none;
    box-shadow: none;
    /* In place the chip grows with its content, so the two lines can have
       more room than the overlay's 1.05 (which must fit inside the chip). */
    line-height: 1.15;
    gap: 1px;
}
/* The last name stays second in weight but reads at a glance (JJ, #316). */
.name-reveal--open .name-reveal__last {
    font-size: 0.92em;
    opacity: 0.9;
}
/* Hosts: tint the overlay to the chip it sits on. */
.note-tag-child.name-reveal {
    --name-reveal-bg: #fce4ec;
}
/* Activity Planner Tagged Children: the remove "×" sits in its own round seat,
   centred on the two-line chip, instead of floating between the name lines. */
.child-tag__remove {
    display: inline-grid;
    place-items: center;
    flex: none;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: rgb(0 96 100 / 10%);
    font-size: 12px;
    font-weight: 700;
    line-height: 1;
}
