/* The White Orchid - Mobile-First Styles */

/* ═══ 0. BRAND FONTS ═══ */

@font-face {
    font-family: 'Fira Sans';
    src: url('/fonts/FiraSans-Light.woff2') format('woff2'), url('/fonts/FiraSans-Light.ttf') format('truetype');
    font-weight: 300;
    font-style: normal;
    font-display: swap;
}
@font-face {
    font-family: 'Fira Sans';
    src: url('/fonts/FiraSans-LightItalic.woff2') format('woff2'), url('/fonts/FiraSans-LightItalic.ttf') format('truetype');
    font-weight: 300;
    font-style: italic;
    font-display: swap;
}
@font-face {
    font-family: 'Fira Sans';
    src: url('/fonts/FiraSans-Regular.woff2') format('woff2'), url('/fonts/FiraSans-Regular.ttf') format('truetype');
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}
@font-face {
    font-family: 'Fira Sans';
    src: url('/fonts/FiraSans-Italic.woff2') format('woff2'), url('/fonts/FiraSans-Italic.ttf') format('truetype');
    font-weight: 400;
    font-style: italic;
    font-display: swap;
}
@font-face {
    font-family: 'Fira Sans';
    src: url('/fonts/FiraSans-Medium.woff2') format('woff2'), url('/fonts/FiraSans-Medium.ttf') format('truetype');
    font-weight: 500;
    font-style: normal;
    font-display: swap;
}
@font-face {
    font-family: 'Fira Sans';
    src: url('/fonts/FiraSans-MediumItalic.woff2') format('woff2'), url('/fonts/FiraSans-MediumItalic.ttf') format('truetype');
    font-weight: 500;
    font-style: italic;
    font-display: swap;
}
@font-face {
    font-family: 'Fira Sans';
    src: url('/fonts/FiraSans-SemiBold.woff2') format('woff2'), url('/fonts/FiraSans-SemiBold.ttf') format('truetype');
    font-weight: 600;
    font-style: normal;
    font-display: swap;
}
@font-face {
    font-family: 'Fira Sans';
    src: url('/fonts/FiraSans-SemiBoldItalic.woff2') format('woff2'), url('/fonts/FiraSans-SemiBoldItalic.ttf') format('truetype');
    font-weight: 600;
    font-style: italic;
    font-display: swap;
}
@font-face {
    font-family: 'Fira Sans';
    src: url('/fonts/FiraSans-Bold.woff2') format('woff2'), url('/fonts/FiraSans-Bold.ttf') format('truetype');
    font-weight: 700;
    font-style: normal;
    font-display: swap;
}
@font-face {
    font-family: 'Fira Sans';
    src: url('/fonts/FiraSans-BoldItalic.woff2') format('woff2'), url('/fonts/FiraSans-BoldItalic.ttf') format('truetype');
    font-weight: 700;
    font-style: italic;
    font-display: swap;
}
@font-face {
    font-family: 'Fira Sans';
    src: url('/fonts/FiraSans-ExtraBold.woff2') format('woff2'), url('/fonts/FiraSans-ExtraBold.ttf') format('truetype');
    font-weight: 800;
    font-style: normal;
    font-display: swap;
}
@font-face {
    font-family: 'Fira Sans';
    src: url('/fonts/FiraSans-ExtraBoldItalic.woff2') format('woff2'), url('/fonts/FiraSans-ExtraBoldItalic.ttf') format('truetype');
    font-weight: 800;
    font-style: italic;
    font-display: swap;
}

/* ═══ 1. RESET & BASE ═══ */

