/* ============================================================================
   SENSAI UI — Design System ("Kitchen Sink") stylesheet
   ----------------------------------------------------------------------------
   RULES OF THIS FILE
   1. Zero !important. Ever. Components win because the app is namespaced
      under .sensai-app, not because rules shout.
   2. Every visual decision comes from a token (Section 1). Change a token,
      the whole system follows.
   3. Naming: .sensai-<component>, variants as --modifier, states as .is-*.
      e.g.  .sensai-btn .sensai-btn--red .is-disabled
   4. New element needed? It gets designed HERE first, then used in the app.
   ============================================================================ */

/* ==========================================================================
   1. TOKENS
   ========================================================================== */
/* :root, not just .sensai-app - every var(--s-*) in this file is invalid at
   computed-value time for anything rendered outside .sensai-app (there was
   no :root block at all until this fix). Found live via the user's own
   Claude-in-Chrome instrumentation: the app-wide loading overlay sits as a
   SIBLING of .sensai-app (not a descendant), so its font-family/color/
   spinner-colour tokens were all silently resolving to browser defaults -
   Times New Roman, black text, black spinner arc - while looking, from the
   CSS source alone, completely correct. body's own `background: var(--s-bg)`
   had the identical latent bug (body is .sensai-app's PARENT, not a
   descendant either) - masked only by luck (child elements' own opaque
   backgrounds cover it). .sensai-app is kept in the selector alongside
   :root (not replaced) so any future .sensai-app-scoped token override
   still cascades exactly as before. */
:root, .sensai-app {
    /* Brand colours */
    --s-purple:        #6a3fd4;
    --s-purple-deep:   #55309e;   /* hover */
    --s-purple-ink:    #4d2f9e;
    --s-purple-050:    #efeaff;
    --s-purple-tint:   #f1eefe;
    --s-purple-line:   #ded5f8;
    --s-purple-link:   #7044dd;

    --s-red:           #d0362c;
    --s-red-deep:      #b02219;   /* hover */
    --s-red-link:      #b32d2e;
    --s-red-link-deep: #8a1f20;
    --s-rose:          #d64562;

    --s-teal:          #14b0a7;
    --s-teal-deep:     #0f8d86;   /* hover */
    --s-teal-050:      #e2f7f5;
    --s-teal-soft:     #76b4af;
    --s-teal-soft-deep:#5fa39d;
    --s-teal-line:     #cfe9e7;
    --s-teal-tint:     #f5fbfb;

    --s-green:         #23a55a;
    --s-green-ink:     #157a2b;
    --s-green-deep:    #176b24;
    --s-green-050:     #e7f7ec;

    --s-coral:         #f26d85;
    --s-coral-050:     #fde8ec;
    --s-amber:         #f5a524;
    --s-amber-050:     #fdf1da;
    --s-amber-ink:     #8a5200;
    --s-sky:           #38bdf8;
    --s-sky-050:       #e3f4fd;
    --s-blue-ink:      #1c4f9c;
    --s-blue-050:      #e2edff;

    /* Neutrals */
    --s-ink:           #22124d;
    --s-text:          #3a4051;
    --s-muted:         #7a8090;
    --s-line:          #ece8f6;
    --s-line-strong:   #d6d9e0;
    --s-bg:            #f6f7fb;
    --s-card:          #ffffff;
    --s-grey-050:      #eef0f2;
    --s-grey-ink:      #555555;

    /* Dark (auth) surface */
    --s-dark-bg:       #2f2451;   /* stand-in: real page bg comes from design */
    --s-dark-text:     #ffffff;

    /* Radii */
    --s-r-sm:   8px;
    --s-r:      10px;
    --s-r-lg:   12px;
    --s-r-xl:   16px;
    --s-round:  999px;

    /* Spacing scale — the ONLY spacing values allowed */
    --s-1:  4px;
    --s-2:  8px;
    --s-3: 12px;
    --s-4: 16px;
    --s-5: 20px;
    --s-6: 24px;
    --s-7: 32px;

    /* Type */
    --s-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
              "Helvetica Neue", Arial, sans-serif;
    --s-fs-xs: 14px;
    --s-fs-sm: 15px;
    --s-fs:    17px;
    --s-fs-md: 19px;
    --s-fs-lg: 23px;
    --s-fs-xl: 29px;

    /* Effects */
    --s-shadow:    0 8px 30px rgba(34, 18, 77, .07);
    --s-shadow-sm: 0 3px 12px rgba(34, 18, 77, .06);
    --s-focus-ring: 0 0 0 3px rgba(106, 63, 212, .14);
    --s-ease: .15s ease;

    /* Stacking order - documented on one scale rather than each component
       picking its own number. The loading overlay deliberately sits ABOVE
       the modal layer: a Livewire action started from inside an open
       modal (e.g. Add Staff Member, a confirm dialogue) must still show
       "in flight" feedback over the top of that modal, not hidden behind
       it - found live as a genuine gap (a submit button inside a modal
       stayed clickable during its own in-flight request, since the
       overlay meant to cover/guard it was rendering underneath). */
    --s-z-dropdown: 20;
    --s-z-scrim:    89000;
    --s-z-nav:      90000;
    --s-z-modal:    100000;
    --s-z-overlay:  100010;
    --s-z-toast:    100020;

    /* Shell layout dimensions/breakpoints - named here even though a
       media query condition can't read a custom property (browsers
       evaluate @media before the cascade resolves var(), so the pixel
       values are necessarily repeated as literals at each @media
       boundary below) - documenting the token is still what keeps every
       repetition of the number honest against the same intended value,
       and --s-nav-w itself IS a real, usable property wherever width is
       actually set in a normal declaration. */
    --s-nav-w:  264px;
    --s-bp-md:  900px;
    --s-bp-sm:  560px;
}

/* ==========================================================================
   2. BASE — scoped normalisation. :where() keeps specificity at zero, so any
      component rule below overrides it without effort. This is what replaces
      the !important war: inside .sensai-app, theme styles are neutralised
      once, here, and never fought again.
   ========================================================================== */
.sensai-app {
    font-family: var(--s-font);
    font-size: var(--s-fs);
    color: var(--s-text);
    line-height: 1.55;
}
.sensai-app :where(button, input, select, textarea) {
    font: inherit;
    color: inherit;
    margin: 0;
    box-shadow: none;
    text-shadow: none;
    text-transform: none;
    letter-spacing: normal;
}
.sensai-app :where(button) {
    border: 0;
    background: none;
    cursor: pointer;
    border-radius: 0;
    padding: 0;
}
.sensai-app :where(a) {
    color: var(--s-purple-link);
    text-decoration: none;
}
.sensai-app :where(h1, h2, h3, h4, p, ul, ol) { margin: 0 0 var(--s-3); }
.sensai-app :where(h1, h2, h3, h4) { color: var(--s-ink); line-height: 1.25; }
.sensai-app :where(h1) { font-size: var(--s-fs-xl); }
.sensai-app :where(h2) { font-size: var(--s-fs-lg); }
.sensai-app :where(h3) { font-size: var(--s-fs-md); }

/* ==========================================================================
   3. BUTTONS — one block, variants only. No other button styling exists.
   ========================================================================== */
