/*
 * #1885: Minimal attendee-facing stylesheet for My JavaZone.
 *
 * Structure: all colors, fonts, radii and spacing live in CSS custom
 * properties on :root so official JavaZone brand assets can be dropped
 * in by overriding these variables in a sibling stylesheet (loaded
 * after this one) without touching component rules.
 *
 * #2442: the palette (light and dark) is aligned to MoreCake's colors —
 * near-white surfaces with a blue accent in light mode, warm charcoal
 * with a blue accent in dark mode. Official JavaZone brand values can
 * still replace these tokens without refactoring component rules.
 */

:root {
    /* #1885 / #2442: Branding variables — official JavaZone values replace
       these without refactoring component rules. Defaults match MoreCake's
       light palette. */
    --myjz-color-bg: #f8f9fa;
    --myjz-color-surface: #ffffff;
    --myjz-color-surface-muted: #f1f5f9;
    --myjz-color-text: #1a1a1a;
    --myjz-color-text-muted: #475569;
    --myjz-color-border: #e5e7eb;
    --myjz-color-accent: #2563eb;
    --myjz-color-accent-hover: #1d4ed8;
    --myjz-color-accent-contrast: #ffffff;
    --myjz-color-banner-bg: #fff8e6;
    --myjz-color-banner-border: #e8a200;

    /* #1894: Semantic status colours. Used by login/admin error notices
       and the admin viewas-active card. #2442: error tones match
       MoreCake's form-error; success tones match its approved badge/accent.
       Kept as additional :root entries so a future JavaZone brand drop-in
       can override them alongside the rest of the palette. */
    --myjz-color-error-bg: #fef2f2;
    --myjz-color-error-border: #fecaca;
    --myjz-color-error-text: #dc2626;
    --myjz-color-success-bg: #dcfce7;
    --myjz-color-success-border: #22c55e;

    --myjz-font-sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    --myjz-font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

    --myjz-radius: 6px;
    --myjz-gap: 1rem;
    --myjz-gap-lg: 1.5rem;

    --myjz-container-max: 56rem;
}

/*
 * #1894: Dark mode via prefers-color-scheme. CSS-only — no JS toggle.
 * Overrides the same :root custom properties so every rule inherits
 * the dark palette without component changes.
 *
 * #2442: palette aligned to MoreCake's dark mode — a warm-charcoal
 * surface stack with a blue accent (matching MoreCake's --color-*
 * dark overrides). Contrast targets WCAG AA (>= 4.5:1 for body text,
 * >= 3:1 for large text / UI chrome):
 *
 *   text #e0e0e0 on bg #1a1a1a           → ~14.9:1  (AAA)
 *   text #e0e0e0 on surface #222222      → ~13.6:1  (AAA)
 *   text-muted #999999 on bg             → ~6.1:1   (AA)
 *   text #e0e0e0 on banner-bg #3a2d0a    → ~11.5:1  (AAA)
 *   error-text #ff8888 on error-bg #3a1a1a → ~6.8:1 (AA body, AAA large)
 *
 * The accent is MoreCake's blue (#5599dd) with white text. White on
 * #5599dd is ~3:1 — meets AA for large text / UI chrome (primary
 * buttons, badges, focus rings), matching MoreCake's own primary
 * buttons. Banner tokens are left as-is: MoreCake has no banner
 * equivalent, and the amber notice reads at AAA on the dark bg.
 */
@media (prefers-color-scheme: dark) {
    :root {
        --myjz-color-bg: #1a1a1a;
        --myjz-color-surface: #222222;
        --myjz-color-surface-muted: #2a2a2a;
        --myjz-color-text: #e0e0e0;
        --myjz-color-text-muted: #999999;
        --myjz-color-border: #333333;
        --myjz-color-accent: #5599dd;
        --myjz-color-accent-hover: #88bbee;
        --myjz-color-accent-contrast: #ffffff;
        --myjz-color-banner-bg: #3a2d0a;
        --myjz-color-banner-border: #e8a200;

        --myjz-color-error-bg: #3a1a1a;
        --myjz-color-error-border: #663333;
        --myjz-color-error-text: #ff8888;
        --myjz-color-success-bg: #1a3a1a;
        --myjz-color-success-border: #2ea043;
    }

    /* #1894: Tell the UA about both schemes so native controls (form
       inputs, scrollbars, focus rings) render with a matching dark
       appearance instead of bright white defaults. */
    html {
        color-scheme: light dark;
    }
}