:root {
    /* Drives the UA canvas/backdrop colour. Without this the browser's base
       layer stays light, so iOS WebKit flashes a pale-cream backdrop during
       cross-document navigation (dashboard ↔ messenger) before the new page's
       dark body paints. [data-theme="dark"] flips it to dark below. */
    color-scheme: light;
    --color-primary: #254E48;
    --color-primary-light: #48716b;
    --color-primary-dark: #022b25;
    --color-on-primary: #fff;
    --color-accent: #DEC5D8;
    --color-whatsapp: #25D366;
    --color-bg: #F5F6E7;
    --color-card: #FFFFFF;
    --color-surface: #FFFFFF;
    --color-text: #2D2D2D;
    --color-text-light: #6B6B6B;
    --color-muted: var(--color-text-light);
    --color-bg-subtle: rgba(0, 0, 0, 0.03);
    --color-border-subtle: rgba(0, 0, 0, 0.08);
    --color-success: #1E6B3D;
    --color-success-bg: #e8f7ee;
    --color-error: #C0392B;
    --color-error-bg: #fdecea;
    --color-border: #e1e2d3;
    --color-shadow: 0 2px 8px rgba(37, 78, 72, 0.1);
    --color-danger: #C0392B;
    --color-msgr-tick-read: #4fc3f7;
    --color-primary-soft: rgba(37, 78, 72, 0.12);
    --color-warning: #f59e0b;
    --color-warning-bg: #fff8e1;
    /* Theme-invariant accents: the attention/pulse red shared by every pending
       dot, and the fixed Canopy brand purple (never follows per-org --color-primary). */
    --color-pulse-dot: #ef4444;
    --color-canopy-brand: #6B3FA8;
    --font-family: 'Fira Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
    --color-surface-alt: #EDEEE0;
    --color-hover: rgba(0, 0, 0, 0.04);
    --color-title-color: var(--color-primary);
    --color-title-font: inherit;

    /* Font size tokens - 5 categories, desktop defaults */
    --font-size-heading: 1.25rem;
    --font-size-subheading: 0.75rem;
    --font-size-body: 0.88rem;
    --font-size-label: 0.82rem;
    --font-size-small: 0.72rem;
    /* Messenger message body. Desktop matches --font-size-body (unchanged); the
       mobile override below bumps it one step larger for comfortable phone reading. */
    --font-size-msgr: var(--font-size-body);

    /* Animation easing tokens */
    --ease-out: cubic-bezier(0.4, 0, 0.2, 1);
    --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
    --ease-keyboard: cubic-bezier(0.38, 0.7, 0.125, 1); /* approximates the iOS soft-keyboard slide curve */

    /* Soft-keyboard inset (px). Runtime value driven by _wireKeyboardInset() in
       js/shared.js from visualViewport — height of the on-screen keyboard, or 0
       when hidden / on desktop. Consumed by .modal-overlay + .modal so a focused
       field is always lifted clear of the keyboard. See js/shared.js for why. */
    --kb-inset: 0px;

    /* Animation duration tokens (orion_motion). The companion to the easing
       tokens above; the Motion Library primitives reference these by name so
       js/animations.js ANIMATION_REGISTRY durations stay parity-checkable. */
    --dur-micro: 120ms;       /* press / tactile give */
    --dur-enter: 220ms;       /* card / list entrance */
    --dur-feedback: 250ms;    /* pop / pulse / value bump */
    --dur-transition: 300ms;  /* tab / view swap */
    --dur-celebrate: 450ms;   /* success */
    --dur-swipe: 500ms;       /* full-panel push / pop (matches Telegram iOS) */

    /* ── DESIGN TOKENS (--dt-*) ──────────────────────────────────────────────
       App-wide design geometry. The values here are the CANONICAL DEFAULTS,
       the single source of truth each token falls back to when no override is
       saved. Overrides are stored centrally in the design_settings table
       (tokens JSONB keyed by CSS var name), edited on the Canopy HQ > Design
       Management tab, and applied as inline documentElement styles by
       js/design-tokens.js (inline shadows these defaults). The Design
       Management UI reads each default via getComputedStyle, so never restate
       these values anywhere else. Adding a token: define the default here,
       consume it in the rules, register it in DESIGN_TOKENS
       (js/design-tokens.js). */

    /* Bottom nav bar. The row is the tappable button area; the gap +
       safe-area inset sit BELOW it (never eat into the row - global
       box-sizing:border-box would otherwise squeeze the buttons - and the gap
       keeps slots off the curved screen edge, L224 family). 44px row + 4px gap
       matches Chrome iOS's ~48pt bottom toolbar while keeping the Apple-HIG
       44px tap target. Mobile and desktop are independent tokens; the derived
       --bottom-nav-row below repoints per width. */
    --dt-bottom-nav-row-mobile: 44px;
    --dt-bottom-nav-row-desktop: 44px;
    --dt-bottom-nav-gap: 4px;
    --dt-bottom-nav-icon: 22px;

    /* Header org logo (.org-logo-img, top-left of the app header). */
    --dt-header-logo-height: 54px;

    /* Buttons (.btn base geometry; .btn-small keeps its own padding). */
    --dt-btn-radius: 8px;
    --dt-btn-pad-v: 12px;

    /* Cards (.card). */
    --dt-card-radius: 12px;
    --dt-card-pad: 24px;

    /* Form inputs (.form-group input / select / textarea). */
    --dt-input-radius: 8px;

    /* Derived internals consumed by the bar + every clearance rule (body
       padding, messenger app-container). The desktop media swap below
       repoints the row var at the desktop token. */
    --bottom-nav-row: var(--dt-bottom-nav-row-mobile);
    --bottom-nav-gap: var(--dt-bottom-nav-gap);
    /* Minimum clearance below the bottom-nav icon row on EVERY platform. The 4px
       --bottom-nav-gap alone left Android/web cramped (env(safe-area-inset-bottom)
       is 0 there) while iOS got gap + ~34px home-indicator inset. The clearance
       rules below floor at this value with max(), still growing to clear the home
       indicator where env() reports one. See css/CLAUDE.md "Safe-Area Insets". */
    --bottom-nav-min-clear: 30px;
}

/* Desktop bottom-nav height: repoint the derived row var at the desktop token. */
@media (min-width: 641px) {
    :root { --bottom-nav-row: var(--dt-bottom-nav-row-desktop); }
}

/* Font size tokens - mobile defaults */
@media (max-width: 767px) {
    :root {
        --font-size-heading: 1.1rem;
        --font-size-subheading: 0.7rem;
        --font-size-body: 0.85rem;
        --font-size-label: 0.78rem;
        --font-size-small: 0.68rem;
        /* Messenger bubble text — half a step above mobile body (0.85rem) so chat
           reads easier on a phone without making bubbles chunky or fitting less. */
        --font-size-msgr: 0.9rem;
    }
}

/* Prevent iOS Safari/Chrome auto-zoom on input focus (requires font-size ≥ 16px).
   !important is needed because many module-scoped selectors (e.g.
   #tab-settings .form-group input) have higher specificity and would otherwise
   pull font-size back down to var(--font-size-body) ≈ 13.6px on mobile. */
@media (max-width: 767px) {
    input, select, textarea {
        font-size: 16px !important;
    }
}

/* ═══ 2. TYPOGRAPHY & FONT SCALE ═══ */