.sensai-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--s-2);
    padding: 10px 20px;
    border: 0;
    border-radius: 9px;
    background: var(--s-purple);
    color: #fff;
    font-weight: 700;
    font-size: 14px;
    line-height: 1.2;
    cursor: pointer;
    text-decoration: none;
    transition: background var(--s-ease), opacity var(--s-ease);
}
.sensai-btn:hover,
.sensai-btn:focus-visible { background: var(--s-purple-deep); color: #fff; }

.sensai-btn--red { background: var(--s-red); }
.sensai-btn--red:hover,
.sensai-btn--red:focus-visible { background: var(--s-red-deep); }

.sensai-btn--teal { background: var(--s-teal); }
.sensai-btn--teal:hover,
.sensai-btn--teal:focus-visible { background: var(--s-teal-deep); }

/* Ghost: bordered, white — secondary actions */
.sensai-btn--ghost {
    background: var(--s-card);
    color: var(--s-text);
    border: 1px solid var(--s-line-strong);
    font-weight: 600;
}
.sensai-btn--ghost:hover,
.sensai-btn--ghost:focus-visible { background: var(--s-bg); color: var(--s-text); }

/* Toggle: opens/closes a panel (e.g. Filters) - pill-shaped to match the
   search bar it usually sits beside, teal-bordered while the panel is
   open (.is-active) rather than a colour used nowhere else in the
   toolbar, so "this is currently on" reads clearly at a glance. */
.sensai-btn--toggle {
    background: var(--s-card); color: var(--s-text);
    border: 1px solid var(--s-line-strong); border-radius: var(--s-round);
    font-weight: 600;
}
.sensai-btn--toggle:hover,
.sensai-btn--toggle:focus-visible { background: var(--s-bg); color: var(--s-text); }
.sensai-btn--toggle.is-active {
    background: var(--s-teal-tint); color: var(--s-teal-deep); border-color: var(--s-teal-line);
}
.sensai-btn--toggle.is-active:hover,
.sensai-btn--toggle.is-active:focus-visible { background: var(--s-teal-050); color: var(--s-teal-deep); }

/* White: solid white, for dark surfaces (e.g. Download as File) */
.sensai-btn--white { background: #fff; color: var(--s-ink); }
.sensai-btn--white:hover,
.sensai-btn--white:focus-visible { background: #fff; color: var(--s-ink); opacity: .85; }

/* Pill: soft lilac utility button (e.g. Generate Password) */
.sensai-btn--pill {
    background: var(--s-purple-tint);
    color: var(--s-purple-ink);
    border: 1px solid var(--s-purple-line);
    border-radius: var(--s-round);
    padding: 8px 16px;
    font-size: var(--s-fs-sm);
}
.sensai-btn--pill:hover,
.sensai-btn--pill:focus-visible { background: #e6dffc; color: var(--s-purple-ink); }

/* Gradient: highest-emphasis single action on a page (e.g. Import
   Centre's Start Import) - a step up from the solid default, used
   sparingly for the one action that matters most. Pair with --large. */
.sensai-btn--gradient {
    background: linear-gradient(135deg, var(--s-purple), var(--s-teal), var(--s-purple));
    background-size: 200% 200%;
    background-position: 0% 50%;
    border-radius: 12px;
    box-shadow: 0 8px 20px rgba(106, 63, 212, .28);
    transition: background-position .5s ease, box-shadow var(--s-ease);
}
.sensai-btn--gradient:hover,
.sensai-btn--gradient:focus-visible {
    background-position: 100% 50%;
    box-shadow: 0 10px 24px rgba(106, 63, 212, .34);
}
@media (prefers-reduced-motion: reduce) {
    .sensai-btn--gradient { transition: box-shadow var(--s-ease); }
}

/* Sizes */
.sensai-btn--slim  { padding: 7px 14px; font-size: var(--s-fs-sm); }
.sensai-btn--large { padding: 13px 26px; font-size: var(--s-fs); }

/* <x-loading-btn>'s spinner+label wrapper - layout only, deliberately
   omits `display` itself: that's Livewire's own [wire\:loading] rule to
   control (hidden by default, shown while its wire:target request is
   in flight). An inline style="display:inline-flex" on this span here
   would permanently win over Livewire's rule regardless of loading
   state, since inline styles always outrank stylesheet rules of equal
   or lower specificity - exactly the bug this class replaces. */
.sensai-btn-loading-content { align-items: center; gap: 6px; }

.sensai-btn.is-disabled,
.sensai-btn:disabled,
.sensai-link.is-disabled,
.sensai-check.is-disabled { opacity: .45; cursor: default; pointer-events: none; }

/* ==========================================================================
   4. LINKS — text-style actions (a or button, identical look)
   ========================================================================== */
.sensai-link {
    display: inline;
    padding: 0;
    border: 0;
    background: none;
    color: var(--s-purple-link);
    font-weight: 700;
    font-size: inherit;
    text-decoration: underline;
    cursor: pointer;
    transition: color var(--s-ease);
}
.sensai-link:hover,
.sensai-link:focus-visible { color: var(--s-purple-ink); background: none; }

.sensai-link--danger { color: var(--s-red-link); }
.sensai-link--danger:hover,
.sensai-link--danger:focus-visible { color: var(--s-red-link-deep); }

.sensai-link--teal { color: var(--s-teal-deep); }
.sensai-link--teal:hover,
.sensai-link--teal:focus-visible { color: #0c7871; }

.sensai-link--amber { color: var(--s-amber-ink); }
.sensai-link--amber:hover,
.sensai-link--amber:focus-visible { color: #6b3e00; }

/* Row link: the whole row is the click target (see .sensai-table tr with
   a click handler forwarding to this), so the link itself reads as
   plain text rather than announcing itself with link-blue/underline -
   the row's own hover background is the affordance instead. Still a
   real <a>, so keyboard/screen-reader/right-click-open-in-new-tab all
   keep working. */
.sensai-link--row {
    color: inherit; font-weight: inherit; text-decoration: none;
}
.sensai-link--row:hover,
.sensai-link--row:focus-visible { color: var(--s-purple-link); text-decoration: underline; }

/* Selectable row: the design-system convention for any .sensai-table
   with a checkbox column - the whole row toggles it, not just the tiny
   checkbox target (checkbox-only clicking is too fiddly on a long
   list). Add this class to the <tr> plus a click handler forwarding to
   the row's own checkbox, guarded so clicking the checkbox directly
   doesn't double-toggle:
     @click="$event.target.closest('input') || $el.querySelector('input[type=checkbox]').click()"
   The row's existing :hover background (above) is the only visual
   affordance needed - no separate hover state for this class. Parallel
   convention to .sensai-link--row above (whole row as click target),
   just toggling a checkbox instead of navigating. */
.sensai-row--selectable { cursor: pointer; }

/* Quiet: grey, underline on hover (e.g. Clear Filters) */
.sensai-link--quiet { color: var(--s-muted); font-weight: 600; text-decoration: none; }
.sensai-link--quiet:hover,
.sensai-link--quiet:focus-visible { color: var(--s-text); text-decoration: underline; }

/* Light: white links for dark surfaces. Rest 75% white, no underline;
   hover fully white, underlined, lifts 2px. */
.sensai-link--light {
    display: inline-block;
    color: var(--s-dark-text);
    opacity: .75;
    font-weight: 600;
    text-decoration: none;
    transform: translateY(0);
    transition: opacity var(--s-ease), transform var(--s-ease);
}
.sensai-link--light:hover,
.sensai-link--light:focus-visible {
    color: var(--s-dark-text);
    opacity: 1;
    text-decoration: underline;
    transform: translateY(-2px);
    background: none;
}

/* ==========================================================================
   5. FORMS
   ========================================================================== */
.sensai-field { display: block; margin: 0 0 var(--s-4); }
.sensai-field > label,
.sensai-field-label {
    display: block;
    font-size: var(--s-fs-sm);
    font-weight: 700;
    color: var(--s-ink);
    margin-bottom: 6px;
}
.sensai-field-hint { font-size: var(--s-fs-xs); color: var(--s-muted); margin-top: 5px; }

.sensai-input,
.sensai-select,
.sensai-textarea {
    display: block;
    width: 100%;
    max-width: 420px;
    box-sizing: border-box;
    padding: 10px 12px;
    border: 1px solid var(--s-line-strong);
    border-radius: 9px;
    background: var(--s-card);
    color: var(--s-text);
    font-size: var(--s-fs);
    transition: border-color var(--s-ease), box-shadow var(--s-ease);
}
.sensai-input:focus,
.sensai-select:focus,
.sensai-textarea:focus {
    outline: none;
    border-color: var(--s-purple);
    box-shadow: var(--s-focus-ring);
}
.sensai-textarea { min-height: 96px; resize: vertical; }
.sensai-input--full { max-width: none; }
.sensai-input--centred { margin-left: auto; margin-right: auto; }

/* Centre-aligned text variant: aligns the typed/placeholder text itself -
   independent of --centred above, which only centres the box within its
   parent. Standard forms keep left-aligned text (the default); a form
   opts into this per-field for a "centre-aligned form" look (e.g. the
   auth surface's centre-aligned login form, section 13). */
.sensai-input--text-centred { text-align: center; }

/* Code entry (TOTP / recovery) */
.sensai-input--code {
    width: 220px;
    max-width: 220px;
    font-size: 26px;
    letter-spacing: 10px;
    text-align: center;
    border: 1.5px solid #cfcbe2;
    border-radius: var(--s-r-lg);
    padding: 12px 14px;
    font-variant-numeric: tabular-nums;
}
.sensai-input--code-rec {
    width: 250px;
    max-width: 250px;
    font-size: 20px;
    letter-spacing: 4px;
    text-transform: uppercase;
}

/* Custom select: a native <select>'s closed box can be styled (that's
   .sensai-select above), but its open option list is drawn by the OS and
   CSS cannot reach it at all - this replaces both halves, and doubles as
   a type-to-filter combobox rather than a click-only replacement (the
   trigger is a real <input>, not a button - typing filters the option
   list live). The closed trigger matches .sensai-select's exact box
   (same border/radius/padding) so the two are indistinguishable at
   rest; the open panel reuses .sensai-search-results/-row's established
   look (same shadow token, same hairline-separated rows) rather than
   inventing a new dropdown language. A leading "Any" row (bold when
   nothing else is selected) replaces a <select>'s empty first <option>.
   Structure: .sensai-dd > (.sensai-dd-trigger > .sensai-dd-input + svg,
   .sensai-dd-panel > .sensai-dd-option* | .sensai-dd-empty). JS (Alpine
   in the app) toggles .is-open on .sensai-dd and shows/hides
   .sensai-dd-panel; see the Dropdown demo in section 4 for the full
   markup and interaction (click or type to open, filters as you type,
   Up/Down/Home/End/Enter/Escape, click-outside). */
.sensai-dd { position: relative; display: block; width: 100%; max-width: 420px; }
.sensai-dd-trigger {
    display: flex; align-items: center; gap: var(--s-2);
    width: 100%; box-sizing: border-box;
    border: 1px solid var(--s-line-strong); border-radius: 9px;
    background: var(--s-card); color: var(--s-text);
    transition: border-color var(--s-ease), box-shadow var(--s-ease);
}
.sensai-dd-trigger:hover { border-color: var(--s-purple); }
.sensai-dd.is-open .sensai-dd-trigger,
.sensai-dd-input:focus-visible {
    border-color: var(--s-purple); box-shadow: var(--s-focus-ring);
}
.sensai-dd-input {
    flex: 1; min-width: 0;
    border: 0; background: none; box-sizing: border-box;
    padding: 10px 0 10px 12px;
    color: var(--s-text); font: inherit;
    text-overflow: ellipsis;
}
.sensai-dd-input:focus { outline: none; }
.sensai-dd-input::placeholder { color: var(--s-muted); opacity: 1; }
.sensai-dd-trigger svg {
    flex-shrink: 0; margin-right: 12px; color: var(--s-muted);
    transition: transform var(--s-ease); pointer-events: none;
}
.sensai-dd.is-open .sensai-dd-trigger svg { transform: rotate(180deg); }

.sensai-dd-panel {
    position: absolute; top: 100%; left: 0; right: 0; z-index: var(--s-z-dropdown);
    margin-top: 6px; border: 1px solid var(--s-line);
    border-radius: var(--s-r-lg); background: var(--s-card);
    box-shadow: var(--s-shadow-sm); overflow: hidden auto; max-height: 260px;
}
.sensai-dd-option {
    display: flex; align-items: center; justify-content: space-between; gap: var(--s-3);
    width: 100%; box-sizing: border-box;
    text-align: left; padding: 10px 14px;
    border: 0; border-bottom: 1px solid var(--s-line);
    background: none; color: var(--s-text); font: inherit; cursor: pointer;
}
.sensai-dd-option:last-child { border-bottom: 0; }
.sensai-dd-option:hover,
.sensai-dd-option.is-highlighted { background: var(--s-purple-050); }
.sensai-dd-option.is-selected { color: var(--s-purple-ink); font-weight: 700; }
.sensai-dd-option svg { color: var(--s-purple); flex-shrink: 0; visibility: hidden; }
.sensai-dd-option.is-selected svg { visibility: visible; }
.sensai-dd-empty { padding: 14px; color: var(--s-muted); font-size: var(--s-fs-sm); text-align: center; }

/* Class-assignment picker (Manage Users, Journey 5) - not a new design,
   a composition of two components that already existed in this file but
   had never been used together: a .sensai-search-style type-to-filter
   input with a .sensai-search-results/-row dropdown for picking a class,
   and .sensai-chip--select (with its .sensai-chip-x remove button) for
   showing what's already assigned. Structure: .sensai-classpicker >
   (.sensai-classpicker-chips > .sensai-chip--select*, .sensai-search >
   input + .sensai-search-results). */
.sensai-classpicker { width: 100%; max-width: 420px; }
.sensai-classpicker-chips { margin-bottom: 8px; }
.sensai-classpicker-chips:empty { display: none; }
.sensai-classpicker .sensai-search-results { max-height: 220px; overflow-y: auto; }
.sensai-classpicker .sensai-search-row { width: 100%; background: none; border: 0; cursor: pointer; font: inherit; color: var(--s-text); }

/* Password hint box + generator row (Journey 5's set-password page,
   plugin/sensai-core.php's sensai_password_hint_html()/[sensai_set_
   password]) - ported onto design tokens here for the first time; the
   legacy WP version (plugin/sensai-core.css) predates the Kitchen Sink
   and used one-off hex values directly. The eye-toggle/generator
   *behaviour* itself comes from the already-shared sensai-core.js, not
   new JS - these are purely the styles that markup expects to find. */
.sensai-pw-hint { margin: 8px 0 14px; padding: 12px 15px; border: 1px solid var(--s-purple-line); border-radius: 10px; background: var(--s-purple-tint); font-size: .9em; color: var(--s-text); }
.sensai-pw-hint strong { display: block; margin-bottom: 6px; color: var(--s-ink); }
.sensai-pw-hint ul { margin: 0; padding-left: 18px; }
.sensai-pw-hint li { margin: 3px 0; }
.sensai-pw-row { display: inline-flex; align-items: center; gap: 10px; flex-wrap: wrap; width: 100%; }
.sensai-pw-gennote { color: var(--s-green-deep); font-size: 12px; font-weight: 600; margin: 6px 0 0; }
.sensai-pw-copylink { display: inline-flex; align-items: center; gap: 6px; background: none; border: 0; padding: 0; color: var(--s-purple); font-size: 12px; font-weight: 700; cursor: pointer; text-decoration: underline; }
.sensai-pw-copylink:hover { color: var(--s-purple-deep); }
.sensai-pw-copylink.is-copied { color: var(--s-green-deep); text-decoration: none; }

/* Checkbox / radio row */
.sensai-check { display: inline-flex; gap: var(--s-2); align-items: center; font-size: .95em; cursor: pointer; }
.sensai-check input { width: 16px; height: 16px; accent-color: var(--s-purple); }

/* Password wrap + eye toggle */
.sensai-pwwrap { position: relative; display: block; width: 100%; max-width: 420px; }
.sensai-pwwrap .sensai-input { padding-right: 40px; padding-left: 40px; max-width: none; }
.sensai-eyebtn {
    position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
    width: 28px; height: 28px; border-radius: var(--s-r-sm);
    background: var(--s-purple-tint); color: var(--s-purple);
    display: flex; align-items: center; justify-content: center;
}
.sensai-eyebtn svg { display: block; }

/* Icon chip — the 28px lilac square (eye, info icon, etc.) */
.sensai-icochip {
    display: inline-flex; align-items: center; justify-content: center;
    width: 28px; height: 28px; border-radius: var(--s-r-sm); line-height: 1;
    background: var(--s-purple-tint); color: var(--s-purple);
    vertical-align: middle; flex-shrink: 0;
}
.sensai-icochip svg { display: block; margin: auto; }
.sensai-icochip { margin-right: var(--s-2); }

/* Copy box — value + copy button as one element */
.sensai-copybox {
    display: inline-flex; align-items: stretch; max-width: 100%;
    border: 1px solid var(--s-teal-line); border-radius: 9px;
    overflow: hidden; background: var(--s-teal-tint);
}
.sensai-copybox-value {
    padding: 10px 14px; display: flex; align-items: center; min-width: 0;
    font-weight: 700; letter-spacing: .04em; color: var(--s-ink);
    overflow-wrap: break-word; word-break: break-all;
}
.sensai-copybox-btn {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 0 14px; border-left: 1px solid var(--s-teal-line);
    background: var(--s-teal); color: #fff; font-weight: 700; font-size: .88em;
}
.sensai-copybox-btn:hover,
.sensai-copybox-btn:focus-visible { background: var(--s-teal-deep); color: #fff; }
.sensai-copybox-btn.is-copied { background: var(--s-green-deep); }

/* ==========================================================================
   6. SEARCH
   ========================================================================== */
.sensai-search { position: relative; max-width: 420px; }
.sensai-search .sensai-input { padding-left: 38px; padding-right: 34px; border-radius: var(--s-round); max-width: none; }
.sensai-search-ico {
    position: absolute; left: 13px; top: 50%; transform: translateY(-50%);
    color: var(--s-muted); pointer-events: none; display: flex;
    z-index: 1; /* themes often make inputs position:relative, painting over icons */
}
/* Custom clear ("x") button, red - replaces the browser's native search-
   input cancel button, which we can't colour or guarantee looks the same
   across browsers. */
.sensai-search input[type="search"]::-webkit-search-cancel-button {
    -webkit-appearance: none;
    appearance: none;
}
.sensai-search-clear {
    position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
    display: flex; align-items: center; justify-content: center;
    width: 20px; height: 20px;
    color: var(--s-red-link); cursor: pointer;
}
.sensai-search-clear svg { width: 14px; height: 14px; }
.sensai-search-clear:hover,
.sensai-search-clear:focus-visible { color: var(--s-red-link-deep); }
.sensai-search-results {
    position: absolute; top: 100%; left: 0; right: 0; z-index: var(--s-z-dropdown);
    margin-top: 6px; border: 1px solid var(--s-line);
    border-radius: var(--s-r-lg); background: var(--s-card);
    box-shadow: var(--s-shadow-sm); overflow: hidden;
}
.sensai-search-row {
    display: flex; align-items: center; gap: var(--s-3);
    width: 100%; min-width: 0; box-sizing: border-box;
    text-align: left; padding: 10px 14px;
    border-bottom: 1px solid var(--s-line);
}
.sensai-search-row:last-child { border-bottom: 0; }
.sensai-search-row:hover { background: var(--s-purple-050); }
/* Hover tracks the same per-kind theme as the row's own avatar colour
   (below), so the colour cue is consistent the whole way through a
   journey - not just on the avatar chip. Student is the default/
   unthemed row (purple), same as its avatar. */
.sensai-search-row--class:hover { background: var(--s-teal-050); }
.sensai-search-row--intervention:hover { background: var(--s-amber-050); }
.sensai-search-avatar {
    width: 32px; height: 32px; border-radius: 50%; flex-shrink: 0;
    display: flex; align-items: center; justify-content: center;
    background: var(--s-purple-tint); color: var(--s-purple-ink);
    font-weight: 700; font-size: var(--s-fs-sm);
}
.sensai-search-avatar--class        { background: var(--s-teal-050); color: var(--s-teal-deep); }
.sensai-search-avatar--intervention { background: var(--s-amber-050); color: var(--s-amber-ink); }
.sensai-search-name { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 700; color: var(--s-ink); }
.sensai-search-meta { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--s-fs-xs); color: var(--s-muted); }

/* ==========================================================================
   7. PILLS, CHIPS, STATUS
   ========================================================================== */
.sensai-pill {
    display: inline-block; padding: 4px 12px; border-radius: var(--s-round);
    font-size: var(--s-fs-xs); font-weight: 700;
    background: var(--s-grey-050); color: var(--s-grey-ink);
}
.sensai-pill--green  { background: var(--s-green-050); color: var(--s-green-ink); }
.sensai-pill--purple { background: #efe7ff; color: #5a32c0; }
.sensai-pill--amber  { background: var(--s-amber-050); color: var(--s-amber-ink); }
.sensai-pill--blue   { background: var(--s-blue-050); color: var(--s-blue-ink); }
.sensai-pill--teal   { background: #d7f4f1; color: #0c7d76; }
.sensai-pill--red    { background: #fde8e8; color: var(--s-red-link); }
/* Icon + count pill (e.g. the amber "review due" flag on an Interventions
   card) - inline-flex instead of the base .sensai-pill's inline-block so
   the icon and number sit centred on one baseline rather than the icon's
   own descender nudging the row height; harmless for a plain icon-less
   pill using this modifier too, since a single text node centres exactly
   the same as it would inline. The flag badge itself is a small SOLID
   glyph, not App\Support\Icon's usual stroke-only line-icon style -
   measured live that a thin stroke at this pill's ~13px scale reads as
   a "P", not a flag; a solid pennant reads clearly at this size where a
   thin outline doesn't. Same reasoning WordPress's own
   sensai_render_intervention_cards() had for hand-writing this one
   badge's SVG inline instead of using its shared icon helper. */
.sensai-pill--icon { display: inline-flex; align-items: center; gap: 4px; }
.sensai-pill--icon svg { flex-shrink: 0; }

.sensai-chip {
    display: inline-block; margin: 2px 4px 2px 0; padding: 4px 10px;
    border-radius: var(--s-round);
    background: #f2edff; border: 1px solid #d9ccff; color: #3e286f;
    font-size: .88em;
}
.sensai-chip--select { cursor: pointer; }
.sensai-chip--select:hover { background: #e6dffc; }
.sensai-chip .sensai-chip-x { margin-left: 6px; font-weight: 700; cursor: pointer; color: var(--s-purple-ink); }

/* Category colour variants - e.g. a student's Barriers/Strategies/
   Supports/Strengths, so the four read at a glance without re-reading
   each chip's own section heading. Same colour-pairing convention as
   .sensai-search-avatar--class/--intervention. */
.sensai-chip--red   { background: #fde8e8; border-color: #f5c6c6; color: var(--s-red-link); }
.sensai-chip--teal  { background: var(--s-teal-050); border-color: var(--s-teal-line); color: var(--s-teal-deep); }
.sensai-chip--blue  { background: var(--s-blue-050); border-color: #c3d9fb; color: var(--s-blue-ink); }
.sensai-chip--green { background: var(--s-green-050); border-color: #b7dfbd; color: var(--s-green-ink); }
.sensai-chip--red .sensai-chip-x   { color: var(--s-red-link); }
.sensai-chip--teal .sensai-chip-x  { color: var(--s-teal-deep); }
.sensai-chip--blue .sensai-chip-x  { color: var(--s-blue-ink); }
.sensai-chip--green .sensai-chip-x { color: var(--s-green-ink); }

.sensai-rag {
    display: inline-block; padding: 3px 11px; border-radius: var(--s-round);
    font-size: var(--s-fs-sm); font-weight: 800;
}
.sensai-rag--none  { background: var(--s-grey-050); color: #777; }
.sensai-rag--red   { background: #fde8e8; color: var(--s-red-link); }
.sensai-rag--amber { background: #fdf3dc; color: #9a6400; }
.sensai-rag--green { background: var(--s-green-050); color: var(--s-green-ink); }

.sensai-dot { display: inline-block; width: 9px; height: 9px; border-radius: 50%; margin-right: var(--s-1); }
.sensai-dot--ok { background: var(--s-green); }
.sensai-dot--no { background: var(--s-rose); }

/* ==========================================================================
   8. NOTICES & FEEDBACK
   ========================================================================== */
.sensai-notice {
    margin: var(--s-3) 0; padding: var(--s-3) 14px;
    border-radius: var(--s-r-sm); font-weight: 600;
    border: 1px solid var(--s-line-strong); background: var(--s-bg); color: var(--s-text);
}
.sensai-notice--success { border-color: #b7dfbd; background: #edf8ef; color: var(--s-green-deep); }
.sensai-notice--error   { border-color: #efb5b0; background: #fff0ef; color: #9f2118; }
.sensai-notice--warn    { border-color: #f0d8a8; background: #fdf6e3; color: #7a5200; }
.sensai-notice--info    { border-color: var(--s-purple-line); background: var(--s-purple-tint); color: var(--s-purple-ink); }
.sensai-notice a { color: #0b5cad; text-decoration: underline; font-weight: 700; }

/* Toast positioning - a modifier, not a replacement: an in-flow notice
   becomes a floating, dismissible one without a second component or a
   duplicated box style. --s-z-toast sits above both --s-z-modal and
   --s-z-overlay deliberately - a failure notice needs to be seen
   regardless of what else is open when it fires. top offset clears the
   header bar (--s-topbar-h, defined on body in the shell layout rules
   above) rather than a flat pixel value, so it can never overlap the
   header search. Available for a future top-positioned toast; the
   app-wide request-failure banner (layouts/shell.blade.php) uses
   --toast-bottom instead - top collided with the stale-results warning
   inside the content area (both fixed, near the top of the viewport),
   found live. */
.sensai-notice--toast {
    position: fixed; left: 50%; transform: translateX(-50%);
    top: calc(var(--s-topbar-h) + var(--s-4));
    z-index: var(--s-z-toast); max-width: 480px; width: calc(100% - 32px);
    box-shadow: var(--s-shadow);
}
.sensai-notice--toast-bottom {
    position: fixed; left: 50%; transform: translateX(-50%);
    bottom: var(--s-5);
    z-index: var(--s-z-toast); max-width: 480px; width: calc(100% - 32px);
    box-shadow: var(--s-shadow);
}
.sensai-notice--toast p, .sensai-notice--toast-bottom p { margin: 0 0 var(--s-3); }

.sensai-progress { height: 8px; border-radius: var(--s-round); background: var(--s-line); overflow: hidden; }
.sensai-progress-fill { height: 100%; border-radius: var(--s-round); background: var(--s-teal); transition: width .3s ease; }

.sensai-spinner {
    width: 48px; height: 48px; border-radius: 50%;
    border: 5px solid #d9ccff; border-top-color: var(--s-teal);
    animation: sensai-spin .8s linear infinite;
}
@keyframes sensai-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .sensai-spinner { animation: none; } }

/* Full-screen loading overlay - a blocking action (e.g. Import Centre's
   Start Import, which runs synchronously) covers the whole screen with
   the spinner above plus a status line, rather than leaving the user
   watching a static page with no feedback. .is-active is only needed for
   the static demo below; the app itself drives this with Livewire's own
   wire:loading, which toggles the same display:flex directly.
   font-family/color set explicitly (not just inherited via the now-
   hoisted :root tokens above) as defence in depth - this element sits
   as a SIBLING of .sensai-app, not a descendant, which is exactly the
   scoping gap that caused it to render with browser-default black serif
   text instead of the design system's font/colour before the :root
   hoist. z-index deliberately below .sensai-modal-backdrop's 100000 -
   see that rule's own comment for why a modal must be able to stay
   interactive/visible above this overlay. */
.sensai-loading-overlay {
    position: fixed; inset: 0; z-index: var(--s-z-overlay);
    display: none;
    align-items: center; justify-content: center;
    background: rgba(255, 255, 255, .78);
    font-family: var(--s-font); color: var(--s-text);
}
.sensai-loading-overlay.is-active { display: flex; }
.sensai-loading-box {
    display: flex; flex-direction: column; align-items: center; gap: 16px;
    text-align: center; padding: 0 24px;
}
.sensai-loading-text {
    max-width: 360px; color: var(--s-text); font-size: 1.02em;
    font-weight: 600; line-height: 1.5;
}
.sensai-loading-eta { display: block; margin-top: 4px; color: var(--s-teal-deep); font-weight: 800; }
/* The overlay's own dismiss control (layouts/shell.blade.php) - found
   live (Journey 5 round 12 feedback) that the plain .sensai-link--quiet
   styling it inherited was a 200x18px hit area (padding:0, min-height:0,
   below WCAG 2.5.8's 24x24 floor and far under the 44x44 the app's own
   hamburger toggle already sets as its house standard), in --s-muted
   (~3.9:1 against the overlay's translucent scrim, below the 4.5:1 AA
   floor for 16px text and variable besides, since it depends on
   whatever page content sits behind the scrim) with no underline, so it
   read as a caption rather than a control. --s-ink (not --s-muted) is a
   dark, near-black tone with comfortable contrast against the overlay's
   own near-white scrim regardless of what's behind it. Scoped to this
   one button, not a blanket .sensai-link--quiet change, since that
   class is used elsewhere (e.g. Student Records' "Clear all" filters
   link) where the quieter, lower-contrast treatment is still correct. */
.sensai-loading-box .sensai-link--quiet {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: var(--s-2) var(--s-4);
    color: var(--s-ink);
    text-decoration: underline;
    text-underline-offset: 3px;
}

/* ==========================================================================
   9. CARDS & STATS
   ========================================================================== */
.sensai-card {
    background: var(--s-card); border: 1px solid var(--s-line);
    border-radius: var(--s-r-xl); padding: var(--s-6);
    box-shadow: var(--s-shadow-sm);
}
.sensai-card-title { font-size: var(--s-fs-md); font-weight: 800; color: var(--s-ink); margin-bottom: var(--s-3); }

/* Definition list - label/value pairs inside a .sensai-card, e.g. a
   profile's "Student Details" or "SEN Profile" facts. */
.sensai-deflist { display: grid; grid-template-columns: max-content 1fr; gap: var(--s-2) var(--s-4); margin: 0; }
.sensai-deflist dt { color: var(--s-muted); font-weight: 600; font-size: var(--s-fs-sm); }
.sensai-deflist dd { margin: 0; color: var(--s-text); font-weight: 700; }

.sensai-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
    gap: 18px;
    margin-bottom: var(--s-6);
}
.sensai-stat {
    --c: var(--s-purple); --c-050: var(--s-purple-050);
    position: relative; overflow: hidden;
    padding: 20px 20px 18px;
    background: var(--s-card);
    border: 1px solid var(--s-line);
    border-radius: var(--s-r-xl);
    box-shadow: var(--s-shadow-sm);
    opacity: 0;
    animation: sensai-fade-up .55s cubic-bezier(.2, .7, .3, 1) forwards;
}
.sensai-stat::after {
    content: ""; position: absolute; right: -30px; top: -30px;
    width: 110px; height: 110px; border-radius: 50%;
    background: var(--c-050); opacity: .6;
}
.sensai-stat > * { position: relative; z-index: 1; }
.sensai-stat--teal  { --c: var(--s-teal);  --c-050: var(--s-teal-050); }
.sensai-stat--coral { --c: var(--s-coral); --c-050: var(--s-coral-050); }
.sensai-stat--amber { --c: var(--s-amber); --c-050: var(--s-amber-050); }
.sensai-stat--sky   { --c: var(--s-sky);   --c-050: var(--s-sky-050); }
.sensai-stat-ico {
    width: 44px; height: 44px; border-radius: 13px;
    display: flex; align-items: center; justify-content: center;
    background: var(--c); color: #fff; margin-bottom: 14px;
    box-shadow: 0 6px 16px color-mix(in srgb, var(--c) 40%, transparent);
}
.sensai-stat-ico svg { width: 23px; height: 23px; }
.sensai-stat-num {
    font-size: 2.15em; font-weight: 800; line-height: 1;
    color: var(--s-ink); letter-spacing: -.5px;
}
.sensai-stat-num .sensai-suffix { font-size: .55em; font-weight: 800; color: var(--c); margin-left: 1px; }
.sensai-stat-label { margin-top: 7px; color: var(--s-muted); font-size: .92em; font-weight: 600; }
.sensai-stat-sub { margin-top: 3px; color: var(--c); font-size: .8em; font-weight: 700; }
.sensai-stat:nth-child(1) { animation-delay: .04s; }
.sensai-stat:nth-child(2) { animation-delay: .12s; }
.sensai-stat:nth-child(3) { animation-delay: .2s; }
.sensai-stat:nth-child(4) { animation-delay: .28s; }
.sensai-stat:nth-child(5) { animation-delay: .36s; }
@keyframes sensai-fade-up { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } }
/* .sensai-stat's own base rule sets opacity: 0 and relies entirely on
   the animation above to reach opacity: 1 - a bare `animation: none`
   override (the pattern used elsewhere in this file) would leave every
   stat card permanently invisible for reduced-motion users rather than
   just static. opacity: 1 has to be restored explicitly alongside it. */
@media (prefers-reduced-motion: reduce) {
    .sensai-stat { opacity: 1; animation: none; }
}

/* Drop zone (Import Centre) - the bare target itself. Used on its own
   for a single simple drop target, or inside .sensai-import-zone (below)
   as one card among several (Census, Staff, SEND Data...), each with its
   own icon tile, numbered badge and template-download link. */
.sensai-dropzone {
    border: 2px dashed var(--s-purple-line); border-radius: var(--s-r-lg);
    background: var(--s-purple-tint); padding: var(--s-6);
    text-align: center; color: var(--s-purple-ink); font-weight: 600;
    transition: background var(--s-ease), border-color var(--s-ease);
}
.sensai-dropzone.is-drag { background: #e6dffc; border-color: var(--s-purple); }
.sensai-dropzone.is-filled { border-style: solid; background: var(--s-green-050); border-color: #b7dfbd; color: var(--s-green-deep); }

/* Import zone card: one tile in Import Centre's grid of upload targets.
   Structure: .sensai-import-zone > (.sensai-import-zone-top >
   .sensai-import-zone-tile + .sensai-import-zone-num,
   .sensai-import-zone-title, .sensai-dropzone > label wrapping a hidden
   file input + .sensai-dropzone-idle / .sensai-dropzone-filled,
   .sensai-import-zone-template). Idle/filled toggle the same way
   .sensai-dd's trigger/panel do - JS (Alpine in the app) toggles
   .is-filled on .sensai-dropzone. */
.sensai-import-zone {
    display: flex; flex-direction: column;
    padding: var(--s-5); background: var(--s-card);
    border: 1px solid var(--s-line); border-radius: var(--s-r-xl);
    box-shadow: var(--s-shadow-sm);
    transition: box-shadow var(--s-ease), transform var(--s-ease);
}
.sensai-import-zone:hover { box-shadow: 0 14px 32px rgba(34, 18, 77, .10); transform: translateY(-2px); }
.sensai-import-zone-top {
    display: flex; align-items: flex-start; justify-content: space-between;
    margin-bottom: var(--s-4);
}
.sensai-import-zone-tile {
    width: 44px; height: 44px; border-radius: 13px; flex-shrink: 0;
    display: flex; align-items: center; justify-content: center;
    background: linear-gradient(135deg, var(--s-purple), var(--s-teal));
    color: #fff; box-shadow: 0 6px 16px rgba(106, 63, 212, .3);
}
.sensai-import-zone-tile svg { width: 22px; height: 22px; }
.sensai-import-zone-num { font-size: var(--s-fs-sm); font-weight: 800; color: var(--s-line-strong); letter-spacing: .05em; }
.sensai-import-zone-title { font-size: var(--s-fs); font-weight: 800; color: var(--s-ink); margin-bottom: var(--s-4); line-height: 1.25; }

.sensai-dropzone { cursor: pointer; }
.sensai-import-zone .sensai-dropzone { margin-top: auto; padding: var(--s-4); min-height: 82px; display: flex; align-items: center; justify-content: center; }
.sensai-dropzone-idle { display: flex; flex-direction: column; align-items: center; gap: var(--s-1); color: var(--s-purple-ink); }
.sensai-dropzone-idle b { color: var(--s-purple-ink); }
.sensai-dropzone-filled { display: flex; flex-direction: column; align-items: center; gap: 7px; }
.sensai-dropzone-check {
    width: 26px; height: 26px; border-radius: 50%;
    background: var(--s-green-ink); color: #fff;
    display: flex; align-items: center; justify-content: center;
}
.sensai-dropzone-filename { font-size: var(--s-fs-sm); font-weight: 700; color: var(--s-green-deep); word-break: break-all; line-height: 1.3; }
.sensai-dropzone-remove { border: 0; background: none; color: var(--s-green-deep); font-weight: 700; font-size: var(--s-fs-sm); cursor: pointer; padding: 2px 4px; }
.sensai-dropzone-remove:hover { text-decoration: underline; }
.sensai-import-zone-template {
    margin-top: var(--s-4); display: inline-flex; align-items: center; gap: var(--s-1);
    font-size: var(--s-fs-sm); color: var(--s-purple-link); text-decoration: none; font-weight: 700;
}
.sensai-import-zone-template:hover { text-decoration: underline; }

/* ==========================================================================
   10. TABLES
   ========================================================================== */
.sensai-table { width: 100%; border-collapse: collapse; background: var(--s-card); }
.sensai-table th {
    text-align: left; padding: 10px 14px;
    font-size: var(--s-fs-xs); font-weight: 800; letter-spacing: .04em;
    text-transform: uppercase; color: var(--s-muted);
    border-bottom: 2px solid var(--s-line);
}
.sensai-table td { padding: 12px 14px; border-bottom: 1px solid var(--s-line); color: var(--s-text); }
.sensai-table tr:hover td { background: var(--s-bg); }

/* Sticky-column fallback for wide tables on narrow viewports. Deliberately
   scoped to small screens only - a full desktop viewport has room to just
   scroll, so this stays off above the breakpoint rather than being on
   unconditionally. Mark up to two leading columns by adding .sensai-
   sticky-1/-2 to both the <th> and every <td> at that position, inside a
   .sensai-table-scroll wrapper. Override --s-sticky-1-w/--s-sticky-2-w on
   .sensai-table if the 160px/110px defaults don't fit your content. */
.sensai-table-scroll { position: relative; overflow-x: auto; }

/* Right-edge fade indicating a wide table has more columns to scroll to -
   an opacity cue rather than a hard shadow line, since a shadow reads as
   a border/end-of-content, the opposite of what's meant here. Toggled by
   the same x-data the .sensai-table-scroll element carries (see the App
   Shell / Tables demo below); a scroll listener sets `scrollable` to
   whether there's any unscrolled width left, so the fade only shows when
   there's genuinely something to scroll to. */
.sensai-table-fade {
    position: absolute; top: 0; right: 0; bottom: 0; width: 48px;
    background: linear-gradient(to right, transparent, var(--s-card) 85%);
    pointer-events: none;
}
@media (max-width: 900px) {
    .sensai-table { --s-sticky-1-w: 160px; --s-sticky-2-w: 110px; }
    .sensai-table th.sensai-sticky-1, .sensai-table td.sensai-sticky-1 {
        position: sticky; left: 0; z-index: 2;
        width: var(--s-sticky-1-w); min-width: var(--s-sticky-1-w); max-width: var(--s-sticky-1-w);
        background: var(--s-card);
    }
    .sensai-table th.sensai-sticky-2, .sensai-table td.sensai-sticky-2 {
        position: sticky; left: var(--s-sticky-1-w); z-index: 2;
        width: var(--s-sticky-2-w); min-width: var(--s-sticky-2-w); max-width: var(--s-sticky-2-w);
        background: var(--s-card);
        box-shadow: 2px 0 4px rgba(34, 18, 77, .06);
    }
    .sensai-table th.sensai-sticky-1, .sensai-table th.sensai-sticky-2 { z-index: 3; }
    .sensai-table tr:hover td.sensai-sticky-1, .sensai-table tr:hover td.sensai-sticky-2 { background: var(--s-bg); }
}

.sensai-nowrap { white-space: nowrap; }
/* Visually hidden but still present/focusable/functional - e.g. the real
   <input type="file"> under a styled .sensai-dropzone label; the label
   itself is the visible, clickable/droppable surface. Not display:none
   (which would break click-to-browse, since a hidden input still needs
   to receive the click via its wrapping <label>) or opacity:0 with no
   sizing (still occupies layout space, capturing clicks intended for
   whatever's visually behind it) - the standard "sr-only" pattern. */
.sensai-visually-hidden {
    position: absolute; width: 1px; height: 1px;
    padding: 0; margin: -1px; overflow: hidden;
    clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.sensai-inlinerow { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3); }
.sensai-inlinerow form { display: inline-flex; align-items: center; }

.sensai-pager { display: flex; align-items: center; gap: var(--s-1); margin-top: var(--s-3); }
.sensai-pagebtn {
    min-width: 34px; height: 34px; padding: 0 8px;
    border: 1px solid var(--s-line-strong); border-radius: var(--s-r-sm);
    background: var(--s-card); color: var(--s-text); font-weight: 600;
}
.sensai-pagebtn:hover { background: var(--s-bg); }
.sensai-pagebtn.is-current { background: var(--s-purple); border-color: var(--s-purple); color: #fff; }
.sensai-pagebtn:disabled { opacity: .45; cursor: default; }

/* ==========================================================================
   11. MODAL (the SENSAI confirm dialogue)
   ========================================================================== */
/* Alpine's x-cloak attribute does nothing on its own - it only strips
   itself off once Alpine has hydrated the element; hiding the element
   *before* that point is the page's own responsibility via this rule.
   Without it, any x-cloak element whose own base class defaults to
   visible (.sensai-modal-backdrop is display:flex, not display:none)
   renders at full size for the split second between the browser
   painting the raw HTML and Alpine evaluating x-show - and with one
   confirm-modal instance per table row, several can flash open and
   overlap simultaneously on page load. Found live (Journey 5 round 2
   feedback: "not having proper line breaks, still bleeding the text
   into the page") - genuinely missing from the whole design system
   until now, not something that regressed. */
[x-cloak] { display: none !important; }

.sensai-modal-backdrop {
    position: fixed; inset: 0; z-index: var(--s-z-modal);
    display: flex; align-items: center; justify-content: center;
    padding: var(--s-5); background: rgba(20, 10, 50, .5);
}
.sensai-modal-backdrop[hidden] { display: none; }
.sensai-modal {
    /* white-space: normal and box-sizing: border-box are both defensive
       resets, not new behaviour of their own - .sensai-modal has never
       declared either, but a modal rendered inside a .sensai-nowrap
       table cell (e.g. Manage Users' actions column) inherits
       white-space: nowrap from that ancestor regardless of
       position:fixed (inheritance follows the DOM tree, not the
       containing-block escape fixed positioning provides), forcing its
       own text onto one unbreakable line that then paints straight past
       the card's edges since overflow is never set to hidden. Found live
       via the user's own Claude-in-Chrome instrumentation (Journey 5
       round 3 feedback) after two rounds of code-reading alone missed
       it entirely - see Claude's own memory for the full measurement
       trail. box-sizing: border-box is the same defensive idea for the
       card's own width (content-box was letting padding push it to
       492px against the declared 440px max-width). */
    position: relative; width: 100%; max-width: 440px; padding: 26px;
    box-sizing: border-box; white-space: normal;
    border-radius: var(--s-r-xl); background: var(--s-card);
    box-shadow: 0 24px 60px rgba(0, 0, 0, .28);
}
/* Corner close (X) - every .sensai-modal now closes this way rather than
   a bottom "Close" text button (typical UI on any other platform), same
   positioning convention as .sensai-form-modal-close below but red,
   matching this dialogue family's own "dismiss" semantics. Confirm
   dialogues (Delete etc.) keep their own Cancel/Confirm pair at the
   bottom too - the X is a faster, additional way out, not a replacement
   for an explicit destructive-action choice. */
.sensai-modal-close {
    position: absolute; top: 18px; right: 18px;
    width: 30px; height: 30px; border-radius: 50%; border: 0;
    display: flex; align-items: center; justify-content: center;
    background: none; color: var(--s-red); cursor: pointer;
}
.sensai-modal-close svg { width: 16px; height: 16px; }
.sensai-modal-close:hover,
.sensai-modal-close:focus-visible { background: #fdecea; color: var(--s-red-deep); }
.sensai-modal-ico {
    width: 46px; height: 46px; border-radius: 13px; margin-bottom: 14px;
    display: flex; align-items: center; justify-content: center;
    background: var(--s-purple-050); color: var(--s-purple);
}
.sensai-modal.is-danger .sensai-modal-ico { background: #fdecea; color: var(--s-red); }
.sensai-modal-ico svg { width: 24px; height: 24px; }
.sensai-modal-title { margin: 0 0 var(--s-2); padding-right: 34px; font-size: 1.2em; color: var(--s-ink); }
.sensai-modal-msg { margin: 0 0 var(--s-4); color: var(--s-text); line-height: 1.55; overflow-wrap: break-word; }
.sensai-modal-actions { display: flex; justify-content: flex-end; gap: var(--s-2); }
/* Modal input-confirmation extras */
.sensai-modal-inputwrap { margin: 0 0 var(--s-4); }
.sensai-modal-err { margin: var(--s-2) 0 0; color: var(--s-red-link); font-size: .88em; font-weight: 600; }

/* Form modal - a genuinely separate component from .sensai-modal (the
   confirm/cancel popup above), not a modifier on it: this one holds a
   real multi-field form (e.g. "Add a Pupil to this Intervention"),
   needs a close (X) control since there's no single "Cancel" button to
   anchor on, and has its own success-message slot that appears in place
   and auto-dismisses rather than a page-level flash. Shares
   .sensai-modal-backdrop for the dimmed overlay - only the box itself
   differs. */
.sensai-form-modal {
    position: relative; width: 100%; max-width: 560px; padding: 28px;
    border-radius: var(--s-r-xl); background: var(--s-card);
    box-shadow: 0 24px 60px rgba(0, 0, 0, .28);
}
.sensai-form-modal-close {
    position: absolute; top: 18px; right: 18px;
    width: 30px; height: 30px; border-radius: 50%; border: 0;
    display: flex; align-items: center; justify-content: center;
    background: none; color: var(--s-muted); cursor: pointer;
}
.sensai-form-modal-close svg { width: 16px; height: 16px; }
.sensai-form-modal-close:hover,
.sensai-form-modal-close:focus-visible { background: var(--s-bg); color: var(--s-text); }
.sensai-form-modal-title { margin: 0 0 var(--s-4); padding-right: 34px; font-size: 1.2em; font-weight: 800; color: var(--s-ink); }
.sensai-form-modal-body { display: flex; flex-direction: column; gap: var(--s-4); }
.sensai-form-modal-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-4); }
.sensai-form-modal-actions { display: flex; justify-content: flex-end; margin-top: var(--s-5); }
.sensai-form-modal-success { margin-top: var(--s-4); }
@media (max-width: 560px) {
    .sensai-form-modal-row { grid-template-columns: 1fr; }
}
.sensai-modal .sensai-input { max-width: none; }


/* ==========================================================================
   12. NAVIGATION
   ========================================================================== */
.sensai-nav-item {
    display: flex; align-items: center; gap: var(--s-3);
    box-sizing: border-box;
    width: 100%; padding: 11px 12px; margin: 2px 0; border-radius: var(--s-r);
    border: 0; background: none; text-align: left;
    color: var(--s-text); font-weight: 600; font-size: .95em;
    text-decoration: none; cursor: pointer;
    transition: background var(--s-ease), color var(--s-ease);
}
.sensai-nav-item svg { width: 19px; height: 19px; flex-shrink: 0; color: var(--s-purple); }
.sensai-nav-item:hover { background: color-mix(in srgb, var(--s-purple) 8%, transparent); color: var(--s-purple-ink); }
.sensai-nav-item:hover svg { color: var(--s-purple); }
.sensai-nav-item.is-active { background: var(--s-purple-050); color: var(--s-purple-ink); }
.sensai-nav-item.is-active svg { color: var(--s-purple); }

/* Danger variant: destructive nav-style actions (Log Out and friends).
   Icon is rose by default (not just on hover) so it doesn't blend in
   with every other, purple-by-default nav icon; hover adds a
   coral-tinted wash with rose text too - same pairing the WordPress
   app already used. */
.sensai-nav-item--danger svg { color: var(--s-rose); }
.sensai-nav-item--danger:hover {
    background: color-mix(in srgb, var(--s-coral) 10%, transparent);
    color: var(--s-rose);
}
.sensai-nav-item--danger:hover svg { color: var(--s-rose); }

.sensai-nav-divider { border: 0; border-top: 1px solid var(--s-line); margin: var(--s-3) 0; }

.sensai-breadcrumbs { font-size: var(--s-fs-sm); color: var(--s-muted); margin-bottom: var(--s-4); }
.sensai-breadcrumbs a { color: var(--s-purple-link); font-weight: 600; }
.sensai-breadcrumbs .sep { margin: 0 6px; color: var(--s-line-strong); }

/* Tabs */
.sensai-tabbar { display: flex; gap: var(--s-1); border-bottom: 2px solid #e4ddf6; margin-bottom: var(--s-4); flex-wrap: wrap; }
.sensai-tab {
    padding: 10px 18px; font-weight: 700; color: #8a8a97;
    border-bottom: 3px solid transparent; margin-bottom: -2px; cursor: pointer;
}
.sensai-tab:hover { color: var(--s-purple); }
.sensai-tab.is-active { color: var(--s-purple-ink); border-bottom-color: var(--s-purple); }

/* Stepper (off-boarding journey) */
.sensai-stepper { display: flex; gap: var(--s-3); position: relative; margin: 0 0 26px; }
.sensai-step { flex: 1; position: relative; padding-top: 26px; font-size: 12.5px; color: var(--s-muted); }
.sensai-step:nth-child(2) { text-align: center; }
.sensai-step:nth-child(3) { text-align: right; }
.sensai-step::before {
    content: ''; position: absolute; top: 9px; left: 0; right: 0;
    height: 3px; background: #d9d5e8;
}
.sensai-step.is-done::before,
.sensai-step.is-current::before { background: var(--s-teal-soft); }
.sensai-step-dot {
    position: absolute; top: 2px; width: 17px; height: 17px;
    border-radius: 50%; background: #d9d5e8; border: 3px solid #fff;
    box-sizing: border-box;
}
.sensai-step:nth-child(1) .sensai-step-dot { left: 0; }
.sensai-step:nth-child(2) .sensai-step-dot { left: 50%; transform: translateX(-50%); }
.sensai-step:nth-child(3) .sensai-step-dot { right: 0; }
.sensai-step.is-done .sensai-step-dot,
.sensai-step.is-current .sensai-step-dot { background: #14a89d; }
.sensai-step.is-current .sensai-step-label,
.sensai-step.is-done .sensai-step-label { color: #1c1b3a; font-weight: 700; }

/* Accordion */
/* No overflow:hidden here - it used to clip a .sensai-dd's open panel
   the moment one was nested inside an accordion body (found live,
   Journey 4/5: the effectiveness rating picker was unclickable because
   its dropdown panel rendered but was cut off by this exact rule).
   Corner-rounding on open/closed states is handled explicitly below
   instead, so content that legitimately needs to overflow (a dropdown
   panel, a tooltip) still can. */
.sensai-accordion { border: 1px solid var(--s-line); border-radius: var(--s-r-lg); }
.sensai-accordion summary { border-radius: var(--s-r-lg); }
.sensai-accordion[open] summary { border-radius: var(--s-r-lg) var(--s-r-lg) 0 0; }
.sensai-accordion-body { border-radius: 0 0 var(--s-r-lg) var(--s-r-lg); }
.sensai-accordion summary {
    padding: 14px var(--s-4); font-weight: 700; color: var(--s-ink);
    cursor: pointer; background: var(--s-bg); list-style: none;
}
.sensai-accordion summary::-webkit-details-marker { display: none; }
.sensai-accordion summary::after { content: '▾'; float: right; color: var(--s-muted); }
.sensai-accordion[open] summary::after { content: '▴'; }
.sensai-accordion-body { padding: var(--s-4); }

/* ==========================================================================
   13. TIMELINE / EVENT HISTORY (compliance case review)
   ========================================================================== */
.sensai-event {
    position: relative; padding: 0 0 var(--s-4) 26px;
    border-left: 2px solid var(--s-line); margin-left: 8px;
}
.sensai-event:last-child { border-left-color: transparent; }
.sensai-event::before {
    content: ''; position: absolute; left: -8px; top: 3px;
    width: 12px; height: 12px; border-radius: 50%;
    background: var(--s-purple); border: 3px solid #fff;
    box-shadow: 0 0 0 1px var(--s-line);
}
.sensai-event--good::before { background: var(--s-teal-soft); }
.sensai-event--bad::before  { background: var(--s-red); }
.sensai-event--warn::before { background: var(--s-amber); }
.sensai-event-type { font-weight: 800; color: var(--s-ink); }
.sensai-event-when { font-size: var(--s-fs-xs); color: var(--s-muted); }
.sensai-event-body { margin-top: 4px; font-size: .95em; }

/* ==========================================================================
   14. DARK (AUTH) SURFACE — login, MFA, recovery
   ========================================================================== */
.sensai-dark {
    background: var(--s-dark-bg);
    color: var(--s-dark-text);
    border-radius: var(--s-r-xl);
    padding: var(--s-7);
}
.sensai-dark :where(h1, h2, h3, h4, label) { color: var(--s-dark-text); }
.sensai-dark .sensai-field > label { color: var(--s-dark-text); }
.sensai-dark .sensai-input::placeholder { color: #fff; opacity: .85; }
.sensai-dark--onpage { background: transparent; padding: 0; border-radius: 0; }

/* Recovery-codes grid (shown once at MFA enrolment) */
.sensai-codegrid {
    display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: var(--s-2); margin: var(--s-4) 0; max-width: 620px;
}
.sensai-codegrid code {
    background: #fff; color: #26253d; border: 1px solid #e2e0ee;
    border-radius: var(--s-r); padding: 10px 12px; text-align: center;
    font-weight: 700; letter-spacing: 2px; font-size: var(--s-fs);
}
.sensai-codegrid-actions {
    display: flex; gap: 18px; flex-wrap: wrap; align-items: center;
    justify-content: center; margin: 6px 0 var(--s-4);
}

/* QR block */
.sensai-qr {
    display: inline-block; background: #fff; padding: var(--s-3);
    border-radius: var(--s-r-lg);
}
.sensai-qr img, .sensai-qr svg { display: block; width: 164px; height: 164px; }

/* ==========================================================================
   15. APP SHELL — identity avatar, footer bar. Added for the Laravel app
   shell (side nav + header + footer): the side nav, breadcrumbs, tabs and
   search box needed no new work (section 6, section 11 already cover
   them) - only these two were genuinely missing.
   ========================================================================== */

/* Shell layout scaffolding (positioning, widths - every visual component
   inside comes from elsewhere in this file) - moved here from an inline
   <style> block in layouts/shell.blade.php, where it had lived since this
   section was first added. That block predated this file's own :root
   token hoist (section 1) and independently hit the identical bug: body's
   `background: var(--s-bg)` was invalid at computed-value time, since
   body is .sensai-app's PARENT, not a descendant - masked only by luck
   (opaque child backgrounds cover it). Found live via the user's own
   Claude-in-Chrome instrumentation, the same investigation that found the
   loading overlay's own version of this bug. Keeping layout CSS outside
   this file's own "single source of truth" was the actual mechanism that
   let the same defect happen twice independently - moved here rather
   than patched in place.

   The nav and header are pinned to the viewport (each an independently-
   scrollable flex column, `body` itself never scrolls) so they stay
   visible while a long table in the content area scrolls - a page's own
   content is the only part that moves. --s-topbar-h is shared by the
   nav's brand block and the header so the divider under the logo lands
   exactly on the header's bottom border, not by coincidence of matching
   padding values - both sides own their full height (box-sizing:
   border-box) from the same Y origin, so this holds regardless of what
   either one's content ends up being. */
html, body { height: 100%; }
body { margin: 0; background: var(--s-bg); overflow: hidden; --s-topbar-h: 88px; }
.sensai-shell { display: flex; height: 100vh; }
.sensai-shell-nav {
    width: var(--s-nav-w); flex-shrink: 0; box-sizing: border-box;
    height: 100vh; overflow-y: auto;
    padding: 0 14px 20px;
    background: var(--s-card);
    border-right: 1px solid var(--s-line);
    display: flex; flex-direction: column;
}
.sensai-shell-brand {
    height: var(--s-topbar-h); box-sizing: border-box;
    display: flex; align-items: center; gap: var(--s-2);
    padding: 0 12px;
}
.sensai-shell-brand img { height: 40px; max-width: 100%; width: auto; }
.sensai-shell-main { flex: 1; min-width: 0; height: 100vh; display: flex; flex-direction: column; }
.sensai-shell-header {
    /* min-height, not height: on a narrow window the identity block
       and search bar wrap onto a second line (flex-wrap below) and
       the header needs to grow to fit that - it should just never be
       shorter than the nav's brand block. */
    min-height: var(--s-topbar-h); box-sizing: border-box; flex-shrink: 0;
    display: flex; align-items: center; gap: var(--s-4); flex-wrap: wrap;
    padding: var(--s-5) var(--s-6);
    margin-bottom: var(--s-4);
    border-bottom: 1px solid var(--s-line);
    background: var(--s-card);
}
.sensai-shell-identity { min-width: 0; }
.sensai-shell-identity-name { font-weight: 800; color: var(--s-ink); margin-bottom: 4px; }
.sensai-shell-search { margin-left: auto; width: 100%; }
.sensai-shell-scroll { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; }
/* Full-width by default - a fixed max-width here just recreates the same
   "cuts off data unnecessarily" problem Student Records hit on every
   other content-heavy page as the app grows. A page that genuinely wants
   a narrow reading measure (long prose, not built yet anywhere) can
   constrain its own content instead. */
.sensai-shell-content { flex: 1; padding: var(--s-6); width: 100%; box-sizing: border-box; transition: opacity var(--s-ease); }
/* Post-navigation focus target only (layouts/shell.blade.php's
   restoreLoadingFocus() sets tabindex="-1" and focuses this element when
   the element that triggered a navigation no longer exists in the DOM,
   so a screen-reader user still gets the page-change announcement). It
   is never reachable by Tab - found live (Journey 5 round 12 feedback)
   that Chrome still paints its default :focus-visible ring
   (outline: auto 1px rgb(0,95,204), since sensai-ui.css defines no
   outline of its own) around the entire content region after any
   keyboard-initiated navigation, and the ring persists until the next
   click. A ring communicates "this is where your next Tab press will
   go" - meaningless on an element that isn't in the tab order, so it
   reads as a rendering artefact, not real focus indication. Scoped to
   this one element only, not a blanket `*:focus { outline: none }` -
   every genuinely interactive control must keep its own focus ring. */
.sensai-shell-content:focus,
.sensai-shell-content:focus-visible {
    outline: none;
}
/* margin-top:0 for whichever notice (the flash partial, or the stale-
   results warning below) happens to be first - was an inline style on
   the warning specifically until found live (Journey 5 round 9
   feedback) as a real instance of the exact design-system bypass this
   file's own header warns against. */
.sensai-shell-content > .sensai-notice:first-child { margin-top: 0; }
/* Bound to $store.sensaiError.stale (layouts/shell.blade.php) - a
   generic, page-agnostic response to a real duty-of-care gap: a failed
   request previously left whatever was already on screen looking
   exactly as current as a genuine result, with nothing to say
   otherwise. This dims the whole content column, rather than a per-
   component "showing results for X, may be out of date" marker on
   every individual table/list in the app - a real but partial answer,
   deliberately scoped down given the size of building that properly
   everywhere search/filtered content exists; not intended as the final
   word on this. `stale` is deliberately a separate flag from the
   toast's own `visible` (see the store's own comment) - found live that
   using `visible` here left the page dimmed indefinitely after a single
   transient failure, even once a fully successful subsequent request
   had already replaced the content with fresh data - `stale` clears
   itself the moment any request succeeds, `visible` only clears on
   Reload/Dismiss. */
/* Deliberately opacity only, no pointer-events: none - the point is to
   signal "what you're looking at may be stale", not to block the user
   from immediately retrying (e.g. re-typing a search), which a fully
   inert region would prevent. */
.sensai-shell-content--stale { opacity: .55; }
/* Content-local "this region is updating" treatment, added Journey 5
   round 12: the app-wide loading overlay stopped firing for in-page
   Livewire component updates (pagination, search, filters) by design -
   it's reserved for genuine page navigation only, see
   .sensai-loading-overlay's own comment in layouts/shell.blade.php.
   Reuses --stale's exact opacity value and same "dim, don't block"
   reasoning (no pointer-events: none, so re-typing a search mid-request
   still works) but is driven by wire:loading on the individual result
   region itself, not a page-wide failure flag - so it reflects "a
   request for THIS table is in flight" rather than "something,
   somewhere, once failed". Paired with wire:loading.attr="aria-busy" on
   the same element in the Blade template. */
.sensai-results--busy { opacity: .55; }
.sensai-shell-footer-wrap { padding: 0 var(--s-6); }

/* Responsive shell - the app had zero media queries touching its own
   layout until this point: a hardcoded 264px sidebar, no collapse, no
   breakpoint, unusable well below 900px (measured live: at 417px the
   sidebar alone was 63% of the viewport). Off-canvas drawer below
   var(--s-bp-md) (900px, mirrored as a literal below - @media
   conditions can't read a custom property, since the browser evaluates
   them before the cascade resolves var()). .sensai-shell-navtoggle is
   the hamburger trigger, hidden above the breakpoint and out of the tab
   order there (display: none, not just visually hidden) since the
   drawer concept doesn't exist above it at all. */
.sensai-shell-navtoggle { display: none; }

@media (max-width: 900px) { /* mirrors --s-bp-md */
    .sensai-shell-navtoggle {
        display: inline-flex; align-items: center; justify-content: center;
        width: 44px; height: 44px; border-radius: var(--s-r-sm);
        flex-shrink: 0; color: var(--s-ink); border: 0; background: none; cursor: pointer;
    }
    .sensai-shell-navtoggle:hover,
    .sensai-shell-navtoggle:focus-visible { background: var(--s-bg); }
    .sensai-shell-nav {
        position: fixed; inset: 0 auto 0 0;
        width: min(var(--s-nav-w), 86vw);
        z-index: var(--s-z-nav);
        transform: translateX(-100%);
        transition: transform .22s cubic-bezier(.2, .7, .3, 1);
        box-shadow: var(--s-shadow);
    }
    .sensai-shell-nav[data-open="true"] { transform: translateX(0); }
    /* Suppresses the entrance animation specifically when the viewport
       has just crossed into this breakpoint (a resize/rotate, not the
       user opening the drawer) - see the suppressTransition comment in
       layouts/shell.blade.php for why crossing the breakpoint alone
       would otherwise visibly slide the drawer away. Real open/close
       transitions triggered afterwards are unaffected, since this
       attribute is only true for two animation frames. */
    .sensai-shell-nav[data-suppress-transition="true"] { transition: none; }
    .sensai-shell-scrim {
        position: fixed; inset: 0; z-index: var(--s-z-scrim);
        background: rgba(19, 22, 33, .44);
    }
    .sensai-shell-header { padding: var(--s-4); gap: var(--s-3); }
    .sensai-shell-search { margin-left: 0; order: 3; flex-basis: 100%; }
}

@media (max-width: 560px) { /* mirrors --s-bp-sm */
    .sensai-shell-header { padding: var(--s-3); }
    .sensai-shell-content { padding-inline: var(--s-3); }
    .sensai-shell-identity-name { font-size: var(--s-fs); }
}

@media (prefers-reduced-motion: reduce) {
    .sensai-shell-nav { transition: none; }
}

/* Identity avatar - initials in a circle, brand-gradient fill (purple to
   teal, the two colours the header sits between). Default size suits a
   page header; --sm matches inline use next to a name in text (e.g. a
   comment or activity row). */
.sensai-avatar {
    display: flex; align-items: center; justify-content: center;
    flex-shrink: 0; border-radius: 50%;
    background: linear-gradient(135deg, var(--s-purple), var(--s-teal));
    color: #fff;
    font-weight: 800; letter-spacing: .02em;
    width: 48px; height: 48px; font-size: var(--s-fs-md);
}
.sensai-avatar--sm { width: 32px; height: 32px; font-size: var(--s-fs-sm); }
.sensai-avatar--lg { width: 64px; height: 64px; font-size: var(--s-fs-lg); }

/* App footer bar - sits under the content area, never competes with it. */
.sensai-app-footer {
    display: flex; align-items: center; justify-content: space-between;
    flex-wrap: wrap; gap: var(--s-3);
    margin-top: var(--s-6); padding: var(--s-4) 0;
    border-top: 1px solid var(--s-line);
    color: var(--s-muted); font-size: var(--s-fs-xs);
}
.sensai-app-footer a { color: var(--s-muted); font-weight: 600; text-decoration: none; }
.sensai-app-footer a:hover,
.sensai-app-footer a:focus-visible { color: var(--s-purple-link); text-decoration: underline; }

/* ==========================================================================
   15. CLASS INSIGHTS - bespoke page components, ported faithfully from
   plugin/sensai-core.css's sensai-ci-, sensai-insight-, sensai-bar- and
   sensai-need- class families (found live, Journey 4/5: the first
   Laravel attempt used only generic cards and was rejected for looking
   nothing like the original). Values mapped onto design tokens where a
   token is an exact or near-exact match (confirmed: --s-shadow was
   already derived from this exact page's picker-card shadow); kept as
   literal hex only where WP used a one-off value with no equivalent
   token (the hero bubble gradients, the chip text colour, the
   areas-of-need bar's teal->violet gradient endpoint).

   REAL BUG THIS COMMENT ITSELF ONCE CAUSED (2026-07-31): the class
   families above were originally written wildcard-then-slash, each
   family name ending in an asterisk immediately followed by a forward
   slash before the next family name - that exact two-character
   sequence (star, slash, no space between) closes a CSS comment
   wherever it appears, comment or not. That silently ended THIS block
   comment ten lines early, dumping the rest of this English explanation
   into the stylesheet as bare (invalid) CSS text - the parser then lost
   its way trying to recover, and every real rule from here to
   end-of-file was silently dropped. No console error, no failed network
   request, a healthy 200 on the file - completely invisible from the
   server side, only found by checking actual parsed rule counts in a
   live browser. Never place an asterisk directly before a forward slash
   anywhere inside a CSS comment, even mid-word; use commas between
   class-family names instead, as above, or at minimum a space before
   the slash if a wildcard-style example is ever truly needed. Worth a
   standing reminder: this class of bug is exactly what a CSS linter
   (e.g. Stylelint) in the build would catch immediately - worth adding
   if this file keeps growing.
   ========================================================================== */

/* Hero (pre-search) state */
.sensai-ci-hero { position: relative; padding: 8px 0 18px; }
.sensai-ci-title { display: flex; align-items: center; gap: 12px; margin: 4px 0 8px; font-size: 2.5em; font-weight: 800; color: var(--s-ink); letter-spacing: -.5px; }
.sensai-ci-titleico { width: 34px; height: 34px; color: var(--s-purple); flex-shrink: 0; }
.sensai-ci-titleico svg { width: 100%; height: 100%; }
.sensai-ci-tagline { max-width: 520px; color: var(--s-muted); font-size: 1.02em; line-height: 1.55; margin: 0; }

/* min-height is load-bearing, not decorative: .sensai-ci-bubbles is
   inset:0 against .sensai-ci-hero, which otherwise only stands as tall
   as its own title+tagline text - meaning the bubbles' top/bottom %
   offsets were being computed against a height that could shrink
   whenever the tagline wrapped differently (a real width, not a
   viewport-only concern), pushing the top-anchored and bottom-anchored
   bubbles toward each other until they overlapped, and letting all
   three spill down into .sensai-ci-card below (found live: "eating
   into the search bar area"). Fixing the container's height removes
   that whole class of instability instead of chasing one symptom. */
.sensai-ci-bubbles { position: absolute; inset: 0; left: calc(40% + 40px); min-height: 280px; pointer-events: none; }
.sensai-ci-bubble {
    position: absolute; width: 142px; height: 142px; border-radius: 38px;
    display: flex; align-items: center; justify-content: center; color: #fff;
    box-shadow: 0 24px 52px rgba(34, 18, 77, .22);
}
.sensai-ci-bubble svg { width: 68px; height: 68px; }
.sensai-ci-b1 { background: linear-gradient(135deg, #6a3fd4, #8b5cf6); left: 28%; top: 14%; animation: sensai-ci-float 5s ease-in-out infinite; }
.sensai-ci-b2 { background: linear-gradient(135deg, #14b0a7, #22c7be); right: 22%; top: 44%; animation: sensai-ci-float 6s ease-in-out infinite .6s; }
/* Coral (activity icon): nudged further than the other two, per explicit
   pixel feedback across two rounds - 20px right on top of the whole
   group's shared 40px (.sensai-ci-bubbles' own left offset above), and
   200px down total (an initial 80px, then a further 120px). */
.sensai-ci-b3 { background: linear-gradient(135deg, #f26d85, #fb7f96); left: calc(30% + 20px); bottom: calc(6% - 200px); animation: sensai-ci-float 5.5s ease-in-out infinite 1.1s; }
@keyframes sensai-ci-float { 0%, 100% { transform: translateY(0) rotate(-3deg); } 50% { transform: translateY(-12px) rotate(3deg); } }
@media (prefers-reduced-motion: reduce) { .sensai-ci-bubble { animation: none; } }

/* Shrink before the old binary hide-at-900px cutoff, rather than the
   bubbles staying full-size right up until they vanish - narrower here
   means less room either side of .sensai-ci-card for a 142px bubble to
   sit without crowding it. */
@media (max-width: 1200px) {
    .sensai-ci-bubbles { min-height: 220px; }
    .sensai-ci-bubble { width: 104px; height: 104px; border-radius: 28px; }
    .sensai-ci-bubble svg { width: 50px; height: 50px; }
}
@media (max-width: 900px) { .sensai-ci-bubbles { display: none; } }

.sensai-ci-card { max-width: 700px; padding: 20px; border: 1px solid var(--s-line); border-radius: var(--s-r-xl); background: var(--s-card); box-shadow: var(--s-shadow); }
.sensai-ci-cardlabel { display: flex; align-items: center; gap: 7px; margin-bottom: 12px; font-size: .72em; font-weight: 800; letter-spacing: .05em; text-transform: uppercase; color: var(--s-muted); }
.sensai-ci-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--s-purple); flex-shrink: 0; }
.sensai-ci-help { margin: 10px 0 0; color: var(--s-muted); font-size: .86em; }

/* Divider between the class-name H1 and the "Class Insights" H2 row -
   reuses the same purple/teal/purple triad as .sensai-btn--gradient so
   it reads as the app's one established "gradient effect", not a new
   one-off colour. */
.sensai-ci-divider { height: 2px; margin: 4px 0 20px; border-radius: 999px; background: linear-gradient(90deg, var(--s-purple), var(--s-teal), var(--s-purple)); }

/* Focused-mode header - same three-icon motif shrunk to a 20px badge row,
   no shadow/animation/absolute positioning (a plain inline row, not the
   hero's floating layer) - kept as a separate class rather than a
   modifier on .sensai-ci-bubble so the hero's positioning rules never
   apply here by accident. */
.sensai-ci-focushead { display: flex; align-items: center; justify-content: space-between; gap: 20px; margin: 4px 0 20px; flex-wrap: wrap; }
.sensai-ci-focusleft { display: flex; flex-direction: column; gap: 7px; }
.sensai-ci-focushead .sensai-ci-title { margin: 0; font-size: 1.7em; white-space: nowrap; }
.sensai-ci-minicons { display: flex; flex-direction: row; gap: 6px; }
.sensai-ci-mini { width: 20px; height: 20px; border-radius: 6px; display: flex; align-items: center; justify-content: center; color: #fff; box-shadow: none; }
.sensai-ci-mini svg { width: 11px; height: 11px; }
.sensai-ci-mini.sensai-ci-b1 { background: linear-gradient(135deg, #6a3fd4, #8b5cf6); }
.sensai-ci-mini.sensai-ci-b2 { background: linear-gradient(135deg, #14b0a7, #22c7be); }
.sensai-ci-mini.sensai-ci-b3 { background: linear-gradient(135deg, #f26d85, #fb7f96); }
.sensai-ci-focushead .sensai-ci-picker { flex: 0 1 400px; min-width: 260px; margin-left: auto; }

/* Picker / search - the input itself is the same pill/white/focus-ring
   look as every other search box in the app (see .sensai-search), only
   the icon inset and the result-row content are bespoke to this page. */
.sensai-ci-picker { position: relative; }
.sensai-ci-ico { position: absolute; left: 16px; top: 50%; transform: translateY(-50%); color: var(--s-muted); display: flex; pointer-events: none; }
.sensai-ci-ico svg { width: 16px; height: 16px; }
.sensai-ci-picker input {
    width: 100%; box-sizing: border-box; padding: 11px 16px 11px 44px;
    border-radius: var(--s-round); background: var(--s-card); border: 1px solid var(--s-line-strong);
    font-size: var(--s-fs-sm); color: var(--s-text); font-family: inherit;
}
.sensai-ci-picker input:focus { outline: none; border-color: var(--s-purple); box-shadow: var(--s-focus-ring); }
.sensai-ci-results {
    position: absolute; top: 100%; left: 0; right: 0; z-index: var(--s-z-dropdown); margin-top: 8px;
    max-height: min(56vh, 480px); overflow-y: auto; overscroll-behavior: contain;
    border: 1px solid var(--s-line); border-radius: var(--s-r-lg); background: var(--s-card);
    box-shadow: 0 18px 44px rgba(34, 18, 77, .16); padding: 6px;
}
.sensai-ci-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; width: 100%; box-sizing: border-box; padding: 11px 13px; border: 0; border-radius: var(--s-r-sm); background: none; color: var(--s-text); text-decoration: none; text-align: left; cursor: pointer; }
.sensai-ci-row:hover, .sensai-ci-row.is-current { background: var(--s-purple-tint); }
.sensai-ci-rowmain { display: flex; flex-direction: column; min-width: 0; }
.sensai-ci-code { font-size: 1.02em; font-weight: 700; color: var(--s-ink); }
.sensai-ci-code b { color: var(--s-purple); font-weight: 800; }
.sensai-ci-rowmeta { display: block; margin-top: 2px; font-size: .84em; color: var(--s-muted); }
.sensai-ci-rowtags { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; flex-shrink: 0; }
.sensai-ci-subject { display: inline-flex; align-items: center; gap: 5px; padding: 3px 10px; border-radius: var(--s-round); background: var(--s-purple-tint); color: var(--s-purple-link); font-size: .78em; font-weight: 700; }
.sensai-ci-subject::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: var(--s-purple); }
.sensai-ci-send { padding: 3px 10px; border-radius: var(--s-round); background: var(--s-blue-050); color: var(--s-blue-ink); font-size: .78em; font-weight: 800; }
.sensai-ci-noresult { padding: 16px; color: var(--s-muted); text-align: center; margin: 0; }

/* Headline stat tiles - deliberately plainer than .sensai-stat (no
   colour accent/icon), matching WP's own restraint here. */
.sensai-insight-stats { display: grid; gap: 14px; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); margin: 18px 0; }
.sensai-insight-stat { padding: 16px; border: 1px solid var(--s-line-strong); border-radius: var(--s-r-lg); background: var(--s-card); text-align: center; }
.sensai-insight-stat .sensai-stat-number { display: block; font-size: 2em; font-weight: 800; color: var(--s-purple-ink); }
.sensai-insight-stat .sensai-stat-label { color: var(--s-muted); font-size: .9em; font-weight: 500; }

/* Areas of Need bars - the teal->violet gradient fill is the same
   signature used by the Interventions Impact tab's own bars
   (App\Services\Interventions), a deliberate shared "SENSAI progress"
   motif, not a one-off. */
.sensai-bar-row { display: flex; align-items: center; gap: 10px; margin: 6px 0; }
.sensai-bar-label { flex: 0 0 220px; font-size: .92em; color: var(--s-text); }
.sensai-bar-track { flex: 1; height: 18px; border-radius: var(--s-round); background: var(--s-purple-050); overflow: hidden; }
.sensai-bar-fill { display: block; height: 100%; border-radius: var(--s-round); background: linear-gradient(90deg, var(--s-teal), #8256f1); }
.sensai-bar-count { flex: 0 0 34px; font-weight: 700; color: var(--s-purple-ink); text-align: right; }

/* Primary-need pupil groups - native <details>/<summary>, no JS needed
   per group (an "expand all" toggle can still drive every [open] at
   once). Pupil sub-cards sit on --s-bg (not --s-card) to read as nested
   inside the group, not a sibling of it. */
.sensai-need-group { margin: 12px 0; border: 1px solid var(--s-line-strong); border-radius: var(--s-r-lg); background: var(--s-card); }
.sensai-need-group > summary { padding: 12px 16px; cursor: pointer; font-weight: 600; list-style: none; color: var(--s-text); }
.sensai-need-group > summary::-webkit-details-marker { display: none; }
/* "Show students" / "Hide students" swap purely via the native details[open]
   state - no JS/Alpine needed, so this can't drift out of sync with the
   accordion's own real open/closed state the way a separately-tracked
   flag could. */
.sensai-need-toggle-hide { display: none; }
.sensai-need-group[open] > summary .sensai-need-toggle-show { display: none; }
.sensai-need-group[open] > summary .sensai-need-toggle-hide { display: inline; }
.sensai-need-student { margin: 0 16px 14px; padding: 12px; border: 1px solid var(--s-line); border-radius: var(--s-r-sm); background: var(--s-bg); }
.sensai-need-student h6 { margin: 0 0 6px; font-size: 1em; }

/* Chip rows - a label above a wrapped run of .sensai-chip (already the
   correct purple by default; see .sensai-chip above) or plain italic
   muted text when nothing's recorded, never a chip with no content. */
.sensai-chip-row { margin: 4px 0; }
.sensai-chip-row .sensai-chip-title { font-size: .85em; font-weight: 700; color: var(--s-muted); text-transform: uppercase; letter-spacing: .03em; margin-right: 4px; }