* { box-sizing: border-box; }

html, body {
    margin: 0;
    padding: 0;
}

.myjz-body {
    font-family: var(--myjz-font-sans);
    color: var(--myjz-color-text);
    background: var(--myjz-color-bg);
    line-height: 1.45;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

.myjz-body--welcome {
    display: flex;
    align-items: center;
    justify-content: center;
}

/* --- Welcome page --- */

.myjz-welcome {
    width: 100%;
    display: flex;
    justify-content: center;
    padding: 2rem 1rem;
}

.myjz-welcome__card {
    background: var(--myjz-color-surface);
    border: 1px solid var(--myjz-color-border);
    border-radius: var(--myjz-radius);
    padding: 2.5rem 2rem;
    max-width: 32rem;
    width: 100%;
    text-align: center;
}

.myjz-welcome__title {
    margin: 0 0 0.75rem;
    font-size: 2rem;
}

.myjz-welcome__lede {
    margin: 0 0 1.5rem;
    color: var(--myjz-color-text-muted);
}

/* --- Header --- */

.myjz-header {
    background: var(--myjz-color-surface);
    border-bottom: 1px solid var(--myjz-color-border);
}

.myjz-header__inner {
    max-width: var(--myjz-container-max);
    margin: 0 auto;
    padding: 0.75rem 1rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--myjz-gap);
}

.myjz-header__brand {
    font-weight: 700;
    color: var(--myjz-color-text);
    text-decoration: none;
    font-size: 1.1rem;
}

.myjz-header__meta {
    display: flex;
    align-items: center;
    gap: var(--myjz-gap);
}

.myjz-header__email {
    color: var(--myjz-color-text-muted);
    font-size: 0.9rem;
}

.myjz-header__logout {
    margin: 0;
}

/* #1897: "Admin" header link, rendered only when session.IsAdmin.
   Styled as a ghost link — understated next to the email + Log out
   button so it doesn't dominate the header. */
.myjz-header__admin {
    color: var(--myjz-color-text);
    text-decoration: none;
    font-weight: 600;
    font-size: 0.9rem;
    padding: 0.35rem 0.75rem;
    border: 1px solid var(--myjz-color-border);
    border-radius: var(--myjz-radius);
    background: var(--myjz-color-surface);
}

.myjz-header__admin:hover,
.myjz-header__admin:focus-visible {
    background: var(--myjz-color-surface-muted);
}

.myjz-main {
    flex: 1;
    width: 100%;
    max-width: var(--myjz-container-max);
    margin: 0 auto;
    padding: var(--myjz-gap-lg) 1rem;
}

/* #1897: .myjz-footer removed with the footer element itself. */

/* --- Buttons --- */

.myjz-button {
    display: inline-block;
    padding: 0.55rem 1.1rem;
    font-size: 1rem;
    font-family: inherit;
    font-weight: 600;
    border-radius: var(--myjz-radius);
    border: 1px solid transparent;
    cursor: pointer;
    text-decoration: none;
    line-height: 1.2;
}

.myjz-button--primary {
    background: var(--myjz-color-accent);
    color: var(--myjz-color-accent-contrast);
}

.myjz-button--primary:hover,
.myjz-button--primary:focus-visible {
    filter: brightness(1.15);
}

.myjz-button--ghost {
    background: transparent;
    color: var(--myjz-color-text);
    border-color: var(--myjz-color-border);
    padding: 0.4rem 0.8rem;
    font-size: 0.9rem;
}

.myjz-button--ghost:hover,
.myjz-button--ghost:focus-visible {
    background: var(--myjz-color-surface-muted);
}

.myjz-button:focus-visible {
    outline: 2px solid var(--myjz-color-accent);
    outline-offset: 2px;
}

/* --- View-as banner (FR-ADMIN-4) --- */