/* Typography utility classes */
.text-heading    { font-size: var(--font-size-heading) !important; }
.text-subheading { font-size: var(--font-size-subheading) !important; }
.text-body       { font-size: var(--font-size-body) !important; }
.text-label      { font-size: var(--font-size-label) !important; }
.text-small      { font-size: var(--font-size-small) !important; }

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

/* Paint the root canvas itself (not just <body>) so the html element behind
   the body is the theme bg, never the UA default. Covers the navigation gap and
   the first pre-body-paint frame, where body's background hasn't drawn yet. */
html {
    background-color: var(--color-bg);
    /* Always reserve the vertical-scrollbar gutter so layout width is constant
       whether or not a tab's content overflows. Without this, switching between a
       short tab (no scrollbar) and a tall one (scrollbar present) changed the
       available width by the scrollbar's ~15px, re-centring the max-width app
       shell and shifting everything sideways. No-op where scrollbars are overlay
       (iOS Safari, the Capacitor webview, Android) so it never affects mobile. */
    scrollbar-gutter: stable;
}

body {
    font-family: var(--font-family);
    background-color: var(--color-bg);
    color: var(--color-text);
    min-height: 100vh;   /* fallback for browsers without dvh support */
    min-height: 100dvh;  /* fits the current visible viewport on mobile */
    display: flex;
    flex-direction: column;
}

/* ═══ 3. LAYOUT (grid, flex, containers) ═══ */

/* App Shell */
.app-container {
    margin: 0 auto;
    padding: 16px;
    width: 100%;
    min-width: 0; /* flex item: prevent expanding past viewport width */
    flex: 1;
    display: flex;
    flex-direction: column;
    position: relative;
}

/* Header */
.app-header {
    text-align: left;
    padding: 24px 0 16px;
    border: none;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
}

.app-header .logo {
    font-size: var(--font-size-heading);
    font-weight: 700;
    color: var(--color-primary);
    letter-spacing: -0.5px;
    display: flex;
    align-items: center;
    gap: 5px;
}

.org-logo-img {
    height: var(--dt-header-logo-height);
    width: auto;
    max-width: 210px;
    object-fit: contain;
    flex-shrink: 0;
}

#orgLogoName {
    color: var(--color-title-color);
    font-family: var(--color-title-font);
}

.app-header .logo small {
    display: block;
    font-size: var(--font-size-subheading);
    font-weight: 400;
    color: var(--color-text-light);
    letter-spacing: 1px;
    text-transform: uppercase;
    margin-top: 4px;
}

/* Navigation bar */
.nav-bar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 8px 0;
    margin-bottom: 16px;
    border: none;
}

.nav-bar .user-info {
    font-size: var(--font-size-body);
    color: var(--color-text-light);
}

.nav-bar .nav-links {
    display: flex;
    align-items: center;
    gap: 12px;
}

.nav-bar .nav-links a {
    font-size: var(--font-size-body);
    color: var(--color-primary);
    text-decoration: none;
}



.nav-bar .nav-links a:hover {
    text-decoration: underline;
}

/* Hide desktop inline links - navigation is the bottom nav bar + More page at
   all sizes (Edit Profile / Update Password / Sign Out live on the More page). */
.nav-bar .nav-links > a {
    display: none;
}

.nav-bar .nav-links > .view-toggle {
    display: none;
}

/* View Toggle */
.view-toggle {
    display: flex;
    background: rgba(107, 76, 138, 0.1);
    border-radius: 20px;
    padding: 3px;
    gap: 2px;
    font-size: var(--font-size-label);
}

.view-toggle a,
.view-toggle span {
    font-size: inherit;
}

.view-toggle-btn {
    padding: 5px 12px;
    border: none;
    border-radius: 18px;
    background: transparent;
    color: var(--color-text-light);
    font-size: inherit;
    font-weight: 500;
    font-family: inherit;
    cursor: pointer;
    transition: all 0.2s;
    white-space: nowrap;
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1.4;
    text-align: center;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

.view-toggle-btn:hover {
    background: rgba(107, 76, 138, 0.12);
    color: var(--color-primary);
    text-decoration: none;
}

.view-toggle-btn.active {
    background: var(--color-primary);
    color: white;
    cursor: default;
}

.view-toggle-btn.active:hover {
    background: var(--color-primary);
    color: white;
}

/* ═══ 4. COMPONENTS (buttons, badges, cards, modals, forms, alerts) ═══ */

/* Cards */
.card {
    background: var(--color-card);
    border-radius: var(--dt-card-radius);
    padding: var(--dt-card-pad);
    margin-bottom: 16px;
    box-shadow: var(--color-shadow);
    border: 1px solid var(--color-border);
}

.card h2 {
    font-size: var(--font-size-heading);
    margin-bottom: 16px;
    color: var(--color-primary-dark);
}

/* Forms */
.form-group {
    margin-bottom: 16px;
}

.form-group label {
    display: block;
    font-size: var(--font-size-label);
    font-weight: 600;
    color: var(--color-text);
    margin-bottom: 6px;
}

.form-group input,
.form-group select,
.form-group textarea {
    width: 100%;
    padding: 12px 14px;
    border: 1px solid var(--color-border);
    border-radius: var(--dt-input-radius);
    font-size: var(--font-size-body);
    background: var(--color-card);
    color: var(--color-text);
    transition: border-color 0.2s;
}

.form-group textarea {
    resize: none;
    overflow-y: auto;
}

/* Date/time inputs: force them to respect width:100% on iOS.
   WebKit's native date/time picker chrome sets an intrinsic min-width
   that overrides width:100%, causing overflow on narrow screens. */
input[type="date"],
input[type="time"] {
    min-width: 0;
    max-width: 100%;
}

/* Hide native number input spinners */
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
    -webkit-appearance: none;
    margin: 0;
}
input[type="number"] {
    -moz-appearance: textfield;
}

