/* ScholarConnect — Oxford-inspired Teal + Slate theme (v1).
   Hardcoded values here mirror the Tailwind token palette in index.html. */

.material-symbols-outlined {
    font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
    vertical-align: middle;
}

/* ===================================================================== */
/* PHASE 19 — GLOBAL RESPONSIVENESS LOCKDOWN (anti-"eating" structure)    */
/* Structural-only: adds shrink/wrap/clip guarantees. Removes no logic.   */
/* ===================================================================== */

/* (a) Predictable box model on every node — Tailwind's preflight already does
       this, but we restate it as a hard guarantee independent of CDN load order
       so padding/border never inflate an element past its declared width. */
*, *::before, *::after { box-sizing: border-box; }

/* Tailwind CDN normally provides this utility. Keep a local fallback so critical
   app gating (login overlay, app shell, modals) still works if the utility CSS
   is unavailable or overridden. */
.hidden { display: none !important; }

/* Core Tailwind utility fallback. The app is intentionally CDN-free at build
   time, but production browsers can still block/lose the Tailwind CDN script.
   These local definitions cover the layout/visibility utilities used across
   every view so the UI remains navigable even when the CDN utilities are absent. */
.block { display: block; }
.inline-block { display: inline-block; }
.inline-flex { display: inline-flex; }
.flex { display: flex; }
.grid { display: grid; }
.fixed { position: fixed; }
.absolute { position: absolute; }
.relative { position: relative; }
.sticky { position: sticky; }
.invisible { visibility: hidden; }
.pointer-events-none { pointer-events: none; }
.select-none { user-select: none; }
.cursor-pointer { cursor: pointer; }
.cursor-not-allowed { cursor: not-allowed; }
.top-0 { top: 0; }
.top-2 { top: .5rem; }
.top-4 { top: 1rem; }
.top-20 { top: 5rem; }
.top-full { top: 100%; }
.top-1\/2 { top: 50%; }
.right-0 { right: 0; }
.right-2 { right: .5rem; }
.-right-1 { right: -.25rem; }
.-right-2 { right: -.5rem; }
.left-0 { left: 0; }
.left-3 { left: .75rem; }
.left-6 { left: 1.5rem; }
.bottom-gutter { bottom: 1.5rem; }
.right-gutter { right: 1.5rem; }
.bottom-\[8\.25rem\] { bottom: 8.25rem; }
.left-\[12\.5\%\] { left: 12.5%; }
.right-\[12\.5\%\] { right: 12.5%; }
.-top-1 { top: -.25rem; }
.-top-2 { top: -.5rem; }
.inset-0 { inset: 0; }
.z-0 { z-index: 0; }
.z-10 { z-index: 10; }
.z-50 { z-index: 50; }
.z-\[60\] { z-index: 60; }
.z-\[70\] { z-index: 70; }
.z-\[80\] { z-index: 80; }
.z-\[85\] { z-index: 85; }
.z-\[90\] { z-index: 90; }
.z-\[95\] { z-index: 95; }
.z-\[100\] { z-index: 100; }
.z-\[110\] { z-index: 110; }
.z-\[120\] { z-index: 120; }
.-translate-y-1\/2 { transform: translateY(-50%); }
.translate-x-12 { transform: translateX(3rem); }
.flex-1 { flex: 1 1 0%; }
.flex-col { flex-direction: column; }
.flex-wrap { flex-wrap: wrap; }
.flex-shrink-0, .shrink-0 { flex-shrink: 0; }
.shrink { flex-shrink: 1; }
.items-start { align-items: flex-start; }
.items-center { align-items: center; }
.items-end { align-items: flex-end; }
.justify-start { justify-content: flex-start; }
.justify-center { justify-content: center; }
.justify-between { justify-content: space-between; }
.justify-end { justify-content: flex-end; }
.grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
.grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.grid-cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.gap-0\.5 { gap: .125rem; }
.gap-1 { gap: .25rem; }
.gap-2 { gap: .5rem; }
.gap-xs { gap: .25rem; }
.gap-sm { gap: .5rem; }
.gap-md { gap: 1rem; }
.gap-lg { gap: 1.5rem; }
.gap-xl { gap: 2rem; }
.gap-x-2 { column-gap: .5rem; }
.gap-y-1 { row-gap: .25rem; }
.gap-y-sm { row-gap: .5rem; }
.space-y-xs > :not([hidden]) ~ :not([hidden]) { margin-top: .25rem; }
.space-y-sm > :not([hidden]) ~ :not([hidden]) { margin-top: .5rem; }
.space-y-md > :not([hidden]) ~ :not([hidden]) { margin-top: 1rem; }
.space-y-lg > :not([hidden]) ~ :not([hidden]) { margin-top: 1.5rem; }
.space-y-xl > :not([hidden]) ~ :not([hidden]) { margin-top: 2rem; }
.m-0 { margin: 0; }
.-m-sm { margin: -.5rem; }
.mx-auto { margin-left: auto; margin-right: auto; }
.mx-2 { margin-left: .5rem; margin-right: .5rem; }
.my-auto { margin-top: auto; margin-bottom: auto; }
.mt-auto { margin-top: auto; }
.mt-0\.5 { margin-top: .125rem; }
.mt-1 { margin-top: .25rem; }
.mt-1\.5 { margin-top: .375rem; }
.mt-2 { margin-top: .5rem; }
.mt-sm { margin-top: .5rem; }
.mt-md { margin-top: 1rem; }
.mt-lg { margin-top: 1.5rem; }
.mt-xl { margin-top: 2rem; }
.mb-0\.5 { margin-bottom: .125rem; }
.mb-1 { margin-bottom: .25rem; }
.mb-2 { margin-bottom: .5rem; }
.mb-xs { margin-bottom: .25rem; }
.mb-sm { margin-bottom: .5rem; }
.mb-md { margin-bottom: 1rem; }
.mb-lg { margin-bottom: 1.5rem; }
.mb-xl { margin-bottom: 2rem; }
.ml-1 { margin-left: .25rem; }
.ml-auto { margin-left: auto; }
.ml-sm { margin-left: .5rem; }
.-ml-sm { margin-left: -.5rem; }
.mr-2 { margin-right: .5rem; }
.p-0 { padding: 0; }
.p-1 { padding: .25rem; }
.p-1\.5 { padding: .375rem; }
.p-2 { padding: .5rem; }
.p-3 { padding: .75rem; }
.p-sm { padding: .5rem; }
.p-md { padding: 1rem; }
.p-lg { padding: 1.5rem; }
.p-xl { padding: 2rem; }
.p-xxl { padding: 3rem; }
.p-gutter { padding: 1.5rem; }
.px-1 { padding-left: .25rem; padding-right: .25rem; }
.px-1\.5 { padding-left: .375rem; padding-right: .375rem; }
.px-2 { padding-left: .5rem; padding-right: .5rem; }
.px-3 { padding-left: .75rem; padding-right: .75rem; }
.px-sm { padding-left: .5rem; padding-right: .5rem; }
.px-md { padding-left: 1rem; padding-right: 1rem; }
.px-lg { padding-left: 1.5rem; padding-right: 1.5rem; }
.px-xl { padding-left: 2rem; padding-right: 2rem; }
.px-xxl { padding-left: 3rem; padding-right: 3rem; }
.px-gutter { padding-left: 1.5rem; padding-right: 1.5rem; }
.py-0\.5 { padding-top: .125rem; padding-bottom: .125rem; }
.py-1 { padding-top: .25rem; padding-bottom: .25rem; }
.py-1\.5 { padding-top: .375rem; padding-bottom: .375rem; }
.py-2 { padding-top: .5rem; padding-bottom: .5rem; }
.py-3 { padding-top: .75rem; padding-bottom: .75rem; }
.py-sm { padding-top: .5rem; padding-bottom: .5rem; }
.py-md { padding-top: 1rem; padding-bottom: 1rem; }
.py-lg { padding-top: 1.5rem; padding-bottom: 1.5rem; }
.py-xl { padding-top: 2rem; padding-bottom: 2rem; }
.pt-16 { padding-top: 4rem; }
.pt-sm { padding-top: .5rem; }
.pt-md { padding-top: 1rem; }
.pt-lg { padding-top: 1.5rem; }
.pt-xl { padding-top: 2rem; }
.pb-1 { padding-bottom: .25rem; }
.pb-sm { padding-bottom: .5rem; }
.pb-md { padding-bottom: 1rem; }
.pl-10 { padding-left: 2.5rem; }
.pr-1 { padding-right: .25rem; }
.pr-4 { padding-right: 1rem; }
.pr-11 { padding-right: 2.75rem; }
.w-2 { width: .5rem; }
.w-2\.5 { width: .625rem; }
.w-5 { width: 1.25rem; }
.w-8 { width: 2rem; }
.w-9 { width: 2.25rem; }
.w-10 { width: 2.5rem; }
.w-14 { width: 3.5rem; }
.w-24 { width: 6rem; }
.w-40 { width: 10rem; }
.w-44 { width: 11rem; }
.w-64 { width: 16rem; }
.w-72 { width: 18rem; }
.w-80 { width: 20rem; }
.w-\[280px\] { width: 280px; }
.w-full { width: 100%; }
.h-0\.5 { height: .125rem; }
.h-2 { height: .5rem; }
.h-2\.5 { height: .625rem; }
.h-3 { height: .75rem; }
.h-4 { height: 1rem; }
.h-5 { height: 1.25rem; }
.h-8 { height: 2rem; }
.h-9 { height: 2.25rem; }
.h-10 { height: 2.5rem; }
.h-14 { height: 3.5rem; }
.h-16 { height: 4rem; }
.h-24 { height: 6rem; }
.h-48 { height: 12rem; }
.h-\[18px\] { height: 18px; }
.h-px { height: 1px; }
.h-full { height: 100%; }
.min-h-\[44px\] { min-height: 44px; }
.min-h-\[120px\] { min-height: 120px; }
.min-w-0 { min-width: 0; }
.min-w-\[16px\] { min-width: 16px; }
.min-w-\[18px\] { min-width: 18px; }
.min-w-\[80px\] { min-width: 80px; }
.min-w-\[150px\] { min-width: 150px; }
.min-w-\[160px\] { min-width: 160px; }
.min-w-\[720px\] { min-width: 720px; }
.max-w-xs { max-width: 20rem; }
.max-w-md { max-width: 28rem; }
.max-w-2xl { max-width: 42rem; }
.max-w-4xl { max-width: 56rem; }
.max-w-5xl { max-width: 64rem; }
.max-w-6xl { max-width: 72rem; }
.max-w-\[80\%\] { max-width: 80%; }
.max-w-\[calc\(100vw-2rem\)\] { max-width: calc(100vw - 2rem); }
.max-w-\[calc\(100vw-3rem\)\] { max-width: calc(100vw - 3rem); }
.max-h-72 { max-height: 18rem; }
.max-h-96 { max-height: 24rem; }
.max-h-\[70vh\] { max-height: 70vh; }
.max-h-\[85vh\] { max-height: 85vh; }
.max-h-\[90vh\] { max-height: 90vh; }
.overflow-hidden { overflow: hidden; }
.overflow-x-auto { overflow-x: auto; }
.overflow-x-hidden { overflow-x: hidden; }
.overflow-y-auto { overflow-y: auto; }
.rounded { border-radius: .25rem; }
.rounded-md { border-radius: .375rem; }
.rounded-lg { border-radius: .5rem; }
.rounded-xl { border-radius: .75rem; }
.rounded-full { border-radius: 9999px; }
.border { border-width: 1px; border-style: solid; }
.border-0 { border-width: 0; }
.border-2 { border-width: 2px; border-style: solid; }
.border-b { border-bottom-width: 1px; border-bottom-style: solid; }
.border-t { border-top-width: 1px; border-top-style: solid; }
.border-r { border-right-width: 1px; border-right-style: solid; }
.border-l-4 { border-left-width: 4px; border-left-style: solid; }
.border-dashed { border-style: dashed; }
.border-collapse { border-collapse: collapse; }
.shadow-sm { box-shadow: 0 1px 2px rgba(15, 23, 42, .08); }
.shadow-md { box-shadow: 0 4px 6px rgba(15, 23, 42, .10); }
.shadow-lg { box-shadow: 0 10px 15px rgba(15, 23, 42, .12); }
.shadow-xl { box-shadow: 0 20px 25px rgba(15, 23, 42, .14); }
.opacity-0 { opacity: 0; }
.opacity-40 { opacity: .4; }
.opacity-50 { opacity: .5; }
.text-left { text-align: left; }
.text-center { text-align: center; }
.text-right { text-align: right; }
.align-middle { vertical-align: middle; }
.font-medium { font-weight: 500; }
.font-bold { font-weight: 700; }
.font-extrabold { font-weight: 800; }
.italic { font-style: italic; }
.uppercase { text-transform: uppercase; }
.underline { text-decoration-line: underline; }
.hover\:underline:hover { text-decoration-line: underline; }
.underline-offset-2 { text-underline-offset: 2px; }
.tracking-wide { letter-spacing: .025em; }
.tracking-wider { letter-spacing: .05em; }
.tracking-\[0\.5em\] { letter-spacing: .5em; }
.leading-none { line-height: 1; }
.leading-4 { line-height: 1rem; }
.leading-tight { line-height: 1.25; }
.leading-relaxed { line-height: 1.625; }
.text-\[9px\] { font-size: 9px; }
.text-\[10px\] { font-size: 10px; }
.text-\[11px\] { font-size: 11px; }
.text-\[13px\] { font-size: 13px; }
.text-xs { font-size: .75rem; line-height: 1rem; }
.text-sm { font-size: .875rem; line-height: 1.25rem; }
.text-base { font-size: 1rem; line-height: 1.5rem; }
.text-lg { font-size: 1.125rem; line-height: 1.75rem; }
.text-xl { font-size: 1.25rem; line-height: 1.75rem; }
.text-2xl { font-size: 1.5rem; line-height: 2rem; }
.text-3xl { font-size: 1.875rem; line-height: 2.25rem; }
.text-4xl { font-size: 2.25rem; line-height: 2.5rem; }
.text-6xl { font-size: 3.75rem; line-height: 1; }
.text-\[18px\] { font-size: 18px; }
.text-\[20px\] { font-size: 20px; }
.font-body-md { font-family: Inter, system-ui, sans-serif; font-size: 14px; line-height: 20px; }
.font-body-lg, .text-body-lg { font-family: Inter, system-ui, sans-serif; font-size: 16px; line-height: 24px; }
.font-body-sm, .text-body-sm { font-family: Inter, system-ui, sans-serif; font-size: 13px; line-height: 18px; }
.font-label-md, .text-label-md { font-family: Inter, system-ui, sans-serif; font-size: 12px; line-height: 16px; font-weight: 600; }
.font-headline-md, .text-headline-md { font-family: Inter, system-ui, sans-serif; font-size: 20px; line-height: 28px; font-weight: 600; }
.font-headline-lg { font-family: Inter, system-ui, sans-serif; font-size: 24px; line-height: 32px; font-weight: 600; }
.font-headline-xl { font-family: Inter, system-ui, sans-serif; font-size: 36px; line-height: 44px; font-weight: 700; }
.font-mono { font-family: "JetBrains Mono", ui-monospace, monospace; }
.break-all { word-break: break-all; }
.break-words { overflow-wrap: break-word; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.whitespace-nowrap { white-space: nowrap; }
.whitespace-pre-wrap { white-space: pre-wrap; }
.outline-none { outline: 2px solid transparent; outline-offset: 2px; }
.transition-all { transition: all .15s ease; }
.transition-colors { transition: color .15s ease, background-color .15s ease, border-color .15s ease; }
.transition-opacity { transition: opacity .15s ease; }
.duration-300 { transition-duration: .3s; }
.duration-500 { transition-duration: .5s; }
.duration-700 { transition-duration: .7s; }
.duration-1000 { transition-duration: 1s; }
.bg-transparent { background-color: transparent; }
.bg-white { background-color: #fff; }
.bg-black\/40 { background-color: rgba(0, 0, 0, .4); }
.bg-black\/50 { background-color: rgba(0, 0, 0, .5); }
.bg-blue-500 { background-color: #3b82f6; }
.bg-green-100 { background-color: #dcfce7; }
.bg-green-500 { background-color: #22c55e; }
.bg-purple-500 { background-color: #a855f7; }
.bg-red-500 { background-color: #ef4444; }
.bg-surface\/95 { background-color: color-mix(in srgb, var(--c-canvas, #f8fafc) 95%, transparent); }
.text-white { color: #fff; }
.text-white\/80 { color: rgba(255, 255, 255, .8); }
.text-white\/90 { color: rgba(255, 255, 255, .9); }
.text-green-800 { color: #166534; }
.border-error\/30 { border-color: rgba(220, 38, 38, .3); }
.bg-surface { background-color: var(--c-canvas, #f8fafc); }
.bg-surface-container, .bg-surface-container-low { background-color: var(--c-surface-2, #f1f5f9); }
.bg-surface-container-lowest, .bg-surface-container-high, .bg-surface-variant { background-color: var(--c-surface, #fff); }
.bg-outline-variant { background-color: var(--c-border, #e2e8f0); }
.bg-primary { background-color: var(--c-primary, #115e59); }
.bg-primary-fixed { background-color: var(--c-badge, #ccfbf1); }
.bg-secondary-fixed { background-color: var(--c-surface-3, #e2e8f0); }
.bg-error, .bg-error-container { background-color: var(--c-danger, #dc2626); }
.text-primary { color: var(--c-primary, #115e59); }
.text-on-primary, .text-on-error, .text-white { color: #fff; }
.text-on-surface { color: var(--c-text, #0f172a); }
.text-on-surface-variant, .text-outline { color: var(--c-muted, #64748b); }
.text-on-secondary-container { color: var(--c-muted, #475569); }
.text-on-tertiary-container, .text-tertiary { color: #2563eb; }
.text-error, .hover\:text-error:hover { color: var(--c-danger, #dc2626); }
.text-on-error-container { color: #991b1b; }
.border-outline, .border-outline-variant { border-color: var(--c-border, #e2e8f0); }
.border-primary, .hover\:border-primary:hover { border-color: var(--c-primary, #115e59); }
.border-error { border-color: var(--c-danger, #dc2626); }
.hover\:bg-surface-container:hover,
.hover\:bg-surface-container-high:hover,
.hover\:bg-surface-container-low:hover { background-color: var(--c-surface-2, #f1f5f9); }
.hover\:bg-error:hover { background-color: var(--c-danger, #dc2626); }
.hover\:text-primary:hover { color: var(--c-primary, #115e59); }
.hover\:text-white:hover, .hover\:text-on-error:hover { color: #fff; }
.hover\:opacity-80:hover { opacity: .8; }
.hover\:scale-110:hover { transform: scale(1.1); }
.hover\:shadow-xl:hover { box-shadow: 0 20px 25px rgba(15, 23, 42, .14); }

@media (min-width: 640px) {
    .sm\:block { display: block !important; }
    .sm\:inline-flex { display: inline-flex !important; }
    .sm\:flex-row { flex-direction: row; }
    .sm\:items-center { align-items: center; }
    .sm\:justify-between { justify-content: space-between; }
    .sm\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    .sm\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
@media (min-width: 768px) {
    .md\:block { display: block !important; }
    .md\:flex { display: flex !important; }
    .md\:flex-row { flex-direction: row; }
    .md\:gap-md { gap: 1rem; }
    .md\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .md\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (min-width: 1024px) {
    .lg\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .lg\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
    .lg\:w-48 { width: 12rem; }
}
@media (min-width: 1280px) {
    .xl\:w-56 { width: 14rem; }
}

/* (b) The page itself must never scroll sideways. NB: we deliberately do NOT
       set `width: 100vw` — 100vw INCLUDES the vertical scrollbar gutter and so
       would itself force a horizontal scrollbar. Clamping the root with
       overflow-x:hidden + max-width:100% is the correct, side-effect-free fix.
       (The SPA <body> is already overflow-hidden/h-screen; this is a backstop.) */
html, body { overflow-x: hidden; max-width: 100%; }

/* (c) THE key anti-eating rule: let flex/grid CONTAINERS shrink below their
       content's intrinsic width, so an over-long child (a title, a wide table,
       a long word) clips/scrolls INSIDE its box instead of shoving siblings off
       screen. Sets only a MINIMUM, so it can never enlarge anything, and items
       marked `shrink-0`/`flex-shrink-0` are unaffected (their flex-shrink wins). */
.flex, .grid { min-width: 0; }

/* (d) Filter / pill bars wrap to a new line the instant width runs out, rather
       than clipping or forcing a horizontal scroll. Buttons keep their size. */
.filter-bar { flex-wrap: wrap; }
.filter-bar > button { flex-shrink: 0; }

/* --- Global loading spinner (Phase 1) ------------------------------------
   A pure-CSS ring spinner. It inherits `currentColor` so on a button it
   matches the label colour automatically. Used by setLoading() in ui.js. */
.sc-spinner {
    display: inline-block;
    width: 1em;
    height: 1em;
    vertical-align: -0.15em;
    border: 2px solid currentColor;
    border-top-color: transparent;
    border-radius: 50%;
    animation: sc-spin 0.6s linear infinite;
}
@keyframes sc-spin { to { transform: rotate(360deg); } }

/* Non-blocking busy overlay laid over a container (list/card/panel). The
   translucent veil dims the content; the spinner sits centred above it. */
.sc-loading-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: color-mix(in srgb, var(--c-canvas, #ffffff) 55%, transparent);
    z-index: 30;
    pointer-events: none; /* the veil shouldn't swallow clicks behind it */
}
.sc-loading-overlay .sc-spinner {
    width: 2rem;
    height: 2rem;
    border-width: 3px;
    color: var(--c-primary);
}
@media (prefers-reduced-motion: reduce) {
    .sc-spinner { animation-duration: 1.4s; }
}

/* --- App shell: NATURAL DOCUMENT (BLOCK) FLOW ----------------------------
   The page scrolls as ONE document (the <body>), so the CONTENT defines the
   page height and the footer falls naturally at the very end — it is never
   forced to the viewport bottom and never appears "prematurely". <body> is no
   longer h-screen/overflow-hidden; the shell and the body row size to their
   content. The fixed header (h-16 = 4rem) is cleared by the row's pt-16. */
#app-shell { height: auto; width: 100%; }
/* The sidebar+content row ONLY (a <div class="flex">). NB: must be `div.flex`,
   NOT `.flex` — the <header> is also a direct .flex child of #app-shell and must
   not be caught by these height rules. */
#app-shell > div.flex {
    /* CRITICAL: override the Tailwind `h-full` (height:100%) on this row. With a
       fixed/percentage height the row collapses to ~viewport height while the
       content (e.g. a long Candidates table) overflows it — and the footer, a
       sibling placed after the row, then lands ON TOP of that overflow. `auto`
       lets the row grow with its content so the footer always sits after it. */
    height: auto;
    min-height: calc(100vh - 4rem);   /* fill the viewport on short pages too */
    align-items: stretch;             /* columns share the row's full height */
}

/* Sidebar: a fixed-width column that STAYS in view (sticky) while the document
   scrolls, scrolling internally only if the nav itself is taller than the
   viewport. The mobile off-canvas rule below overrides this to position:fixed. */
#app-sidebar {
    position: sticky;
    top: 4rem;                 /* sit just below the fixed 4rem header */
    align-self: flex-start;
    height: calc(100vh - 4rem);
    overflow-y: auto;
    overflow-x: hidden;
    flex-shrink: 0;
}

/* Main content pane: BLOCK FLOW — its height is defined by the content; the
   page (body) scrolls, NOT this pane. It still flexes to fill the width beside
   the sidebar. No min-height, no forced viewport fill, so the footer simply
   falls after the content. */
.main-content {
    display: block;
    flex: 1 1 auto;
    min-width: 0;
    overflow: visible;
}

/* Footer: a distinct, FULL-WIDTH block at the very bottom of the document flow.
   It is now a direct child of #app-shell (AFTER the sidebar+content row), not
   nested in the content column — so it spans the whole width and falls naturally
   at the end of the page. NO margin-top:auto, NO fixed/sticky: it is never pinned
   to the viewport. (position:static + margin-top:0 also neutralise any stray
   utility classes.) */
#app-shell > .app-footer {
    margin-top: 0;
    flex-shrink: 0;
    position: static;
    width: 100%;
}

/* Work surfaces (Grading / Schedule / Chat): on desktop give them a definite
   height so the inner h-full panes resolve and keep their OWN overflow scroll
   (this also keeps the splitter + drag-and-drop working exactly as before).
   They fill the area below the header and the full-width footer follows after —
   reached by scrolling to the end, never overlapped. (On mobile ≤768px these
   stack and flow naturally via the responsive rules further down.) */
@media (min-width: 769px) {
    #reviews-view, #program-view, #messages-view {
        height: calc(100vh - 4rem);
        min-height: 520px;
    }
}

/* ═══════════════════════════════════════════════════════════════════════════
   GLOBAL LAYOUT RESET — force natural document flow everywhere so the footer is
   a normal in-flow block and can NEVER overlay content. (#root / .app-container
   / .page-wrapper don't exist in this DOM but are kept so the reset is complete
   and future-proof; the active selectors here are body, .app-footer and
   .main-content = #app-main.)
   ═══════════════════════════════════════════════════════════════════════════ */
body, #root, .app-container {
    display: block !important;     /* natural document flow */
    min-height: auto !important;
    height: auto !important;
    overflow: visible !important;  /* the document (body) scrolls, nothing clips */
}

/* The footer behaves exactly like body text: an in-flow block, never positioned.
   margin-top:0 — the footer already carries a 1px top border + 3rem of internal
   top padding (.footer-grid), so any extra margin only produced a visible empty
   band ("the gap") between the content and the footer. Flush is correct here.
   (#app-shell's footer is the global one; the gateway/login footer shares the
   class and also flows naturally.) */
.app-footer {
    display: block !important;
    position: relative !important;
    margin-top: 0 !important;
    z-index: 10;
}

/* Content wrapper: allowed to grow with its content, never a clipping/overlay
   container. min-height keeps sparse pages from collapsing. */
.main-content, .page-wrapper {
    min-height: 50vh;
    overflow: visible !important;
}

/* ═══════════════════════════════════════════════════════════════════════════
   HEADER SEARCH STABILITY — the active-view breadcrumb (#nav-active-view) is the
   ONLY variable-width element in the header's left group, so as its label
   changed length (Dashboard ↔ Review Queue ↔ New Submission) the left group
   grew/shrank and shoved the centered search box sideways (~26px of "jump").
   Reserving a fixed width pins the left group, so the search box holds one
   position across every view. Labels fit at all text-size tiers (width + text
   both scale in rem), so it never clips. flex stays for vertical centering. */
#nav-active-view {
    flex: 0 0 12rem;
    width: 12rem;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* ═══════════════════════════════════════════════════════════════════════════
   BRAND LOGO — the Conferience squirrel lockup, theme-aware. The wordmark colour
   must adapt: a navy "Conferience" on light themes (Academic, Light), the white
   lockup on dark themes (Dark, Geometric). We use background-image (not <img>) on
   the desktop marks so the source can swap purely in CSS per [data-theme]. The
   blue squirrel reads on every background, so the mobile/favicon icon is a single
   <img>. Aspect ratio of the lockup is 1221:301 ≈ 4.06. (?v=9 busts the cache.)
   ═══════════════════════════════════════════════════════════════════════════ */
/* The variant follows the CHROME colour BEHIND each logo, not the theme's name —
   e.g. the Academic (oxford) theme has a DARK navy header but a LIGHT cream login
   page, so its header needs the white lockup while its gateway needs the navy one.
   Measured backgrounds: header is dark for oxford/dark/geometric (white lockup) and
   light only for the Light theme (navy lockup); the gateway is light for
   oxford/light (navy lockup) and dark for dark/geometric (white lockup). */
#app-logo.brand-mark {
    display: inline-block;
    height: 2.4rem;
    width: 9.75rem;                 /* 2.4rem × 4.06 ≈ 9.75rem, keeps the lockup undistorted */
    /* default: white lockup on the dark headers (oxford, dark, geometric) */
    background: url('../assets/img/logo-dark.png?v=9') left center / contain no-repeat;
}
[data-theme="light"] #app-logo.brand-mark {
    background-image: url('../assets/img/logo-light.png?v=9'); /* white header → navy lockup */
}
#gateway-logo.brand-hero {
    display: block;
    height: 64px;
    width: 260px;                   /* 64 × 4.06 ≈ 260 */
    max-width: 100%;
    /* default: navy lockup on the light login pages (oxford cream, light white) */
    background: url('../assets/img/logo-light.png?v=9') center / contain no-repeat;
}
[data-theme="dark"] #gateway-logo.brand-hero {
    background-image: url('../assets/img/logo-dark.png?v=9'); /* dark login page → white lockup */
}
/* The squirrel-only icon: hidden on desktop, shown ≤768px (the lockup + "Abstracts"
   carry .hide-on-mobile and disappear there). */
.brand-icon { display: none; height: 2.25rem; width: auto; }
@media (max-width: 768px) {
    .brand-icon { display: inline-block; }
}

/* ═══════════════════════════════════════════════════════════════════════════
   PRE-AUTH GATEWAY PREFERENCES — language · theme · text size, shown below the
   sign-in card so users can set them before logging in. All colours are theme
   variables so the row reads on every login background (cream / white / black).
   ═══════════════════════════════════════════════════════════════════════════ */
.gateway-prefs {
    position: static;
    z-index: 1;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 0.4rem 0.6rem;
    max-width: 100%;
    margin: 1rem auto 0;
}
/* On the login/gateway screen only, keep a clear gap between the sign-in card
   (its last line is the "Create an account" toggle) and the shared footer. The
   global .app-footer sits flush by design; here we scope an override so the two
   never touch, even in register mode when the taller card collapses the auto
   margins. Does not affect the in-app footer (#app-shell > .app-footer). */
#gateway-view > .app-footer {
    margin-top: 2rem !important;
}
/* Show/hide password eye button, pinned inside the password input. */
.pw-toggle {
    position: absolute;
    right: 0.5rem;
    top: 50%;
    transform: translateY(-50%);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.25rem;
    border: 0;
    border-radius: 0.375rem;
    background: transparent;
    color: var(--c-text-variant, #64748b);
    cursor: pointer;
    transition: color 0.15s ease;
}
.pw-toggle:hover { color: var(--c-accent, #0ea5e9); }
.gw-pref-group { display: inline-flex; align-items: center; gap: 0.35rem; }
.gw-pref-sep { width: 1px; height: 1.25rem; background: var(--c-border, #e2e8f0); }
.gw-pref-btn {
    display: inline-flex; align-items: center; gap: 0.3rem;
    padding: 0.3rem 0.6rem; border-radius: 9999px;
    border: 1px solid var(--c-border, #e2e8f0);
    color: var(--c-text-variant, #475569);
    font-size: 0.8rem; font-weight: 700; line-height: 1;
    transition: color 0.15s ease, border-color 0.15s ease;
}
.gw-pref-btn .material-symbols-outlined { font-size: 18px; }
.gw-pref-btn:hover { color: var(--c-accent, #0f766e); border-color: var(--c-accent, #0f766e); }
.gw-theme-swatch {
    width: 1.6rem; height: 1.6rem; border-radius: 0.5rem;
    border: 1px solid var(--c-border, #e2e8f0);
    cursor: pointer;
    transition: transform 0.12s ease;
}
.gw-theme-swatch:hover { transform: scale(1.08); }
.gw-font-btn {
    min-width: 1.9rem; height: 1.9rem; padding: 0 0.35rem;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 0.5rem; border: 1px solid var(--c-border, #e2e8f0);
    color: var(--c-text-variant, #475569); font-weight: 700; line-height: 1;
    transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
}
.gw-font-btn:hover { color: var(--c-accent, #0f766e); border-color: var(--c-accent, #0f766e); }
.gw-font-btn.gw-active {
    background: var(--c-primary, #0f766e);
    color: var(--c-on-primary, #ffffff);
    border-color: var(--c-primary, #0f766e);
}

/* ═══════════════════════════════════════════════════════════════════════════
   HEADER PREFERENCE CONTROLS — the compact language pill + text-size group in
   the top-right of the in-app header (moved out of the removed Settings modal).
   Colours follow the theme tokens; chrome overrides below keep them legible on
   the header's chrome background in every theme.
   ═══════════════════════════════════════════════════════════════════════════ */
.header-pref-btn {
    display: inline-flex; align-items: center; gap: 0.3rem;
    padding: 0.3rem 0.6rem; border-radius: 9999px;
    border: 1px solid var(--c-border, #e2e8f0);
    color: var(--c-text-variant, #475569);
    font-size: 0.8rem; font-weight: 700; line-height: 1;
    transition: color 0.15s ease, border-color 0.15s ease;
}
.header-pref-btn:hover { color: var(--c-accent, #0ea5e9); border-color: var(--c-accent, #0ea5e9); }
.header-font-group { display: inline-flex; align-items: center; gap: 0.25rem; }
.header-font-btn {
    min-width: 1.7rem; height: 1.7rem; padding: 0 0.3rem;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 0.45rem; border: 1px solid var(--c-border, #e2e8f0);
    color: var(--c-text-variant, #475569); font-weight: 700; line-height: 1;
    transition: color 0.15s ease, border-color 0.15s ease, background-color 0.15s ease;
}
.header-font-btn:hover { border-color: var(--c-accent, #0ea5e9); color: var(--c-accent, #0ea5e9); }
.header-font-btn.gw-active {
    background: var(--c-primary, #0ea5e9);
    color: var(--c-on-primary, #ffffff);
    border-color: var(--c-primary, #0ea5e9);
}

/* Round icon buttons in the header (text-size trigger + theme toggle). */
.header-icon-btn {
    display: inline-flex; align-items: center; justify-content: center;
    padding: 0.4rem; border-radius: 9999px; cursor: pointer;
    color: var(--c-text-variant, #475569);
    transition: background-color 0.15s ease, color 0.15s ease;
}
.header-icon-btn:hover { background: var(--c-surface-3, #e7edf3); color: var(--c-accent, #0ea5e9); }

/* Text-size dropdown rows: glyph preview + label, with a check on the active tier
   (syncGatewayPrefs toggles .gw-active via the .font-toggle contract). */
.fontsize-item {
    display: flex; align-items: center; gap: 0.6rem; width: 100%;
    padding: 0.45rem 0.6rem; border-radius: 0.55rem; text-align: left; line-height: 1;
    color: var(--c-text, #0f1729);
    transition: background-color 0.12s ease, color 0.12s ease;
}
.fontsize-item:hover { background: var(--c-surface-3, #e7edf3); }
.fontsize-glyph { width: 1.5rem; text-align: center; font-weight: 800; flex-shrink: 0; }
.fontsize-check { margin-left: auto; color: var(--c-primary, #0ea5e9); visibility: hidden; }
.fontsize-item.gw-active { color: var(--c-primary, #0ea5e9); font-weight: 700; }
.fontsize-item.gw-active .fontsize-check { visibility: visible; }

/* "Abstracts" should read as the SECOND word of the wordmark, not a bold label
   bolted on. So: nestle it right after "Conferience" (negative margin absorbs the
   lockup image's trailing whitespace), drop to the thin Inter 300 weight that
   echoes the logo's slender wordmark, match its cap height, and nudge the baseline
   so the two words sit on one line. Colour stays the brand primary as an accent. */
.brand-lockup { gap: 0; }
.brand-suffix {
    font-family: 'Inter', sans-serif;
    font-weight: 300;
    font-size: 1.5rem;          /* matches the "Conferience" cap height */
    line-height: 1;
    letter-spacing: 0.005em;
    margin-left: 0.2rem;        /* breathing room after "Conferience" (was -0.3rem, too tight) */
    align-self: center;
    position: relative;
    /* The wordmark inside the lockup image sits in its LOWER half (the squirrel
       makes the image tall), so its baseline is near the image bottom. "Abstracts"
       is row-centred, so it must drop ~0.5em to share that baseline. */
    top: 0.46em;
}

/* --- Rich-text editor + rendered content (Phase 2, Task 2) ---------------- */
.rt-toolbar {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.25rem 0.5rem;
    border: 1px solid var(--c-border, #cbd5e1);
    border-bottom: 0;
    border-radius: 0.5rem 0.5rem 0 0;
    background: var(--c-surface, #f8fafc);
    flex-wrap: wrap;
}
.rt-toolbar .rt-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0.25rem;
    border-radius: 0.375rem;
    color: var(--c-text-muted, #64748b);
}
.rt-toolbar .rt-btn:hover { background: var(--c-surface-2, #e2e8f0); color: var(--c-primary, #0f766e); }
.rt-toolbar .rt-sep { width: 1px; align-self: stretch; margin: 0.15rem 0.25rem; background: var(--c-border, #cbd5e1); }

.rt-editor {
    width: 100%;
    /* Theme-aware surface + text so the abstract body reads correctly in BOTH
       modes (dark text on light, light text on dark) — was hardcoded #fff. */
    background: var(--c-surface, #ffffff);
    color: var(--c-text, #0f1729);
    border: 1px solid var(--c-border, #cbd5e1);
    border-radius: 0 0 0.5rem 0.5rem;
    padding: 0.75rem;
    font-size: 0.875rem;
    line-height: 1.5;
    overflow-y: auto;
    outline: none;
}
.rt-editor:focus { border-color: var(--c-primary, #0f766e); box-shadow: 0 0 0 2px color-mix(in srgb, var(--c-primary, #0f766e) 20%, transparent); }
/* Placeholder for the empty contentEditable (data-placeholder set from i18n). */
.rt-editor:empty::before { content: attr(data-placeholder); color: var(--c-text-muted, #94a3b8); pointer-events: none; }

/* Tailwind's reset strips list markers — restore them inside the editor and any
   rendered rich body so bullet/numbered lists actually look like lists. */
.rt-editor ul, .rich-content ul { list-style: disc; padding-left: 1.5rem; margin: 0.25rem 0; }
.rt-editor ol, .rich-content ol { list-style: decimal; padding-left: 1.5rem; margin: 0.25rem 0; }
.rt-editor li, .rich-content li { margin: 0.1rem 0; }
.rich-content b, .rich-content strong { font-weight: 700; }
.rich-content i, .rich-content em { font-style: italic; }
.rich-content u { text-decoration: underline; }

/* --- Phase 6, Task 2 + Phase 7, Task 1: consistent page width on all main
   content views. A single harmonized container (~1280px) prevents both
   over-wide "stretch" on ultrawide monitors and "squish" on mid screens;
   width:95% keeps comfortable side gutters; margin auto centers. ID specificity
   beats the Tailwind max-w-* utilities already on these views, so no !important
   is needed. The full-bleed work surfaces (Grading #reviews-view, Schedule
   #program-view) are excluded so their drag-and-drop calendar/grading panes keep
   the full width. */
#submissions-list, #candidate-home, #reviewer-queue, #reviewer-leaderboard,
#analytics-view, #admin-candidates, #admin-reviewers, #admin-feedback,
#messages-view {
    max-width: 1280px;
    width: 95%;
    margin-left: auto;
    margin-right: auto;
}
/* Phase 7, Task 1: the Messages + Reviewer pane layouts must fill the width of
   their container — no narrow, half-empty panels. The conversation/calendar
   pane next to the (fixed-width) list/sidebar flexes to absorb all spare width. */
#messages-view .chat-thread-pane,
#reviews-view .flex-1,
#program-view .flex-1 { flex: 1 1 0%; min-width: 0; }
/* Wide panels (leaderboard rows, user lists) scroll horizontally inside the
   wrapper instead of clipping, on narrow screens. */
#leaderboard-list, #admin-reviewers-list { overflow-x: auto; }

/* --- Phase 7, Task 2: responsive, centered modal sizing. Every content modal
   (Settings, abstract analysis, legal docs, etc.) shares one fluid box:
   width:90% so it breathes on phones, max-width:800px so it never sprawls on
   desktop, and max-height:90vh + scroll so it stays fully on-screen at ANY text
   size (Normal → Extra Large). ID specificity overrides the Tailwind max-w-2xl /
   max-h-[85vh] utilities already in the markup, so no !important needed. The
   small confirm/alert dialog keeps its compact max-width but adopts the same
   responsive width + safe height. */
#app-modal > .relative {
    width: 90%;
    max-width: 800px;
    max-height: 90vh;
    overflow-y: auto;
}
#confirm-modal > .relative {
    width: 90%;
    max-height: 90vh;
    min-height: 0;          /* beat .widget-card's min-height:min-content so max-height
                               can cap the box and the content scrolls instead of clipping */
    overflow-y: auto;
}

/* --- Brand logo placeholder (Phase 4, Task 2) ----------------------------
   A dashed, primary-tinted slot standing in for the real company logo.
   Swap the inner <span> for an <img src="…"> when the asset is ready. */
.logo-placeholder {
    background: color-mix(in srgb, var(--c-primary, #0f766e) 12%, transparent);
    border: 2px dashed var(--c-primary, #0f766e);
    color: var(--c-primary, #0f766e);
}

/* --- Global footer (Phase 5.1): 4-column grid + copyright bar ------------- */
.app-footer {
    flex-shrink: 0;
    margin-top: auto;            /* never overlaps content; sits below short pages */
    background-color: var(--c-surface-2, #f1f5f9);   /* soft, theme-aware pastel/neutral */
    color: var(--c-text-variant, #475569);
    border-top: 1px solid var(--c-border, var(--c-chrome-border, #e2e8f0));
    font-size: 14px;             /* readable per spec */
    line-height: 1.6;            /* comfortable, clickable link spacing */
}
.footer-grid {
    display: grid;
    grid-template-columns: 1.4fr 1fr 1fr 1fr;
    gap: 2rem 3rem;              /* generous breathing room between columns */
    padding: 3rem 1.5rem 2.5rem;
}
.footer-brand { font-weight: 700; font-size: 1rem; color: var(--c-text, #0f172a); }
.footer-tagline { max-width: 28ch; color: var(--c-text-variant, #475569); }
.footer-h {
    font-weight: 700; font-size: 0.95rem; color: var(--c-text, #0f172a);
    margin-bottom: 0.9rem;
}
.footer-list { display: flex; flex-direction: column; gap: 0.6rem; }
.footer-link {
    color: var(--c-text-variant, #475569);
    display: inline-flex; align-items: center; gap: 0.5rem;
    line-height: 1.6; text-align: left; transition: color 0.15s ease;
}
.footer-link:hover { color: var(--c-primary, #3b82f6); }
.footer-list .material-symbols-outlined { color: var(--c-primary, #3b82f6); font-size: 18px; }
.footer-social {
    display: inline-flex; align-items: center; justify-content: center;
    width: 38px; height: 38px; border-radius: 9999px;
    background: var(--c-surface, #ffffff); color: var(--c-text-variant, #475569);
    border: 1px solid var(--c-border, var(--c-chrome-border, #e2e8f0));
    transition: all 0.15s ease;
}
.footer-social:hover { background: var(--c-primary, #3b82f6); color: #ffffff; border-color: var(--c-primary, #3b82f6); }
.footer-copyright {
    border-top: 1px solid var(--c-border, var(--c-chrome-border, #e2e8f0));
    padding: 1rem 1.5rem; text-align: center; font-size: 13px;
    color: var(--c-text-variant, #64748b);
}
@media (max-width: 900px) { .footer-grid { grid-template-columns: 1fr 1fr; gap: 2rem; } }
@media (max-width: 560px) { .footer-grid { grid-template-columns: 1fr; gap: 1.75rem; } }

/* Back-to-Top floating button (Phase 5.1) — fixed bottom-right; .hidden until
   the user has scrolled the main pane down a little. */
#back-to-top {
    position: fixed; bottom: 1.5rem; right: 1.5rem; z-index: 55;
    width: 2.75rem; height: 2.75rem; border-radius: 9999px;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--c-primary, #3b82f6); color: var(--c-on-primary, #ffffff);
    box-shadow: 0 6px 20px rgba(15, 23, 42, 0.25);
    transition: transform 0.2s ease, opacity 0.2s ease;
}
#back-to-top:hover { transform: translateY(-2px); }

/* Bottom-LEFT corner quick-actions (Notifications + Support), styled EXACTLY like
   the bottom-right back-to-top button and stacked in the corner for symmetry. */
#corner-actions {
    position: fixed; bottom: 1.5rem; left: 1.5rem; z-index: 55;
    display: flex; flex-direction: column; gap: 0.75rem;
}
.fab-btn {
    width: 2.75rem; height: 2.75rem; border-radius: 9999px;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--c-primary, #3b82f6); color: var(--c-on-primary, #ffffff);
    box-shadow: 0 6px 20px rgba(15, 23, 42, 0.25);
    transition: transform 0.2s ease, opacity 0.2s ease;
    cursor: pointer;
}
.fab-btn:hover { transform: translateY(-2px); }

/* Card / widget: white surface, hairline slate border, soft ambient shadow.
   On hover the border deepens to the primary teal (flat, professional feel). */
.widget-card {
    background: #ffffff;
    border: 1px solid #e2e8f0;
    border-radius: 0.75rem;
    box-shadow: 0 1px 2px rgba(15, 23, 42, 0.04), 0 1px 3px rgba(15, 23, 42, 0.06);
    transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.widget-card:hover {
    border-color: #115e59;
}

.interactive-btn {
    transition: all 0.2s ease;
}
.interactive-btn:active {
    transform: scale(0.98);
}

.page-transition {
    animation: fadeIn 0.3s ease-in-out;
}
@keyframes fadeIn {
    from { opacity: 0; transform: translateY(10px); }
    to { opacity: 1; transform: translateY(0); }
}

/* Drag-and-drop upload zone — accent tint on hover-over (theme-aware) */
.upload-area.dragover {
    background-color: color-mix(in srgb, var(--c-accent) 10%, var(--c-surface));
    border-color: var(--c-accent);
}

/* ─────────────────────────────────────────────────────────────────────────
   Phase 7 — Responsive design (native CSS media queries; mobile ≤ 768px).
   Mobile-only controls (hamburger, sidebar close, chat back) and the overlay are
   hidden by default and revealed only in the mobile media query below. Layout
   overrides are id-scoped so they outrank the CDN's single-class utilities
   regardless of stylesheet order.
   ───────────────────────────────────────────────────────────────────────── */
.sidebar-toggle, .sidebar-close, .chat-back { display: none; }
#sidebar-overlay { display: none; }
.abs-track-mobile { display: none; } /* mobile-only Track dropdown (shown ≤768px) */

/* ─────────────────────────────────────────────────────────────────────────
   Schedule calendar — TABLET/iPad fix (Phase 9.5). Next to the app sidebar AND
   the "Unscheduled" panel, a 5-column grid has too little width on tablets
   (≈768–1100px) and the day columns overlap/look broken. So we trigger the
   vertical column stacking (full-width day blocks, scroll down) at ≤1100px —
   not just on phones. Wider than the 768px breakpoint used for everything else,
   hence its own query. Desktop (>1100px) keeps the original 5-column grid.
   ───────────────────────────────────────────────────────────────────────── */
@media (max-width: 1100px) {
    #calendar-grid { display: flex; flex-direction: column; min-width: 0; height: auto; }
    #calendar-grid > .day-column { width: 100%; }
}

/* Desktop: the primary navigation lives in the header top bar, so the left
   sidebar is retired here — it now serves only as the ≤768px off-canvas menu.
   #app-main is flex-1 and reclaims the full width automatically. */
@media (min-width: 769px) {
    #app-sidebar { display: none; }
}

@media (max-width: 768px) {
    /* Header: reveal hamburger, hide the wordmark, tighten the search margins. */
    .sidebar-toggle { display: inline-flex; }
    .hide-on-mobile { display: none !important; }
    .header-search { margin-left: 0.5rem !important; margin-right: 0.5rem !important; }

    /* Off-canvas sidebar: fixed below the 4rem header, slides in over the content. */
    #app-sidebar {
        position: fixed;
        top: 4rem; left: 0; bottom: 0;
        width: 280px; max-width: 85vw;
        transform: translateX(-100%);
        transition: transform 0.25s ease;
        z-index: 60;
        box-shadow: 0 10px 40px rgba(15, 23, 42, 0.25);
    }
    body.sidebar-open #app-sidebar { transform: translateX(0); }
    .sidebar-close { display: inline-flex; }

    /* Dimmed overlay behind the open sidebar (tap anywhere to close). */
    #sidebar-overlay {
        display: block;
        position: fixed; inset: 4rem 0 0 0;
        background: rgba(15, 23, 42, 0.45);
        z-index: 55;
        opacity: 0; pointer-events: none;
        transition: opacity 0.25s ease;
    }
    body.sidebar-open #sidebar-overlay { opacity: 1; pointer-events: auto; }

    /* Grading + Schedule split panes stack vertically (sliders/textareas already
       stack via the grid's mobile-default single column). */
    #reviews-view > .flex,
    #program-view > .flex {
        flex-direction: column;
        height: auto;
        min-height: 100%;
    }
    #reviews-view > .flex > aside,
    #program-view > .flex > aside {
        width: 100%;
        height: auto;
        max-height: 45vh;
        border-right: none;
        border-bottom: 1px solid var(--c-border);
    }

    /* Schedule header (Phase 9.5): on phones the week range + "Today" button
       overflowed and got clipped. Let the header wrap, drop the 150px min-width
       on the date label, and scale the label/nav down so "Today" stays visible. */
    #schedule-header { gap: 0.5rem; padding: 0.85rem 1rem; }
    #schedule-header #cal-week-label {
        min-width: 0;
        font-size: 0.72rem;
        text-align: left;
    }
    #schedule-header .cal-week-nav { gap: 0.15rem; row-gap: 0.35rem; }

    /* Messages: contacts-first. Once a contact is chosen, chat.js adds .chat-active
       to #messages-view → the contacts list hides and the thread takes the screen. */
    .chat-back { display: inline-flex; }
    #messages-view .chat-contacts-pane { width: 100%; border-right: none; }
    #messages-view .chat-thread-pane { display: none; }
    #messages-view.chat-active .chat-contacts-pane { display: none; }
    #messages-view.chat-active .chat-thread-pane { display: flex; width: 100%; }

    /* Single-contact roles (Candidate/Reviewer): skip the contacts list entirely —
       land in the Admin thread, no Back button. */
    #messages-view.single-contact .chat-contacts-pane { display: none; }
    #messages-view.single-contact .chat-thread-pane { display: flex; width: 100%; }
    #messages-view.single-contact .chat-back { display: none; }

    /* (1) Dashboard "New Submission": compact pill, not a full-width block. */
    .dash-new-btn {
        padding: 0.4rem 0.9rem;
        font-size: 0.8125rem;
        border-radius: 9999px;
        box-shadow: none;
        align-self: flex-start;
        max-width: max-content;
    }

    /* (2) Wizard nav (Back / Cancel / Next): smaller padding + text; wrap cleanly. */
    #wizard-nav { gap: 0.5rem; flex-wrap: wrap; }
    #wizard-nav button { padding: 0.45rem 0.8rem; font-size: 0.8125rem; }

    /* (3+4) Track filters (My Abstracts AND Review Queue): hide the scrolling
       buttons; show the Track dropdown (grows) beside any sort dropdown. */
    #track-filter, #queue-track-filter { display: none; }
    .abs-track-mobile { display: block; flex: 1 1 auto; min-width: 0; }

    /* Mobile tap-to-move: the selected schedule card gets a clear accent ring. */
    .tap-selected {
        outline: 2px solid var(--c-accent); outline-offset: 1px;
        box-shadow: 0 0 0 4px var(--c-accent-ring) !important;
    }

    /* Phase 9 — long-press touch-drag. These three elements only ever exist on
       touch screens (created by src/reviewer/program.js, gated to ≤768px):
         .touch-drag-clone  — the floating copy that follows the finger
         .touch-drag-source — the original card, dimmed while it's "lifted"
         .touch-drop-hover  — the day column / tray under the finger right now */
    .touch-drag-clone {
        position: fixed;
        z-index: 9999;
        margin: 0;
        pointer-events: none;            /* so elementFromPoint() sees the drop zone */
        transform: scale(1.04) rotate(1.5deg);
        box-shadow: 0 12px 28px rgba(15, 23, 42, 0.28);
        opacity: 0.96;
        transition: none;
    }
    .touch-drag-source { opacity: 0.35 !important; }
    .touch-drop-hover {
        outline: 2px solid var(--c-accent);
        outline-offset: -2px;
        background: color-mix(in srgb, var(--c-accent) 12%, transparent) !important;
    }

    /* Admin data table → stacked cards. Hide the header row; each <tr> is a card
       and each <td> shows its column name (data-label) above the value. */
    #admin-candidates table { min-width: 0; }
    #admin-candidates thead { display: none; }
    #admin-candidates tbody,
    #admin-candidates tr,
    #admin-candidates td { display: block; width: 100%; }
    #admin-candidates tr {
        border: 1px solid var(--c-border);
        border-radius: 0.75rem;
        margin-bottom: 0.75rem;
        padding: 0.5rem 0.85rem;
        background: var(--c-surface);
    }
    #admin-candidates td {
        padding: 0.35rem 0;
        border: none;
        text-align: left;
        white-space: normal;
    }
    #admin-candidates td::before {
        content: attr(data-label);
        display: block;
        font-weight: 700;
        text-transform: uppercase;
        font-size: 0.625rem;
        letter-spacing: 0.03em;
        color: var(--c-text-variant);
        margin-bottom: 0.15rem;
    }
}

/* ─────────────────────────────────────────────────────────────────────────
   Leaderboard cards — NARROW-PHONE fix (Phase 9.6). On ≤480px (iPhone SE 375,
   Galaxy S8 360) the medal + avatar + name + "REVIEWS" + chevron couldn't fit
   on one row and the text overlapped. We let the row wrap: medal · avatar · name
   stay on the top line (the name block keeps the remaining width); the "REVIEWS"
   stat drops to its own full-width line below, left-aligned and inline; and the
   decorative chevron is hidden (the whole card is already a button). Phones wider
   than 480px and desktop are untouched — they keep the single-row layout.
   Targets both the reviewer list (#leaderboard-list) and the admin list
   (#admin-reviewers-list). ───────────────────────────────────────────────── */
@media (max-width: 480px) {
    #leaderboard-list > button,
    #admin-reviewers-list > button {
        flex-wrap: wrap;
        column-gap: 0.6rem;
        row-gap: 0.4rem;
    }
    /* Name + "avg score given" block fills the rest of the top line. */
    #leaderboard-list > button > .flex-1,
    #admin-reviewers-list > button > .flex-1 {
        flex: 1 1 8rem;
    }
    /* "REVIEWS" stat wraps to its own full-width line, left-aligned, with the
       count and label sitting inline ("12  REVIEWS") instead of stacked-right. */
    #leaderboard-list > button > .text-right,
    #admin-reviewers-list > button > .text-right {
        flex-basis: 100%;
        text-align: left;
        display: flex;
        align-items: baseline;
        gap: 0.35rem;
    }
    /* Hide the trailing chevron on narrow screens to free horizontal space. */
    #leaderboard-list > button > .material-symbols-outlined,
    #admin-reviewers-list > button > .material-symbols-outlined {
        display: none;
    }
}

/* Deep-link highlight: a brief pulse on the card a notification routed to, so the
   user sees exactly which abstract was referenced. Animates an accent ring + lift.
   Theme-aware via the accent variables (one keyframe serves every theme). */
.deep-link-highlight {
    animation: deepLinkPulse 2.2s ease-out;
}
@keyframes deepLinkPulse {
    0%   { box-shadow: 0 0 0 0 var(--c-accent); border-color: var(--c-accent); }
    25%  { box-shadow: 0 0 0 6px var(--c-accent-ring); border-color: var(--c-accent); }
    100% { box-shadow: 0 0 0 0 rgba(0, 0, 0, 0); }
}

/* Schedule date-finalization padlock (Reviewer calendar): open=tentative (amber),
   closed=finalized (teal/primary). Colours come from Tailwind utilities; this just
   adds the press/hover feel consistent with our other interactive controls. */
.schedule-lock-btn { transition: transform 0.15s ease, color 0.15s ease; cursor: pointer; }
.schedule-lock-btn:active { transform: scale(0.9); }

/* Wizard progress dots (theme-aware accent fills) */
/* Scoped under [data-theme] (0,3,0) so the accent fill outranks the `.bg-white`
   utility the dots also carry (0,2,0) — otherwise the light surface fill would
   win and the white number would vanish against it. The number is always
   --c-on-primary, which contrasts the accent fill in every theme. */
[data-theme] .wizard-dot.active { background-color: var(--c-primary); border-color: var(--c-primary); color: var(--c-on-primary); }
[data-theme] .wizard-dot.completed { background-color: var(--c-accent); border-color: var(--c-accent); color: var(--c-on-primary); }

.spa-view { display: none; }
.spa-view.active { display: block; }

.custom-scrollbar::-webkit-scrollbar { width: 4px; }
.custom-scrollbar::-webkit-scrollbar-track { background: transparent; }
.custom-scrollbar::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 10px; }

/* Range slider (review scores) — accent thumb on a soft accent track (theme-aware) */
input[type=range] {
    -webkit-appearance: none;
    width: 100%;
    background: transparent;
}
input[type=range]::-webkit-slider-thumb {
    -webkit-appearance: none;
    height: 18px;
    width: 18px;
    border-radius: 50%;
    background: var(--c-accent);
    cursor: pointer;
    margin-top: -7px;
    box-shadow: 0 0 2px rgba(0,0,0,0.2);
}
input[type=range]::-webkit-slider-runnable-track {
    width: 100%;
    height: 4px;
    cursor: pointer;
    background: var(--c-badge);
    border-radius: 2px;
}

/* ═════════════════════════════════════════════════════════════════════════
   PREMIUM THEMING ENGINE (Phase 11).

   The colour system is now driven by CSS custom properties keyed off a single
   `data-theme` attribute on <html> (set by the inline boot script before first
   paint, and by core/settings.js when the user picks a theme). Each theme below
   declares the same vocabulary of variables; one shared block of utility-class
   mappings (scoped under `[data-theme]` so it always outranks the CDN's
   single-class utilities) paints the whole app from those variables.

   `light` and `dark` reproduce the original hex EXACTLY, so the legacy look is
   byte-for-byte preserved — the six extra palettes are pure additions.

   Variable vocabulary:
     --c-canvas      app background (bg-surface / bg-background / body)
     --c-surface     cards + inputs (white / lowest / bright)
     --c-surface-2   low containers (container / container-low / secondary-container)
     --c-surface-3   raised containers (container-high / variant / dim / fixed)
     --c-surface-4   strongest hover surface
     --c-text        primary text (on-surface)
     --c-text-variant muted text (on-surface-variant / outline text)
     --c-text-faint  placeholder text
     --c-accent      bright accent: links, icons, active borders (text-primary)
     --c-accent-ring translucent accent for focus rings / pulses
     --c-primary     solid accent FILL for buttons (bg-primary)
     --c-on-primary  text on the solid fill (text-on-primary)
     --c-border      hairline borders (outline-variant)
     --c-border-strong stronger borders (outline)
     --c-error       error/destructive text
     --c-tertiary    tertiary info text
     --c-badge       small filled badge background (bg-primary-fixed)
     --c-badge-text  text on that badge (text-on-primary-fixed)
     --c-shadow      card shadow
     --c-scrollbar   custom scrollbar thumb
     --c-pattern     decorative background image (Geometric theme only)
   ═════════════════════════════════════════════════════════════════════════ */

/* ── CHROME variables ───────────────────────────────────────────────────────
   In addition to the content palette above, each theme declares a "chrome"
   palette for the app frame (left sidebar + top header). This lets a theme give
   the chrome a strong accent (e.g. Oxford's navy) WITHOUT colouring the reading
   area. Neutral themes simply point their chrome vars at their surface palette,
   so their chrome stays neutral with accent active-states (the classic look).
     --c-chrome-bg          sidebar + header background
     --c-chrome-fg          icons / default foreground on the chrome
     --c-chrome-accent      wordmark, username, active header tab (text + underline)
     --c-chrome-muted       role label, inactive nav items, dividers' text
     --c-chrome-border      chrome borders / dividers
     --c-chrome-hover       hover background for chrome controls
     --c-chrome-active-bg   active sidebar nav item + avatar background
     --c-chrome-active-text text on the active item / avatar
   ──────────────────────────────────────────────────────────────────────── */

/* 1 — OXFORD (Ακαδημαϊκό) — DEFAULT. Warm cream reading area, deep academic navy
   chrome (sidebar/header) + navy primary buttons. Inspired by Oxford Abstracts. */
:root,
[data-theme="oxford"] {
    --c-canvas: #f4f1ea;
    --c-surface: #fbfaf7;
    --c-surface-2: #efeae0;
    --c-surface-3: #e6e0d2;
    --c-surface-4: #d8d1bf;
    --c-text: #1f2430;
    --c-text-variant: #6b6657;
    --c-text-faint: #9c9684;
    --c-accent: #0f2041;
    --c-accent-ring: rgba(15, 32, 65, 0.22);
    --c-primary: #0f2041;
    --c-on-primary: #ffffff;
    --c-border: #e4ddcd;
    --c-border-strong: #cfc6b2;
    --c-error: #b91c1c;
    --c-tertiary: #1e3a5f;
    --c-badge: #dce2ee;
    --c-badge-text: #0f2041;
    --c-shadow: 0 1px 2px rgba(31, 26, 15, 0.05), 0 1px 3px rgba(31, 26, 15, 0.09);
    --c-scrollbar: #cfc6b2;
    --c-pattern: none;
    /* Navy chrome */
    --c-chrome-bg: #0f2041;
    --c-chrome-fg: #e7eaf1;
    --c-chrome-accent: #ffffff;
    --c-chrome-muted: #9da8c0;
    --c-chrome-border: #20335c;
    --c-chrome-hover: #1a2f57;
    --c-chrome-active-bg: #27457f;
    --c-chrome-active-text: #ffffff;
}

/* 2 — LIGHT (Φωτεινό) — Classic: pure white reading area, turquoise/teal accents,
   neutral white chrome with teal active states (the original setup). */
[data-theme="light"] {
    --c-canvas: #ffffff;
    --c-surface: #ffffff;
    --c-surface-2: #f1f5f9;
    --c-surface-3: #e2e8f0;
    --c-surface-4: #cbd5e1;
    --c-text: #0f172a;
    --c-text-variant: #64748b;
    --c-text-faint: #94a3b8;
    --c-accent: #0f766e;
    --c-accent-ring: rgba(15, 118, 110, 0.22);
    --c-primary: #0f766e;
    --c-on-primary: #ffffff;
    --c-border: #e2e8f0;
    --c-border-strong: #cbd5e1;
    --c-error: #dc2626;
    --c-tertiary: #1e3a8a;
    --c-badge: #ccfbf1;
    --c-badge-text: #134e4a;
    --c-shadow: 0 1px 2px rgba(15, 23, 42, 0.04), 0 1px 3px rgba(15, 23, 42, 0.06);
    --c-scrollbar: #cbd5e1;
    --c-pattern: none;
    /* Neutral white chrome, teal accents */
    --c-chrome-bg: #ffffff;
    --c-chrome-fg: #0f172a;
    --c-chrome-accent: #0f766e;
    --c-chrome-muted: #64748b;
    --c-chrome-border: #e2e8f0;
    --c-chrome-hover: #f1f5f9;
    --c-chrome-active-bg: #0f766e;
    --c-chrome-active-text: #ffffff;
}

/* 3 — DARK (Σκοτεινό) — High Contrast: pure-black reading area, white/gray accents. */
[data-theme="dark"] {
    --c-canvas: #000000;
    --c-surface: #121212;
    --c-surface-2: #161616;
    --c-surface-3: #1f1f1f;
    --c-surface-4: #2a2a2a;
    --c-text: #f5f5f5;
    --c-text-variant: #a3a3a3;
    --c-text-faint: #6e6e6e;
    --c-accent: #ffffff;
    --c-accent-ring: rgba(255, 255, 255, 0.28);
    --c-primary: #fafafa;
    --c-on-primary: #0a0a0a;
    --c-border: #2a2a2a;
    --c-border-strong: #3d3d3d;
    --c-error: #f87171;
    --c-tertiary: #d4d4d4;
    --c-badge: #1f1f1f;
    --c-badge-text: #ffffff;
    --c-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
    --c-scrollbar: #3d3d3d;
    --c-pattern: none;
    /* Near-black chrome, white active pill */
    --c-chrome-bg: #0c0c0c;
    --c-chrome-fg: #f5f5f5;
    --c-chrome-accent: #ffffff;
    --c-chrome-muted: #a3a3a3;
    --c-chrome-border: #262626;
    --c-chrome-hover: #1f1f1f;
    --c-chrome-active-bg: #ffffff;
    --c-chrome-active-text: #0a0a0a;
}

/* 4 — GEOMETRIC (Γεωμετρικό) — Subtle Pattern: #18181B reading area with a faint
   CSS-only grid texture, soft-blue accents. */
[data-theme="geometric"] {
    --c-canvas: #18181b;
    --c-surface: #202024;
    --c-surface-2: #202024;
    --c-surface-3: #2a2a30;
    --c-surface-4: #38383f;
    --c-text: #e4e4e7;
    --c-text-variant: #a1a1aa;
    --c-text-faint: #71717a;
    --c-accent: #60a5fa;
    --c-accent-ring: rgba(96, 165, 250, 0.26);
    --c-primary: #3b82f6;
    --c-on-primary: #ffffff;
    --c-border: #2a2a30;
    --c-border-strong: #3f3f46;
    --c-error: #f87171;
    --c-tertiary: #93c5fd;
    --c-badge: #1e3a5f;
    --c-badge-text: #bfdbfe;
    --c-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
    --c-scrollbar: #3f3f46;
    /* Whisper-faint neutral grid (CSS-only) — texture, not colour. */
    --c-pattern:
        linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px),
        linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
    /* Darker zinc chrome, soft-blue accents */
    --c-chrome-bg: #121214;
    --c-chrome-fg: #e4e4e7;
    --c-chrome-accent: #60a5fa;
    --c-chrome-muted: #9a9aa3;
    --c-chrome-border: #26262b;
    --c-chrome-hover: #202024;
    --c-chrome-active-bg: #3b82f6;
    --c-chrome-active-text: #ffffff;
}

/* ─────────────────────────────────────────────────────────────────────────
   SHARED UTILITY MAPPINGS — paint every theme from the variables above.
   Scoped under `[data-theme]` (specificity 0,2,0) so they reliably outrank the
   Tailwind CDN's single-class utilities (0,1,0) regardless of stylesheet order.
   ───────────────────────────────────────────────────────────────────────── */
[data-theme] body { background-color: var(--c-canvas); color: var(--c-text); }

/* Geometric: lay the decorative grid over the canvas (40px cells). */
[data-theme] body {
    background-image: var(--c-pattern);
    background-size: 40px 40px;
    background-attachment: fixed;
}

/* Surfaces */
[data-theme] .bg-surface,
[data-theme] .bg-background { background-color: var(--c-canvas); }
[data-theme] .bg-white,
[data-theme] .bg-surface-bright,
[data-theme] .bg-surface-container-lowest { background-color: var(--c-surface); }
[data-theme] .bg-surface-container-low,
[data-theme] .bg-surface-container,
[data-theme] .bg-secondary-container { background-color: var(--c-surface-2); }
[data-theme] .bg-surface-container-high,
[data-theme] .bg-surface-container-highest,
[data-theme] .bg-surface-variant,
[data-theme] .bg-surface-dim,
[data-theme] .bg-secondary-fixed { background-color: var(--c-surface-3); }

/* Solid accent fill (primary buttons, toggles) */
[data-theme] .bg-primary { background-color: var(--c-primary); }
[data-theme] .text-on-primary { color: var(--c-on-primary); }

/* Text */
[data-theme] .text-on-surface,
[data-theme] .text-on-background { color: var(--c-text); }
[data-theme] .text-on-surface-variant,
[data-theme] .text-outline { color: var(--c-text-variant); }
[data-theme] .text-primary { color: var(--c-accent); }
[data-theme] .text-error { color: var(--c-error); }
[data-theme] .text-tertiary { color: var(--c-tertiary); }
[data-theme] .text-on-secondary-container { color: var(--c-text-variant); }
[data-theme] .text-on-primary-fixed { color: var(--c-badge-text); }

/* Borders */
[data-theme] .border-outline-variant { border-color: var(--c-border); }
[data-theme] .border-outline { border-color: var(--c-border-strong); }
[data-theme] .border-primary { border-color: var(--c-accent); }
[data-theme] .border-l-primary { border-left-color: var(--c-accent); }

/* Filled tokens */
[data-theme] .bg-outline-variant { background-color: var(--c-border); }
[data-theme] .bg-primary-fixed { background-color: var(--c-badge); }

/* Cards + form controls */
[data-theme] .widget-card { background: var(--c-surface); border-color: var(--c-border); box-shadow: var(--c-shadow); }
[data-theme] .widget-card:hover { border-color: var(--c-accent); }
[data-theme] input,
[data-theme] select,
[data-theme] textarea { background-color: var(--c-surface); color: var(--c-text); border-color: var(--c-border); }
[data-theme] input::placeholder,
[data-theme] textarea::placeholder { color: var(--c-text-faint); }

/* Scrollbar */
[data-theme] .custom-scrollbar::-webkit-scrollbar-thumb { background: var(--c-scrollbar); }

/* Hover states (the CDN-compiled hover utilities carry the light hex). */
[data-theme] .hover\:bg-surface-container:hover,
[data-theme] .hover\:bg-surface-container-low:hover { background-color: var(--c-surface-3); }
[data-theme] .hover\:bg-surface-container-high:hover { background-color: var(--c-surface-4); }
[data-theme] .hover\:border-primary:hover { border-color: var(--c-accent); }
[data-theme] .hover\:text-primary:hover { color: var(--c-accent); }
[data-theme] .hover\:text-error:hover { color: var(--c-error); }
[data-theme] .hover\:ring-primary:hover { --tw-ring-color: var(--c-accent); }

/* Focus states on form controls. */
[data-theme] .focus\:border-primary:focus { border-color: var(--c-accent); }
[data-theme] .focus\:ring-primary\/20:focus { --tw-ring-color: var(--c-accent-ring); }
/* `focus:bg-white` would flash to white on dark themes; keep the input surface. */
[data-theme] .focus\:bg-white:focus { background-color: var(--c-surface); }

/* ─────────────────────────────────────────────────────────────────────────
   APP CHROME (left sidebar + top header) — painted from the --c-chrome-* vars
   so a theme can give the frame a strong accent (Oxford's navy) while the
   reading area stays neutral. Neutral themes point chrome vars at their surface
   palette, so their frame is unchanged (white/dark with accent active items).
   Scoped by #id so they outrank the generic surface/text mappings above.
   The notification popover lives inside the header but is targeted precisely
   (by id) so its neutral surface never inherits the chrome colours.
   ───────────────────────────────────────────────────────────────────────── */
[data-theme] #app-header { background-color: var(--c-chrome-bg); border-bottom-color: var(--c-chrome-border); }
[data-theme] #app-sidebar { background-color: var(--c-chrome-bg); border-right-color: var(--c-chrome-border); }

/* Foreground icons in the chrome (hamburger, bell, mobile close). */
[data-theme] #app-header .sidebar-toggle,
[data-theme] #app-header #notif-bell,
[data-theme] #app-sidebar .sidebar-close { color: var(--c-chrome-fg); }

/* Final Polish, Task 5: header quick-toggles legible on themed chrome (the
   language pill + theme swatches) — avoids white-on-white / dark-on-dark. */
[data-theme] #app-header #header-lang-btn { color: var(--c-chrome-fg); border-color: var(--c-chrome-border); }
[data-theme] #app-header #header-lang-btn:hover { color: var(--c-chrome-accent); border-color: var(--c-chrome-accent); }
[data-theme] #app-header #header-theme-picker .gw-theme-swatch { border-color: var(--c-chrome-border); }
[data-theme] #app-header .header-icon-btn { color: var(--c-chrome-fg); }
[data-theme] #app-header .header-icon-btn:hover { background-color: var(--c-chrome-hover); color: var(--c-chrome-accent); }

/* Header language pill + text-size group: legible on the themed chrome bar. */
[data-theme] #app-header .header-pref-btn,
[data-theme] #app-header .header-font-btn { color: var(--c-chrome-muted); border-color: var(--c-chrome-border); }
[data-theme] #app-header .header-pref-btn:hover,
[data-theme] #app-header .header-font-btn:hover { color: var(--c-chrome-accent); border-color: var(--c-chrome-accent); }
[data-theme] #app-header .header-font-btn.gw-active {
    background: var(--c-chrome-active-bg); color: var(--c-chrome-active-text); border-color: var(--c-chrome-active-bg);
}

/* Horizontal header nav (desktop): inactive items muted, hover gets the chrome
   hover surface; the active item is handled by the .bg-primary rule below. */
[data-theme] #app-header #header-nav .nav-btn { color: var(--c-chrome-muted); }
[data-theme] #app-header #header-nav .nav-btn:hover { background-color: var(--c-chrome-hover); color: var(--c-chrome-fg); }

/* Accent text inside the chrome: wordmark, active header tab, username. */
[data-theme] #app-wordmark,
[data-theme] #nav-active-view,
[data-theme] #app-sidebar #sidebar-user-name { color: var(--c-chrome-accent); }
[data-theme] #nav-active-view { border-color: var(--c-chrome-accent); }

/* Muted text inside the chrome: role label, inactive nav items, logout. */
[data-theme] #app-sidebar #sidebar-user-role,
[data-theme] #app-sidebar .nav-btn,
[data-theme] #app-sidebar .logout-btn { color: var(--c-chrome-muted); }

/* Chrome divider lines (sidebar only — the header's sole divider is its border). */
[data-theme] #app-sidebar .border-outline-variant { border-color: var(--c-chrome-border); }

/* Hover surfaces for chrome controls (nav items, profile button, header icons).
   The logout button keeps its own red warning hover (defined later). */
[data-theme] #app-sidebar .nav-btn:hover,
[data-theme] #app-sidebar [data-action="open-profile"]:hover,
[data-theme] #app-header .sidebar-toggle:hover,
[data-theme] #app-header #header-theme-toggle:hover,
[data-theme] #app-header #notif-bell:hover { background-color: var(--c-chrome-hover); }

/* Active nav item (header + sidebar) + avatars (router toggles .bg-primary/
   .text-on-primary; the account-menu avatars carry the accent fill directly). */
[data-theme] #app-header #header-nav .nav-btn.bg-primary,
[data-theme] #app-header #user-menu-btn,
[data-theme] #app-header #header-avatar-lg,
[data-theme] #app-sidebar .nav-btn.bg-primary,
[data-theme] #app-sidebar #sidebar-avatar { background-color: var(--c-chrome-active-bg); color: var(--c-chrome-active-text); }

/* ─────────────────────────────────────────────────────────────────────────
   ROLE / STATUS PILLS on dark themes (Phase 13 contrast fix).
   The pills (role badges next to usernames in chat/comments, and submission/
   ticket status chips) are fixed Tailwind pairs — `bg-X-100` (very light) +
   `text-X-800` (dark). On the dark + geometric themes a light pill with dark
   text reads poorly, so we recolour them to a SUBTLE dark-tinted background with
   BRIGHT accent text. Scoped to the two dark themes (0,2,0 beats the 0,1,0
   utilities); the light themes keep the original light pills.
   ───────────────────────────────────────────────────────────────────────── */
[data-theme="dark"] .bg-blue-100,   [data-theme="geometric"] .bg-blue-100   { background-color: rgba(96, 165, 250, 0.16); }
[data-theme="dark"] .text-blue-800, [data-theme="geometric"] .text-blue-800 { color: #93c5fd; }
[data-theme="dark"] .bg-green-100,   [data-theme="geometric"] .bg-green-100   { background-color: rgba(74, 222, 128, 0.16); }
[data-theme="dark"] .text-green-800, [data-theme="geometric"] .text-green-800 { color: #86efac; }
[data-theme="dark"] .bg-amber-100,   [data-theme="geometric"] .bg-amber-100   { background-color: rgba(251, 191, 36, 0.16); }
[data-theme="dark"] .text-amber-800, [data-theme="geometric"] .text-amber-800 { color: #fcd34d; }
[data-theme="dark"] .bg-red-100,   [data-theme="geometric"] .bg-red-100   { background-color: rgba(248, 113, 113, 0.16); }
[data-theme="dark"] .text-red-800, [data-theme="geometric"] .text-red-800 { color: #fca5a5; }
[data-theme="dark"] .bg-purple-100,   [data-theme="geometric"] .bg-purple-100   { background-color: rgba(192, 132, 252, 0.16); }
[data-theme="dark"] .text-purple-800, [data-theme="geometric"] .text-purple-800 { color: #d8b4fe; }

/* ─────────────────────────────────────────────────────────────────────────
   Anti-FOUC boot overlay. `app-booting` is set on <html> by the inline head
   script (synchronously, before first paint) and removed by src/main.js once
   the theme is applied and the auth/view state is resolved. Until then the
   app is hidden behind a themed blank so no light/login flash is visible.
   ───────────────────────────────────────────────────────────────────────── */
html.app-booting #gateway-view,
html.app-booting #app-shell { opacity: 0; }
html.app-booting::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 9999;
    background: var(--c-canvas);    /* matches the active theme's canvas */
}
#gateway-view,
#app-shell { transition: opacity 0.15s ease-in; }

/* ─────────────────────────────────────────────────────────────────────────
   Browser autofill (Chrome/Edge/Safari). The UA paints a pale yellow/blue
   background and overrides the text colour on autofilled fields, which breaks
   our themed inputs. There is no real "background" property we can set — the
   UA paints over it — so we mask it with a full-size inset box-shadow in the
   input's own surface colour and force the text fill colour to match. The
   ~9999s transition defers the UA's repaint effectively forever. The mask colour
   and text fill now come from the active theme's variables, so autofilled fields
   stay on-palette in every theme. ───────────────────────────────────────────── */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
input:-webkit-autofill:active,
textarea:-webkit-autofill,
textarea:-webkit-autofill:hover,
textarea:-webkit-autofill:focus,
select:-webkit-autofill,
select:-webkit-autofill:hover,
select:-webkit-autofill:focus {
    -webkit-box-shadow: 0 0 0 1000px var(--c-surface) inset !important;
    box-shadow: 0 0 0 1000px var(--c-surface) inset !important;
    -webkit-text-fill-color: var(--c-text) !important;
    caret-color: var(--c-text);
    transition: background-color 9999s ease-in-out 0s;
}

/* ─────────────────────────────────────────────────────────────────────────
   A11y: FOUR-TIER GLOBAL TEXT SIZE (Settings → Text Size).
   One root variable drives the <html> font-size, so EVERY rem-based Tailwind
   utility — text sizes AND paddings/margins/gaps/containers — scales together.
   The whole platform (desktop + mobile) grows proportionally and stays fluid;
   nothing is hard-coded per-component. The tier is set via a `data-text-size`
   attribute on <html> by src/core/settings.js (applyFontSize) + the boot script,
   and persists in localStorage. "normal" (16px) matches the browser default, so
   the desktop layout is 100% unchanged until the user opts in.
   Phase 7, Task 4: calibrated to gentle, even 2px steps so each tier is a
   natural increment and Large stays readable, not overwhelming.
     small = 14px · normal = 16px · large = 18px
   ───────────────────────────────────────────────────────────────────────── */
:root { --base-font-size: 16px; }
html { font-size: var(--base-font-size); transition: font-size 0.18s ease; }
html[data-text-size="small"]  { --base-font-size: 14px; }
html[data-text-size="large"]  { --base-font-size: 18px; }

/* Layout protection at the Large size (Phase 12.1 + 14): let long/unbreakable words
   wrap instead of overflowing their flex/grid containers, so the larger tier never
   produces horizontal scrolling. Applied broadly (harmless on normal text — it only
   breaks tokens that would otherwise overflow); truncated elements are unaffected
   (white-space:nowrap still wins). Scoped to Large so the default layout is
   100% untouched. */
html[data-text-size="large"] * { overflow-wrap: break-word; }

/* ─────────────────────────────────────────────────────────────────────────
   "Switch Role / Log out" is a destructive/exit action. On hover it adopts a
   subtle warning (red) aesthetic to signal that it leaves the session. The
   click itself is intercepted by a custom confirm dialog (see src/main.js).
   ───────────────────────────────────────────────────────────────────────── */
.logout-btn:hover {
    background-color: color-mix(in srgb, var(--c-error) 12%, transparent);
    color: var(--c-error);
}

/* ═════════════════════════════════════════════════════════════════════════
   PHASE 12 — MOTION DESIGN (pure CSS). Snappy, professional micro-interactions
   (0.2s–0.3s ease-out, no bounce).
   ═════════════════════════════════════════════════════════════════════════ */

/* ── Modals & alerts: content pops in (scale + fade); backdrop fades in. ──────
   Dialogs are shown by removing `.hidden` (display:none → flex), so a keyframe on
   the children replays every time the dialog opens — no JS needed. Content boxes
   carry `.relative`, backdrops `.absolute` (see index.html). */
@keyframes modal-pop {
    from { opacity: 0; transform: scale(0.95); }
    to   { opacity: 1; transform: scale(1); }
}
@keyframes backdrop-fade {
    from { opacity: 0; }
    to   { opacity: 1; }
}
#app-modal:not(.hidden) > .relative,
#confirm-modal:not(.hidden) > .relative {
    animation: modal-pop 0.25s ease-out;
}
#app-modal:not(.hidden) > .absolute,
#confirm-modal:not(.hidden) > .absolute {
    animation: backdrop-fade 0.25s ease-out;
}

/* ── Hover lift: abstract cards, primary buttons, and leaderboard rows rise 2px
   and gain a stronger shadow to read as interactive. Sidebar nav items, the
   avatar, and wizard dots are excluded so the chrome stays calm. */
[data-theme] [data-abs-card],
[data-theme] button.bg-primary:not(.nav-btn),
[data-theme] a.bg-primary:not(.nav-btn),
[data-theme] #leaderboard-list > button,
[data-theme] #admin-reviewers-list > button {
    transition: all 0.2s ease;
}
[data-theme] [data-abs-card]:hover,
[data-theme] #leaderboard-list > button:hover,
[data-theme] #admin-reviewers-list > button:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.12);
}
[data-theme] button.bg-primary:not(.nav-btn):hover,
[data-theme] a.bg-primary:not(.nav-btn):hover {
    transform: translateY(-2px);
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);
}

/* Respect reduced-motion preferences: disable the decorative animations. */
@media (prefers-reduced-motion: reduce) {
    #app-modal:not(.hidden) > .relative,
    #confirm-modal:not(.hidden) > .relative,
    #app-modal:not(.hidden) > .absolute,
    #confirm-modal:not(.hidden) > .absolute { animation: none; }
    [data-theme] [data-abs-card]:hover,
    [data-theme] #leaderboard-list > button:hover,
    [data-theme] #admin-reviewers-list > button:hover,
    [data-theme] button.bg-primary:not(.nav-btn):hover,
    [data-theme] a.bg-primary:not(.nav-btn):hover { transform: none; }
    html { transition: none; }
}

/* ═════════════════════════════════════════════════════════════════════════
   PHASE 14 — MESSAGES on very small phones (≤ 400px, e.g. 320px).
   Tighten the chat frame so the input field + Send button always fit within the
   viewport instead of being pushed off-screen to the right. Pure layout: smaller
   paddings, a shrinkable input, and a compact Send button. Targeted by id so the
   desktop/tablet chat layout is untouched.
   ═════════════════════════════════════════════════════════════════════════ */
@media (max-width: 400px) {
    /* Never let the messages view exceed the viewport width. */
    #messages-view,
    #messages-view .chat-thread-pane { max-width: 100vw; }

    /* Chat header: tighter padding so the title + back button fit. */
    #messages-view .chat-thread-pane > div:first-child { padding: 0.6rem 0.7rem; gap: 0.5rem; }

    /* Message list: smaller side padding to widen the usable area. */
    #chat-thread { padding: 0.7rem; }

    /* Input row (the div wrapping #chat-input): shrink padding + gap. */
    #messages-view div:has(> #chat-input) { padding: 0.55rem 0.6rem; gap: 0.4rem; }

    /* Compact paddings only — the flex/wrap behaviour is handled globally (14.3). */
    #chat-input { padding: 0.5rem 0.65rem; }
    #chat-send { padding-left: 0.85rem; padding-right: 0.85rem; }
}

/* ═════════════════════════════════════════════════════════════════════════
   PHASE 14.1 — Chat/Discussion message + modal layout at 320px (incl. XL text).
   The message row is: [avatar] [column: header(name+role badge) · bubble · time].
   At tiny widths with large text the header used to push the avatar/bubble into
   overlap. These rules keep every piece within bounds. Scoped to the two threads
   (#chat-thread = Messages, #comments-timeline = abstract Discussion).
   ═════════════════════════════════════════════════════════════════════════ */

/* (1) Avatars (and the round role badges) never squash, at any text size. */
#chat-thread .rounded-full,
#comments-timeline .rounded-full { flex-shrink: 0; }

/* (2) Message header (sender name + role badge + admin delete) wraps gracefully
   instead of forcing the row wider than its column. */
#chat-thread .flex.items-center.gap-1,
#comments-timeline .flex.items-center.gap-1 { flex-wrap: wrap; gap: 0.5rem; }

/* (3) Bubbles break long words and never exceed their column (the desktop 80%
   cap from `max-w-[80%]` is preserved; the 100% override is mobile-only below). */
#chat-thread .rounded-xl,
#comments-timeline .rounded-xl { word-break: break-word; overflow-wrap: break-word; }

/* (5) Global audit: no single long word (URL, long ID, etc.) may cause horizontal
   scrolling on a 320px screen — harmless on normal text, applies at every size. */
p, span, a, li, td, th, dd, dt, h1, h2, h3, h4, label { overflow-wrap: break-word; }

/* (4) Modal padding: reclaim horizontal space on small phones so message text has
   room. The Discussion opens in #app-modal; confirm/alert use #confirm-modal. */
@media (max-width: 480px) {
    #app-modal > .relative,
    #confirm-modal > .relative { padding: 0.85rem; }
    /* Let bubbles use the full column width on tiny screens (desktop keeps 80%). */
    #chat-thread .rounded-xl,
    #comments-timeline .rounded-xl { max-width: 100%; }
}

/* ═════════════════════════════════════════════════════════════════════════
   PHASE 14.3 — GLOBAL ANTI-OVERFLOW & FLUID LAYOUTS (no rigid breakpoints).
   Dynamic font scaling makes per-pixel media queries unreliable, so these rules
   are a platform-wide failsafe against horizontal overflow + a fluid chat input
   that wraps the instant it runs out of room — at ANY viewport or text size.
   ═════════════════════════════════════════════════════════════════════════ */

/* (1) Strict box-sizing + the page can never scroll sideways. */
*, *::before, *::after { box-sizing: border-box; }
html, body { max-width: 100vw; overflow-x: hidden; }

/* (4) Global word break (overflow-wrap inherits, so this cascades everywhere) —
   no single long word/URL/ID can push UI elements off-screen. Truncated elements
   are unaffected (their white-space:nowrap still wins). */
body { overflow-wrap: break-word; word-wrap: break-word; }

/* (3) UI containment: these elements may never exceed their container width. */
.widget-card, input, textarea, select, button { max-width: 100%; }

/* (2) Fluid chat / discussion input rows. The wrapper that directly holds the
   field becomes a wrapping flex row; the input keeps a 200px ideal width but can
   shrink (min-width:0), so the Send button naturally drops onto its own line the
   moment they'd collide — no @media query needed. Both stretch when stacked. */
div:has(> #comment-input),
div:has(> #chat-input) { display: flex; flex-wrap: wrap; gap: 0.5rem; }
#comment-input, #chat-input { flex: 1 1 200px; min-width: 0; max-width: 100%; }
#comment-send, #chat-send { flex: 1 1 auto; }

/* ===================================================================== */
/* PHASE 15 — Notification swipe/hover delete + chat conversation delete  */
/* ===================================================================== */

/* Each notification is a layered row: ONE clean red "delete" square is anchored
   to the right, sitting BEHIND an opaque foreground. Touch slides the foreground
   left to reveal it (iOS-style). Desktop gets a single subtle trash that fades in
   on hover (no layout shift, no second icon). JS only sets the slide transform. */
.notif-row { position: relative; overflow: hidden; }

/* The single swipe-delete action: a defined red square hard-anchored to the
   right, BEHIND the foreground. It is fully transparent at rest (so not a single
   red pixel can seam past the foreground's edge) and only fades in the instant a
   swipe begins or the row is latched open. */
.notif-row-action {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    width: 80px;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: #ef4444;
    color: #fff;
    border: none;
    z-index: 1;            /* behind the foreground (which is z-index: 10) */
    cursor: pointer;
    opacity: 0;            /* hidden at rest → zero red-line artifact */
    transition: opacity 0.1s ease;
}
.notif-row[data-swiping="1"] .notif-row-action,
.notif-row[data-open="1"] .notif-row-action { opacity: 1; }
.notif-row-action .material-symbols-outlined { font-size: 22px; }

/* Opaque foreground — a solid, full-width surface layer that completely covers
   the red square until JS applies a translateX. The dim for read rows lives on
   the inner button, NEVER here, so the foreground always stays fully opaque.
   transition is toggled off by JS during a drag, restored on release. */
.notif-row-fg {
    position: relative;
    z-index: 10;
    width: 100%;
    display: flex;
    align-items: stretch;
    background: var(--c-surface, #fff);
    transition: transform 0.2s ease; /* Gmail-style snap */
    touch-action: pan-y; /* allow vertical scroll; we handle horizontal ourselves */
}
.notif-row-fg .notif-open { flex: 1 1 auto; min-width: 0; }
/* Read rows dim ONLY their own content — the foreground stays opaque, so nothing
   behind it shows through. */
.notif-open.is-read { opacity: 0.5; }

/* Desktop hover-delete: a single trash that fades in over the row's right edge
   WITHOUT shifting content. A surface gradient lets it sit cleanly over whatever
   is beneath; it's grey at rest and turns red on hover. Hidden on touch devices,
   which use the swipe instead — so there is never a second icon on screen. */
.notif-row-trash {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    width: 72px;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    padding-right: 18px;
    border: none;
    cursor: pointer;
    color: var(--c-on-surface-variant, #6b7280);
    background: linear-gradient(to right, transparent, var(--c-surface, #fff) 45%);
    opacity: 0;
    transition: opacity 0.15s ease, color 0.15s ease;
    z-index: 2;            /* above the foreground content */
}
.notif-row:hover .notif-row-trash { opacity: 1; }
.notif-row-trash:hover { color: #ef4444; }
@media (hover: none) {
    .notif-row-trash { display: none; } /* touch devices use swipe, not hover */
}

/* Chat contact rows: reveal the per-conversation trash button on hover/focus.
   Always visible on touch (no hover) so mobile users can still delete. */
.chat-del-btn {
    color: var(--c-on-surface-variant, #6b7280);
    opacity: 0;
    transition: opacity 0.15s ease, color 0.15s ease, background-color 0.15s ease;
}
.chat-contact-row:hover .chat-del-btn,
.chat-del-btn:focus-visible { opacity: 1; }
.chat-del-btn:hover { color: var(--c-error, #B3261E); background-color: rgba(179,38,30,0.10); }
/* The active (selected) conversation uses on-primary text, so tint its delete icon to match. */
.chat-contact-row.bg-primary .chat-del-btn { color: var(--c-on-primary, #fff); opacity: 0.85; }
@media (hover: none) {
    .chat-del-btn { opacity: 1; } /* touch: always show, nothing to hover */
}

/* ===================================================================== */
/* PHASE 15.2 — Leaderboard/Reviewers row wrapping (430px + XL font)      */
/* ===================================================================== */

/* The name + inline badges (medal already sits in its own column) must wrap
   gracefully instead of crushing/overlapping when the font is large and the
   viewport is narrow. */
.lb-name {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start; /* a wrapped badge pushes DOWN, never overlaps */
    gap: 0.5rem;
    min-width: 0;
}

/* On small screens, let the whole row wrap so the review-count block + chevron
   drop below the name instead of squeezing it into the avatar. The stats block
   then aligns under the name (past the medal + avatar columns). */
@media (max-width: 480px) {
    .lb-row { flex-wrap: wrap; }
    .lb-row .lb-main { flex: 1 1 60%; }
    .lb-row .lb-stats {
        flex: 1 0 100%;
        text-align: left;
        margin-left: 3.5rem; /* clear the medal (w-10) + avatar (w-9) gutter */
    }
    /* Inline the count + label so the wrapped stats read as one tidy line. */
    .lb-row .lb-stats p { display: inline; }
    .lb-row .lb-stats p + p::before { content: " · "; }
}

/* ===================================================================== */
/* PHASE 15.3 — Global anti-overlap guarantees (320px + Extra Large)      */
/* ===================================================================== */

/* ── ROOT CAUSE ──────────────────────────────────────────────────────────
   The Tailwind CDN compiles arbitrary font sizes (e.g. text-[10px]) WITH
   `line-height: 1`. At line-height 1 there is zero leading, so when a muted
   label wraps it overlaps itself, and a padded pill/badge overflows its own
   line box onto the text below it. Restoring a humane line-height on those
   tiny sizes — and on the body — is what actually stops the overlapping. */

/* 1. Comfortable global baseline: wrapped lines always breathe. */
body { line-height: 1.5; }

/* 2. Relax the tiny arbitrary sizes the CDN pins to line-height:1. These are all
   small muted labels / badge text, where 1.4 leading is correct and safe. The
   `!important` guarantees we beat the runtime-injected CDN rule regardless of
   stylesheet order. Single-line uses are unaffected (they just gain a hair of
   height, absorbed by existing padding). */
.text-\[9px\],
.text-\[10px\],
.text-\[11px\],
.text-\[13px\] { line-height: 1.4 !important; }

/* 3. Every card / list-item / row / stat primitive MUST grow to fit wrapped
   content — never a fixed or clipped height. (Avatars, material icons and the
   calendar grid are deliberately NOT listed, so their intentional fixed sizing
   is preserved.) */
.widget-card,
[data-abs-card],
.lb-row, .lb-main, .lb-stats,
.chat-contact-row,
.notif-row, .notif-row-fg, .notif-open,
.stat-block {
    height: auto !important;
    min-height: min-content;
}

/* 4. The concrete reported case (Reviewers list): top-align every column and the
   name/badge cluster, and stack the name + "Avg Score Given" as a gapped column
   so a wrapped badge or long name pushes the line below it DOWN, never over it. */
.lb-row { align-items: flex-start; }
.lb-main {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;        /* gap, not margin — no collapse, predictable spacing */
}
.lb-main > * { margin: 0; }

/* 5. Generic guarantee for any "label + badge(s)" horizontal cluster: if a child
   wraps, the container grows and pushes siblings down instead of overlapping. */
.wrap-row {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 0.5rem;
    min-width: 0;
}

/* ===================================================================== */
/* PHASE 16 — System-wide proportional scaling on phones (≤480 / ≤360)    */
/* ===================================================================== */

/* ── THE LEVER ───────────────────────────────────────────────────────────
   The Large/Extra-Large tiers raise the rem ROOT (18px / 21px), and EVERY
   rem-based padding, gap and button scales with it. On a 320–430px phone that
   makes dialog buttons ("Cancel", "Logout") look grotesque. Instead of patching
   each button, we clamp the root proportionally as the viewport narrows — so the
   WHOLE UI shrinks together and stays in proportion, while still honouring the
   user's larger-text preference (just a little less extreme on a tiny screen).
   The Normal tier (16px) is already fine and is left untouched. */
@media (max-width: 480px) {
    html[data-text-size="large"]  { --base-font-size: 17px; }
}
@media (max-width: 360px) {
    html[data-text-size="large"]  { --base-font-size: 16px; }
}

/* ── DIALOG / MODAL / WIZARD ACTION ROWS ─────────────────────────────────
   Footers must WRAP rather than overflow, and their buttons must shed the huge
   rem paddings (px-xl = 2rem) on a small screen so they read as buttons, not
   slabs. Global — covers every modal footer, the confirm/alert dialog, and the
   wizard nav at once (no per-dialog patching). */
@media (max-width: 480px) {
    #confirm-modal .flex.justify-end,
    #app-modal .flex.justify-end,
    #wizard-nav {
        flex-wrap: wrap;
        gap: 0.5rem;
    }
    /* Cap the oversized horizontal padding; keep comfortable, tappable targets. */
    #confirm-modal .flex.justify-end button,
    #app-modal .flex.justify-end button,
    #wizard-nav button {
        padding-left: 1rem;
        padding-right: 1rem;
        flex: 0 1 auto;        /* natural width; wrap when they can't fit */
        min-height: 2.5rem;    /* keep a usable touch target even when shrunk */
    }
    /* Reclaim modal padding on tiny screens so content isn't crushed to a sliver. */
    #app-modal > .relative,
    #app-modal > .absolute,
    #confirm-modal > .relative {
        padding: 0.9rem;
    }
}

/* ===================================================================== */
/* PHASE 16.1 — Modal scroll containment + auto-wrapping theme grid       */
/* ===================================================================== */

/* TASK 1 — Settings/content modal: cap the box height and make the BODY scroll,
   not the whole document, so Extra-Large font can grow the content without ever
   pushing the footer "Close" button off-screen. Header stays put; body scrolls.
   (Pure CSS over the existing markup — the box is #app-modal > .relative and the
   scrollable content is #app-modal-body. ID specificity overrides the Tailwind
   max-h-[85vh]/overflow-y-auto utilities already on the box.) */
#app-modal > .relative {
    display: flex;
    flex-direction: column;
    max-height: calc(100vh - 4rem); /* never taller than the viewport */
    min-height: 0;                  /* CRITICAL: .widget-card sets min-height:min-content,
                                       and min-height BEATS max-height — without this the
                                       box grows to full content height and a long legal
                                       doc / settings panel spills off-screen instead of
                                       letting the body scroll. */
    overflow: hidden;               /* the inner body owns the scroll */
}
#app-modal-body {
    flex: 1 1 auto;
    overflow-y: auto;
    min-height: 0;                  /* lets the flex child shrink so it can scroll */
}

/* TASK 2 — Theme picker: stop forcing a rigid 4-up row. Auto-fit columns wrap to
   2×2 (or 1-up) the moment the tiles can't keep ≥120px each — so Extra-Large font
   never squishes the labels. Overrides the grid-cols-2 sm:grid-cols-4 utilities. */
#settings-theme-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
    gap: 0.75rem;
}

/* ===================================================================== */
/* PHASE 16 (Task 1) — Invisible native scrolling + no horizontal scroll  */
/* ===================================================================== */

/* Reusable utility: keep scrolling, hide the scrollbar chrome. */
.no-scrollbar::-webkit-scrollbar { display: none; }            /* Chrome/Safari/Opera */
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; } /* IE/Edge + Firefox */

/* Apply invisible scrolling to every app scroll container — modal bodies, the
   sidebar, chat panes, comment timelines, the notifications + search dropdowns.
   (Replaces the visible styled bar from .custom-scrollbar with a clean hidden one.) */
#app-modal-body,
#app-modal > .relative,
#confirm-modal > .relative,
#app-sidebar,
#chat-thread,
#chat-contacts,
#comments-timeline,
#notif-list,
#global-search-dropdown,
.custom-scrollbar {
    -ms-overflow-style: none;
    scrollbar-width: none;
}
#app-modal-body::-webkit-scrollbar,
#app-modal > .relative::-webkit-scrollbar,
#confirm-modal > .relative::-webkit-scrollbar,
#app-sidebar::-webkit-scrollbar,
#chat-thread::-webkit-scrollbar,
#chat-contacts::-webkit-scrollbar,
#comments-timeline::-webkit-scrollbar,
#notif-list::-webkit-scrollbar,
#global-search-dropdown::-webkit-scrollbar,
.custom-scrollbar::-webkit-scrollbar { display: none; }

/* Settings/content modal: forbid horizontal scrolling entirely — content wraps
   (the global overflow-wrap / max-width:100% rules force long content to fit). */
#app-modal-body { overflow-x: hidden; }

/* ===================================================================== */
/* PHASE 17.2 — Grading view column proportions (desktop split panes)     */
/* ===================================================================== */

/* On split-screen / narrow desktops the abstract-list column was eating the
   width and the grading form (scores + comments) got squished and clipped by its
   overflow-hidden. Fix: keep the list strict & narrow, and let the form be fluid
   with min-width:0 so it can actually shrink to fit instead of overflowing. The
   form's inner ".flex-1.overflow-y-auto" region already supplies the vertical
   scroll, so we add none here. Scoped to ≥769px so the ≤768px stacking layout
   (aside → full width) is untouched. */
@media (min-width: 769px) {
    #reviews-view > .flex > aside {
        flex: 0 0 300px;   /* strict: never grows, never shrinks away */
        max-width: 340px;
    }
    #reviews-view > .flex > .flex-1 {
        flex: 1 1 auto;
        min-width: 0;      /* critical — lets the grading form fit, not clip */
    }
}

/* ===================================================================== */
/* PHASE 18 — User-resizable list pane in the 3-pane layouts              */
/* ===================================================================== */

/* The left/list column in Grading, Schedule, and Messages is resized via a
   FULL-HEIGHT draggable splitter bar (.pane-splitter) — the user can grab the
   entire vertical separator, not just a corner grip (Final Polish, Task 4).
   JS (main.js) sets the aside's width on drag; here we just establish the
   flex sizing. The right pane stays fluid (flex:1) and may shrink (min-width:0)
   so it absorbs whatever width the user gives the list. Scoped to ≥769px so the
   ≤768px stacked layout (single column) is untouched. Overrides the Phase 17.2
   fixed grading-list width above. */
@media (min-width: 769px) {
    #reviews-view > .flex > aside,
    #program-view > .flex > aside,
    #messages-view .chat-contacts-pane {
        flex: 0 0 auto;        /* width is authoritative: initial from w-80/w-72, then the drag */
        overflow: hidden;
        min-width: 250px;
        max-width: 60vw;
    }
    /* Right pane fills the remaining space and may shrink. */
    #reviews-view > .flex > .flex-1,
    #program-view > .flex > .flex-1,
    #messages-view .chat-thread-pane {
        flex: 1 1 auto;
        min-width: 0;
    }
}

/* Draggable pane splitter — a wide, full-height grab zone straddling the
   divider (negative margins overlap the panes so its net layout width is ~0).
   A thin line is always visible; it brightens to the primary colour on hover
   or while dragging. Hidden on the ≤768px stacked layout. */
.pane-splitter {
    flex: 0 0 8px;
    align-self: stretch;
    margin: 0 -4px;            /* straddle the border; net width impact ≈ 0 */
    cursor: col-resize;
    position: relative;
    z-index: 5;
    background: transparent;
    touch-action: none;        /* let pointer drag work on touch devices */
}
.pane-splitter::before {
    content: '';
    position: absolute;
    top: 0; bottom: 0; left: 50%;
    width: 1px; transform: translateX(-0.5px);
    background: var(--c-border, var(--c-chrome-border, #e2e8f0));
    transition: background 0.15s ease, width 0.15s ease;
}
.pane-splitter:hover::before,
.pane-splitter.dragging::before {
    width: 3px;
    background: var(--c-primary, #3b82f6);
}
@media (max-width: 768px) { .pane-splitter { display: none; } }

/* =====================================================================
   PHASE 2 / GROUP A — Global shell, header and modal baseline
   =====================================================================
   These are shared layout guarantees only. Role-specific dashboards keep their
   existing responsive rules; this layer makes the app chrome and dialogs less
   likely to create horizontal pressure before any view-level tuning happens. */

/* Header: every zone must be allowed to shrink, and the row must hide accidental
   horizontal overflow inside the chrome instead of pushing the document wider. */
#app-header {
    gap: clamp(0.35rem, 1vw, 0.75rem);
    padding-left: clamp(0.75rem, 2vw, 1.5rem);
    padding-right: clamp(0.75rem, 2vw, 1.5rem);
    min-width: 0;
    overflow: visible;
}
#app-header > .flex,
#header-nav,
#user-menu,
.header-search { min-width: 0; }

/* Desktop/tablet nav: keep all role tabs reachable even when the available
   middle column narrows. The native scrollbar is hidden by the global
   no-scrollbar rules; keyboard/touchpad scrolling still works. */
#header-nav {
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    overscroll-behavior-inline: contain;
    scroll-behavior: smooth;
    justify-content: safe center;
    padding-inline: 0.5rem;
    scroll-padding-inline: 0.5rem;
}
#header-nav::-webkit-scrollbar { display: none; }
@media (hover: hover) and (pointer: fine) {
    #header-nav {
        scrollbar-width: none;
    }

    #header-nav::-webkit-scrollbar {
        display: none;
    }
}
#header-nav .nav-btn {
    flex: 0 0 auto;
    max-width: none;
    overflow: visible;
    text-overflow: clip;
    white-space: nowrap;
}

/* The search box is the first right-side element to yield width. It keeps a
   useful desktop size, but can shrink on tablets without covering the controls. */
.header-search {
    flex: 1 1 12rem;
    width: clamp(8.5rem, 15vw, 14rem) !important;
}

@media (min-width: 769px) {
    #app-header > .flex:last-of-type {
        flex: 0 1 auto;
        min-width: max-content;
    }

    .header-search {
        flex: 0 1 clamp(2.75rem, 12vw, 12rem);
    }
}

/* Medium-width tablets still use the desktop nav, so compact the logo and nav
   before the full mobile shell takes over at 768px. */
@media (max-width: 1100px) and (min-width: 769px) {
    #app-logo.brand-mark { width: 7.5rem; height: 2rem; }
    .brand-suffix { font-size: 1.25rem; }
    #header-nav {
        justify-content: safe center;
        padding-left: 0.5rem;
        padding-right: 0.5rem;
    }
    #header-nav .nav-btn {
        padding-left: 0.55rem;
        padding-right: 0.55rem;
        max-width: none;
    }
    .header-search { width: clamp(7.5rem, 13vw, 10rem) !important; }
}

/* At narrow tablet widths, keep search functional without letting it consume the
   whole header: it rests as a compact icon and expands while focused. */
@media (max-width: 900px) and (min-width: 769px) {
    .header-search {
        flex: 0 0 2.75rem;
        width: 2.75rem !important;
    }
    .header-search #global-search {
        padding-right: 0;
        color: transparent;
    }
    .header-search #global-search::placeholder { color: transparent; }
    .header-search:focus-within {
        position: absolute;
        top: 0.75rem;
        right: 5.75rem;
        z-index: 90;
        width: min(22rem, calc(100vw - 11rem)) !important;
        flex-basis: auto;
    }
    .header-search:focus-within #global-search {
        padding-right: 1rem;
        color: var(--c-text);
    }
    .header-search:focus-within #global-search::placeholder { color: var(--c-text-faint); }
}

/* Mobile header: keep controls inside one stable row and prevent the account
   chrome from widening the viewport. */
@media (max-width: 768px) {
    #app-header {
        height: 4rem;
        overflow: visible;
    }
    #app-header > .flex:last-of-type {
        flex: 1 1 auto;
        justify-content: flex-end;
        min-width: 0;
    }
    .header-search {
        display: block;
        flex: 1 1 auto;
        width: auto !important;
        min-width: 0;
        max-width: 14rem;
    }
}

@media (max-width: 480px) {
    #app-header { padding-left: 0.75rem; padding-right: 0.75rem; }
    .header-search { max-width: 11rem; }
    #global-search { padding-right: 0.75rem; }
}

@media (max-width: 380px) {
    .header-search { max-width: 8.75rem; }
}

/* Shell: all top-level app panes stay contained inside the viewport width. */
#app-shell,
#app-shell > div.flex,
#app-main {
    max-width: 100%;
    min-width: 0;
}

/* Modal baseline: use dynamic viewport units where available, keep action
   buttons visible, and remove horizontal pressure from long modal content. */
#app-modal,
#confirm-modal {
    padding: clamp(0.75rem, 2.5vw, 1.5rem);
}
#app-modal > .relative,
#confirm-modal > .relative {
    width: min(100%, 800px);
    max-height: min(90vh, calc(100dvh - 2rem));
}
#confirm-modal > .relative { max-width: min(100%, 28rem); }
#app-modal-title,
#app-modal-body,
#confirm-title,
#confirm-message {
    min-width: 0;
    overflow-wrap: anywhere;
}

@media (max-width: 480px) {
    #app-modal,
    #confirm-modal { align-items: stretch; }
    #app-modal > .relative,
    #confirm-modal > .relative {
        width: 100%;
        max-height: calc(100dvh - 1.5rem);
        border-radius: 14px;
    }
    #toast-container,
    #notification-stack {
        left: 0.75rem;
        right: 0.75rem;
        width: auto;
        max-width: none;
    }
}

/* =====================================================================
   PHASE 2 / STRICT MOBILE HEADER REFACTOR — 320px hard guarantee
   =====================================================================
   Mobile rule: at <=768px the header has exactly two visible controls:
   logo on the left, hamburger on the right. Navigation, search, language,
   text-size, theme and profile controls live in the off-canvas sidebar. */

html,
body {
    width: 100%;
    min-width: 0;
    max-width: 100vw;
    overflow-x: hidden !important;
}

#gateway-view,
#app-shell,
#app-shell > div.flex,
#app-main,
.spa-view {
    width: 100%;
    max-width: 100%;
    min-width: 0;
}

img,
svg,
canvas,
video,
iframe,
table,
pre,
code {
    max-width: 100%;
}

@media (max-width: 768px) {
    #app-header {
        display: grid !important;
        grid-template-columns: minmax(0, 1fr) auto;
        align-items: center;
        gap: 0.5rem;
        height: 4rem;
        width: 100%;
        max-width: 100vw;
        padding: 0 0.75rem;
        overflow: hidden;
    }

    #app-header > .flex:first-of-type {
        grid-column: 1;
        min-width: 0;
        max-width: 100%;
        overflow: hidden;
    }

    #app-header .brand-lockup {
        min-width: 0;
        max-width: 100%;
        overflow: hidden;
    }

    #app-header .brand-icon {
        display: inline-block;
        width: clamp(1.9rem, 10vw, 2.25rem);
        height: auto;
        flex: 0 0 auto;
    }

    #header-nav,
    #app-header > .flex:nth-of-type(2),
    #user-menu,
    #fontsize-menu,
    #header-theme-toggle,
    #app-header .header-pref-btn,
    #app-header .header-search {
        display: none !important;
    }

    #app-header .mobile-menu-btn {
        grid-column: 2;
        display: inline-flex !important;
        width: 2.75rem;
        height: 2.75rem;
        margin: 0;
        padding: 0;
        flex: 0 0 auto;
        color: var(--c-chrome-fg, var(--c-text));
    }

    #app-header .mobile-menu-btn .material-symbols-outlined {
        font-size: 1.75rem;
        line-height: 1;
    }

    #notif-dropdown {
        left: 0.75rem;
        right: 0.75rem;
        bottom: 5.5rem;
        width: auto;
        max-width: none;
    }

    #app-main {
        overflow-x: hidden !important;
    }

    #app-main .spa-view {
        width: 100%;
        max-width: 100%;
        padding-left: clamp(0.75rem, 4vw, 1rem);
        padding-right: clamp(0.75rem, 4vw, 1rem);
    }

    #gateway-view {
        padding-left: clamp(0.75rem, 4vw, 1rem);
        padding-right: clamp(0.75rem, 4vw, 1rem);
    }

    .gateway-prefs {
        left: 0.75rem;
        right: 0.75rem;
        justify-content: center;
        max-width: calc(100vw - 1.5rem);
    }

    #gateway-view > .w-full {
        max-width: min(100%, 28rem);
    }

    #gateway-view .widget-card {
        width: 100%;
        max-width: 100%;
        padding: clamp(1rem, 5vw, 1.5rem);
    }

    #gateway-logo.brand-hero {
        width: min(15rem, calc(100vw - 2rem));
        height: clamp(3rem, 17vw, 4.75rem);
    }

    #google-signin-btn,
    #google-signin-btn > *,
    #google-signin-btn iframe {
        max-width: 100% !important;
    }

    #gateway-view .app-footer {
        max-width: 100%;
        overflow-x: hidden;
    }
}

@media (max-width: 360px) {
    #app-header {
        padding-left: 0.625rem;
        padding-right: 0.625rem;
    }

    #app-header .mobile-menu-btn {
        width: 2.5rem;
        height: 2.5rem;
    }

    #app-header .brand-icon {
        width: 1.9rem;
    }
}

/* =====================================================================
   ACCESSIBILITY LAYOUT MODE — Large text without overlap
   =====================================================================
   Large text is not just a font-size change. This scoped mode gives controls
   room to wrap, keeps fixed chrome simple, and moves pre-auth preferences back
   into the login flow so they cannot collide with the logo/card. */

html[data-text-size="large"] {
    --a11y-control-gap: 0.75rem;
    --a11y-card-pad: clamp(1rem, 4vw, 1.75rem);
}

html[data-text-size="large"] body {
    line-height: 1.55;
}

html[data-text-size="large"] button,
html[data-text-size="large"] input,
html[data-text-size="large"] select,
html[data-text-size="large"] textarea {
    line-height: 1.35;
}

html[data-text-size="large"] button,
html[data-text-size="large"] .interactive-btn,
html[data-text-size="large"] .header-pref-btn,
html[data-text-size="large"] .gw-pref-btn,
html[data-text-size="large"] .header-font-btn,
html[data-text-size="large"] .gw-font-btn {
    height: auto;
    min-height: 2.5rem;
    white-space: normal;
}

html[data-text-size="large"] .widget-card {
    height: auto;
    min-height: min-content;
}

html[data-text-size="large"] .flex {
    min-width: 0;
}

html[data-text-size="large"] .flex.items-center,
html[data-text-size="large"] .flex.justify-between {
    row-gap: 0.5rem;
}

html[data-text-size="large"] #gateway-view .flex,
html[data-text-size="large"] #app-sidebar .flex.items-center,
html[data-text-size="large"] #app-main .spa-view > header.flex {
    flex-wrap: wrap;
}

/* Login / gateway: keep preferences in normal flow and let the hero/card breathe. */
html[data-text-size="large"] #gateway-view {
    align-items: center;
    justify-content: flex-start;
    padding: clamp(0.75rem, 3vw, 1.5rem);
    overflow-x: hidden;
}

html[data-text-size="large"] #gateway-view > .w-full {
    margin-top: auto;
    margin-bottom: auto;
    max-width: min(100%, 30rem);
}

html[data-text-size="large"] #gateway-view .text-center.mb-xl {
    margin-bottom: clamp(0.875rem, 3vw, 1.5rem);
}

html[data-text-size="large"] #gateway-logo.brand-hero {
    width: min(16rem, calc(100vw - 2rem));
    height: clamp(3rem, 12vw, 4.25rem);
    margin-bottom: 0.75rem;
}

html[data-text-size="large"] #gateway-view h1 {
    font-size: clamp(1.45rem, 7vw, 2rem);
    line-height: 1.15;
}

html[data-text-size="large"] #gateway-view .widget-card {
    padding: var(--a11y-card-pad);
}

html[data-text-size="large"] #gateway-view .space-y-lg > :not([hidden]) ~ :not([hidden]) {
    margin-top: 1rem;
}

html[data-text-size="large"] .gateway-prefs {
    position: static;
    inset: auto;
    z-index: 1;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.5rem;
    max-width: 100%;
    margin: 1rem auto 0;
}

html[data-text-size="large"] .gw-pref-sep {
    display: none;
}

html[data-text-size="large"] .gw-pref-group {
    flex-wrap: wrap;
    justify-content: center;
}

@media (max-width: 768px) {
    html[data-text-size="large"] .gateway-prefs {
        left: auto;
        right: auto;
        justify-content: center;
        max-width: 100%;
    }
}

html[data-text-size="large"] #google-signin-btn,
html[data-text-size="large"] #google-signin-btn > *,
html[data-text-size="large"] #google-signin-btn iframe {
    max-width: 100% !important;
}

html[data-text-size="large"] #auth-toggle-text {
    line-height: 1.45;
}

html[data-text-size="large"] #gateway-view .app-footer .flex {
    align-items: flex-start;
}

/* Large text header: preserve the same structural breakpoints as Normal/Small.
   The desktop/tablet header remains a single row through 769px; nav overflow is
   handled by the horizontal scroller instead of abruptly moving controls into the
   sidebar at small-laptop widths. */
@media (max-width: 1200px) and (min-width: 769px) {
    html[data-text-size="large"] #app-header {
        display: flex !important;
        align-items: center;
        justify-content: space-between;
        gap: clamp(0.35rem, 1vw, 0.75rem);
        height: 4rem;
        padding-left: clamp(0.75rem, 2vw, 1.5rem);
        padding-right: clamp(0.75rem, 2vw, 1.5rem);
        overflow: visible;
    }

    html[data-text-size="large"] #app-header > .flex:first-of-type {
        min-width: 0;
        max-width: 100%;
        overflow: visible;
    }

    html[data-text-size="large"] #app-header .brand-lockup {
        min-width: 0;
        max-width: 100%;
        overflow: visible;
    }

    html[data-text-size="large"] #app-header .brand-icon {
        display: none;
        width: clamp(1.9rem, 5vw, 2.35rem);
        height: auto;
        flex: 0 0 auto;
    }

    html[data-text-size="large"] #app-header .hide-on-mobile {
        display: inline-block !important;
    }

    html[data-text-size="large"] #header-nav {
        display: flex !important;
        justify-content: safe center;
        padding-inline: 0.5rem;
    }

    html[data-text-size="large"] #app-header > .flex:nth-of-type(2) {
        display: flex !important;
    }

    html[data-text-size="large"] #fontsize-menu {
        display: block !important;
    }

    html[data-text-size="large"] #user-menu {
        display: block !important;
    }

    html[data-text-size="large"] #header-theme-toggle,
    html[data-text-size="large"] #app-header .header-pref-btn {
        display: inline-flex !important;
    }

    html[data-text-size="large"] #app-header .header-search {
        display: block !important;
    }

    html[data-text-size="large"] #app-header .mobile-menu-btn {
        display: none !important;
        width: 2.75rem;
        height: 2.75rem;
        min-height: 2.75rem;
        margin: 0;
        padding: 0;
        flex: 0 0 auto;
    }

    html[data-text-size="large"] #app-header .mobile-menu-btn .material-symbols-outlined {
        font-size: 1.75rem;
        line-height: 1;
    }

    html[data-text-size="large"] #app-sidebar {
        display: flex;
        position: fixed;
        top: 4rem;
        left: 0;
        bottom: 0;
        width: min(20rem, 86vw);
        max-width: 86vw;
        transform: translateX(-100%);
        transition: transform 0.25s ease;
        z-index: 60;
        box-shadow: 0 10px 40px rgba(15, 23, 42, 0.25);
    }

    html[data-text-size="large"] body.sidebar-open #app-sidebar {
        transform: translateX(0);
    }

    html[data-text-size="large"] .sidebar-close {
        display: inline-flex;
    }

    html[data-text-size="large"] #sidebar-overlay {
        display: block;
        position: fixed;
        inset: 4rem 0 0 0;
        background: rgba(15, 23, 42, 0.45);
        z-index: 55;
        opacity: 0;
        pointer-events: none;
        transition: opacity 0.25s ease;
    }

    html[data-text-size="large"] body.sidebar-open #sidebar-overlay {
        opacity: 1;
        pointer-events: auto;
    }

    html[data-text-size="large"] #app-sidebar .header-font-group,
    html[data-text-size="large"] #app-sidebar .font-toggle {
        flex-wrap: wrap;
    }

    html[data-text-size="large"] #app-sidebar,
    html[data-text-size="large"] #sidebar-overlay,
    html[data-text-size="large"] .sidebar-close {
        display: none;
    }
}

@media (min-width: 1201px) {
    html[data-text-size="large"] #app-header {
        min-height: 4.25rem;
        height: auto;
        padding-top: 0.35rem;
        padding-bottom: 0.35rem;
    }

    html[data-text-size="large"] #header-nav {
        justify-content: safe center;
    }

    html[data-text-size="large"] #header-nav .nav-btn {
        max-width: none;
        padding-left: 0.75rem;
        padding-right: 0.75rem;
    }

    html[data-text-size="large"] .header-search {
        width: clamp(8rem, 12vw, 12rem) !important;
        flex-basis: clamp(8rem, 12vw, 12rem);
    }
}

@media (max-width: 420px) {
    html[data-text-size="large"] #gateway-view {
        padding-top: 0.75rem;
        padding-bottom: 0.75rem;
    }

    html[data-text-size="large"] #gateway-logo.brand-hero {
        width: min(12.5rem, calc(100vw - 1.5rem));
        height: clamp(2.75rem, 16vw, 3.5rem);
    }

    html[data-text-size="large"] .gateway-prefs {
        left: auto;
        right: auto;
        justify-content: center;
        max-width: 100%;
        gap: 0.4rem;
    }

    html[data-text-size="large"] .gw-pref-btn,
    html[data-text-size="large"] .gw-font-btn {
        min-height: 2.35rem;
        padding-left: 0.55rem;
        padding-right: 0.55rem;
    }
}

.char-counter {
    display: none !important;
}