.myjz-viewas {
    background: var(--myjz-color-banner-bg);
    border-bottom: 3px solid var(--myjz-color-banner-border);
}

.myjz-viewas__inner {
    max-width: var(--myjz-container-max);
    margin: 0 auto;
    padding: 0.65rem 1rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--myjz-gap);
    flex-wrap: wrap;
}

.myjz-viewas__text {
    color: var(--myjz-color-text);
}

.myjz-viewas__form {
    margin: 0;
}

/* --- Dashboard list --- */

.myjz-dashboard__heading {
    margin: 0 0 var(--myjz-gap);
    font-size: 1.4rem;
}

.myjz-dashboard__empty {
    color: var(--myjz-color-text-muted);
    background: var(--myjz-color-surface);
    border: 1px dashed var(--myjz-color-border);
    border-radius: var(--myjz-radius);
    padding: 1.5rem;
    text-align: center;
}

.myjz-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: var(--myjz-gap);
}

.myjz-item {
    background: var(--myjz-color-surface);
    border: 1px solid var(--myjz-color-border);
    border-radius: var(--myjz-radius);
    padding: var(--myjz-gap);
}

.myjz-item__meta {
    display: flex;
    gap: var(--myjz-gap);
    align-items: baseline;
    color: var(--myjz-color-text-muted);
    font-size: 0.85rem;
    margin-bottom: 0.25rem;
    flex-wrap: wrap;
}

.myjz-item__type {
    font-family: var(--myjz-font-mono);
    padding: 0.1rem 0.45rem;
    background: var(--myjz-color-surface-muted);
    border-radius: 999px;
    color: var(--myjz-color-text);
}

.myjz-item__title {
    margin: 0.25rem 0 0.5rem;
    font-size: 1.1rem;
}

.myjz-item__payload {
    background: var(--myjz-color-surface-muted);
    border-radius: var(--myjz-radius);
    padding: 0.75rem;
    margin: 0;
    overflow-x: auto;
    font-family: var(--myjz-font-mono);
    font-size: 0.85rem;
    color: var(--myjz-color-text);
    white-space: pre-wrap;
    word-break: break-word;
}

/* #1936: Removed legacy jz.talk renderer rules (.myjz-talks*, .myjz-talk-row*,
   .myjz-talk-badge*). The talks renderer now relies entirely on the
   #1928 design primitives (.myjz-section, .myjz-list--flow, .myjz-list__row,
   .myjz-badge + variants). The `.myjz-dashboard__subheading` rule below is
   kept — it has independent callers outside the talks renderer. */

.myjz-dashboard__subheading {
    margin: 0 0 var(--myjz-gap);
    font-size: 1.1rem;
    color: var(--myjz-color-text-muted);
    font-weight: 600;
}

/* --- Admin surface (#1890, #1897) --- */

/* #1897: Grid uses auto-fit so placeholder tiles reflow responsively
   without media queries; drop-in brand CSS can override colours and
   fonts without touching the layout. Previously inline in admin.templ;
   moved here when admin adopted the shared Layout. */

.myjz-admin {
    display: flex;
    flex-direction: column;
    gap: var(--myjz-gap-lg);
}

.myjz-admin__heading {
    margin: 0;
    font-size: 1.5rem;
}

/* #1938: Removed the legacy admin "view-as" boxed-section rule blocks.
   Every caller (me / talks / admin view-as / travel-refunds add /
   hotels add) is on the #1928 .myjz-section primitives now; the
   per-field input affordances carry through the shared form styling
   rather than a bespoke boxed-section rule set. See task #1938 for
   the full migration sweep. */

.myjz-admin__grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
    gap: var(--myjz-gap);
}

.myjz-admin__error {
    padding: 0.5rem 0.75rem;
    border-left: 4px solid var(--myjz-color-error-border);
    background: var(--myjz-color-error-bg);
    margin-bottom: 0.75rem;
    color: var(--myjz-color-error-text);
}