.form-group select {
    padding-right: 36px;
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%236B6B6B' d='M2 4l4 4 4-4'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 12px center;
    cursor: pointer;
}

.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
    outline: none;
    border-color: var(--color-primary);
    box-shadow: 0 0 0 3px rgba(107, 76, 138, 0.15);
}

.form-group input:disabled,
.form-group select:disabled,
.form-group textarea:disabled {
    opacity: 0.45;
    cursor: not-allowed;
    pointer-events: none;
}

.radio-group {
    display: flex;
    gap: 16px;
    padding: 8px 0;
}

.radio-label {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: var(--font-size-body);
    cursor: pointer;
    color: var(--color-text);
}

/* Global: constrain all checkboxes/radios - Safari iOS ignores flex sizing without this */
input[type="checkbox"],
input[type="radio"] {
    width: 16px;
    height: 16px;
    min-width: 16px;
    min-height: 16px;
    flex-shrink: 0;
    accent-color: var(--color-primary);
    margin: 0;
    vertical-align: middle;
    cursor: pointer;
}

.radio-label input[type="radio"] {
    accent-color: var(--color-primary);
    width: 16px;
    height: 16px;
    margin: 0;
    vertical-align: middle;
    cursor: pointer;
}

/* Announcement audience pill selector */
.ann-audience-pills {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    padding: 4px 0;
}
.ann-pill {
    padding: 5px 14px;
    border-radius: 20px;
    border: 1.5px solid var(--color-border);
    background: var(--color-card);
    color: var(--color-text-light);
    font-size: var(--font-size-label);
    cursor: pointer;
    transition: all 0.15s ease;
}
.ann-pill:hover { border-color: var(--color-primary); color: var(--color-primary); }
.ann-pill.active {
    background: var(--color-primary);
    border-color: var(--color-primary);
    color: #fff;
}

/* Document body formatting toolbar */
.doc-format-toolbar {
    display: flex;
    align-items: center;
    gap: 2px;
    padding: 5px 8px;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-bottom: none;
    border-radius: 8px 8px 0 0;
}
.doc-format-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 4px 8px;
    min-width: 30px;
    border: none;
    background: transparent;
    border-radius: 4px;
    cursor: pointer;
    color: var(--color-text);
    font-size: var(--font-size-small);
    line-height: 1;
    transition: background 0.12s;
}
.doc-format-btn:hover { background: var(--color-bg); }
.doc-format-btn-italic { font-style: italic; }
.doc-format-divider {
    width: 1px;
    height: 16px;
    background: var(--color-border);
    margin: 0 4px;
}
.doc-format-toolbar + textarea {
    border-top-left-radius: 0;
    border-top-right-radius: 0;
}

/* Message/Document type toggle pill buttons */
.msg-type-btn {
    padding: 5px 14px;
    border-radius: 20px;
    border: 1.5px solid var(--color-border);
    background: transparent;
    color: var(--color-text-light);
    font-size: var(--font-size-body);
    cursor: pointer;
    transition: all 0.15s ease;
}
.msg-type-btn:hover { border-color: var(--color-primary); color: var(--color-primary); }
.msg-type-btn.active {
    background: var(--color-primary);
    border-color: var(--color-primary);
    color: #fff;
}

/* Announcement segmented control */
.ann-seg-control {
    display: flex;
    border: 1.5px solid var(--color-border);
    border-radius: 8px;
    overflow: hidden;
}
.ann-seg {
    flex: 1;
    padding: 7px 6px;
    border: none;
    background: var(--color-card);
    color: var(--color-text-light);
    font-size: var(--font-size-label);
    cursor: pointer;
    transition: background 0.15s var(--ease-out), color 0.15s var(--ease-out);
    text-align: center;
}
.ann-seg:not(:last-child) { border-right: 1.5px solid var(--color-border); }
.ann-seg:hover { background: var(--color-bg); }
.ann-seg.active {
    background: var(--color-primary);
    color: #fff;
}
/* Mobile touch target — desktop padding (7px) yields ~28px height; thumb taps need 44px. */
@media (max-width: 767px) {
    .ann-seg { min-height: 44px; }
}

.form-group input::placeholder {
    color: #B0A0C0;
}

.form-group .hint {
    font-size: var(--font-size-small);
    color: var(--color-text-light);
    margin-top: 4px;
}

/* Password field with show/hide toggle */
.password-field-wrap {
    position: relative;
    display: flex;
    align-items: stretch;
}

.password-field-wrap input {
    flex: 1;
    padding-right: 44px !important;
}

.pw-toggle {
    position: absolute;
    right: 0;
    top: 0;
    bottom: 0;
    width: 42px;
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    color: var(--color-text-light);
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 0 8px 8px 0;
    transition: color 0.15s;
}

.pw-toggle:hover {
    color: var(--color-text);
}

.pw-toggle svg {
    width: 18px;
    height: 18px;
    pointer-events: none;
}

/* Info Icon - the fi-rr-info glyph, click to open the info modal. The markup
   carries `fi fi-rr-info`, so the glyph itself stays sourced from the Flaticon
   CDN (the single source of truth for every icon glyph) rather than a hardcoded
   codepoint; this class only adds the interactive affordance (muted colour,
   hover, click target, inline alignment). font-size:0 collapses the legacy
   literal 'i' that older markup still nests inside the span, so only the
   Flaticon glyph renders. */
