/* =================================================================
   ProvincialSuite Design System — Component Layer
   -----------------------------------------------------------------
   Builds on css/design-system.css (the --ps-* tokens). Two parts:
     A) .ps-* reusable component kit (new classes; bespoke pages opt
        in — zero blast radius).
     B) Conservative global refinements to Radzen primitives + brand
        alignment of Radzen's --rz-* color vars, so existing pages
        inherit the new look without markup changes.
   DESIGN/UI ONLY — no behavior. All colors flow through --theme-*/
   --ps-* so per-province templates keep overriding.
   ================================================================= */

/* =================================================================
   FIXED-OVERLAY ESCAPE — un-trap inline full-screen overlays.
   -----------------------------------------------------------------
   Radzen 5.5's material-base.css sets `.rz-layout .rz-body { transform:
   translateZ(0) }` (a GPU-layer scroll-perf hack). A `transform` makes
   .rz-body the CONTAINING BLOCK for every `position: fixed` descendant,
   so a full-screen overlay rendered INLINE in a page (NOT via a Radzen
   dialog — those render in .rz-dialog-wrapper, OUTSIDE .rz-body) is
   trapped inside the content box: it sits below the topbar / right of the
   sidebar and its dark backdrop can't cover the viewport, so later feed
   content paints on top and the page never dims. This broke the Community
   feed's "Add a photo" chooser + Image Manager (both are inline custom
   overlays), which then didn't match the centered-modal + full dark
   backdrop pattern the CMS dialogs get for free.

   Fix: drop .rz-body's transform ONLY while such an overlay is actually
   mounted (each overlay class only exists in the DOM when open), so the
   overlay's own `position: fixed` resolves against the viewport again and
   covers the whole screen. Scoped via :has() so nothing else is affected;
   when these components are used inside a Radzen dialog the overlay isn't
   a descendant of .rz-body, so this never matches there (no regression). */
   2026-07-26: widened from `.rz-body` alone to the whole shell (.rz-layout,
   .rz-body, .page-content-area) and from `transform` alone to the other
   containing-block-creating properties, after the School Profile logo picker still
   opened centred inside the content area with the page undimmed. The components
   ALSO ask for the escape directly at open time (`window.psOverlayEscape` in
   js/imageManager.js), because an inline style survives a stale CSS bundle and a
   browser without :has() — this rule is the no-JS path, not the only path.
   Everything is scoped to "an overlay is currently mounted". */
body:has(.image-manager-overlay) .rz-layout,
body:has(.image-manager-overlay) .rz-body,
body:has(.image-manager-overlay) .page-content-area,
body:has(.cpc-chooser-backdrop) .rz-layout,
body:has(.cpc-chooser-backdrop) .rz-body,
body:has(.cpc-chooser-backdrop) .page-content-area,
body:has(.image-upload-overlay) .rz-layout,
body:has(.image-upload-overlay) .rz-body,
body:has(.image-upload-overlay) .page-content-area,
body:has(.video-manager-overlay) .rz-layout,
body:has(.video-manager-overlay) .rz-body,
body:has(.video-manager-overlay) .page-content-area {
    transform: none !important;
    filter: none !important;
    perspective: none !important;
    backdrop-filter: none !important;
    will-change: auto !important;
    contain: none !important;
}

/* =================================================================
   B0) Align Radzen's color + radius variables with the brand.
   (Low risk: unknown var names simply no-op.)
   ================================================================= */
:root {
    --rz-primary: var(--ps-primary-600);
    --rz-primary-light: var(--ps-primary-400);
    --rz-primary-dark: var(--ps-primary-700);
    --rz-primary-lighter: var(--ps-primary-100);
    --rz-secondary: var(--ps-neutral-500);
    --rz-info: var(--ps-info);
    --rz-success: var(--ps-success);
    --rz-warning: var(--ps-warning);
    --rz-danger: var(--ps-danger);
    --rz-text-color: var(--ps-text);
    --rz-text-secondary-color: var(--ps-text-muted);
    --rz-border-radius: var(--ps-radius-md);
    --rz-link-color: var(--ps-primary-600);
}

/* =================================================================
   A) .ps-* REUSABLE COMPONENT KIT
   ================================================================= */

/* ---- Card --------------------------------------------------------- */
.ps-card {
    background: var(--theme-surface, var(--ps-surface));
    border: 1px solid var(--theme-border, var(--ps-border));
    border-radius: var(--theme-radius-lg, var(--ps-radius-lg));
    box-shadow: var(--ps-shadow-sm);
    padding: var(--ps-space-6);
    transition: transform var(--ps-duration) var(--ps-ease), box-shadow var(--ps-duration) var(--ps-ease), border-color var(--ps-duration) var(--ps-ease);
}
.ps-card--interactive { cursor: pointer; }
.ps-card--interactive:hover {
    transform: translateY(-4px);
    box-shadow: var(--ps-shadow-lg);
    border-color: var(--theme-primary, var(--ps-primary-300));
}
.ps-card--flush { padding: 0; }

/* ---- Page header / hero band ------------------------------------- */
.ps-page-header {
    position: relative;
    overflow: hidden;
    background: linear-gradient(135deg, var(--theme-gradient-start, var(--ps-primary-600)) 0%, var(--theme-gradient-end, var(--ps-primary-800)) 100%);
    color: #fff;
    border-radius: var(--theme-radius-lg, var(--ps-radius-xl));
    /* Tighter hero padding (was space-8/2rem — too tall). */
    padding: var(--ps-space-6) var(--ps-space-7, 1.75rem);
    box-shadow: var(--ps-shadow-md);
}
/* Decorative gradient highlight — mirrors the working .search-hero pattern so
   the hero reads as "designed" (depth) instead of a flat block. */
.ps-page-header::before {
    content: '';
    position: absolute;
    top: -50%;
    right: -10%;
    width: 400px;
    height: 400px;
    background: rgba(255, 255, 255, 0.1);
    border-radius: 50%;
    pointer-events: none;
}
/* Keep hero content above the decorative circle. */
.ps-page-header > * { position: relative; z-index: 1; }
.ps-page-header__eyebrow {
    display: inline-block;
    padding: 5px 12px;
    background: rgba(255, 255, 255, 0.16);
    border: 1px solid rgba(255, 255, 255, 0.28);
    border-radius: var(--ps-radius-full);
    font-size: var(--ps-text-xs);
    font-weight: 600;
    letter-spacing: 0.04em;
    margin-bottom: var(--ps-space-2);
    backdrop-filter: blur(8px);
}
/* Icon + title row for page heroes (matches the Community pages' HeroIcon pattern). */
.ps-page-header__titlerow {
    display: flex;
    align-items: center;
    gap: var(--ps-space-3, 0.75rem);
    margin-bottom: var(--ps-space-2, 0.5rem);
}
.ps-page-header__titlerow .ps-page-header__title {
    margin: 0;
}
.ps-page-header__title {
    font-family: var(--ps-font-display);
    font-size: var(--ps-text-3xl);
    font-weight: 800;
    letter-spacing: var(--ps-tracking-tight);
    margin: 0 0 var(--ps-space-2);
    line-height: var(--ps-leading-tight);
}
.ps-page-header__subtitle {
    font-size: var(--ps-text-base);
    opacity: 0.92;
    margin: 0;
    max-width: 60ch;
}

/* ---- Section heading --------------------------------------------- */
.ps-section { margin-bottom: var(--ps-space-10); }
/* .ps-section is a transparent LAYOUT wrapper (margin only), NOT a content card — but its name
   matches the global [class*="-section"] card-padding rule (in the mobile/tablet blocks below),
   which insets its children by 0.75rem/1rem and makes the .ps-page-header hero look "framed"
   inside a phantom card. Keep its horizontal sides flush so the hero/content span the page gutter
   like the Community heroes do. (Vertical handled by the hero's own padding + margins.) */
.page-content-area .ps-section {
    padding-left: 0 !important;
    padding-right: 0 !important;
}
.ps-section-header { text-align: center; margin-bottom: var(--ps-space-8); }
.ps-section-title {
    font-family: var(--ps-font-display);
    font-size: var(--ps-text-3xl);
    font-weight: 700;
    color: var(--theme-text-primary, var(--ps-text));
    letter-spacing: var(--ps-tracking-tight);
    margin: 0 0 var(--ps-space-2);
}
.ps-section-subtitle {
    font-size: var(--ps-text-lg);
    color: var(--theme-text-secondary, var(--ps-text-muted));
    margin: 0;
}

/* ---- Grid helpers ------------------------------------------------ */
.ps-grid { display: grid; gap: var(--ps-space-6); }
.ps-grid--cards { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
.ps-grid--tight { gap: var(--ps-space-4); }

/* ---- Stat / metric card ----------------------------------------- */
.ps-stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: var(--ps-space-4); }
.ps-stat {
    background: var(--ps-gradient-surface);
    border: 1px solid var(--theme-border, var(--ps-border));
    border-radius: var(--theme-radius-lg, var(--ps-radius-lg));
    padding: var(--ps-space-5);
    box-shadow: var(--ps-shadow-xs);
    transition: transform var(--ps-duration) var(--ps-ease), box-shadow var(--ps-duration) var(--ps-ease);
}
.ps-stat:hover { transform: translateY(-3px); box-shadow: var(--ps-shadow-md); }
.ps-stat__icon {
    width: 44px; height: 44px;
    border-radius: var(--ps-radius-md);
    display: flex; align-items: center; justify-content: center;
    background: var(--theme-primary-light, var(--ps-primary-50));
    color: var(--theme-primary, var(--ps-primary-600));
    font-size: 1.25rem;
    margin-bottom: var(--ps-space-3);
}
.ps-stat__num {
    font-family: var(--ps-font-display);
    font-size: var(--ps-text-3xl);
    font-weight: 800;
    color: var(--theme-text-primary, var(--ps-text));
    line-height: 1;
}
.ps-stat__label {
    font-size: var(--ps-text-sm);
    color: var(--theme-text-secondary, var(--ps-text-muted));
    font-weight: 500;
    margin-top: var(--ps-space-1);
}