/* #1930: Admin tile anchor. Pairs `.myjz-section` +
   `.myjz-section__heading` + `.myjz-section__lede` on an `<a>` so tiles
   flow inside `.myjz-admin__grid` without per-card border chrome.
   Hover/focus accents the heading only, matching the primitive spec's
   "less box-chrome" direction.
   #1936: Removed legacy `.myjz-admin__tile*` chrome rules — the
   migration is complete, so the neutralisation comments (border:none /
   padding:0 / background:transparent) are no longer needed. The
   .myjz-admin__tile-link class now just adds anchor-specific hover +
   focus affordances on top of the .myjz-section primitive. */
.myjz-admin__tile-link {
    color: inherit;
    text-decoration: none;
    /* .myjz-section sets margin-bottom for standalone use; collapse it
       inside the grid, which provides its own gap. */
    margin-bottom: 0;
    border-radius: var(--myjz-radius);
    transition: color 0.15s ease;
}

.myjz-admin__tile-link:focus-visible {
    outline: 2px solid var(--myjz-color-accent);
    outline-offset: 2px;
}

.myjz-admin__tile-link:hover .myjz-section__heading,
.myjz-admin__tile-link:focus-visible .myjz-section__heading {
    color: var(--myjz-color-accent);
}

/* #1898: Talks list — compact rows so many synced sessions remain
   scannable without taking over the admin surface. */
.myjz-admin__talks {
    list-style: none;
    padding: 0;
    margin: 0;
}

.myjz-admin__talk {
    padding: 0.75rem 1rem;
    border: 1px solid var(--myjz-color-border);
    border-radius: var(--myjz-radius);
    background: var(--myjz-color-surface);
    margin-bottom: 0.5rem;
}

.myjz-admin__talk-title {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    flex-wrap: wrap;
}

.myjz-admin__talk-status {
    font-size: 0.75rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--myjz-color-text-muted);
    padding: 0.1rem 0.4rem;
    border: 1px solid var(--myjz-color-border);
    border-radius: var(--myjz-radius);
}

.myjz-admin__talk-meta {
    margin-top: 0.25rem;
    color: var(--myjz-color-text-muted);
    font-size: 0.85rem;
}

/* #1936: Removed legacy .myjz-admin__table* rules. The travel-refunds
   and hotels admin listings migrated to .myjz-list--flow primitives in
   #1931 / #1932; no templates reference the table classes anymore. */

.myjz-admin__lede {
    color: var(--myjz-color-text-muted);
    margin-bottom: 1rem;
}

/* #1898: Flash banner surfaces the POST → redirect sync result. */
.myjz-admin__flash {
    padding: 0.75rem 1rem;
    border-left: 4px solid var(--myjz-color-success-border);
    background: var(--myjz-color-success-bg);
    margin-bottom: 1rem;
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
    align-items: baseline;
}

/* #1897: Accessible hide-for-sighted-users utility, used by admin
   section labels that only need to exist for screen readers. */