.info-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0;
    color: var(--color-text-light);
    cursor: pointer;
    margin-left: 4px;
    flex-shrink: 0;
    user-select: none;
    vertical-align: middle;
    position: relative;
    top: -1px;
    transition: color 0.15s;
    line-height: 1;
}
.info-icon::before { font-size: 15px; }
.info-icon:hover {
    color: var(--color-primary);
}

/* Student Profile Photos */
.student-photo-thumb {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    object-fit: cover;
    display: block;
}

.student-photo-placeholder {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: var(--color-border);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--color-primary);
}

.student-photo-section {
    text-align: center;
    margin-bottom: 16px;
}

img.student-photo-large,
.student-photo-large img {
    width: 80px;
    height: 80px;
    border-radius: 50%;
    object-fit: cover;
    display: block;
    margin: 0 auto 8px;
}

.student-photo-placeholder-lg {
    width: 80px;
    height: 80px;
    border-radius: 50%;
    background: var(--color-border);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--color-primary);
    margin: 0 auto;
}

/* Profile photo in detail/edit modals - 128px, no border */
.modal-profile-photo img {
    width: 128px;
    height: 128px;
    border-radius: 50%;
    object-fit: cover;
    display: block;
    margin: 0 auto 8px;
}

.modal-profile-photo .student-photo-placeholder-lg {
    width: 128px;
    height: 128px;
    font-size: 2.4rem;
    margin-bottom: 8px;
}

.student-photo-upload-btn {
    cursor: pointer;
    font-size: 0.8rem !important;
}

/* Settings School List */
.settings-school-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 8px 0;
    border-bottom: 1px solid var(--color-border);
}

.settings-school-item:last-child {
    border-bottom: none;
}

.school-item-actions {
    display: flex;
    gap: 6px;
    align-items: center;
    flex-shrink: 0;
}

.school-item .school-name {
    font-size: var(--font-size-body);
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

.school-item .btn {
    font-size: var(--font-size-label);
    padding: 5px 12px;
}

/* Settings tab - compact sizing */
#tab-settings .card h2 {
    font-size: var(--font-size-body);
    margin-bottom: 10px;
}

#tab-settings .card > p.text-muted {
    font-size: var(--font-size-label);
}

#tab-settings .form-group label {
    font-size: var(--font-size-label);
}

#tab-settings .form-group select,
#tab-settings .form-group input {
    font-size: var(--font-size-body);
    padding: 9px 12px;
}

#tab-settings .btn {
    font-size: var(--font-size-label);
    padding: 9px 18px;
}

#tab-settings .btn.btn-small {
    font-size: var(--font-size-label);
    padding: 5px 12px;
}

/* History tab - compact Load More */
#tab-history .btn {
    font-size: var(--font-size-label);
    padding: 9px 18px;
}

/* Flaticon icons - ensure vertical centering inside any container */
.fi {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    line-height: 1;
    /* Icon glyphs are icon-font characters (selectable text), so a long-press or
       drag would select them and paint the blue text-selection box / raise the
       iOS callout. Icons are never meant to be selected anywhere - kill it
       globally for every .fi glyph. */
    -webkit-user-select: none;
    user-select: none;
    -webkit-touch-callout: none;
}

/* Buttons */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: var(--dt-btn-pad-v) 24px;
    border: none;
    border-radius: var(--dt-btn-radius);
    font-size: var(--font-size-body);
    font-weight: 600;
    cursor: pointer;
    transition: background-color 0.2s cubic-bezier(0.22, 1, 0.36, 1), transform 0.15s cubic-bezier(0.22, 1, 0.36, 1);
    text-decoration: none;
    width: 100%;
}

.btn:active {
    transform: scale(0.98);
}

.btn-primary {
    background-color: var(--color-primary);
    color: white;
}

.btn-primary:hover {
    background-color: var(--color-primary-dark);
    transform: translateY(-1px);
}

.btn-primary:disabled {
    background-color: var(--color-primary-light);
    opacity: 0.6;
    cursor: not-allowed;
}

.btn-secondary {
    background-color: transparent;
    color: var(--color-primary);
    border: 1px solid var(--color-primary);
}

.btn-secondary:hover {
    background-color: rgba(107, 76, 138, 0.05);
}

.btn-secondary:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

.btn-danger {
    background-color: var(--color-error);
    color: white;
}

.btn-danger:hover {
    background-color: #A93226;
}

.btn-ghost-danger {
    background-color: transparent;
    color: var(--color-error);
    border: none;
    padding-left: 8px;
    padding-right: 8px;
}

.btn-ghost-danger:hover {
    background-color: rgba(var(--color-error-rgb, 180, 50, 50), 0.08);
}

.btn-success {
    background-color: var(--color-success);
    color: #fff;
}

.btn-success:hover {
    filter: brightness(1.15);
}

.btn-small {
    padding: 10px 16px;
    font-size: var(--font-size-body);
    width: auto;
}

.btn-group {
    display: flex;
    gap: 8px;
    margin-top: 16px;
}

.btn-group .btn {
    flex: 1;
}

.btn-group--wrap {
    flex-wrap: wrap;
}
.btn-group--wrap .btn {
    flex: 1 1 calc(50% - 4px);
}

/* Links */
.text-link {
    color: var(--color-primary);
    text-decoration: none;
    font-size: var(--font-size-body);
    text-align: center;
    display: block;
    margin-top: 12px;
}