/* ---- Empty state (kills the "wall of zeros") -------------------- */
.ps-empty {
    text-align: center;
    padding: var(--ps-space-12) var(--ps-space-6);
    color: var(--theme-text-secondary, var(--ps-text-muted));
}
.ps-empty__icon {
    width: 72px; height: 72px;
    margin: 0 auto var(--ps-space-4);
    border-radius: var(--ps-radius-full);
    display: flex; align-items: center; justify-content: center;
    background: var(--theme-primary-light, var(--ps-primary-50));
    color: var(--theme-primary, var(--ps-primary-600));
    font-size: 2rem;
}
.ps-empty__title {
    font-family: var(--ps-font-display);
    font-size: var(--ps-text-xl);
    font-weight: 700;
    color: var(--theme-text-primary, var(--ps-text));
    margin: 0 0 var(--ps-space-2);
}
.ps-empty__text { font-size: var(--ps-text-base); margin: 0 auto; max-width: 44ch; }

/* ---- Buttons ----------------------------------------------------- */
.ps-btn {
    display: inline-flex; align-items: center; justify-content: center; gap: var(--ps-space-2);
    font-family: var(--ps-font-body);
    font-weight: 600;
    font-size: var(--ps-text-sm);
    padding: 10px 18px;
    border-radius: var(--ps-radius-md);
    border: 1px solid transparent;
    cursor: pointer;
    text-decoration: none;
    transition: background var(--ps-duration) var(--ps-ease), transform var(--ps-duration) var(--ps-ease), box-shadow var(--ps-duration) var(--ps-ease);
}
.ps-btn--primary { background: var(--theme-primary, var(--ps-primary-600)); color: #fff; box-shadow: var(--ps-shadow-sm); }
.ps-btn--primary:hover { background: var(--theme-primary-dark, var(--ps-primary-700)); box-shadow: var(--ps-shadow-md); transform: translateY(-1px); }
.ps-btn--secondary { background: var(--theme-surface, #fff); color: var(--theme-primary, var(--ps-primary-700)); border-color: var(--theme-border, var(--ps-primary-200)); }
.ps-btn--secondary:hover { background: var(--theme-primary-light, var(--ps-primary-50)); }
.ps-btn--ghost { background: transparent; color: var(--theme-text-secondary, var(--ps-text-muted)); }
.ps-btn--ghost:hover { background: var(--ps-neutral-100); color: var(--theme-text-primary, var(--ps-text)); }
.ps-btn--accent { background: var(--theme-accent, var(--ps-accent-500)); color: var(--ps-neutral-900); box-shadow: var(--ps-shadow-sm); }
.ps-btn--accent:hover { background: var(--ps-accent-600); color: #fff; }
.ps-btn--lg { padding: 14px 24px; font-size: var(--ps-text-base); border-radius: var(--ps-radius-lg); }

/* ---- Pills / badges --------------------------------------------- */
.ps-pill { display: inline-flex; align-items: center; gap: 4px; padding: 4px 12px; border-radius: var(--ps-radius-full); font-size: var(--ps-text-xs); font-weight: 600; }
.ps-pill--primary { background: var(--ps-primary-50); color: var(--ps-primary-700); }
.ps-pill--success { background: var(--ps-success-bg); color: var(--ps-success-700, #15803d); }
.ps-pill--warning { background: var(--ps-warning-bg); color: var(--ps-warning-700, #b45309); }
.ps-pill--danger  { background: var(--ps-danger-bg);  color: var(--ps-danger-700, #b91c1c); }
.ps-pill--info    { background: var(--ps-info-bg);    color: var(--ps-info-700, #0369a1); }
.ps-pill--neutral { background: var(--ps-neutral-100); color: var(--ps-neutral-600); }

/* ---- Skeleton loading shimmer ----------------------------------- */
.ps-skeleton {
    background: linear-gradient(90deg, var(--ps-neutral-100) 25%, var(--ps-neutral-200) 37%, var(--ps-neutral-100) 63%);
    background-size: 400% 100%;
    animation: ps-shimmer 1.4s ease infinite;
    border-radius: var(--ps-radius-sm);
}
@keyframes ps-shimmer { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }

/* Touch devices: comfortable tap targets for the kit's buttons */
@media (hover: none) and (pointer: coarse) {
    .ps-btn { min-height: var(--ps-tap-min, 44px); }
}

/* Small screens: kit headers scale down */
@media (max-width: 640px) {
    .ps-page-header { padding: var(--ps-space-4) var(--ps-space-5); }
    .ps-page-header__title, .ps-section-title { font-size: var(--ps-text-2xl); }
    .ps-section-subtitle { font-size: var(--ps-text-base); }
    /* Kit cards get a small phone gutter; stat tiles stay tight (just a number,
       so they pack onto a phone row). */
    .ps-card { padding: 0.5rem; }
    .ps-stat { padding: 4px; }
    /* Radzen cards used across ~58 pages — give their content a small but visible
       gutter on phones (not flush to the border). Scoped under .page-content-area
       so it outranks Radzen's default .rz-card. */
    .page-content-area .rz-card,
    .page-content-area .rz-card-body { padding: 0.5rem; }

    /* Many pages hardcode 2–3rem padding on custom page-local wrapper classes
       (*-hero / *-section / *-card) with no mobile override, wasting width and
       compounding when nested. Trim them globally here so every page benefits
       without per-page edits. Scoped to .page-content-area + common suffixes;
       higher specificity than the page-local single-class rules so it wins.
       Keep a ~12px inner gutter so text never sits flush against the card border.
       NOTE: deliberately NOT matching "-form"/"-state" — those collide with
       Radzen's .rz-template-form / .rz-state-* classes. */
    .page-content-area [class*="-hero"]:not([class*="-content"]):not([class*="-icon"]):not([class*="-title"]) {
        padding: var(--ps-space-4) 0.75rem;
    }
    /* Sections AND content cards keep a real inner gutter so text never sits
       flush to the border. (Don't zero the section horizontal padding globally:
       many document-style pages — About/Privacy/Terms/FAQ — use a *-section AS
       the content card with text directly inside, so zeroing flushes their text.
       Card-grid-wrapper sections that need to sit at the body gutter handle that
       in their own per-page "dissolve" rule instead, e.g. Community Announcements.)
       COVER-WRAPPER EXCLUSIONS (-image/-thumbnail/-cover): list-card cover wrappers are
       BEM-named `*-card-image` / `*-card-cover` (e.g. announcement-card-image, event-card-image,
       poll-card-image, grp-card-cover), so they ALSO match [class*="-card"] and would get padded
       here — which insets the <img> INSIDE the wrapper even though the full-bleed escape below
       pulls the wrapper to the card edge (net: a gutter around the photo, list pages only). Exclude
       image/thumbnail/cover wrappers from padding so only real cards get the gutter. (Home-section
       wrappers like `announcement-image` have no `-card` token, so they were never padded — which
       is why sections looked flush but list cards didn't.) */
    .page-content-area [class*="-section"]:not([class*="-title"]):not([class*="-subtitle"]):not([class*="-icon"]):not([class*="-header"]):not([class*="cm-section"]),
    .page-content-area [class*="-card"]:not([class*="-title"]):not([class*="-icon"]):not([class*="-desc"]):not([class*="-header"]):not([class*="-img"]):not([class*="-image"]):not([class*="-thumbnail"]):not([class*="-cover"]):not([class*="feed-card"]) {
        padding: 0.5rem;
    }

    /* COVER IMAGE FULL-BLEED (phones). The card padding above insets the cover photo;
       cancel it on the image's top/left/right so the photo is flush to the card edges
       (like the community feed), while the body text keeps its gutter. The cover WRAPPER
       (a direct child of the card, class ~ *-image / *-thumbnail / *-cover) gets a
       negative margin equal to the card padding and stretches to +2×padding wide; the
       card's own overflow:hidden clips it to the rounded corners. Excludes detail-page
       covers (*-detail*, intentionally rounded/inset), spotlight shape variants (round/
       portrait), avatars (*-photo/*-avatar — different names), and overlays. */
    .page-content-area [class*="-card"]:not([class*="detail"]):not([class*="spotlight"]) > [class*="-image"],
    .page-content-area [class*="-card"]:not([class*="detail"]):not([class*="spotlight"]) > [class*="-thumbnail"],
    .page-content-area [class*="-card"]:not([class*="detail"]):not([class*="spotlight"]) > [class*="-cover"]:not([class*="overlay"]) {
        margin: -0.5rem -0.5rem 0 !important;
        width: calc(100% + 1rem) !important;
        max-width: calc(100% + 1rem) !important;
        display: block !important;
    }

    /* ===== COMPACT VERTICAL RHYTHM (phones) — match the CMS homepage =====
       Portal pages (Community Announcements, Events, Discussions, Donation Drives,
       etc.) give each top-level block — hero, stats-section, involvement-section —
       its own `margin-bottom: 1.5rem`. Stacked down a phone these read much looser
       than the home page, whose sections share a single tight 0.75rem gap. Trim the
       bottom margin of those top-level wrappers so every portal page reads as compact
       as the homepage, without per-page edits. Higher specificity than the page-local
       single-class rules, so it wins. Card GRIDS keep their own `gap`; this only
       touches the section/hero wrappers (the [class*="-card"] arm is excluded so
       individual cards inside a stack don't lose their separation). */
    .page-content-area [class*="-hero"]:not([class*="-content"]):not([class*="-icon"]):not([class*="-title"]),
    .page-content-area [class*="-section"]:not([class*="-title"]):not([class*="-subtitle"]):not([class*="-icon"]):not([class*="-header"]):not([class*="cm-section"]) {
        margin-bottom: 0.75rem;
    }

    /* DATA-CARD GRID WIDTH (phones). The Campaign Strategy pages (and many others)
       wrap a RadzenDataGrid in: .data-card > .card-header + .card-body { padding:
       1.5rem }. With the generic [class*="-card"] rule above ALSO padding .data-card,
       the grid loses ~2.5rem of width on each side — wasteful on a ~360px phone.
       Make .data-card a flush frame (header/body already provide the inset) and trim
       the header/body horizontal gutters so the grid/table uses nearly the full
       screen width. Vertical padding is kept. Applies app-wide to this shared
       pattern, so every Strategy page benefits without per-page edits. */
    .page-content-area .data-card { padding: 0 !important; }
    .page-content-area .data-card .card-header {
        padding-left: 0.75rem !important;
        padding-right: 0.75rem !important;
    }
    .page-content-area .data-card .card-body {
        padding-left: 0.5rem !important;
        padding-right: 0.5rem !important;
    }

}

/* iPad / tablet (641–1024px): a slightly roomier 1rem card gutter (phones use 0.75rem above).
   Same selectors/specificity as the phone block, scoped to its own width range so the two
   never conflict. Lets every card-bearing page use nearly the full tablet width too. */
@media (min-width: 641px) and (max-width: 1024px) {
    .ps-page-header { padding: var(--ps-space-5) var(--ps-space-6); }
    .page-content-area [class*="-hero"]:not([class*="-content"]):not([class*="-icon"]):not([class*="-title"]) {
        padding: var(--ps-space-6) 1rem;
    }
    .ps-card { padding: 1rem; }
    .ps-stat { padding: 6px; }
    .page-content-area .rz-card,
    .page-content-area .rz-card-body { padding: 1rem; }
    .page-content-area [class*="-section"]:not([class*="-title"]):not([class*="-subtitle"]):not([class*="-icon"]):not([class*="-header"]):not([class*="cm-section"]),
    .page-content-area [class*="-card"]:not([class*="-title"]):not([class*="-icon"]):not([class*="-desc"]):not([class*="-header"]):not([class*="-img"]):not([class*="-image"]):not([class*="-thumbnail"]):not([class*="-cover"]):not([class*="feed-card"]) {
        padding: 1rem;
    }
    /* COVER IMAGE FULL-BLEED (tablet) — mirror of the phone rule above, matching the
       1rem tablet card padding. See that block for the rationale + exclusions. */
    .page-content-area [class*="-card"]:not([class*="detail"]):not([class*="spotlight"]) > [class*="-image"],
    .page-content-area [class*="-card"]:not([class*="detail"]):not([class*="spotlight"]) > [class*="-thumbnail"],
    .page-content-area [class*="-card"]:not([class*="detail"]):not([class*="spotlight"]) > [class*="-cover"]:not([class*="overlay"]) {
        margin: -1rem -1rem 0 !important;
        width: calc(100% + 2rem) !important;
        max-width: calc(100% + 2rem) !important;
        display: block !important;
    }
    /* Compact vertical rhythm on tablets too (a touch roomier than phones). */
    .page-content-area [class*="-hero"]:not([class*="-content"]):not([class*="-icon"]):not([class*="-title"]),
    .page-content-area [class*="-section"]:not([class*="-title"]):not([class*="-subtitle"]):not([class*="-icon"]):not([class*="-header"]):not([class*="cm-section"]) {
        margin-bottom: 1rem;
    }
}

/* ============================================================================
   DETAIL-PAGE COVER FULL-BLEED (opted in 2026-07-09)
   Content detail pages render inside a padded `.detail-card`. The app-wide cover-bleed escape
   (above) deliberately EXCLUDES detail cards (`:not([class*="detail"])`), so their cover image
   normally stays inset. The product owner asked detail covers to go edge-to-edge like the
   feed/list cards. Bleed the shared detail cover classes (.detail-image / .event-image /
   .detail-gallery) HORIZONTALLY to the card edges — horizontal-only so it works whether the image
   sits at the card top or mid-card, and never needs overflow clipping (the kept vertical padding
   keeps the image off the card's rounded corners). Negative margin = the detail card's padding at
   each breakpoint: phones/tablet inherit the GLOBAL card padding (0.5rem / 1rem); desktop uses the
   card's own 1.5rem horizontal padding (Events normalised from 2rem to match). */
.page-content-area .detail-card > .detail-image,
.page-content-area .detail-card > .event-image,
.page-content-area .detail-card > .detail-gallery {
    border-radius: 0;
}
@media (min-width: 1025px) {
    .page-content-area .detail-card > .detail-image,
    .page-content-area .detail-card > .event-image,
    .page-content-area .detail-card > .detail-gallery {
        margin-left: -1.5rem; margin-right: -1.5rem;
        width: calc(100% + 3rem); max-width: calc(100% + 3rem);
    }
}
@media (min-width: 641px) and (max-width: 1024px) {
    .page-content-area .detail-card > .detail-image,
    .page-content-area .detail-card > .event-image,
    .page-content-area .detail-card > .detail-gallery {
        margin-left: -1rem; margin-right: -1rem;
        width: calc(100% + 2rem); max-width: calc(100% + 2rem);
    }
}
@media (max-width: 640px) {
    .page-content-area .detail-card > .detail-image,
    .page-content-area .detail-card > .event-image,
    .page-content-area .detail-card > .detail-gallery {
        margin-left: -0.5rem; margin-right: -0.5rem;
        width: calc(100% + 1rem); max-width: calc(100% + 1rem);
    }
}

/* RADZEN STEPS HEADER OVERFLOW (phones + tablets/iPad, up to 1024px). RadzenSteps
   renders the step header as inline-block <li>s with NO whitespace between them, so
   there is no soft-wrap opportunity — a multi-step wizard (Register Business: 5 steps,
   Geographic Import) runs off the right edge and the later steps are hidden. This bites
   on phones AND on iPad/tablet when the main nav is EXPANDED and squeezes the content
   (the labels "Subscription"/"Review & Submit" clip off-screen). Make the header a flex
   row that WRAPS. flex-wrap only wraps when the row actually overflows, so applying it
   up to 1024px is safe (stays one row when there's room). Scoped via :has(> .rz-steps-item)
   to the header list ONLY — never the in-step content lists. */
@media (max-width: 1024px) {
    .page-content-area .rz-steps ul:has(> .rz-steps-item) {
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
        gap: 0.5rem 1.25rem;
    }
    .page-content-area .rz-steps-item { white-space: normal; }
    .page-content-area .rz-steps-title { font-size: 0.8rem; }
}

/* =================================================================
   B) GLOBAL RADZEN REFINEMENTS (conservative, token-based)
   Only safe properties (radius, weight, shadow, transition, focus)
   that won't shift existing layouts.
   ================================================================= */

/* Buttons — softer radius, confident weight, smooth feedback */
.rz-button {
    border-radius: var(--ps-radius-md);
    font-weight: 600;
    transition: background-color var(--ps-duration) var(--ps-ease), box-shadow var(--ps-duration) var(--ps-ease), transform var(--ps-duration) var(--ps-ease);
}
.rz-button:not(.rz-state-disabled):hover { transform: translateY(-1px); }
.rz-button:not(.rz-state-disabled):active { transform: translateY(0); }

/* Cards — modern radius, hairline border, soft elevation */
.rz-card {
    border-radius: var(--ps-radius-lg);
    border: 1px solid var(--theme-border, var(--ps-border));
    box-shadow: var(--ps-shadow-sm);
}

/* Inputs — consistent radius + brand focus ring */
.rz-textbox,
.rz-textarea,
.rz-dropdown,
.rz-datepicker,
.rz-numeric,
.rz-autocomplete,
.rz-mask,
.rz-password {
    border-radius: var(--ps-radius-md);
}
.rz-textbox:focus,
.rz-textarea:focus,
.rz-textbox.rz-state-focused,
.rz-dropdown.rz-state-focused,
.rz-numeric.rz-state-focused,
.rz-datepicker.rz-state-focused {
    box-shadow: var(--ps-ring);
    border-color: var(--theme-primary, var(--ps-primary-500));
}

/* Call-to-action card (shared <CtaCard> component) */
.ps-cta {
    background: var(--theme-primary-light, #eff6ff);
    border: 1px solid var(--theme-border, #e2e8f0);
    border-radius: var(--ps-radius-lg, 14px);
    padding: 2.5rem;
    text-align: center;
    margin-top: 2rem;
}
.ps-cta--gradient {
    background: linear-gradient(135deg, var(--theme-gradient-start, #2563eb), var(--theme-gradient-end, #1e3a8a));
    border: none;
    color: white;
}
.ps-cta-icon {
    width: 80px;
    height: 80px;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--theme-gradient-start, #2563eb), var(--theme-gradient-end, #1e3a8a));
    display: flex;
    align-items: center;
    justify-content: center;
    margin: 0 auto 1.5rem;
    color: white;
}
/* Gradient variant: plain centred icon, no circle (matches the original cta-card) */
.ps-cta--gradient .ps-cta-icon {
    width: auto;
    height: auto;
    background: none;
    border-radius: 0;
    margin-bottom: 1rem;
}
.ps-cta-title {
    font-size: 1.5rem;
    font-weight: 700;
    color: var(--theme-text-primary, #0f172a);
    margin-bottom: 0.75rem;
    font-family: var(--ps-font-display, inherit);
    letter-spacing: var(--ps-tracking-tight, -0.02em);
}
.ps-cta--gradient .ps-cta-title { color: white; }
.ps-cta-subtitle {
    color: var(--theme-text-secondary, #64748b);
    margin: 0 auto 1.5rem;
    max-width: 500px;
    line-height: 1.6;
}
.ps-cta--gradient .ps-cta-subtitle { color: rgba(255, 255, 255, 0.9); }
.ps-cta-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    justify-content: center;
}

/* Election selector dropdown (used across ElectionHub public pages) */
.election-selector {
    min-width: 300px;
    max-width: 100%;
}
@media (max-width: 640px) {
    .election-selector {
        min-width: 0;
        flex: 1 1 0;
    }
}

/* Badges & chips */
.rz-badge, .rz-chip { border-radius: var(--ps-radius-full); font-weight: 600; }

/* Dialogs & menus — premium rounding + depth */
.rz-dialog { border-radius: var(--ps-radius-lg); box-shadow: var(--ps-shadow-xl); overflow: hidden; max-width: 96vw; }
.rz-menu, .rz-paginator { border-radius: var(--ps-radius-md); }

/* DataGrid — calmer header, lighter row separators */
.rz-datatable .rz-datatable-thead > tr > th,
.rz-grid-table thead th {
    background: var(--ps-neutral-50);
    font-weight: 600;
    color: var(--theme-text-primary, var(--ps-text));
    border-bottom: 1px solid var(--ps-border);
}
.rz-datatable .rz-datatable-data > tr > td,
.rz-grid-table tbody td {
    border-bottom: 1px solid var(--ps-neutral-100);
}

/* Tabs — rounded active indicator feel */
.rz-tabview .rz-tabview-nav li.rz-state-active a { color: var(--theme-primary, var(--ps-primary-600)); }

/* =================================================================
   MOBILE/TABLET DIALOGS — keep action buttons reachable (phones & tablets)
   On a phone a dialog can grow taller than the *visible* viewport (vh
   counts the area behind the browser chrome), pushing the footer
   (Cancel / Save) off-screen — the same class of bug fixed on the
   Upload & Crop dialog. This caps every Radzen dialog to the visible
   viewport (dvh) and scrolls its body so the footer stays reachable.
   The ~120 in-app dialogs using the shared .dialog-header/.dialog-content/
   .dialog-footer pattern additionally get a centered shell with a pinned
   header + footer and a scrolling body (matched via :has). Browsers
   without :has still get the generic cap+scroll safety net.
   ================================================================= */

/* =================================================================
   DIALOG HEADER TEXT CONTRAST
   The shared .dialog-header sits on a dark blue gradient and sets
   color:white. But RadzenText/RadzenHeading render their own default
   (dark) theme color, overriding the inherited white -> dark-on-blue
   unreadable titles. Force every text node inside the header to inherit
   the header's white. Inline color on a RadzenText still wins, so the
   few headers that already set color:white are unaffected.
   ================================================================= */
/* Match [class*="rz-text-"] (NOT bare ".rz-text" — that class does not exist;
   RadzenText only ever renders .rz-text-h5 / .rz-text-caption / .rz-text-body1
   etc., each with its OWN dark color). The trailing dash keeps it from matching
   .rz-textbox inputs. Without this the dialog SUBTITLE (a Caption = <span>, not
   <p>) was never caught and stayed dark on the blue header. Icons included too. */
.dialog-header :is([class*="rz-text-"], h1, h2, h3, h4, h5, h6, p, .rzi, .rz-icon, .notranslate),
.committee-dialog-header :is([class*="rz-text-"], h1, h2, h3, h4, h5, h6, p, .rzi, .rz-icon, .notranslate),
.member-dialog-header :is([class*="rz-text-"], h1, h2, h3, h4, h5, h6, p, .rzi, .rz-icon, .notranslate) {
    color: inherit !important;
}

/* =================================================================
   HERO / BANNER TEXT CONTRAST  (same problem as dialog headers above)
   Page heroes sit on a dark/colored gradient and set color:white (or
   color:var(--theme-surface)) on the wrapper, expecting the title,
   subtitle and stat numbers/labels to inherit it. But RadzenText/
   RadzenIcon render their OWN default (dark) theme color on the element
   itself, which beats inheritance -> dark, unreadable text on the blue
   hero (seen on Community Events and most stat-hero pages). Force every
   text node + icon inside a hero/banner to inherit the wrapper's color.
   Scoped to text + icons ONLY (NOT .rz-textbox/.rz-dropdown/.rz-button)
   so filter-hero inputs and action buttons keep their own colors. A hero
   that deliberately sets an inline color on a RadzenText still wins.
   NOTE: matches the *-hero wrappers (events-hero, announcements-hero,
   marketplace-hero, detail-hero-banner [contains "-hero"], …) — every one
   sets a light color:white / var(--theme-surface) on the wrapper, so
   inheriting it is correct. Deliberately NOT ".hero-section" (shared with
   the Home landing hero, whose wrapper sets NO color — inheriting there
   pulls the dark page color and breaks it; [class*="-hero"] doesn't match
   "hero-section" anyway, no leading-dash). And deliberately NOT
   [class*="-banner"]: that also matches LIGHT callouts (.info-banner amber-
   on-cream, .selected-school-banner) that set no light wrapper color, so
   inheriting would wipe their intended colors. Dark hero banners are named
   *-hero-banner and are already covered by the "-hero" match. */
[class*="-hero"] :is([class*="rz-text-"], h1, h2, h3, h4, h5, h6, p, .rzi, .rz-icon, .notranslate) {
    color: inherit !important;
}

/* =================================================================
   HERO ICON BADGE  (the <HeroIcon> reusable component)
   Standard amber-gradient rounded-square badge + white icon shown
   beside every page-hero title. Retune ALL hero icons app-wide from
   here. The icon is an inline SVG (AppIcon) or webfont fallback (.rzi);
   either way it inherits the badge's white. NOTE: class is
   "hero-icon-badge" (no leading-dash "hero"), so the hero text-contrast
   rule above doesn't recolor the badge itself — only the icon inside it,
   which we want white anyway.
   ================================================================= */
.hero-icon-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 52px;
    height: 52px;
    flex: 0 0 auto;
    border-radius: var(--ps-radius-md, 12px);
    background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);
    box-shadow: 0 6px 16px rgba(245, 158, 11, 0.35);
    color: #ffffff;
}
.hero-icon-badge .app-icon,
.hero-icon-badge .rzi,
.hero-icon-badge .rz-icon {
    width: 1.6rem;
    height: 1.6rem;
    font-size: 1.6rem;
    line-height: 1;
    color: #ffffff;
}
@media (max-width: 640px) {
    .hero-icon-badge {
        width: 44px;
        height: 44px;
        border-radius: 10px;
    }
    .hero-icon-badge .app-icon,
    .hero-icon-badge .rzi,
    .hero-icon-badge .rz-icon {
        width: 1.4rem;
        height: 1.4rem;
        font-size: 1.4rem;
    }
}

/* =================================================================
   HERO STAT ROW OVERFLOW (phones only)
   Page heroes/banners lay out a horizontal RadzenStack with the title on
   the left and stat blocks (number + label) on the right, using
   JustifyContent.SpaceBetween. On a narrow phone the long title and the
   stats can't share one row, so the stats spill past the hero's right
   edge and get clipped (the hero is overflow:hidden). On phones, stack
   that top row vertically — title above, stats below, left-aligned — so
   nothing is cut off. Scoped to hero/banner containers and to the
   space-between row, so other stacks are untouched. Radzen emits both a
   utility class and (older) an inline style, so we match either.
   NOTE: [class*="-hero"] matches marketplace-hero / my-biz-hero etc., but
   NOT the templated Election Hub / Alumni Hub family, whose container is
   class="hero-section" (substring "hero-", no leading dash). Those are the
   pages whose action buttons (Add Target, Export Data, …) were clipping off
   the right edge on phones, so .hero-section is matched explicitly below.
   ================================================================= */
@media (max-width: 767.98px) {
    [class*="-hero"] .rz-stack.rz-justify-content-space-between,
    [class*="-hero"] .rz-stack[style*="space-between"],
    .hero-section > .rz-stack.rz-justify-content-space-between,
    .hero-section > .rz-stack[style*="space-between"],
    [class*="-banner"] .rz-stack.rz-justify-content-space-between,
    [class*="-banner"] .rz-stack[style*="space-between"] {
        flex-direction: column !important;
        align-items: flex-start !important;
    }
}

/* Hero FILTER rows (phones + tablets + iPad, up to 1024px). Many heroes (esp. the
   AlumniHub Public directory pages — Colleges, Schools, High/Elementary, Directory)
   put a search box + town/type dropdowns, each with a FIXED inline width (200px/150px),
   in a horizontal stack on the right of the hero. The ~550px row runs off the hero's
   right edge — NOT just on phones but on iPad/tablet portrait (768–1024px) too, where
   the phone-only rule never reached ("Select Towns" / "All Types" clipped off-screen).
   For ANY hero whose row contains form inputs: (1) column-stack the OUTER hero row so
   the filters drop BELOW the title, then (2) let the filter row + inputs fill the width
   (width:100% beats their non-important inline width). :has() scopes this to filter
   heroes only — stat/action-button heroes are untouched.
   `.rz-autocomplete` belongs in every list here: it is what [SchoolSwitcher] renders, and
   while it was missing the School Events / Announcements heroes kept their "Search
   school…" box beside the action button at half width on a phone. */
@media (max-width: 1024px) {
    .hero-section > .rz-stack:has(.rz-textbox, .rz-dropdown, .rz-lookup, .rz-datepicker, .rz-numeric, .rz-autocomplete),
    [class*="-hero"] > .rz-stack:has(.rz-textbox, .rz-dropdown, .rz-lookup, .rz-datepicker, .rz-numeric, .rz-autocomplete) {
        flex-direction: column !important;
        align-items: stretch !important;
    }
    .hero-section .rz-stack:has(> .rz-textbox, > .rz-dropdown, > .rz-lookup, > .rz-datepicker, > .rz-numeric, > .rz-autocomplete),
    [class*="-hero"] .rz-stack:has(> .rz-textbox, > .rz-dropdown, > .rz-lookup, > .rz-datepicker, > .rz-numeric, > .rz-autocomplete) {
        width: 100% !important;
        flex-wrap: wrap !important;
    }
    /* An input paired with an action button (School Events "Create Event", Announcements
       "New Announcement"): the BUTTON goes on top and the input takes the full row below
       it. The button is last in the DOM, so column-reverse puts it first visually without
       touching any page's markup. */
    .hero-section .rz-stack:has(> .rz-autocomplete):has(> .rz-button),
    [class*="-hero"] .rz-stack:has(> .rz-autocomplete):has(> .rz-button) {
        flex-direction: column-reverse !important;
        align-items: stretch !important;
        flex-wrap: nowrap !important;
    }
    .hero-section .rz-stack > .rz-textbox,
    .hero-section .rz-stack > .rz-dropdown,
    .hero-section .rz-stack > .rz-lookup,
    .hero-section .rz-stack > .rz-datepicker,
    .hero-section .rz-stack > .rz-numeric,
    .hero-section .rz-stack > .rz-autocomplete,
    [class*="-hero"] .rz-stack > .rz-textbox,
    [class*="-hero"] .rz-stack > .rz-dropdown,
    [class*="-hero"] .rz-stack > .rz-lookup,
    [class*="-hero"] .rz-stack > .rz-datepicker,
    [class*="-hero"] .rz-stack > .rz-numeric,
    [class*="-hero"] .rz-stack > .rz-autocomplete {
        width: 100% !important;
        /* [SchoolSwitcher] carries an inline max-width:360px — release it here too. */
        max-width: none !important;
        flex: 1 1 100% !important;
        min-width: 0 !important;
    }
}

/* =================================================================
   DIALOG FORM ROWS — one field per row on phones.
   The Alumni dialogs (and any dialog that copied the pattern) lay their fields out
   with a page-local `.form-row` (2 columns) / `.form-row-3` (3 columns) grid, and
   15 of them shipped with NO mobile override — so a phone got three ~90px inputs
   side by side ("Conta… | Email | Phone") with the labels truncated.
   These class names are already effectively global (a razor style block is not
   scoped), so the breakpoint belongs here once rather than in every dialog.
   !important because the page-local rule has the same specificity and is injected
   into the body, i.e. AFTER this file. Flex-based `.form-row`s (EmailCompose) are
   untouched — grid-template-columns does nothing to them.
   `.form-row-3` collapses one breakpoint earlier: three long inputs need the room
   even on a small tablet, where two still fit.
   ================================================================= */
@media (max-width: 767.98px) {
    .form-row-3 { grid-template-columns: 1fr !important; }
}
@media (max-width: 640px) {
    .form-row, .form-row-2 { grid-template-columns: 1fr !important; }
}
/* FORM CARDS ON PHONES — one field per row, and compact card spacing.
   The Alumni editor pages (School Profile, Settings, …) put 2–3 fields side by side with
   `<RadzenStack Orientation="Horizontal">` and `flex: 1` children instead of the `.form-row`
   grid, which squeezes each field to ~110px on a phone ("School Ty…", "F…"). Stack ANY
   horizontal Radzen stack whose children are form fields — vertical stacks are already
   column, so they're unaffected — and trim the card padding/margins that were tuned for
   desktop. The existing phone rule above only trims the card's LEFT/RIGHT gutters. */
@media (max-width: 640px) {
    .rz-stack:has(> .rz-form-field),
    .rz-stack:has(> * > .rz-form-field) {
        flex-direction: column !important;
        align-items: stretch !important;
        gap: 0.75rem !important;
    }
    .page-content-area .data-card { margin-bottom: 0.75rem !important; }
    .page-content-area .data-card .card-header {
        padding-top: 0.75rem !important;
        padding-bottom: 0.75rem !important;
    }
    .page-content-area .data-card .card-body {
        padding-top: 0.75rem !important;
        padding-bottom: 0.75rem !important;
    }
    .page-content-area .form-section { margin-bottom: 0.75rem !important; }
}

/* Same treatment for the READ-ONLY `.info-grid` label/value pairs in the view dialogs
   (Member / Batch / School / Event / Announcement / Donation / Project / ContentBlock /
   EmailTemplate): a fixed `repeat(2, 1fr)` squeezed both pairs into ~150px columns and
   wrapped the values.
   SCOPED TO `.rz-dialog` ON PURPOSE. Three PAGES (School Applications, Verification
   Queue, Poll Watcher Info) use the same class name for a compact chip grid built on
   `auto-fit/minmax(120–280px)`, which already reflows on its own — forcing 1fr there
   would stretch each little "Batch / Members" chip across the whole width. Dialogs only. */
@media (max-width: 640px) {
    .rz-dialog .info-grid { grid-template-columns: 1fr !important; }
}

/* A grid ITEM's default min-width is its content size, so a long unbroken value (an email,
   a company name) made its column wider than its 1fr share and the text painted straight
   over the neighbouring pair — the "Edward De Guzman / edeguzman@…IT Professional" overlap
   on the Verification Queue cards. Let the item shrink and let the value break. Applies to
   every .info-grid (pages + dialogs); labels are short and unaffected. */
.info-grid .info-item { min-width: 0; }
.info-grid .info-item span { overflow-wrap: anywhere; }

/* =================================================================
   SCHOOL SWITCHER ROW (Alumni Hub school-management pages).
   The single-select school autocomplete that sits as its own row above the hero
   (dashboard) or below it (profile/officers/donations/gallery/projects/reports/
   verification). On desktop it stays a compact 360px switcher; on tablet/phone it
   should span the FULL hero width (no left inset) and hug the hero with a tighter
   gap. The inner .rz-autocomplete carries an inline max-width:360px, so the
   breakpoint override needs !important to release it.
   ================================================================= */
.school-switch { margin-bottom: 1.5rem; max-width: 360px; }
.school-switch .rz-autocomplete { width: 100% !important; }
@media (max-width: 1024px) {
    .school-switch { max-width: 100%; margin-bottom: 0.5rem; }
    .school-switch .rz-autocomplete { max-width: 100% !important; }
}

/* The same switcher also appears INSIDE an empty state ("Select a school to view
   batches"), where it is the page's only control — there it should use the width it
   has instead of sitting as a 360px box with wide dead margins either side. The
   component carries an inline max-width, hence !important. Phones also lose the
   empty state's 1.5rem side padding so the box runs nearly edge to edge. */
.ps-empty .rz-autocomplete { width: 100% !important; max-width: 420px !important; }
@media (max-width: 640px) {
    .ps-empty { padding-left: 0.75rem; padding-right: 0.75rem; }
    .ps-empty .rz-autocomplete { max-width: none !important; }
}

/* =================================================================
   FILTER-BAR RESPONSIVE SIZING (Alumni Hub + admin list pages).
   The .filter-bar rows carry filter controls (dropdowns, multi-select, search
   boxes) with fixed inline widths (e.g. 150/180/200/350px). On desktop those
   fixed widths stay. On:
     - tablet (<=1024px): the controls share the row EQUALLY and fill its full
       width (flex: 1 1 0), so there's no dead space on the right.
     - phone (<=768px): the bar stacks and each control takes its own FULL-width
       row.
   Scoped to DIRECT Radzen-control children only, so action buttons (.rz-button)
   and flex spacers keep their natural size, and RadzenRow-based filter bars
   (Election admin) — where the direct child is .rz-row, not a control — are
   untouched. !important is needed to beat the inline widths.
   `.ps-picker-field` is in the list because the School/Town/User/Entity PICKER FIELDS are the one
   filter control that is NOT a Radzen control — it renders its own wrapper, so before it was listed
   the "All Schools" picker on the Verification Queue kept its inline 280px while every neighbouring
   filter went full width on a phone. */
@media (max-width: 1024px) {
    .filter-bar > .rz-dropdown,
    .filter-bar > .rz-textbox,
    .filter-bar > .rz-lookup,
    .filter-bar > .rz-datepicker,
    .filter-bar > .rz-numeric,
    .filter-bar > .ps-picker-field {
        flex: 1 1 0 !important;
        width: auto !important;
        min-width: 0 !important;
    }
}
@media (max-width: 768px) {
    .filter-bar {
        flex-direction: column;
        align-items: stretch;
    }
    .filter-bar > .rz-dropdown,
    .filter-bar > .rz-textbox,
    .filter-bar > .rz-lookup,
    .filter-bar > .rz-datepicker,
    .filter-bar > .rz-numeric,
    .filter-bar > .ps-picker-field {
        flex: 0 0 auto !important;
        width: 100% !important;
    }
}

/* =================================================================
   DROPDOWN / LOOKUP POPUP OVERFLOW (phones).
   A RadzenDropDownDataGrid / RadzenLookup opens its options in a JS-positioned,
   absolutely-placed .rz-dropdown-panel (sometimes wrapped in .rz-popup). When the
   grid has fixed-width columns (Town/City 180 + Type 120 + District 100 + checkbox
   50 = ~450px) the panel's natural width exceeds the phone, so the popup spills off
   BOTH edges (the first column is clipped on the left). Cap the panel/popup to the
   viewport and let the grid scroll horizontally inside it instead. Found on many
   pages that open a picker grid (AlumniHub town/school filters, etc.).
   ================================================================= */
@media (max-width: 767.98px) {
    /* Baseline cap for every dropdown/lookup popup (harmless for normal item-list
       dropdowns, which are already narrow). */
    .rz-popup,
    .rz-dropdown-panel {
        max-width: 96vw !important;
        min-width: 0 !important;
        box-sizing: border-box !important;
    }
    /* Grid pickers specifically: Radzen's JS sets inline left/width for the FULL grid
       width (~450px) before this CSS caps it, so capping width alone leaves the panel
       at a negative left (clipped on the left). Re-anchor the grid popups to a fixed
       on-screen span (2vw..98vw) and let the datagrid scroll horizontally inside.
       :has() limits this to grid popups, so ordinary dropdowns keep their natural
       under-the-trigger positioning. */
    .rz-dropdown-panel:has(.rz-data-grid, .rz-datatable, .rz-grid-table) {
        left: 2vw !important;
        right: 2vw !important;
        width: auto !important;
        max-width: 96vw !important;
        overflow-x: auto !important;
    }
    .rz-popup .rz-data-grid,
    .rz-popup .rz-datatable,
    .rz-popup .rz-grid-table,
    .rz-dropdown-panel .rz-data-grid,
    .rz-dropdown-panel .rz-datatable,
    .rz-dropdown-panel .rz-grid-table {
        max-width: 100% !important;
    }
}

@media (max-width: 1024px) {
    /* Generic safety net for ALL Radzen dialogs (now covers tablets/iPad too) */
    .rz-dialog {
        max-width: 96vw !important;
        max-height: 96vh !important;
        max-height: 96dvh !important;
    }
    .rz-dialog .rz-dialog-content {
        max-height: 96vh !important;
        max-height: 96dvh !important;
        overflow-y: auto !important;
        -webkit-overflow-scrolling: touch;
    }

    /* HARDCODED DIALOG MIN-WIDTH OVERFLOW (phones/tablets).
       ~64 in-app dialogs hardcode min-width: 600–800px on their OWN root wrapper
       (.dialog-container, .telegram-campaign-dialog, .route-dialog-container, …).
       On a narrow screen that forces the wrapper wider than the 96vw dialog cap,
       so the dialog overflows horizontally and the footer's primary button (Create
       Campaign / Go Live) is pushed off the right edge — only Cancel stays visible.
       Neutralize the min-width on the wrapper(s) inside .rz-dialog-content so they
       fit. The flex children get min-width:0 too (a flex item's default min-width
       is its content size, which would otherwise still overflow). NOTE: dropdown
       popups (.rz-popup .rz-dropdown-panel) set their own min-width and render in a
       SEPARATE overlay — they are not descendants of .rz-dialog-content, so this
       rule deliberately doesn't touch them. */
    .rz-dialog .rz-dialog-content > *:not(style):not(script),
    .rz-dialog .rz-dialog-content [class*="dialog-container"],
    .rz-dialog .rz-dialog-content [class*="-dialog"] {
        min-width: 0 !important;
        max-width: 100% !important;
    }

    /* Shared in-app dialog pattern: pin header + footer, scroll the body.
       Anchored near the TOP (not vertically centered) — centering a tall dialog
       positions it by its full height, which can push the header above the
       viewport. Top-anchoring keeps the header visible; the body scrolls.
       Scoped to dialogs that actually use the pattern.
       The Alumni dialogs came in FOUR naming families — .dialog-*, .view-* (the 8
       *ViewDialogs), .note-* (verification notes) and .album-* (AlbumViewDialog) —
       and only the first was listed here, so the other 11 scrolled as one block and
       their Cancel/Save sat below the fold on a phone. All four are matched now. */
    .rz-dialog:has(.dialog-footer),
    .rz-dialog:has(.view-footer),
    .rz-dialog:has(.note-footer),
    .rz-dialog:has(.album-footer) {
        position: fixed !important;
        top: 2dvh !important;
        left: 50% !important;
        transform: translateX(-50%) !important;
        height: auto !important;
        max-height: 96vh !important;
        max-height: 96dvh !important;
        display: flex !important;
        flex-direction: column !important;
        overflow: hidden !important;
    }
    .rz-dialog:has(.dialog-footer) .rz-dialog-titlebar,
    .rz-dialog:has(.view-footer) .rz-dialog-titlebar,
    .rz-dialog:has(.note-footer) .rz-dialog-titlebar,
    .rz-dialog:has(.album-footer) .rz-dialog-titlebar {
        flex: 0 0 auto !important;
    }
    .rz-dialog:has(.dialog-footer) .rz-dialog-content,
    .rz-dialog:has(.view-footer) .rz-dialog-content,
    .rz-dialog:has(.note-footer) .rz-dialog-content,
    .rz-dialog:has(.album-footer) .rz-dialog-content {
        flex: 1 1 auto !important;
        min-height: 0 !important;
        overflow: hidden !important; /* the inner .dialog-content does the scrolling */
        padding: 0 !important;
        display: flex !important;
        flex-direction: column !important;
    }
    /* The component root becomes the flex column that holds header / content /
       footer. Exclude <style>/<script> — Blazor renders a component's <style>
       block as a sibling of the root div, and forcing display:flex on it would
       make the CSS text visible AND steal flex space from the real content. */
    .rz-dialog:has(.dialog-footer) .rz-dialog-content > *:not(style):not(script),
    .rz-dialog:has(.view-footer) .rz-dialog-content > *:not(style):not(script),
    .rz-dialog:has(.note-footer) .rz-dialog-content > *:not(style):not(script),
    .rz-dialog:has(.album-footer) .rz-dialog-content > *:not(style):not(script) {
        flex: 1 1 auto !important;
        min-height: 0 !important;
        display: flex !important;
        flex-direction: column !important;
    }
    .rz-dialog .dialog-header,
    .rz-dialog .dialog-footer,
    .rz-dialog .view-header,
    .rz-dialog .view-footer,
    .rz-dialog .note-header,
    .rz-dialog .note-footer,
    .rz-dialog .album-header,
    .rz-dialog .album-footer {
        flex: 0 0 auto !important;
    }
    .rz-dialog .dialog-content,
    .rz-dialog .view-content,
    .rz-dialog .note-content,
    .rz-dialog .album-content {
        flex: 1 1 auto !important;
        min-height: 0 !important;
        max-height: none !important; /* override fixed max-heights (e.g. 600px) */
        overflow-y: auto !important;
        -webkit-overflow-scrolling: touch;
    }

    /* The floating chat assistant (z-index:10000) sits above dialogs/overlays
       and covers their controls — hide it while any dialog or full-screen
       overlay is open (Radzen dialogs + the custom overlays). */
    body:has(.rz-dialog) .aibot-fab,
    body:has(.video-manager-overlay) .aibot-fab,
    body:has(.image-upload-overlay) .aibot-fab,
    body:has(.modal-gallery) .aibot-fab,
    body:has(.stream-viewer-overlay) .aibot-fab {
        display: none !important;
    }
}

/* ============================================================================
   COMPACT DIALOG PADDING — PHONES ONLY (≤640px).
   The dialog SHELL already fits the screen (96vw cap + the :has(.dialog-footer)
   pinning above), but the shared in-app dialog body uses generous desktop
   paddings — .dialog-header/.dialog-content/.dialog-footer at 24px and the inner
   cards (.section-card ×411, .form-section, .info-card, .upload-area) at 20–24px.
   On a phone that double inset (card-in-a-dialog) wastes a lot of width, just
   like the .rz-body gutter did on pages. Trim it to a tight, uniform frame so
   dialogs read as compact as the redesigned pages. Scoped under .rz-dialog so it
   only touches dialog content; desktop/tablet keep the roomier padding.
   ============================================================================ */
@media (max-width: 640px) {
    /* Same four naming families as the pinning rules above. */
    .rz-dialog .dialog-header,
    .rz-dialog .view-header,
    .rz-dialog .note-header,
    .rz-dialog .album-header {
        padding: 14px 16px !important;
    }
    .rz-dialog .dialog-content,
    .rz-dialog .view-content,
    .rz-dialog .note-content,
    .rz-dialog .album-content {
        padding: 12px !important;
    }
    .rz-dialog .dialog-footer,
    .rz-dialog .view-footer,
    .rz-dialog .note-footer,
    .rz-dialog .album-footer {
        padding: 12px !important;
    }
    /* Inner cards inside the dialog body — the "card inside the dialog" the user
       flagged. Drop their padding + bottom margin to a compact 12px. */
    .rz-dialog .section-card,
    .rz-dialog .form-section,
    .rz-dialog .info-card,
    .rz-dialog [class*="-section-card"] {
        padding: 12px !important;
        margin-bottom: 12px !important;
        /* The shared .dialog-content already supplies a 12px side gutter on phones;
           the cards' own 24px side margins stacked on top wasted ~36px of width.
           Zero them so cards sit flush to the content padding (uniform 12px frame). */
        margin-left: 0 !important;
        margin-right: 0 !important;
    }
    /* Upload / drop zones nested one level deeper still — keep a little breathing
       room but well below the 24px desktop value. */
    .rz-dialog .upload-area,
    .rz-dialog .image-upload-area,
    .rz-dialog [class*="upload-area"] {
        padding: 14px !important;
    }
}

/* COMPACT DIALOG PADDING — TABLET (iPad / Android, 641–1024px).
   Proportionate step between the phone's 12px and desktop's 24px: 16px. Same
   shared dialog pattern, same scope (.rz-dialog), so dialogs read consistently
   tighter on tablets without looking cramped like a phone. */
@media (min-width: 641px) and (max-width: 1024px) {
    .rz-dialog .dialog-header {
        padding: 16px 20px !important;
    }
    .rz-dialog .dialog-content {
        padding: 16px !important;
    }
    .rz-dialog .dialog-footer {
        padding: 16px !important;
    }
    .rz-dialog .section-card,
    .rz-dialog .form-section,
    .rz-dialog .info-card,
    .rz-dialog [class*="-section-card"] {
        padding: 16px !important;
        margin-bottom: 16px !important;
        margin-left: 0 !important;
        margin-right: 0 !important;
    }
    .rz-dialog .upload-area,
    .rz-dialog .image-upload-area,
    .rz-dialog [class*="upload-area"] {
        padding: 16px !important;
    }
}

/* ============================================================================
   GLOBAL badge/pill case fix — render badges in natural MIXED-CASE.
   The About page .tech-badge (which has NO text-transform) centers correctly on
   iOS; the badges that ride high all share one thing: `text-transform: uppercase`,
   which iOS WebKit lays out shifted UP. Dropping the transform centers the label
   cleanly on iOS, Android AND desktop — no other CSS needed. `!important` here so
   this ALWAYS wins over each page's per-badge rule (important beats non-important
   regardless of selector specificity / source order), so no page can re-uppercase.
   Applies on every page (one global rule, not per-page).
   ============================================================================ */
.priority-badge, .featured-badge, .category-badge, .pinned-badge,
.event-month, .compact-month, .featured-month, .detail-month, .schedule-date-month,
.title-badge, .carousel-title-badge, .spotlight-badge, .carousel-badge,
.spotlight-category, .carousel-spotlight-category,
.status-badge, .impl-role-badge, .alert-severity,
.employment-badge, .urgent-badge, .remote-badge, .deadline-badge,
.badge-featured, .badge-category, .event-category,
/* Admin content-editor dialogs + ElectionHub / AlumniHub card badges that
   share the same uppercase-pill bug (the Home-card fix, extended to the rest). */
.role-badge, .priority-pill, .position-badge, .severity-badge,
.preview-category, .featured-banner, .material-format,
.donation-type-badge, .achievement-type-badge, .media-type-badge,
.event-date-month,
/* Native Radzen badges/chips (e.g. Role Management: System / Custom / "N permissions" /
   Active). The Radzen Standard theme uppercases .rz-badge by default — same ride-high bug. */
.rz-badge, .rz-chip {
    text-transform: none !important;
}

/* iOS-ONLY badge text centering (V1 — user-confirmed on device). Plain text-only pill labels
   ride high on iOS WebKit; asymmetric TOP-HEAVY padding nudges the text down to center. em-relative
   so it scales with each badge's font-size; only padding-top/bottom are touched (horizontal kept).
   :has() skips icon badges (shifting padding would move the icon too — those get a text nudge).
   Scoped to iOS via -webkit-touch-callout so Android & desktop keep their original symmetric
   padding. !important so it wins over each page's per-badge padding. NOTE: .alert-severity is an
   AppButton (already centered) so it's intentionally NOT in this list. */
@supports (-webkit-touch-callout: none) {
    :is(.priority-badge, .featured-badge, .category-badge, .pinned-badge,
        .event-month, .compact-month, .featured-month, .detail-month, .schedule-date-month,
        .title-badge, .carousel-title-badge, .spotlight-badge, .carousel-badge,
        .spotlight-category, .carousel-spotlight-category, .status-badge, .impl-role-badge,
        .event-category, .employment-badge, .urgent-badge, .remote-badge,
        .deadline-badge, .badge-featured, .badge-category,
        .role-badge, .priority-pill, .position-badge, .severity-badge,
        .preview-category, .featured-banner, .material-format,
        .donation-type-badge, .achievement-type-badge, .media-type-badge,
        .event-date-month, .rz-badge, .rz-chip):not(:has(svg)):not(:has(img)):not(:has([class*="icon"])) {
        padding-top: 0.6em !important;
        padding-bottom: 0.13em !important;
    }
}

/* ============================================================================
   iOS input auto-zoom fix (app-wide) — PHONES ONLY.
   iOS Safari zooms the whole page whenever a focused text field has a font-size
   below 16px. On fixed-height layouts (chat composer, pinned comment boxes) that
   zoom shoves the input off-screen and enlarges everything ("the text is big").
   Enforce a 16px MINIMUM on form controls so focus never triggers zoom. Only
   ENLARGES sub-16px controls; desktop/tablet keep their own sizing.
   NOTE: do NOT use `interactive-widget` in the viewport meta — it was tried and
   broke bottom inputs app-wide (reverted). This font-size floor is the safe fix.
   ============================================================================ */
@media (max-width: 768px) {
    input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="color"]):not([type="file"]),
    textarea,
    select,
    .rz-textbox,
    .rz-textarea,
    .rz-inputtext,
    .rz-dropdown .rz-dropdown-label,
    .rz-autocomplete .rz-inputtext {
        font-size: 16px !important;
    }
}

/* Global page-error recovery UI (the ErrorBoundary around @Body in MainLayout). */
.app-error-recover {
    max-width: 460px;
    margin: 10vh auto;
    padding: 2rem 1.5rem;
    text-align: center;
}
.app-error-recover__icon { font-size: 3rem; color: var(--ps-warning, #f59e0b); }
.app-error-recover__title {
    font-size: 1.25rem; font-weight: 700; margin: 0.75rem 0 0.4rem;
    color: var(--ps-neutral-900, #0f172a);
}
.app-error-recover__text { color: var(--ps-neutral-500, #64748b); margin-bottom: 1.25rem; }
.app-error-recover__actions { display: flex; gap: 0.75rem; justify-content: center; flex-wrap: wrap; }

/* ============================================================================
   Grid-panel school / town pickers — [SchoolMultiSelect] and [TownMultiSelect],
   in BOTH their modes (multi by default, `Multiple="false"` for one pick).
   Radzen portals each dropdown panel to <body> as #popup-{Id}, putting it
   outside the component's own markup, so these rules have to live globally.

   Matched by id PREFIX (`popup-school*` / `popup-town*`) so every instance is
   covered — the page filters (`schoolFilterDropdown` / `townDropdown`) and the
   single-pick instances in dialogs alike. That is what makes a picker inside a
   dialog look exactly like the "Select School" dropdown on the Alumni Directory
   page, so any new instance just needs an Id starting with "school"/"town" —
   and, for the phone column-trimming below, "schoolSelect"/"townSelect" when it
   is a single-pick instance.
   ========================================================================= */

/* ---------------------------------------------------------------------------
   SINGLE-PICK FIELD (SchoolMultiSelect / TownMultiSelect with Multiple="false")
   The field is a plain <button> that opens [SchoolPickerDialog]/[TownPickerDialog] —
   NOT a dropdown panel, which cannot escape a dialog's scrolling overflow:hidden
   ancestor. The outlined floating label is drawn here rather than with a
   RadzenFormField so there is no component boundary and nothing to re-render.
   --------------------------------------------------------------------------- */
.ps-picker-field { position: relative; width: 100%; padding-top: 0.5rem; }
.ps-picker-field__label {
    position: absolute;
    top: 0.5rem;
    transform: translateY(-50%);
    left: 0.75rem;
    z-index: 1;
    padding: 0 0.25rem;
    background: var(--rz-base-background-color, var(--theme-surface, #fff));
    font-size: 0.75rem;
    line-height: 1.3;
    color: var(--theme-text-secondary, #64748b);
    pointer-events: none;
    white-space: nowrap;
    max-width: calc(100% - 1.5rem);
    overflow: hidden;
    text-overflow: ellipsis;
}
.ps-picker-field:focus-within .ps-picker-field__label { color: var(--theme-primary, #2563eb); }

.ps-picker-trigger {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    min-height: 2.75rem;              /* comfortable touch target */
    padding: 0.5rem 0.5rem 0.5rem 0.75rem;
    background: var(--rz-base-background-color, var(--theme-surface, #fff));
    border: 1px solid var(--theme-border, #cbd5e1);
    border-radius: var(--ps-radius-sm, 6px);
    font: inherit;
    font-size: 1rem;                  /* ≥16px: never zooms the page on iOS */
    color: var(--theme-text-primary, #0f172a);
    text-align: start;
    cursor: pointer;
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.ps-picker-trigger:hover:not(:disabled) { border-color: var(--theme-primary, #2563eb); }
.ps-picker-trigger:focus-visible {
    outline: none;
    border-color: var(--theme-primary, #2563eb);
    box-shadow: 0 0 0 2px var(--theme-primary-light, #dbeafe);
}
.ps-picker-trigger--disabled, .ps-picker-trigger:disabled {
    background: var(--ps-neutral-50, #f8fafc);
    color: var(--theme-text-secondary, #64748b);
    cursor: not-allowed;
}
.ps-picker-trigger__value {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    flex: 1 1 auto;
    min-width: 0;
}
.ps-picker-trigger__value--empty { color: var(--theme-text-secondary, #94a3b8); }
.ps-picker-trigger__text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ps-picker-trigger__logo {
    width: 24px; height: 24px; border-radius: var(--ps-radius-sm, 6px);
    object-fit: cover; flex-shrink: 0;
}
.ps-picker-trigger__caret { color: var(--theme-text-secondary, #64748b); flex-shrink: 0; }

/* ---- the picker dialog itself ----
   The dialog is opened with Style="padding: 0", but Radzen's own `.rz-dialog-content`
   keeps a gutter of its own, which left a white frame around the gradient header. Zero
   it here so the header goes edge to edge, and on phones/tablets let the dialog take
   almost the full width — it is a list, it wants the room. */
.rz-dialog:has(.ps-picker-dialog) .rz-dialog-content { padding: 0 !important; }
.rz-dialog:has(.ps-picker-dialog) { padding: 0 !important; overflow: hidden; }

@media (max-width: 1024px) {
    .rz-dialog:has(.ps-picker-dialog) {
        width: calc(100vw - 1rem) !important;   /* small, even gutter left and right */
        max-width: calc(100vw - 1rem) !important;
    }
}

.ps-picker-dialog { display: flex; flex-direction: column; max-height: 88vh; }
.ps-picker-dialog__header {
    display: flex; align-items: center; gap: 0.625rem;
    padding: 0.75rem 1rem;
    background: linear-gradient(135deg, var(--theme-gradient-start, var(--theme-primary, #2563eb)) 0%, var(--theme-gradient-end, var(--theme-secondary, #1e3a8a)) 100%);
    color: #fff;
}
.ps-picker-dialog__header .rzi, .ps-picker-dialog__header .rz-icon { font-size: 1.375rem; }
.ps-picker-dialog__title { font-weight: 600; font-size: 1.05rem; line-height: 1.3; }
.ps-picker-dialog__subtitle { font-size: 0.8125rem; opacity: 0.9; line-height: 1.3; }
.ps-picker-dialog__body {
    padding: 0.75rem;
    display: flex; flex-direction: column; gap: 0.5rem;
    min-height: 0;                     /* lets the list scroll instead of the dialog */
}
.ps-picker-dialog__search input, .ps-picker-dialog__search { font-size: 1rem; }

/* The LIST is what scrolls — capped at roughly five rows, exactly like the multi-select
   panel's row body — so choosing 10 or 15 per page scrolls here instead of growing the
   dialog past the viewport. */
.ps-picker-dialog__list {
    display: flex; flex-direction: column; gap: 0.25rem;
    overflow-y: auto;
    max-height: 20rem;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
}
/* Thin overlay scrollbar — same treatment as the multi-select panel: transparent track,
   so nothing is drawn until there is something to scroll; the thumb firms up on hover. */
.ps-picker-dialog__list {
    scrollbar-width: thin;
    scrollbar-color: var(--theme-border, #cbd5e1) transparent;
}
.ps-picker-dialog__list::-webkit-scrollbar { width: 6px; height: 6px; }
.ps-picker-dialog__list::-webkit-scrollbar-track { background: transparent; }
.ps-picker-dialog__list::-webkit-scrollbar-thumb {
    background: var(--theme-border, #cbd5e1);
    border-radius: 3px;
}
.ps-picker-dialog__list:hover::-webkit-scrollbar-thumb { background: var(--theme-text-secondary, #94a3b8); }

.ps-picker-dialog__empty {
    display: flex; flex-direction: column; align-items: center; gap: 0.5rem;
    padding: 2rem 1rem; color: var(--theme-text-secondary, #64748b);
}

/* Pager: one centred row (buttons then the page-size picker), matching the multi-select
   panel's pager so both pickers feel the same. */
.ps-picker-dialog__pager.rz-pager, .ps-picker-dialog__pager .rz-pager {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 0.25rem 0;
    background: transparent;
}
.ps-picker-dialog__pager .rz-pager-element {
    min-width: 36px; min-height: 36px; margin: 0;
    display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto;
}
.ps-picker-dialog__pager .rz-dropdown, .ps-picker-dialog__pager .rz-paginator-element {
    flex: 0 0 auto; margin: 0;
    width: 72px !important; min-width: 72px !important; max-width: 72px !important;
}
.ps-picker-dialog__pager .rz-pager-summary { font-size: 0.8125rem; white-space: nowrap; }

.ps-picker-dialog__clear { align-self: center; }

.ps-picker-row {
    display: flex; align-items: center; gap: 0.75rem;
    width: 100%;
    padding: 0.625rem 0.75rem;
    background: none;
    border: 1px solid transparent;
    border-radius: var(--ps-radius-md, 10px);
    font: inherit;
    text-align: start;
    cursor: pointer;
    transition: background 0.15s ease, border-color 0.15s ease;
}
.ps-picker-row:hover { background: var(--theme-primary-light, #eff6ff); }
.ps-picker-row--selected {
    background: var(--theme-primary-light, #eff6ff);
    border-color: var(--theme-primary, #2563eb);
}
.ps-picker-row__logo {
    width: 36px; height: 36px; border-radius: var(--ps-radius-sm, 6px);
    object-fit: cover; flex-shrink: 0;
}
.ps-picker-row__logo--placeholder {
    display: flex; align-items: center; justify-content: center;
    background: var(--theme-primary-light, #eff6ff);
    color: var(--theme-primary, #2563eb);
}
/* People, not entities: a round avatar. Used by [UserPickerDialog] / [UserPickerField], where the
   placeholder holds initials rather than an icon. */
.ps-picker-row__logo--avatar { border-radius: 50%; }
.ps-picker-row__logo--avatar.ps-picker-row__logo--placeholder {
    font-size: 0.8125rem; font-weight: 600; letter-spacing: 0.02em;
}
.ps-picker-row__main { display: flex; flex-direction: column; gap: 2px; flex: 1 1 auto; min-width: 0; }
.ps-picker-row__name {
    font-weight: 500; color: var(--theme-primary, #2563eb); line-height: 1.3;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ps-picker-row__meta {
    display: flex; align-items: center; gap: 4px;
    font-size: 0.75rem; line-height: 1.3; color: var(--theme-text-secondary, #64748b);
}
.ps-picker-row__meta .rzi, .ps-picker-row__meta .rz-icon { font-size: 14px; }
.ps-picker-row__check { color: var(--theme-primary, #2563eb); flex-shrink: 0; }

@media (max-width: 640px) {
    /* Phones: trim the gutters further and let the list use the height it has. */
    .ps-picker-dialog__body { padding: 0.5rem; gap: 0.375rem; }
    .ps-picker-dialog__header { padding: 0.625rem 0.75rem; }
    .ps-picker-dialog__list { max-height: 55vh; }
    .ps-picker-row { padding: 0.5rem; gap: 0.5rem; }
    /* The wordy summary/label cost more than they explain on a narrow pager row. */
    .ps-picker-dialog__pager .rz-pager-summary,
    .ps-picker-dialog__pager .rz-pagesize-text { display: none !important; }
}
[id^="popup-town"],
[id^="popup-school"] {
    /* Was a hard 580px, which overflowed the viewport on phones. */
    width: min(580px, calc(100vw - 1.5rem)) !important;
    /* The ROW LIST (.rz-datatable-tablewrapper below) is what scrolls and is
       capped at ~5 rows, so the panel keeps a fixed, compact shape no matter the
       page size. This max-height is only a viewport safety net. */
    max-height: 85vh !important;
    overflow-y: auto !important;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
    right: 0 !important;
    left: auto !important;
}

/* Thin overlay scrollbar that stays out of the way — same treatment as the Home
   page section lists (see .polls-section .poll-options in PollSection.razor).
   Transparent track, so nothing is drawn until there is actually something to
   scroll; the thumb firms up on hover. Applies to the panel and any scrollable
   descendant Radzen renders inside it. */
[id^="popup-town"], [id^="popup-town"] *,
[id^="popup-school"], [id^="popup-school"] * {
    scrollbar-width: thin;
    scrollbar-color: var(--theme-border, #cbd5e1) transparent;
}
[id^="popup-town"]::-webkit-scrollbar, [id^="popup-town"] ::-webkit-scrollbar,
[id^="popup-school"]::-webkit-scrollbar, [id^="popup-school"] ::-webkit-scrollbar {
    width: 6px;
    height: 6px;
}
[id^="popup-town"]::-webkit-scrollbar-track, [id^="popup-town"] ::-webkit-scrollbar-track,
[id^="popup-school"]::-webkit-scrollbar-track, [id^="popup-school"] ::-webkit-scrollbar-track {
    background: transparent;
}
[id^="popup-town"]::-webkit-scrollbar-thumb, [id^="popup-town"] ::-webkit-scrollbar-thumb,
[id^="popup-school"]::-webkit-scrollbar-thumb, [id^="popup-school"] ::-webkit-scrollbar-thumb {
    background: var(--theme-border, #cbd5e1);
    border-radius: 3px;
}
[id^="popup-town"]:hover::-webkit-scrollbar-thumb, [id^="popup-town"] :hover::-webkit-scrollbar-thumb,
[id^="popup-school"]:hover::-webkit-scrollbar-thumb, [id^="popup-school"] :hover::-webkit-scrollbar-thumb {
    background: var(--theme-text-secondary, #94a3b8);
}

[id^="popup-town"] .rz-datatable-data tr:nth-child(odd),
[id^="popup-school"] .rz-datatable-data tr:nth-child(odd) { background-color: var(--theme-primary-light, #eff6ff); }
[id^="popup-town"] .rz-datatable-data tr:nth-child(even),
[id^="popup-school"] .rz-datatable-data tr:nth-child(even) { background-color: var(--theme-surface, #ffffff); }
[id^="popup-town"] .rz-datatable-data tr:hover,
[id^="popup-school"] .rz-datatable-data tr:hover { background-color: var(--theme-primary-light, #dbeafe); cursor: pointer; }
[id^="popup-town"] .rz-datatable-header th,
[id^="popup-school"] .rz-datatable-header th {
    background: linear-gradient(135deg, var(--theme-gradient-start, var(--theme-primary, #2563eb)) 0%, var(--theme-gradient-end, var(--theme-secondary, #1e3a8a)) 100%);
    color: #fff;
    font-weight: 600;
}

/* Cap the ROW LIST at ~5 rows and scroll it internally (with the thin overlay
   scrollbar above). This is what keeps a page size of 10/15 from stretching the
   whole popup — the search box, column header and pager stay put; only the rows
   move.
   Radzen 11 renders the DropDownDataGrid body as `.rz-data-grid-data`
   (overflow:auto; flex:1) — NOT the legacy `.rz-datatable-tablewrapper`, so the
   old selector never matched and `flex:1` let the body grow with the page size.
   Radzen already pins `.rz-grid-table thead` sticky, so capping the body alone
   keeps the column header visible. The `.rz-datatable-*` selectors are kept as a
   fallback for any legacy grid. !important beats the base `flex:1`. */
[id^="popup-town"] .rz-data-grid-data,
[id^="popup-school"] .rz-data-grid-data,
[id^="popup-town"] .rz-datatable-tablewrapper,
[id^="popup-school"] .rz-datatable-tablewrapper {
    max-height: 20rem !important;
    overflow-y: auto !important;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
}
[id^="popup-town"] .rz-datatable-tablewrapper thead th,
[id^="popup-school"] .rz-datatable-tablewrapper thead th {
    position: sticky;
    top: 0;
    z-index: 2;
}

/* NOTE: there is no single-select POPUP any more. Picking ONE school/town is
   [SchoolPickerField]/[TownPickerField] — a field that opens a dialog — because a dropdown panel
   cannot escape a dialog's scrolling `overflow: hidden` ancestor. These rules are the MULTI-select
   filter panels only (Alumni Directory, Notable Alumni, the level pages). */

/* Row templates — the multi-select panels. */
.ps-town-select__row,
.ps-school-select__row { display: flex; align-items: center; gap: 8px; padding: 4px 0; cursor: pointer; }
.ps-town-select__logo { width: 20px; height: 20px; border-radius: 4px; object-fit: cover; flex-shrink: 0; }
.ps-school-select__logo {
    width: 32px; height: 32px; border-radius: var(--ps-radius-sm, 6px);
    object-fit: cover; flex-shrink: 0;
}
.ps-school-select__logo--placeholder {
    background: var(--theme-primary-light, #eff6ff);
    display: flex; align-items: center; justify-content: center;
}
.ps-school-select__name { font-weight: 500; color: var(--theme-primary, #2563eb); }
.ps-school-select__town {
    display: flex; align-items: center; gap: 4px;
    font-size: 12px; color: var(--theme-text-secondary, #64748b);
}
.ps-school-select__count {
    display: flex; align-items: center; justify-content: center; gap: 4px;
    font-weight: 600; color: var(--theme-primary, #2563eb);
}

/* Search row: the filter input already filters as you type (autocomplete), so the
   icon-only "search" button Radzen renders beside it is dead weight — and on a phone
   it eats a third of the row. Hidden on every grid-panel picker, single and multi. */
[id^="popup-town"] .rz-lookup-search > .rz-button,
[id^="popup-school"] .rz-lookup-search > .rz-button {
    display: none !important;
}
[id^="popup-town"] .rz-lookup-search-input-container,
[id^="popup-school"] .rz-lookup-search-input-container {
    flex: 1 1 auto;
    width: 100%;
}

/* Pager: one centred row — page buttons then the page-size picker. Radzen's
   default `space-between` flung the buttons to opposite edges of the panel, so
   the group is centred and held together by a small gap instead. */
[id^="popup-town"] .rz-paginator, [id^="popup-town"] .rz-pager,
[id^="popup-school"] .rz-paginator, [id^="popup-school"] .rz-pager {
    display: flex;
    flex-wrap: nowrap;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem;
}
[id^="popup-town"] .rz-pager-pages,
[id^="popup-school"] .rz-pager-pages {
    display: flex;
    align-items: center;
    gap: 2px;
}
/* Touch-sized without being spread out: 36px hit area, 2px apart. */
[id^="popup-town"] .rz-pager-element,
[id^="popup-school"] .rz-pager-element {
    min-width: 36px;
    min-height: 36px;
    margin: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
}
/* Keep the page-size picker on the same row as the buttons, never wrapped below.
   It only ever shows "5"/"10"/"15", so pin it narrow — left to size itself it
   stretched wide enough to push the first-page button off the row. Radzen sizes
   the picker's own popup from this trigger width, so the 5/10/15 list follows. */
[id^="popup-town"] .rz-pager .rz-dropdown, [id^="popup-town"] .rz-paginator-element,
[id^="popup-school"] .rz-pager .rz-dropdown, [id^="popup-school"] .rz-paginator-element {
    flex: 0 0 auto;
    margin: 0;
    width: 72px !important;
    min-width: 72px !important;
    max-width: 72px !important;
}
[id^="popup-town"] .rz-pager-summary,
[id^="popup-school"] .rz-pager-summary { font-size: 0.8125rem; white-space: nowrap; }

/* Centre the panel horizontally — EVERY width, not just phone/tablet. Radzen pins the popup
   to the trigger with an INLINE left style, and the base rule above adds `right: 0`, so on
   desktop a 580px grid opened from a filter bar hung off the right edge of the window.
   The panel stays `position: absolute` on purpose: `.rz-body` carries a translateZ(0), which
   makes it the containing block AND the clipper for a `position: fixed` panel — that is what
   squashed the centred panel to a ~110px sliver with its search box and pager cut off (same
   trap as [rz-body Fixed-Overlay]). Absolute keeps Radzen's vertical anchor under the trigger
   and nothing clips it. */
[id^="popup-town"],
[id^="popup-school"] {
    left: 50% !important;
    right: auto !important;
    transform: translateX(-50%);
}

/* Phone only: drop the wordy "items per page" / "N towns" labels so the buttons
   and the page-size picker fit one row. Tablet and desktop keep them. */
@media (max-width: 640px) {
    [id^="popup-town"] .rz-pager-summary, [id^="popup-town"] .rz-pagesize-text,
    [id^="popup-school"] .rz-pager-summary, [id^="popup-school"] .rz-pagesize-text {
        display: none !important;
    }
}