.myjz-sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* --- Admin modals (#1920) ---
 *
 * Shared dialog styling for /admin CRUD pages. Uses the same surface /
 * border / radius tokens as the rest of the admin chrome so modal
 * content reads as a natural extension of the page chrome.
 * #1938: This comment previously pointed at the legacy admin "view-as"
 * boxed-section rules; those were removed once every caller migrated
 * to .myjz-section primitives.
 *
 * Dark-mode parity comes for free: every colour below resolves to a
 * --myjz-color-* custom property, so the @media (prefers-color-scheme:
 * dark) block at the top of the file flips these surfaces alongside
 * the rest of the UI.
 *
 * Progressive enhancement: the [open] attribute styling ensures the
 * dialog renders as a centered overlay when JS opens it via
 * showModal(). For browsers that don't run the enhancement script we
 * fall back to inline rendering so the forms stay reachable — see the
 * :not([open]) rule.
 */
.myjz-modal {
    padding: 0;
    border: 1px solid var(--myjz-color-border);
    border-radius: var(--myjz-radius);
    background: var(--myjz-color-surface);
    color: var(--myjz-color-text);
    max-width: 32rem;
    width: 90%;
}

.myjz-modal::backdrop {
    background: rgba(0, 0, 0, 0.5);
}

/* #1924: The previous `.no-js .myjz-modal:not([open])` fallback
   (introduced in #1922) has been removed — admin modals are no longer
   pre-rendered inline, so there is nothing to fall back to when JS is
   disabled. The noscript blocks in the admin pages now surface direct
   links for JS-off operators instead. */

/* #1936: Removed legacy .myjz-modal__form / __heading / __meta / __actions
   rules. The admin dialogs now use #1928 primitives (.myjz-stack on the
   form, .myjz-section__heading on the title, .myjz-section__lede on the
   meta paragraph, .myjz-cluster myjz-cluster--end on the action row).
   The .myjz-modal__field* rules below remain — they are still the sole
   styling hook for the per-field label/input pairs inside each dialog
   (no primitive-level form-field replacement yet). */

.myjz-modal__field {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
}

.myjz-modal__field label {
    font-size: 0.9rem;
    font-weight: 600;
}

.myjz-modal__field input {
    padding: 0.5rem;
    font-size: 1rem;
    font-family: inherit;
    border: 1px solid var(--myjz-color-border);
    border-radius: var(--myjz-radius);
    background: var(--myjz-color-surface);
    color: var(--myjz-color-text);
}

/* #1936: Dialog content padding. The removed .myjz-modal__form rule
   previously carried `padding: 1.25rem`; the replacement is a padding
   rule on the dialog-form combination so the content keeps the same
   inset without re-introducing the legacy class. Form elements inside
   a .myjz-modal already wear .myjz-stack, which provides the gap. */
.myjz-modal > form {
    padding: 1.25rem;
    margin: 0;
}

/* #1936: Removed legacy .myjz-travel-refund* and .myjz-hotel* rules.
   Both dashboard renderers migrated to #1928 primitives (.myjz-section,
   .myjz-list--flow, .myjz-list__row, .myjz-cluster, .myjz-section__note)
   in #1933 / #1934. No templates reference these legacy classes
   anymore. */

/* --- Small screens --- */

@media (max-width: 480px) {
    .myjz-header__inner {
        flex-wrap: wrap;
    }
    .myjz-header__email {
        width: 100%;
        order: 3;
    }
}

/* =========================================================================
 * Design primitives (#1928)
 *
 * The rules below are the reusable primitive set every migrated page
 * uses. #1936 completed the sweep of the legacy `.myjz-*` renderer
 * rules that these replaced — callers now rely on the primitives
 * directly, so the dual-class migration window is closed.
 *
 * See `docs/design-primitives.md` for the full spec, API, and the
 * visual direction (editorial restraint, typography-first, whitespace
 * as separator, borders where earned).
 *
 * Dark-mode parity comes for free: every rule below resolves colour
 * through the existing `--myjz-color-*` custom properties, which the
 * top-of-file `prefers-color-scheme: dark` block already flips.
 * ======================================================================= */

/* --- .myjz-section ----------------------------------------------------- */

/* #1928: Titled sub-section inside a page. Replaces ad-hoc
   heading+body containers. No border, no background — the heading
   itself defines the boundary. */
.myjz-section {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    margin-bottom: var(--myjz-gap-lg);
}

.myjz-section__heading {
    margin: 0;
    font-size: 1.15rem;
    font-weight: 700;
    color: var(--myjz-color-text);
    line-height: 1.25;
}

.myjz-section__lede {
    margin: 0;
    color: var(--myjz-color-text-muted);
    font-size: 0.95rem;
}

/* #1928: Optional explanatory note at the tail of a section (e.g. the
   talks "edit in submission system" line). Same muted treatment as
   __lede but slightly smaller, reserved for footnote-style copy. */
.myjz-section__note {
    margin: 0.25rem 0 0 0;
    color: var(--myjz-color-text-muted);
    font-size: 0.85rem;
}

.myjz-section__note a {
    color: var(--myjz-color-accent);
}

/* --- .myjz-list--flow + .myjz-list__row -------------------------------- */

/* #1928: Row-separated list pattern. The list itself is the single
   bordered surface; per-row top-borders separate siblings without
   doubling outlines. Opts in via the `--flow` modifier so existing
   `.myjz-list` callers (grid-with-gap) are not disturbed. */
.myjz-list--flow {
    display: block;
    gap: 0;
    list-style: none;
    margin: 0;
    padding: 0;
    background: var(--myjz-color-surface);
    border: 1px solid var(--myjz-color-border);
    border-radius: var(--myjz-radius);
    overflow: hidden;
}

.myjz-list__row {
    padding: 0.65rem var(--myjz-gap);
    display: flex;
    flex-direction: column;
    gap: 0.2rem;
}

.myjz-list__row + .myjz-list__row {
    border-top: 1px solid var(--myjz-color-border);
}

/* #1928: Primary line of a row. Carries the record's headline (talk
   title, hotel name). Weight 600 keeps hierarchy clear even when the
   meta line wraps to two visual rows. */
.myjz-list__row-title {
    color: var(--myjz-color-text);
    font-weight: 600;
    font-size: 1rem;
    line-height: 1.3;
}

/* #1933: Muted variant of the row title. Used when the headline is a
   placeholder (e.g. a hotel booking that exists but has no hotel name
   assigned yet). Resolves through --myjz-color-text-muted so dark-mode
   parity is automatic. Weight drops slightly so the muted copy reads
   as secondary without losing its role as the row's headline. */
.myjz-list__row-title--muted {
    color: var(--myjz-color-text-muted);
    font-weight: 500;
}

/* #1928: Secondary line: muted, horizontal-cluster behaviour so
   badges + inline text wrap together on narrow viewports. Callers
   typically also apply `.myjz-cluster` so the wrap gap matches the
   rest of the UI. */
.myjz-list__row-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem 0.5rem;
    font-size: 0.85rem;
    color: var(--myjz-color-text-muted);
}