.text-link:hover {
    text-decoration: underline;
}

/* Alerts */
.alert {
    padding: 12px 16px;
    border-radius: 8px;
    margin-bottom: 16px;
    font-size: var(--font-size-body);
    display: none;
}

.alert.show {
    display: block;
}

.alert-error {
    background-color: #FDEDEC;
    color: var(--color-error);
    border: 1px solid #F5C6CB;
}

.alert-info {
    background-color: #F0E8F7;
    color: var(--color-primary-dark);
    border: 1px solid var(--color-border);
}

/* QR Code Display */
.qr-container {
    text-align: center;
    padding: 24px;
}

.qr-container canvas,
.qr-container img {
    max-width: 280px;
    width: 100%;
    height: auto;
    border-radius: 12px;
}

.qr-timer {
    margin-top: 12px;
    font-size: var(--font-size-body);
    color: var(--color-text-light);
}

.qr-timer .countdown {
    font-weight: 700;
    color: var(--color-primary);
    font-size: var(--font-size-heading);
}

/* Scanner result */
.scan-result {
    text-align: center;
    padding: 24px;
    border-radius: 12px;
    margin-top: 16px;
    display: none;
}

.scan-result.show {
    display: block;
}

.scan-result.valid {
    background-color: #E8F5E9;
    border: 2px solid var(--color-success);
}

.scan-result.invalid {
    background-color: #FDEDEC;
    border: 2px solid var(--color-error);
}

.scan-result .result-icon {
    font-size: 3rem;
    margin-bottom: 12px;
}

.scan-result .parent-name {
    font-size: var(--font-size-heading);
    font-weight: 700;
    margin-bottom: 8px;
}

.scan-result .student-list {
    font-size: var(--font-size-body);
    color: var(--color-text-light);
}

/* Student cards */
.student-card {
    display: flex;
    align-items: center;
    padding: 12px;
    background: var(--color-bg);
    border-radius: 8px;
    margin-bottom: 8px;
}

.student-card .student-avatar {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: var(--color-primary-light);
    color: white;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: var(--font-size-body);
    margin-right: 12px;
    flex-shrink: 0;
}

.student-card .student-info h3 {
    font-size: var(--font-size-body);
    margin-bottom: 2px;
}

.student-card .student-info p {
    font-size: var(--font-size-label);
    color: var(--color-text-light);
}

/* Admin tables */
.data-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--font-size-body);
}

.data-table-fixed {
    table-layout: fixed;
}

.data-table th,
.data-table td {
    padding: 10px 8px;
    text-align: left;
    border-bottom: 1px solid var(--color-border);
}
.data-table th:last-child,
.data-table td:last-child {
    padding-right: 16px;
}

.data-table .btn.btn-small {
    padding: 3px 10px;
    font-size: var(--font-size-label);
}

.data-table th {
    font-weight: 600;
    color: var(--color-primary-dark);
    background: var(--color-bg);
    position: sticky;
    top: 0;
}

.data-table tr:hover td {
    background-color: rgba(107, 76, 138, 0.03);
}