/* #1928: Speakers / primary attributed-names span inside a row-meta.
   Kept on-token (full text colour) so the name reads at the same weight
   as the title rather than fading into the badge strip. */
.myjz-list__row-speakers {
    color: var(--myjz-color-text);
}

/* --- .myjz-badge + variants -------------------------------------------- */

/* #1928: Generalised badge primitive. Used across talks, hotels,
   travel-refunds, and admin rows to pill-wrap short metadata values. */
.myjz-badge {
    display: inline-block;
    padding: 0.1rem 0.5rem;
    font-size: 0.75rem;
    font-family: var(--myjz-font-mono);
    color: var(--myjz-color-text);
    background: var(--myjz-color-surface-muted);
    border: 1px solid var(--myjz-color-border);
    border-radius: 999px;
    line-height: 1.4;
    white-space: nowrap;
}

/* #1928: Accent variant — the single "important" state per row
   (approval, success-of-action). Inverts on the accent pair so it
   dark-mode-flips for free. */
.myjz-badge--accent {
    color: var(--myjz-color-accent-contrast);
    background: var(--myjz-color-accent);
    border-color: var(--myjz-color-accent);
}

/* #1928: Muted variant — passive metadata (language, length). Same
   pill as default but the text fades to muted so the accent badge on
   the same row stands out by contrast rather than by colour. */
.myjz-badge--muted {
    color: var(--myjz-color-text-muted);
}

/* #1928: Semantic variants sourced from existing success / error /
   banner tokens. Use sparingly — one semantic badge per row is the
   norm; more than one and the visual hierarchy collapses. */
.myjz-badge--success {
    color: var(--myjz-color-text);
    background: var(--myjz-color-success-bg);
    border-color: var(--myjz-color-success-border);
}

.myjz-badge--warning {
    color: var(--myjz-color-text);
    background: var(--myjz-color-banner-bg);
    border-color: var(--myjz-color-banner-border);
}

.myjz-badge--error {
    color: var(--myjz-color-error-text);
    background: var(--myjz-color-error-bg);
    border-color: var(--myjz-color-error-border);
}

/* --- .myjz-stack + .myjz-cluster --------------------------------------- */

/* #1928: Vertical flow utility. Children stacked with a shared gap.
   No chrome — this is pure spacing. Modifiers tune the gap without
   needing a dedicated class per callsite. */
.myjz-stack {
    display: flex;
    flex-direction: column;
    gap: var(--myjz-gap);
}

.myjz-stack--tight {
    gap: 0.5rem;
}

.myjz-stack--loose {
    gap: var(--myjz-gap-lg);
}

/* #1928: Horizontal cluster utility. Wraps on narrow viewports,
   baseline-aligned so badge strips sit on the same line as text.
   Use for badge rows, button groups, and meta lines. */
.myjz-cluster {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.4rem 0.5rem;
}

.myjz-cluster--tight {
    gap: 0.25rem 0.35rem;
}

.myjz-cluster--end {
    justify-content: flex-end;
}

/* #1939: Spread variant — first child left, last child right, with
   wrapping preserved. Used for header rows where a page heading sits
   opposite a primary action button (travel-refunds, hotels admin). */
.myjz-cluster--spread {
    justify-content: space-between;
    align-items: center;
}

/* --- .myjz-checkbox-grid ---------------------------------------------- */

/* #1943: Compact checkbox grid for checkbox groups (Preferences /
   Allergies / Intolerances on the food form). Replaces single-column
   vertical stacks that wasted horizontal space in the modal. Stays on
   existing spacing tokens; dark-mode parity is automatic because no
   new colour rules are introduced.
   #1944: Bumped from 2 to 3 columns with a tighter gap so all
   dietary options fit comfortably within the modal width. */
.myjz-checkbox-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0.35rem;
}

/* #1944: Step down the column count on narrower viewports — three
   columns get cramped in a modal below ~500px, and two become too
   tight below ~380px. */
@media (max-width: 500px) {
    .myjz-checkbox-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (max-width: 380px) {
    .myjz-checkbox-grid {
        grid-template-columns: 1fr;
    }
}

/* --- .myjz-textarea --------------------------------------------------- */

/* #1943: Styled textarea primitive. Visually aligned with
   .myjz-modal__field input so the "Other comments" free-text field
   reads as part of the same form family. Resolves colour through the
   existing --myjz-color-* tokens so dark mode flips automatically.
   resize: vertical keeps the layout predictable while still letting
   long comments grow the control. */
.myjz-textarea {
    width: 100%;
    min-height: 5rem;
    padding: 0.5rem 0.75rem;
    font: inherit;
    color: var(--myjz-color-text);
    background: var(--myjz-color-surface);
    border: 1px solid var(--myjz-color-border);
    border-radius: var(--myjz-radius);
    resize: vertical;
}

.myjz-textarea:focus-visible {
    outline: 2px solid var(--myjz-color-accent);
    outline-offset: 2px;
    border-color: transparent;
}

/* --- .myjz-fieldset --------------------------------------------------- */

/* #1945: Invisible fieldset utility. The food dialog groups
   (Preferences / Allergies / Intolerances) still need <fieldset> +
   <legend> for screen-reader grouping, but the owner wants the
   default browser chrome (border + floating legend cut-out) gone so
   the modal reads as a single compact form rather than three nested
   boxes. min-width: 0 defeats the classic fieldset flex/grid shrink
   bug where the element refuses to shrink below its intrinsic
   content size. */
.myjz-fieldset {
    border: none;
    padding: 0;
    margin: 0;
    min-width: 0;
}

/* #1945: Render <legend> as a compact uppercase section label that
   sits on its own line above the group's controls. float + width:100%
   pulls the legend out of the fieldset's baseline flow (legends are
   quirky — they don't participate in normal block layout by default)
   and `clear: left` on the following sibling restarts normal flow
   under it. The muted colour matches .myjz-section__lede so legends
   read as secondary headings rather than competing with
   .myjz-section__heading. */
.myjz-fieldset > legend {
    padding: 0;
    margin-bottom: 0.4rem;
    float: left;
    width: 100%;
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--myjz-color-text-muted);
}

.myjz-fieldset > legend + * {
    clear: left;
}

/* #1945: Compact stack variant for the food modal groupings. Sits
   between .myjz-stack--tight (0.5rem) and the default (var(--myjz-gap))
   so the three fieldsets + the "Other comments" textarea flow closer
   together without looking cramped. Named --compact rather than
   piggy-backing on --tight because --tight is already documented as a
   0.5rem utility and is used elsewhere. */