/* Vendor Tagging (admin-manage-enrichments) */
.vt-list { border-top: 1px solid var(--color-border); }
.vt-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 0;
    border-bottom: 1px solid var(--color-border);
}
.vt-row-label { min-width: 180px; font-weight: 500; }
.vt-row-vendors { flex: 1; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.vt-pill {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 8px 3px 12px;
    border-radius: 999px;
    background: var(--color-primary);
    color: #fff;
    font-size: var(--font-size-small);
}
.vt-pill-remove {
    background: none;
    border: none;
    color: rgba(255,255,255,0.75);
    cursor: pointer;
    font-size: 14px;
    line-height: 1;
    padding: 0;
    display: flex;
    align-items: center;
}
.vt-pill-remove:hover { color: #fff; }
.vt-add-select {
    height: 28px;
    font-size: var(--font-size-small);
    padding: 0 8px;
    border-radius: 999px;
    border: 1.5px dashed var(--color-border);
    background: transparent;
    color: var(--color-text-light);
    cursor: pointer;
}

/* Sortable / filterable table headers */
.th-sortable {
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
}
.th-sortable:hover { color: var(--color-primary); }

.th-sort-arrow {
    font-size: var(--font-size-label);
    margin-left: 3px;
    color: var(--color-text-light);
    vertical-align: middle;
}
.th-sort-arrow.active { color: var(--color-primary); }

.th-filterable {
    cursor: pointer;
    user-select: none;
    font-size: var(--font-size-body);
    margin-left: 4px;
    color: var(--color-text-light);
    padding: 1px 3px;
    border-radius: 3px;
    vertical-align: middle;
}
.th-filterable:hover { color: var(--color-primary); }
.th-filterable.active {
    color: var(--color-primary);
    font-weight: 700;
    background: rgba(107, 76, 138, 0.1);
}

/* Filter dropdown */
.table-filter-dropdown {
    position: fixed;
    background: var(--color-card, #fff);
    border: 1px solid var(--color-border);
    border-radius: 8px;
    box-shadow: 0 4px 16px rgba(0,0,0,0.12);
    z-index: 2000;
    min-width: 130px;
    padding: 4px 0;
}
.table-filter-option {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    cursor: pointer;
    font-size: var(--font-size-body);
    color: var(--color-text);
}
.table-filter-option:hover { background: var(--color-bg); }
.table-filter-option.selected { color: var(--color-primary); font-weight: 600; }
.filter-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    border: 2px solid var(--color-text-light);
    flex-shrink: 0;
}
.table-filter-option.selected .filter-dot {
    border-color: var(--color-primary);
    background: var(--color-primary);
}

.table-container {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
}
@media (min-width: 769px) {
    .table-container {
        overflow-x: hidden;
    }
}

/* Search bar */
.search-bar {
    display: flex;
    gap: 8px;
    margin-bottom: 16px;
}

.search-bar input {
    flex: 1;
    padding: 10px 14px;
    border: 1px solid var(--color-border);
    border-radius: 8px;
    font-size: var(--font-size-body);
}

/* Tabs */
.tabs {
    display: none;
    flex-wrap: wrap;
    border-bottom: 2px solid var(--color-border);
    margin-bottom: 16px;
    gap: 0;
}

.tabs .tab {
    padding: 8px 14px;
    font-size: var(--font-size-body);
    font-weight: 600;
    color: var(--color-text-light);
    cursor: pointer;
    border-bottom: 2px solid transparent;
    margin-bottom: -2px;
    white-space: nowrap;
    background: none;
    border-top: none;
    border-left: none;
    border-right: none;
}

.tabs .tab.active {
    color: var(--color-primary);
    border-bottom-color: var(--color-primary);
}

.tab-content {
    display: none;
}

.tab-content.active {
    display: block;
}

/* A lazily-loaded tab is marked .module-loading from when its DOM is shown until
   its feature scripts finish loading (js/module-loader.js). Suppress pointer
   events so a button in the freshly-shown markup can't fire a handler that the
   still-loading module has not defined yet. Removed once the module's init runs. */
.tab-content.module-loading {
    pointer-events: none;
    cursor: progress;
}

/* Badge */
.badge {
    display: inline-flex;
    align-items: center;
    padding: 3px 8px 2px; /* asymmetric: +1px top compensates for cap-height sitting high in em square */
    border-radius: 12px;
    font-size: var(--font-size-small);
    font-weight: 600;
    line-height: 1;
    text-transform: uppercase;
}

.badge-success {
    background: rgba(30, 107, 61, 0.12);
    color: var(--color-success);
}

.badge-warning {
    background: var(--color-warning-bg);
    color: var(--color-warning);
}

.badge-error {
    background: rgba(192, 57, 43, 0.1);
    color: var(--color-error);
}

.badge-muted {
    background: rgba(107, 107, 107, 0.12);
    color: var(--color-text-light);
}

.badge-trial {
    background: rgba(222, 197, 216, 0.35);
    color: #6B3F62;
}

[data-theme="dark"] .badge-trial {
    background: rgba(222, 197, 216, 0.18);
    color: #E8C4DF;
}

.status-edit-btn {
    background: none;
    border: none;
    padding: 2px 4px;
    color: var(--color-text-light);
    cursor: pointer;
    font-size: var(--font-size-small);
    line-height: 1;
    border-radius: 4px;
}
.status-edit-btn:hover { color: var(--color-primary); background: var(--color-primary-soft); }

/* Modal */
.modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.5);
    display: none;
    align-items: center;
    justify-content: center;
    z-index: 1000;
    padding: 16px;
    /* Reserve the soft-keyboard height at the bottom so the flex-centred modal
       re-centres in the band ABOVE the keyboard (any field near the modal's
       bottom would otherwise sit under the keyboard on the messenger page,
       where keyboard resize mode is "none", and in mobile web Safari, where
       the layout viewport never shrinks). --kb-inset is 0 at rest / on desktop,
       so this is a no-op there. */
    padding-bottom: calc(16px + var(--kb-inset, 0px));
    transition: padding-bottom 0.25s var(--ease-keyboard);
}

.modal-overlay.show {
    display: flex;
}

/* Feedback modals must float above all other modals */
#genericSuccessModal,
#customErrorModal,
#customConfirmModal {
    z-index: 1100;
}

.modal {
    background: var(--color-card);
    border-radius: 12px;
    padding: 24px;
    width: 100%;
    max-width: 700px;
    /* Cap at 90vh, then also subtract the safe-area insets so a tall modal stays
       clear of the status bar / Dynamic Island (top) and home indicator (bottom)
       on edge-to-edge iOS: it shrinks and scrolls its own content (overflow-y)
       and re-centres inside the safe band rather than poking under the notch.
       env()=0 on desktop / Android / web / cap-inset-always native (the OS
       already insets there), so it stays a plain 90vh. --kb-inset (0 at rest)
       lifts it above the soft keyboard. See css/CLAUDE.md "Safe-Area Insets". */
    max-height: calc(90vh - env(safe-area-inset-top, 0px) - env(safe-area-inset-bottom, 0px) - var(--kb-inset, 0px));
    overflow-y: auto;
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
    transition: max-height 0.25s var(--ease-keyboard);
}

/* Hero-image modal - image bleeds edge-to-edge at top */
.modal-hero {
    padding: 0;
    position: relative;
}

.modal-hero .modal-close {
    position: absolute;
    top: 10px;
    right: 10px;
    background: rgba(255,255,255,0.88);
    border-radius: 50%;
    width: 30px;
    height: 30px;
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 20;
    font-size: 18px; /* fixed - circle is fixed-size, must not scale with font preset */
    line-height: 1;
    float: none;
}

.modal-hero-content {
    padding: 20px 24px 24px;
}

/* Clip image at modal top corners; remove side rounding & bottom margin */
.modal-hero .enrich-carousel {
    border-radius: 12px 12px 0 0;
    margin-bottom: 0;
    overflow: hidden;
}