.myjz-stack--compact {
    gap: 0.75rem;
}

/* --- .myjz-qr --------------------------------------------------------- */

/* #1946: QR code image primitive used by the dashboard ticket
   renderer. The QR itself must remain black-on-white for camera
   reliability (dark-mode inversion would break scanners), so this
   class pins a white background + light padding + rounded corners
   regardless of theme. The surrounding card (.myjz-ticket) still
   inherits light/dark tokens — only the image tile itself is locked
   to white. max-width: 100% lets the code shrink gracefully on
   narrow phones below the 240px intrinsic size; height auto keeps
   it square.
   #1948: Tightened padding now that the generated PNG no longer
   carries the library's default 4-module quiet-zone (the renderer
   sets `DisableBorder = true` and the CSS tile owns the entire
   quiet-zone). 0.35rem reads as ~2 modules of margin around a
   240px QR — the scanner-safe minimum for solid-white tiles.
   Removed `margin: 0 auto` because `.myjz-ticket__qr` now owns the
   horizontal placement inside the two-column layout; the figure
   centers the image on narrow (stacked) viewports. */
.myjz-qr {
    display: block;
    width: 240px;
    max-width: 100%;
    height: auto;
    background: #fff;
    padding: 0.35rem;
    border-radius: var(--myjz-radius);
}

/* --- .myjz-ticket ----------------------------------------------------- */

/* #1948: Ticket card primitive for the dashboard `jz.ticket`
   renderer. Replaces the heading-led `.myjz-section` treatment with
   a two-column "ticket" look — identifier + type badges on the left,
   QR on the right. The card wears a subtle border (drawn on this
   primitive, not on `.myjz-section`) because a ticket is the single
   load-bearing artefact on the dashboard and earns its chrome, while
   other sections on the page stay borderless per the #1928
   editorial-restraint direction.
   Columns:
     - left column (details) stretches to fill available space
       (1fr) so long identifiers / many type badges don't crowd the
       QR.
     - right column (QR) is min-content so it collapses to exactly
       the QR width (240px or less) without forcing the left column
       to wrap.
   Alignment:
     - `align-items: center` vertically centres the two columns so
       the identifier line sits next to the middle of the QR rather
       than floating at the top.
   The whole card stacks below 500px (see the media query further
   down). */
.myjz-ticket {
    display: grid;
    grid-template-columns: 1fr min-content;
    gap: var(--myjz-gap-lg);
    align-items: center;
    padding: var(--myjz-gap);
    background: var(--myjz-color-surface);
    border: 1px solid var(--myjz-color-border);
    border-radius: var(--myjz-radius);
    margin-bottom: var(--myjz-gap-lg);
}

/* #1948: Left column — identifier + type badges flow vertically
   with the shared tight gap. Kept as its own class (rather than
   re-using `.myjz-stack--tight`) so the details column has a stable
   hook for any future ticket-specific adjustments (e.g. a secondary
   line) without mutating the stack utility's contract. */
.myjz-ticket__details {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    min-width: 0;
}

/* #1948: Identifier line. Mono font at a generous size so it quotes
   1:1 with what the scanner decodes at the gate — attendees can
   read it aloud to the door staff when the QR fails. word-break
   lets long identifiers wrap gracefully on narrow viewports without
   forcing horizontal overflow. */
.myjz-ticket__identifier {
    font-family: var(--myjz-font-mono);
    font-size: 1.1rem;
    font-weight: 600;
    color: var(--myjz-color-text);
    word-break: break-all;
    line-height: 1.3;
}

/* #1948: Right column wrapper. Centres the QR image and its caption
   on stacked (narrow) viewports where the figure spans the full
   card width, and keeps the caption muted + tight under the image. */
.myjz-ticket__qr {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.35rem;
    margin: 0;
}

/* #1948: Stack the ticket card below 500px so the QR drops under
   the details rather than squeezing the identifier line. Matches
   the breakpoint already used by `.myjz-checkbox-grid` so the UI
   has a single narrow-viewport pivot. */
@media (max-width: 500px) {
    .myjz-ticket {
        grid-template-columns: 1fr;
    }
}