.modal-hero .enrich-carousel-single {
    border-radius: 12px 12px 0 0;
    margin-bottom: 0;
}

.modal h2 {
    font-size: var(--font-size-heading);
    margin-bottom: 16px;
    color: var(--color-primary-dark);
}

.modal-close {
    float: right;
    background: none;
    border: none;
    font-size: var(--font-size-heading);
    cursor: pointer;
    color: var(--color-text-light);
    line-height: 1;
}

/* Loading spinner. linear (not ease) is correct here: a continuously rotating
   indicator must spin at constant speed, or it visibly pulses each revolution. */
.spinner {
    display: inline-block;
    width: 20px;
    height: 20px;
    border: 3px solid rgba(255, 255, 255, 0.3);
    border-radius: 50%;
    border-top-color: white;
    animation: spin 0.8s linear infinite;
}

.spinner-dark {
    border-color: rgba(107, 76, 138, 0.2);
    border-top-color: var(--color-primary);
}

@keyframes spin {
    to { transform: rotate(360deg); }
}

/* ── Ambient loaders ──────────────────────────────────────────────────────
   A waiting state should breathe, never freeze. These are AMBIENT (declarative,
   looping) indicators - the counterpart to the played Motion Library primitives
   in Section 5. The global prefers-reduced-motion block freezes them to a
   single static frame, so reduced-motion users get a calm placeholder.

   .loading-text  - the canonical breathing "Loading…" placeholder. Replaces the
                    dead grey <p class="text-muted">Loading...</p> pattern.
   .skeleton      - shows the SHAPE of content that is still loading; compose
                    with a sized box or the .skeleton-circle / .skeleton-text
                    shape helpers. */
.loading-text {
    color: var(--color-text-light);
    animation: loadingPulse 1.4s var(--ease-out) infinite;
}
@keyframes loadingPulse {
    0%, 100% { opacity: 0.45; }
    50%      { opacity: 1; }
}

.skeleton {
    background: linear-gradient(90deg,
        var(--color-border) 25%,
        var(--color-card) 50%,
        var(--color-border) 75%);
    background-size: 200% 100%;
    animation: skeletonShimmer 1.4s ease-in-out infinite;
    border-radius: 6px;
}
@keyframes skeletonShimmer {
    0%   { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}
.skeleton-circle { border-radius: 50%; }
.skeleton-text   { height: 0.72em; border-radius: 4px; }

/* View As banner - sticky, in normal document flow, full page width.
   Sits above .app-container so it pushes the entire page down naturally.
   Pins below the safe-area inset when scrolling so the Exit button always
   stays clear of the status bar / notch. (It used to pin at top:0 = behind the
   status bar, sliding the bar out of reach on native edge-to-edge builds.) */

/* Sticky thead inside a scrollable modal */
.sticky-thead th {
    position: sticky;
    top: 0;
    background: var(--color-card);
    z-index: 1;
}

.view-as-bar {
    position: sticky;
    top: env(safe-area-inset-top, 0px);
    z-index: 975; /* above nav menu (950) but below modals (1000+) */
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 10px 20px;
    background: #fff3cd;
    border-bottom: 2px solid #ffc107;
    gap: 12px;
    box-shadow: 0 2px 8px rgba(0,0,0,0.12);
}
.view-as-label {
    font-size: var(--font-size-body);
    color: #856404;
}
[data-theme="dark"] .view-as-bar {
    background: #3a2e00;
    border-bottom-color: #ffc107;
}
[data-theme="dark"] .view-as-label { color: #ffd966; }
/* contentInset:"always" builds: the OS already insets the webview below the
   notch (body padding-top is suppressed to 0), so pin at the webview top.
   env() would push the bar down by a phantom inset on those builds. */
html.cap-inset-always .view-as-bar { top: 0; }

/* Greeting emoji spin animation */
.greeting-emoji {
    display: inline-block;
    margin-left: 2px;
}

@keyframes greetingEmojiSpin {
    0%   { transform: rotate(0deg); }
    100% { transform: rotate(720deg); }
}

.greeting-emoji--spin {
    animation: greetingEmojiSpin 1.1s cubic-bezier(0.34, 1.3, 0.64, 1);
}

/* Event banner - Announcements tab */
.event-banner {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 16px;
    border-radius: 8px;
    margin-bottom: 10px;
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-left-width: 3px;
    cursor: pointer;
    transition: background 0.15s;
}
.event-banner:hover        { background: var(--color-border); }
.event-banner-live         { border-left-color: var(--color-error); }
.event-banner-upcoming     { border-left-color: var(--color-primary); }
.event-banner-badge {
    font-size: var(--font-size-small);
    font-weight: 700;
    letter-spacing: 0.07em;
    padding: 3px 9px;
    border-radius: 20px;
    flex-shrink: 0;
    white-space: nowrap;
}
.event-banner-live .event-banner-badge {
    background: #fdecea;
    color: var(--color-error);
}
.event-banner-upcoming .event-banner-badge {
    background: #ede6f5;
    color: var(--color-primary);
}
.event-banner-badge-live { animation: livePulse 1.5s ease-in-out infinite; }
.event-banner-name {
    font-weight: 600;
    font-size: var(--font-size-body);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    color: var(--color-text);
}
.event-banner-sub {
    font-size: var(--font-size-label);
    color: var(--color-text-light);
    margin-top: 2px;
}
@keyframes livePulse {
    0%, 100% { opacity: 1; }
    50%       { opacity: 0.45; }
}

