/* =============================================================
   Design tokens — palette lifted from the "Παιδιών Λέγειν"
   business card via the handoff design (teal + lime).

   REQ-F23-003 — three brand-aware variables (--rheasys-primary,
   --rheasys-secondary, --rheasys-accent) are injected at runtime by
   <BrandingStyleSheet />. The teal/lime tokens below are kept as
   the structural palette + ship as the default fallback (`var(...,
   <fallback>)`); switching tenants only swaps the rheasys-* layer.
   ============================================================= */
:root {
    color-scheme: light;
    /* F23-003 fallbacks — overridden by the runtime <style> block. */
    --rheasys-primary:   #2A4D69;
    --rheasys-secondary: #4B86B4;
    --rheasys-accent:    #ADCBE3;
    --teal-900: oklch(0.38 0.08 215);
    --teal-800: oklch(0.54 0.11 210);   /* card plaque / primary button */
    --teal-700: oklch(0.62 0.11 208);
    --teal-600: oklch(0.70 0.10 205);
    --teal-100: oklch(0.93 0.035 205);
    --teal-50:  oklch(0.975 0.015 205);
    --lime-600: oklch(0.78 0.19 130);
    --lime-500: oklch(0.85 0.19 128);   /* accent */
    --lime-300: oklch(0.92 0.14 128);
    --cream:    oklch(0.985 0.010 100);
    --ink:      oklch(0.22 0.03 215);
    --ink-soft: oklch(0.44 0.04 215);
    --line:     oklch(0.88 0.025 210);
    --danger:   oklch(0.55 0.160 25);
    --success:  oklch(0.62 0.130 145);
    --card:     #ffffff;

    /* REQ-F29-NFR-001 — visible keyboard focus ring. A dark teal that
       clears WCAG 2.2 §1.4.11 non-text contrast (≥ 3:1) against the cream
       inputs and white surfaces, unlike the old low-chroma lime shadow that
       relied on --lime-500 at 25% alpha. Used app-wide via :focus-visible. */
    --focus-ring: var(--teal-900);
    --focus-ring-width: 3px;

    /* ================================================================
       TYPE SCALE — added 2026-08-15 on Elias's «Do type-scale and spacing.
       Document style and standard. Follow it in the future.»

       🔴 MEASURED FIRST, then chosen. This stylesheet carried FIFTY-SIX
       distinct font sizes across 294 declarations — 0.8, 0.8125, 0.82, 0.825
       and 0.85rem all coexisting, which is 12.8px to 13.6px: differences no
       reader can name and every screen inherits. That is not a design, it is
       sediment.

       The seven steps below were picked to ABSORB the existing clusters rather
       than to impose a ratio from outside, so the migration moves most text by
       under a pixel and the screens keep their character:

         --text-xs    12px   0.70 / 0.72 / 0.75 / 0.78    labels, table meta
         --text-sm    13px   0.80 / 0.8125 / 0.82 / 0.825 badges, hints
         --text-base  14px   0.85 / 0.875                 TABLES — the workhorse
         --text-md    15px   0.9 / 0.925 / 0.9375         body prose
         --text-lg    16px   0.95 / 1.0                   emphasis, lead-in
         --text-xl    18px   1.05 / 1.1 / 1.15            section headings
         --text-2xl   20px   1.25                         page headings

       The full rule, and how to choose between them, is in
       docs/design/type-and-spacing.md. New CSS uses a token; the guard test
       TypeAndSpacingScaleTests fails a new raw value and carries the
       still-unmigrated ones as a shrinking ledger.

       px, not rem, deliberately: rem here would compound with the sample-size
       control on /dev/fonts and with any future root resize, and «13px» is what
       the design conversation is actually about. */
    /* 10px exists for exactly one thing — the tiny uppercase tick on a badge —
       and is listed so it is a CHOICE rather than a straggler. Nothing new
       should reach for it: below 12px Greek accents stop being legible at
       normal viewing distance. */
    --text-2xs:  10px;
    --text-xs:   12px;
    --text-sm:   13px;
    --text-base: 14px;
    --text-md:   15px;
    --text-lg:   16px;
    --text-xl:   18px;
    --text-2xl:  20px;

    /* Display steps. Deliberately a separate run of the scale: these are page
       titles and the big KPI figures on the dashboard, and they answer to
       «how loud», not to «how readable at density». Absorbs 1.35 / 1.4 / 1.5 /
       1.75 / 1.85 / 2.1 / 2.5rem. */
    --text-3xl:  24px;
    --text-4xl:  30px;
    --text-5xl:  40px;

    /* Line heights, because a scale without them is half a scale: dense table
       rows and running prose need different leading from the same size. */
    --leading-tight: 1.25;   /* table cells, badges, buttons */
    --leading-normal: 1.5;   /* running prose, hints, explanations */

    --radius: 12px;
    --radius-lg: 20px;
    --radius-sm: 8px;
    --shadow-card:
        0 1px 0 oklch(1 0 0 / 0.8) inset,
        0 30px 60px -25px oklch(0.22 0.05 215 / 0.45),
        0 10px 30px -15px oklch(0.22 0.05 215 / 0.35);
    --shadow-plaque: 0 6px 18px -10px oklch(0.25 0.05 215 / 0.5);

    /* -----------------------------------------------------------
       Design-system extension (REQ: #87). Added alongside the
       original palette so no existing page markup has to change.
       Downstream PRs (#88 responsive nav, #89 tables-as-cards)
       consume these tokens.
       ----------------------------------------------------------- */

    /* Spacing scale on a 4 px ramp — use --space-1 for inline gaps,
       --space-4 for row padding, --space-8 for large section breaks. */
    /* 🔴 2px, added 2026-08-15. Not a whim: 38 declarations were using
       0.1rem / 0.15rem (1.6–2.4px) for hairline gaps between a label and its
       control, and with no step below 4px they had nowhere to land. A scale
       that cannot express what the design already does gets bypassed, which is
       how this stylesheet ended up with 168 hand-typed spacings beside a
       perfectly good scale. */
    --space-05: 2px;
    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 24px;
    --space-6: 32px;
    --space-7: 48px;
    --space-8: 64px;

    /* 🔴 THE FLUID SCALE THAT WAS HERE IS GONE — and how it died is the
       reason the --text-* scale above ships with a guard test.

       It declared --fs-sm … --fs-2xl as clamp() steps and said «downstream
       components can @apply these via var() or stay on hard-coded px until
       they migrate». Nothing migrated. In ~8,000 lines it was used EXACTLY
       ONCE, while 294 font-size declarations went on inventing 56 different
       values beside it.

       That is the whole lesson: a scale nobody is required to use is not a
       scale, it is a suggestion, and suggestions lose to whatever the last
       person typed. The replacement is enforced by
       TypeAndSpacingScaleTests, which fails a NEW raw value and carries the
       remaining ones as a shrinking ledger — the same mechanism this codebase
       already uses for branch scoping and seeder writes.

       Responsive type is still a good idea and is not lost: it belongs in a
       root font-size media query, applied once, rather than in seven tokens
       each component has to remember to reach for. Deliberately not done in
       this pass — it is a visual change, and this pass is about removing
       drift, not introducing motion. */

    /* WCAG 2.2 tap-target minimum. Buttons, form inputs, and
       navigation items should respect this as their min-height. */
    --tap-min: 44px;

    /* Breakpoint tokens exposed for JS consumers and documented
       here for CSS consumers (media queries still need the px
       literal until @custom-media is universally shipped). */
    --bp-phone:  640px;
    --bp-tablet: 1024px;

    /* Motion: base transition envelope. Wrapping components
       should additionally gate animations behind the
       prefers-reduced-motion media query at the bottom of
       this file. */
    --transition-fast: 120ms ease-out;
    --transition-base: 200ms ease-out;
    --transition-slow: 320ms ease-out;

    /* 🔴 VERDANA — Elias, 2026-08-15: «I would like Verdana for fonts.»
       Chosen from /dev/fonts against real screens of this product.

       It fixes a live defect rather than being a taste change. The app shipped
       'Nunito' for body and 'Fraunces' for headings, and the vendored files are
       literally nunito-LATIN.woff2 and fraunces-LATIN.woff2 — latin subsets. On
       a UI that is entirely Greek, EVERY Greek glyph was drawn by whatever
       system-ui happened to resolve to, differently on each machine. #704.

       Verdana carries Greek, ships with Windows and macOS, and costs ZERO
       bytes: no @font-face, no download, no CDN, nothing to preload. It was
       drawn for screen legibility at small sizes, which is what this product
       is — dense Greek tables.

       ⚠️ It is WIDE. Same px size reads larger than most faces (tall x-height)
       and each character takes more room, so column crowding is the thing to
       watch; the tables were screenshotted after this change for exactly that.

       The fallbacks are metric companions, not decoration: DejaVu Sans is the
       Linux face closest to Verdana in feel and has full Greek (it is what this
       box resolves Greek to anyway), Geneva is the classic macOS companion, and
       Noto Sans Greek is the last resort that is guaranteed to have the glyphs. */
    font-family: Verdana, 'DejaVu Sans', Geneva, 'Noto Sans Greek', sans-serif;
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

/* =============================================================
   Dark-mode pair (REQ #87). Overrides only the tokens that
   change contrast — palette stays teal-tinted. No manual toggle
   in this PR; browsers already resolve prefers-color-scheme per
   user preference.
   ============================================================= */
@media (prefers-color-scheme: dark) {
    :root {
        color-scheme: dark;
        --teal-900: oklch(0.90 0.05 210);
        --teal-800: oklch(0.76 0.08 208);
        --teal-700: oklch(0.66 0.08 208);
        --teal-100: oklch(0.30 0.04 210);
        --teal-50:  oklch(0.20 0.03 212);
        --cream:    oklch(0.17 0.02 215);
        --ink:      oklch(0.95 0.01 210);
        --ink-soft: oklch(0.70 0.02 210);
        --line:     oklch(0.32 0.02 210);
        --card:     oklch(0.23 0.03 212);
    }

    /* In dark mode --teal-800 (header background) flips to a light teal,
       so the default white hamburger bars + white-alpha borders vanish.
       Swap to a stable dark ink so the control stays visible. */
    .nav-hamburger { border-color: rgba(0, 0, 0, 0.25); }
    .nav-hamburger:hover { background: rgba(0, 0, 0, 0.08); }
    .nav-hamburger:active { background: rgba(0, 0, 0, 0.15); }
    .nav-hamburger span { background: oklch(0.22 0.03 215); }

    /* Text selection across the app — the browser default has near-zero
       contrast against the dark card/input backgrounds. Use the lime
       accent so selections are unmistakable. */
    ::selection {
        background: var(--lime-500);
        color: oklch(0.15 0.02 215);
    }
}

/* Selection highlight for light mode — keep it legible too. */
::selection {
    background: var(--lime-300, oklch(0.92 0.14 128));
    color: var(--ink);
}

*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; min-height: 100%; }
body { color: var(--ink); background: var(--teal-50); }

.display { font-family: Verdana, 'DejaVu Sans', Geneva, sans-serif; }
.mono-cap { letter-spacing: 0.22em; text-transform: uppercase; font-size: var(--text-xs); font-weight: 600; }

button { font: inherit; cursor: pointer; }
input { font: inherit; color: inherit; }
a { color: inherit; }

::placeholder { color: oklch(0.65 0.02 210); opacity: 1; }

@keyframes spin { to { transform: rotate(360deg); } }
@keyframes rise {
    from { opacity: 0; transform: translateY(10px); }
    to   { opacity: 1; transform: translateY(0); }
}
@keyframes floatY {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-8px); }
}

.boot { padding: var(--space-6); color: var(--ink-soft); }

h1 { font-size: var(--text-4xl); font-weight: 600; letter-spacing: -0.01em; margin: 0 0 var(--space-4); }
h2 { font-size: var(--text-2xl); font-weight: 600; margin: 0 0 var(--space-3); }
.muted { color: var(--ink-soft); font-size: var(--text-md); }

/* =============================================================
   App shell (authenticated pages) — teal header, white body.
   ============================================================= */
.app-shell { min-height: 100%; display: flex; flex-direction: column; background: var(--teal-50); }

.app-header {
    /* REQ-F23-003 — primary brand surface; defaults to teal-800 when
       the tenant has not customised the palette. */
    background: var(--rheasys-primary, var(--teal-800));
    color: #fff;
    border-bottom: 1px solid var(--teal-900);
    padding: 0.625rem 1.25rem;
    display: flex;
    align-items: center;
    gap: var(--space-5);
    flex-wrap: wrap;
    min-height: 84px;
    /* Sticky so the brand + search + user menu stay visible while
       long pages (calendar, lists) scroll. The mobile drawer
       (z-index 20) and modal overlays sit above this. */
    position: sticky;
    top: 0;
    z-index: 30;
}
.app-header .brand {
    display: inline-flex;
    align-items: center;
    color: #fff;
    text-decoration: none;
    line-height: 0;
}
.app-header .brand-logo {
    display: block;
    height: 64px;
    width: auto;
    max-height: none;
    object-fit: contain;
    border-radius: 6px;
}
.app-title {
    margin: 0;
    /* Corrected while self-hosting the fonts (#566). This used to claim
       "Nunito has full Greek glyph coverage in the loaded subset" — it does
       not, and never did. Neither Nunito nor Fraunces ships a Greek subset,
       on the CDN or upstream (google/fonts METADATA.pb lists only latin,
       latin-ext, menu, vietnamese for both). What actually fixed the boxes
       was the second entry in this stack: Greek here is drawn by system-ui.
       Keep a real Greek-capable fallback in any stack that renders Greek. */
    font-family: Verdana, 'DejaVu Sans', Geneva, sans-serif;
    font-weight: 700;
    font-size: var(--text-4xl);
    line-height: 1.05;
    letter-spacing: 0.06em;
    color: #ffffff;
    text-transform: uppercase;
    white-space: nowrap;
}

/* Push the user/help/branch cluster to the right edge while keeping
   brand + title + search anchored on the left. */
/* `min-width: 0` + wrapping. Without them this nowrap row's min-content
   (name + «Αναφορά» + «Αποσύνδεση» ≈ 460 px) became the floor for the whole
   document: every phone rendered a 524 px page and scrolled sideways on 14
   of 15 screens. Wrapping never engages on desktop, where the row fits. */
.app-header-right {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--space-2) var(--space-4);
    margin-left: auto;
    min-width: 0;
}
.branch-switcher {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    color: oklch(0.92 0.03 205);
    font-size: var(--text-md);
}
.branch-switcher-label { color: oklch(0.88 0.025 205); opacity: 0.85; font-size: var(--text-sm); }
.branch-switcher-select {
    background: transparent;
    color: #ffffff;
    border: 1px solid oklch(0.80 0.05 205);
    border-radius: 0.375rem;
    padding: var(--space-1) var(--space-2);
    font-size: var(--text-base);
    min-width: 12rem;
}
.branch-switcher-select:disabled { opacity: 0.6; }
.branch-switcher-select option { color: #111; background: #fff; }
.user-menu {
    display: inline-flex;
    align-items: center;
    gap: var(--space-3);
    color: oklch(0.92 0.03 205);
    font-size: var(--text-md);
    min-width: 0;
}
.user-menu .user-menu-label { color: oklch(0.88 0.025 205); opacity: 0.85; font-size: var(--text-sm); }
.user-menu .user-menu-email { color: #ffffff; font-weight: 600; max-width: 20rem; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.user-menu-logout {
    background: transparent;
    color: #ffffff;
    border-color: oklch(0.80 0.05 205);
}
.user-menu-logout:hover:not(:disabled) { background: var(--teal-900); border-color: oklch(0.88 0.04 205); }
.user-menu-report {
    background: transparent;
    color: #ffffff;
    border-color: oklch(0.80 0.05 205);
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
}
.user-menu-report:hover:not(:disabled) { background: var(--teal-900); border-color: oklch(0.88 0.04 205); }
.user-menu-report-icon { font-size: var(--text-md); line-height: 1; }
/* Only drawn under the phone breakpoint, where «Αποσύνδεση» collapses to
   an icon-only button (the text label stays as the accessible name). */
.user-menu-logout-icon { display: none; flex: 0 0 auto; }

.report-dialog-card { width: min(34rem, 100%); }
.report-dialog-categories { display: flex; gap: var(--space-5); }
.report-dialog-categories label { display: inline-flex; align-items: center; gap: var(--space-2); font-weight: 400; }
.report-dialog-files { margin: var(--space-2) 0 0; padding-left: 1.1rem; }
.report-dialog-build { padding: var(--space-2) 1.25rem 0.9rem; font-size: var(--text-xs); text-align: right; opacity: 0.7; }
.report-dialog-files .link-button {
    background: none; border: 0; padding: 0 0 0 var(--space-1); margin: 0;
    color: #2f6fed; cursor: pointer; font: inherit; text-decoration: underline;
}
.report-dialog-files .link-button:disabled { color: inherit; cursor: default; text-decoration: none; opacity: 0.5; }
.report-dialog-capture { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; margin-top: var(--space-2); }
.report-capture-privacy { flex-basis: 100%; margin: var(--space-1) 0 0; font-size: var(--text-sm); }
.report-dialog-page p { margin: 0 0 var(--space-1); }
.report-dialog-page-path { font-size: var(--text-sm); word-break: break-all; }
.report-dialog-page-confirm { display: inline-flex; align-items: center; gap: var(--space-2); font-weight: 400; }

/* Full-screen crop overlay (report-capture.js). */
.report-capture-overlay {
    position: fixed; inset: 0; z-index: 3000;
    display: flex; flex-direction: column;
    background: rgba(15, 20, 30, 0.92);
}
.report-capture-bar {
    display: flex; align-items: center; gap: var(--space-3);
    padding: 0.6rem var(--space-4); background: #101828; color: #f2f4f7;
    flex-wrap: wrap;
}
.report-capture-hint { flex: 1 1 20rem; font-size: var(--text-base); line-height: 1.35; }
.report-capture-stage { flex: 1 1 auto; overflow: auto; display: flex; align-items: center; justify-content: center; padding: var(--space-2); }
.report-capture-canvas { max-width: 100%; max-height: 100%; cursor: crosshair; box-shadow: 0 0 0 1px rgba(255,255,255,0.15); touch-action: none; }

@media (max-width: 720px) {
    .app-title { font-size: var(--text-xl); letter-spacing: 0.04em; }
    .user-menu .user-menu-label { display: none; }
}

.app-body { flex: 1; display: flex; }
.side-nav {
    width: 14rem;
    background: var(--card);
    border-right: 1px solid var(--line);
    padding: 1.25rem 0.875rem;
    min-height: calc(100vh - 54px);
}
.side-nav ul { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: var(--space-05); }
.side-nav a {
    display: block;
    padding: 0.55rem var(--space-3);
    border-radius: var(--radius-sm);
    color: var(--ink);
    text-decoration: none;
    font-size: var(--text-lg);
    font-weight: 500;
}
.side-nav a:hover { background: var(--teal-50); }
.side-nav a.active { background: var(--rheasys-primary, var(--teal-800)); color: #fff; }
.side-nav a.active:hover { background: var(--teal-900); }
/* #174 quick-action accents — the two "+" entries at the top of the
   nav. Subtle lime tint so they read as "create" without drowning
   the rest of the nav. */
.side-nav a.side-nav-accent {
    color: var(--teal-900);
    font-weight: 600;
}
.side-nav a.side-nav-accent:hover { background: var(--lime-300); }
.side-nav a.side-nav-accent.active { background: var(--lime-500); color: var(--ink); }

/* Grouped sections that mirror the legacy "Παιδιών Λέγειν" desktop
   IA (docs/legacy/paidion-legein-mainmenu.jpg). The group is a list
   item containing a small caps header and a nested <ul>; nested ULs
   reset back to the same flex-column / 0 padding as the outer list
   so existing link styles (.side-nav a, hover, active) apply
   unchanged. */
.side-nav .side-nav-group { display: flex; flex-direction: column; padding-top: var(--space-3); }
.side-nav .side-nav-group ul { padding: 0; margin: 0; list-style: none; display: flex; flex-direction: column; gap: var(--space-05); }
.side-nav-group-title {
    display: block;
    padding: var(--space-1) var(--space-3);
    font-size: var(--text-xs);
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-soft);
}

/* Lens 2: a nested developer/tools sub-heading inside a nav group.
   Rendered lighter + with a top separator so the relocated dev tools
   (demo-seed / session-reports) read as a distinct, secondary section
   rather than another everyday-settings entry. */
.side-nav-subgroup-title {
    margin-top: var(--space-2);
    padding-top: var(--space-2);
    border-top: 1px solid var(--line, rgba(0, 0, 0, 0.08));
    font-size: var(--text-2xs);
    opacity: 0.85;
}

.app-main { flex: 1; min-width: 0; padding: var(--space-5) 1.75rem; background: var(--card); }

/* Build/copyright strip that sits below the main content. Font is
   deliberately small so it reads as metadata, not chrome. Text color
   uses var(--ink-soft) so it flips correctly in dark mode. */
.app-footer {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    gap: var(--space-2) var(--space-4);
    padding: var(--space-2) 1.25rem;
    border-top: 1px solid var(--line);
    background: var(--card);
    color: var(--ink-soft);
    font-size: var(--text-xs);
    letter-spacing: 0.01em;
}
.app-footer-build { font-variant-numeric: tabular-nums; }
.app-footer-copy { opacity: 0.85; }
@media (max-width: 640px) {
    .app-footer {
        padding: 0.5rem 1rem calc(0.5rem + var(--tap-min, 44px) + env(safe-area-inset-bottom));
        justify-content: center;
        text-align: center;
    }
}

/* =============================================================
   Responsive navigation (REQ #88). Desktop keeps the existing
   side nav. Under 1024 px the side nav becomes a drawer opened
   by a hamburger label in the header; under 640 px a bottom
   tabbar appears with the four most-used routes and "Περισσότερα"
   opens the drawer. All open/close state lives in a hidden
   checkbox — 100 % CSS, no JavaScript, no Blazor state.
   ============================================================= */

/* Hidden checkbox whose :checked state drives the drawer. Kept
   off-screen so it never receives focus and is announced as
   aria-hidden. The <label for> buttons (hamburger + scrim +
   "Περισσότερα" tab) toggle it. */
.nav-drawer-toggle {
    position: absolute;
    left: -9999px;
    width: 0;
    height: 0;
    opacity: 0;
}

.nav-hamburger {
    display: none; /* shown only under the tablet breakpoint */
    width: var(--tap-min, 44px);
    height: var(--tap-min, 44px);
    align-items: center;
    justify-content: center;
    flex-direction: column;
    gap: var(--space-1);
    cursor: pointer;
    border: 1px solid rgba(255, 255, 255, 0.25);
    border-radius: var(--radius-sm);
    background: transparent;
    padding: 0;
    transition: background var(--transition-fast, 120ms ease-out);
}
.nav-hamburger:hover { background: rgba(255, 255, 255, 0.08); }
.nav-hamburger:active { background: rgba(255, 255, 255, 0.15); }
.nav-hamburger span {
    display: block;
    width: 20px;
    height: 2px;
    background: #fff;
    border-radius: 2px;
}

.nav-drawer-scrim {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(9, 28, 32, 0.5);
    z-index: 19;
    cursor: pointer;
}

.mobile-tabbar {
    display: none; /* shown only under the phone breakpoint */
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    background: var(--card);
    border-top: 1px solid var(--line);
    padding-bottom: env(safe-area-inset-bottom);
    min-height: var(--tap-min, 44px);
    z-index: 15;
}
.mobile-tab {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-05);
    padding: var(--space-2) var(--space-1);
    text-decoration: none;
    color: var(--ink-soft);
    font-size: var(--text-xs);
    font-weight: 600;
    min-height: var(--tap-min, 44px);
    cursor: pointer;
}
.mobile-tab.active { color: var(--teal-800); }
.mobile-tab-icon { font-size: var(--text-2xl); line-height: 1; }

/* -------- Tablet: 641 – 1024 px -------- */
@media (max-width: 1024px) {
    .app-body { position: relative; }
    .nav-hamburger { display: inline-flex; }

    /* Sidebar morphs into a slide-in drawer. Off-screen by
       default, revealed by the :checked sibling state above. */
    .side-nav {
        position: fixed;
        top: 0;
        bottom: 0;
        left: 0;
        width: min(17rem, 85vw);
        padding-top: calc(68px + 0.75rem);
        transform: translateX(-100%);
        transition: transform var(--transition-base, 200ms ease-out);
        box-shadow: 2px 0 14px oklch(0.22 0.05 215 / 0.15);
        z-index: 20;
        overflow-y: auto;
    }
    .nav-drawer-toggle:checked ~ .app-shell .side-nav { transform: translateX(0); }
    .nav-drawer-toggle:checked ~ .app-shell .nav-drawer-scrim { display: block; }

    .app-main {
        padding: 1.25rem 1.25rem calc(1.25rem + var(--tap-min, 44px));
        max-width: none;
    }

    /* Scroll lock behind the drawer, the help panel and the full-screen
       search. The drawer is a pure-CSS checkbox with no JS anywhere in the
       loop, so :has() is the only lever that reaches <html>; scoped to this
       breakpoint so desktop scrolling is untouched.

       The modal backdrop USED to be in this list, and only here — which
       meant that above 1024 px the page went on scrolling behind an open
       dialog. Measured at 1280×800 with a real wheel gesture over the
       backdrop: the list moved 1 031 px under all four dialogs the gate
       now covers (#749 part 3). It is locked at every width now, next to
       the rest of the modal rules. */
    html:has(.nav-drawer-toggle:checked),
    html:has(.help-panel-scrim),
    html:has(.global-search--mobile-open) {
        overflow: hidden;
    }

    /* WCAG 2.2 target size on the 31 drawer links — 40 px at the desktop
       padding, which stays exactly as it is above this breakpoint. */
    .side-nav a {
        display: flex;
        align-items: center;
        min-height: var(--tap-min, 44px);
    }
    /* 9.92 px was the smallest text anywhere in the app. */
    .side-nav-group-title { font-size: var(--text-xs); }
    .side-nav-subgroup-title { font-size: var(--text-xs); }
}

/* -------- Phone: ≤ 640 px -------- */
@media (max-width: 640px) {
    .mobile-tabbar { display: flex; justify-content: space-around; }

    /* Tighter header on phones: hide the big Greek title, keep
       logo + actions. Hamburger stays for the drawer. */
    .app-header {
        padding: var(--space-2) var(--space-3);
        min-height: 56px;
        gap: var(--space-2);
    }
    .app-title { display: none; }
    .app-header .brand-logo { height: 40px; }

    /* Reserve space for the bottom tabbar. */
    .app-main {
        padding: 1rem 1rem calc(var(--tap-min, 44px) + 1rem + env(safe-area-inset-bottom));
    }

    /* ---- Header action cluster ------------------------------------
       «Αναφορά» is how the owner files reports and «Αποσύνδεση» ends a
       session on a shared reception phone, so BOTH stay on screen at
       320 px — collapsed to 44 px icon buttons rather than dropped. The
       signed-in name stays too, truncated, with the full value in its
       title attribute. */
    .app-header-right { gap: var(--space-2); }
    .user-menu { gap: var(--space-2); }
    .user-menu .user-menu-email { max-width: 7rem; font-size: var(--text-base); }
    .user-menu-report,
    .user-menu-logout {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-width: var(--tap-min, 44px);
        min-height: var(--tap-min, 44px);
        gap: 0;
        padding: 0;
    }
    .user-menu-report-icon { font-size: var(--text-xl); }
    .user-menu-logout-icon { display: block; }
    /* Clipped, NOT display:none — the buttons keep «Αναφορά» /
       «Αποσύνδεση» as their accessible names for screen readers. */
    .user-menu-btn-text {
        position: absolute;
        width: 1px;
        height: 1px;
        padding: 0;
        margin: -1px;
        overflow: hidden;
        clip: rect(0, 0, 0, 0);
        white-space: nowrap;
        border: 0;
    }

    /* A multi-branch tenant also renders the branch picker up here; its
       12 rem select alone is 60 % of a 320 px viewport. */
    .branch-switcher-label { display: none; }
    .branch-switcher-select { min-width: 0; max-width: 9rem; }

    /* Tap targets for the header's icon-only controls live at the very
       end of this sheet — their base rules are declared thousands of
       lines below this block, so an override here loses on source order. */

    /* Nothing in the phone navigation chrome drops below 12 px. */
    .mobile-tab { font-size: var(--text-xs); }
}

/* =============================================================
   Auth layout (login page) — full-bleed teal with waves + logo
   plaque card, faithful to the handoff design.
   ============================================================= */
.auth-viewport {
    min-height: 100vh;
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-8) var(--space-5) 80px;
    overflow: hidden;
}

.auth-bg {
    position: fixed;
    inset: 0;
    z-index: 0;
    overflow: hidden;
    background:
        radial-gradient(120% 80% at 20% 10%,
            oklch(0.62 0.11 208) 0%,
            oklch(0.52 0.11 213) 55%,
            oklch(0.42 0.09 218) 100%);
}
.auth-bg .waves,
.auth-bg .blobs { position: absolute; inset: 0; width: 100%; height: 100%; }
.auth-bg .blob-1 { animation: floatY 9s ease-in-out infinite; }
.auth-bg .blob-2 { animation: floatY 11s ease-in-out infinite; animation-delay: -2s; }
.auth-bg .blob-3 { animation: floatY 13s ease-in-out infinite; animation-delay: -5s; }
.auth-bg .blob-4 { animation: floatY 10s ease-in-out infinite; animation-delay: -1s; }

.auth-announce {
    position: fixed;
    top: 0; left: 0; right: 0;
    z-index: 4;
    background: oklch(0.32 0.06 218);
    color: oklch(0.94 0.03 205);
    font-size: var(--text-xs);
    letter-spacing: 0.04em;
    font-weight: 500;
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 9px var(--space-4);
    gap: 14px;
}
.auth-announce .dot {
    display: inline-block;
    width: 6px; height: 6px; border-radius: 50%;
    background: var(--lime-500);
}
.auth-announce .label {
    opacity: 0.75;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    font-size: var(--text-2xs);
    font-weight: 700;
}

.auth-card {
    position: relative;
    z-index: 2;
    width: 100%;
    max-width: 440px;
    background: var(--card);
    border-radius: var(--radius-lg);
    padding: 36px 40px 30px;
    box-shadow: var(--shadow-card);
    animation: rise 600ms cubic-bezier(.2,.7,.2,1) both;
    overflow: hidden;
}
.auth-card-band {
    position: absolute;
    left: 0; right: 0; top: 0;
    height: 6px;
    background: linear-gradient(90deg,
        var(--lime-500) 0%, var(--lime-500) 60%, var(--lime-600) 100%);
}

.auth-head {
    display: flex;
    flex-direction: column;
    align-items: center;
    margin-top: var(--space-1);
    margin-bottom: var(--space-5);
}
.auth-logo-plaque {
    background: oklch(0.46 0.09 212);
    padding: 10px var(--space-4);
    border-radius: var(--radius);
    box-shadow: var(--shadow-plaque);
    display: inline-block;
}
.auth-logo-plaque img {
    display: block;
    width: 180px;
    height: auto;
    border-radius: 4px;
}
.auth-logo-plaque .auth-logo-fallback {
    display: none;
    color: #fff;
    font-family: Verdana, 'DejaVu Sans', Geneva, sans-serif;
    font-size: var(--text-3xl);
    padding: var(--space-2) var(--space-1);
}
.auth-role {
    margin-top: var(--space-3);
    color: var(--teal-800);
}
.auth-center-name {
    margin: var(--space-2) 0 var(--space-1);
    font-family: Verdana, 'DejaVu Sans', Geneva, sans-serif;
    font-weight: 600;
    font-size: var(--text-4xl);
    letter-spacing: -0.01em;
    text-align: center;
    color: var(--teal-900);
    line-height: 1.1;
}
.auth-tagline {
    font-size: var(--text-sm);
    color: var(--ink-soft);
    text-align: center;
    font-weight: 500;
    letter-spacing: 0.02em;
    max-width: 320px;
    text-wrap: pretty;
}

.auth-fields { display: flex; flex-direction: column; gap: var(--space-3); }
.auth-field { display: block; }
.auth-field .label-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    margin-bottom: 6px;
}
.auth-field .label {
    font-size: var(--text-xs);
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ink-soft);
}
.auth-field.has-error .label { color: var(--danger); }
.auth-field .err-inline {
    font-size: var(--text-xs);
    color: var(--danger);
    font-weight: 600;
}
.auth-field .input-wrap {
    position: relative;
    background: var(--card);
    border: 1.5px solid var(--line);
    border-radius: var(--radius);
    transition: border-color 160ms ease, box-shadow 160ms ease;
}
.auth-field .input-wrap:focus-within {
    border-color: var(--teal-800);
    /* REQ-F29-NFR-001 — high-contrast focus ring (≥ 3:1) on the login inputs,
       replacing the low-chroma lime glow. */
    box-shadow: 0 0 0 var(--focus-ring-width) var(--focus-ring);
}
.auth-field.has-error .input-wrap { border-color: var(--danger); }
.auth-field input {
    width: 100%;
    border: none; outline: none; background: transparent;
    padding: 14px var(--space-4);
    font-size: var(--text-md); color: var(--ink);
    border-radius: var(--radius);
}
.auth-field input[data-with-right] { padding-right: 44px; }
.auth-field .input-right {
    position: absolute;
    right: 6px; top: 50%;
    transform: translateY(-50%);
}
.eye-btn {
    background: transparent; border: none; padding: var(--space-2);
    color: var(--ink-soft); border-radius: 8px; line-height: 0;
    display: inline-flex;
}

.auth-row-actions {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-top: 14px;
    font-size: var(--text-sm);
}
.auth-remember { display: inline-flex; align-items: center; gap: var(--space-2); color: var(--ink-soft); cursor: pointer; font-weight: 500; }
.auth-remember input { accent-color: var(--teal-800); width: 15px; height: 15px; }
.auth-forgot { color: var(--teal-800); text-decoration: none; font-weight: 600; }
.auth-forgot:hover { text-decoration: underline; }

.auth-submit {
    margin-top: 20px;
    width: 100%;
    padding: 14px var(--space-4);
    background: var(--teal-800);
    color: #ffffff;
    border: none; border-radius: var(--radius);
    letter-spacing: 0.04em;
    font-size: var(--text-md);
    font-weight: 700;
    transition: background 160ms ease, transform 120ms ease, opacity 160ms ease, box-shadow 160ms ease;
    box-shadow: 0 8px 20px -10px oklch(0.38 0.08 215 / 0.7);
}
.auth-submit:hover:not(:disabled) { background: var(--teal-900); }
.auth-submit:active:not(:disabled) { transform: translateY(1px); }
.auth-submit:disabled { background: oklch(0.75 0.03 210); opacity: 0.75; cursor: not-allowed; box-shadow: none; }
.auth-submit-inner { display: inline-flex; align-items: center; gap: 10px; }
.spinner {
    display: inline-block;
    width: 15px; height: 15px;
    border: 2px solid oklch(1 0 0 / 0.4);
    border-top-color: #fff;
    border-radius: 50%;
    animation: spin 700ms linear infinite;
}

.auth-sub-row {
    margin-top: 20px;
    padding-top: var(--space-4);
    border-top: 1px solid var(--line);
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: var(--text-sm);
    color: var(--ink-soft);
    gap: var(--space-3);
}
.auth-sub-row a {
    color: var(--teal-800);
    text-decoration: none;
    font-weight: 700;
}
.auth-sub-row a:hover { text-decoration: underline; }

.auth-server-err {
    font-size: var(--text-sm);
    color: var(--danger);
    background: oklch(0.97 0.04 25);
    border: 1px solid oklch(0.85 0.08 25);
    padding: 10px var(--space-3);
    border-radius: 10px;
    margin-bottom: 14px;
}

.auth-success { padding: 28px var(--space-3) var(--space-3); text-align: center; }
.auth-success .tick {
    width: 48px; height: 48px; border-radius: 50%;
    background: oklch(0.93 0.12 135);
    color: oklch(0.35 0.12 140);
    display: inline-flex; align-items: center; justify-content: center;
    margin-bottom: 14px;
}
.auth-success h2 {
    font-family: Verdana, 'DejaVu Sans', Geneva, sans-serif;
    font-weight: 500;
    font-size: var(--text-3xl);
    color: var(--teal-900);
    margin: 0 0 var(--space-1);
}

.auth-foot {
    position: fixed;
    bottom: 0; left: 0; right: 0;
    z-index: 3;
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 18px 28px;
    font-size: var(--text-xs);
    color: oklch(0.92 0.03 205);
    gap: var(--space-3);
    flex-wrap: wrap;
}
.auth-foot .mono-cap { opacity: 0.8; }
.auth-foot .addr { display: flex; gap: 18px; opacity: 0.88; flex-wrap: wrap; justify-content: center; }
.auth-foot .sep { opacity: 0.5; }

/* =============================================================
   Forms and buttons inside the authenticated app (customer form).
   Styled to harmonise with the auth card but lighter.
   ============================================================= */
.form-field { display: flex; flex-direction: column; gap: var(--space-1); margin-bottom: var(--space-4); }
.form-field label { font-size: var(--text-base); font-weight: 600; color: var(--ink); }
.form-field input,
.form-field select,
.form-field textarea {
    padding: var(--space-3) var(--space-3);
    border: 1.5px solid var(--line);
    border-radius: var(--radius-sm);
    font-size: var(--text-lg);
    background: var(--cream);
    color: var(--ink);
    transition: border-color 120ms ease, box-shadow 120ms ease, background-color 120ms ease;
}
.form-field select option { color: var(--ink); background: var(--card); }
.form-field input:focus,
.form-field select:focus,
.form-field textarea:focus {
    border-color: var(--teal-800);
    background: var(--card);
}
/* REQ-F29-NFR-001 — high-contrast focus-visible ring (≥ 3:1) replaces the
   old low-chroma lime box-shadow. Keyboard focus only, so a mouse click
   doesn't paint a ring. */
.form-field input:focus-visible,
.form-field select:focus-visible,
.form-field textarea:focus-visible {
    outline: var(--focus-ring-width) solid var(--focus-ring);
    outline-offset: 1px;
}
.form-field input:disabled,
.form-field select:disabled,
.form-field textarea:disabled { opacity: 0.6; cursor: not-allowed; }
.form-field .validation-message { color: var(--danger); font-size: var(--text-sm); }
/* A short note UNDER a field that is neither an error nor a permanent hint —
   it states something the last action did or did not do, e.g. «the address
   search returned no number, fill it in». It needs its own rule rather than
   `.muted` because a bare <p> inside `.form-field` picks up the browser's
   1em block margins and breaks the field's own `gap` rhythm; `.field-hint`,
   the name already used on five screens, has NO rule in this stylesheet at
   all and would have rendered this unstyled — the exact defect
   RenderedClassesExistInStylesheetTests exists for. */
.form-field .form-field-note { margin: 0; font-size: var(--text-base); color: var(--ink-soft); }

/* ---------- Field widths, sized by MEANING (#762) ----------------------
   Elias, from the live demo on v0.110.0: «session details uses too much
   horizontal space for fields that should only be 10 chars or so long.
   This results in unnecessary scrolling.»

   MEASURED before this block existed, at 1280 px:
     * «Αλλαγή τιμής» rendered its price as a 356 px number input in a
       768 px card.
     * The session panel's cost override was 947 px wide in a 1024 px card
       — for a value like «45,00».
   A `.form-field` is `flex-direction: column`, so an input stretches to
   the full width of whatever contains it. That is right for a name and
   absurd for a date: the field cannot share its line with the one next to
   it, so every field below it moves down, and the operator scrolls to
   reach the action row. The wasted space is VERTICAL, which is why a
   horizontal complaint is really a scrolling one.

   🔴 THE TOKENS ARE SEMANTIC, NOT NUMERIC, and that is the whole design.
   A rule that says «a date input is date-width» survives a redesign, a
   font change and a new dialog nobody has written yet. «12rem here» does
   not: it has to be repeated at every call site, it is invisible to a
   reviewer, and the thirty-eighth dialog will pick a different number.
   So there are six names, applied by what the value MEANS:

     --field-w-time    a clock time            «14:30»
     --field-w-short   a count or a duration   «45», «12 ημέρες»
     --field-w-afm     a nine-digit ΑΦΜ
     --field-w-date    a date                  «31/12/2026»
     --field-w-choice  one of a short list,
                       or a short label        «Σε αναμονή», «Γ' Δημοτικού»
     --field-w-amka    an eleven-digit ΑΜΚΑ

   Three of them are applied by input TYPE, which needs no markup change
   and therefore reaches every dialog at once, including the ones nobody
   has opened (#761 counted 33 of those). The rest cannot be: ΑΜΚΑ, ΑΦΜ
   and a short enumerated choice are all `type="text"` or a `<select>`
   and are indistinguishable from any other value to CSS, so they are
   opt-in classes on the `.form-field` and are listed in
   `Components/README.md` next to the convention they belong to.

   🔴 `--field-w-amount` WAS the sixth name and is RETIRED, because the
   residual #762 named was «declared but never applied» and a token can
   be honest in only two ways: applied, or gone. MEASURED before removing
   it — every money field in the app is `type="number"`, all EIGHTEEN of
   them, with no exception anywhere: eleven `InputNumber`, seven
   `<input type="number">`. So the type rule above already caps every one
   of them at `--field-w-short`, and «1.234,56» is eight glyphs plus a
   spinner — comfortably inside 8rem. A 9rem token could therefore never
   make a money field NARROWER; the only thing it could ever do is widen
   one, in a ticket filed because fields were too wide. A name that can
   only act against its own ticket is decoration, so it is gone rather
   than left declared for a reader to assume is doing something.

   `max-width`, not `width`: the field still collapses on a narrow screen
   (the phone breakpoint needs no special case) and a longer localised
   value is never clipped — it is a ceiling, not a size. */
:root {
    --field-w-time: 7rem;
    --field-w-short: 8rem;
    --field-w-afm: 10rem;
    --field-w-date: 10rem;
    /* 11rem is MEASURED, not chosen. In Chromium at this app's font the
       longest content these fields ever hold is «π.χ. Γ' Δημοτικού» at
       158 px and the «— Επιλέξτε —» prompt at 146 px; 11rem is 176 px, so
       the widest one keeps 18 px of headroom. At --field-w-short (8 rem /
       128 px) BOTH clip — measured, by 30 px and 18 px — and a hint the
       operator cannot read is a REQ-F29-007 defect, not a tight fit. */
    --field-w-choice: 11rem;
    --field-w-amka: 12rem;
}
.form-field input[type="time"] { max-width: var(--field-w-time); }
.form-field input[type="date"] { max-width: var(--field-w-date); }
.form-field input[type="number"] { max-width: var(--field-w-short); }
/* Opt-in, for the meanings CSS cannot see from the markup.
   ✅ NOW APPLIED — this comment previously read «DECLARED, NOT YET
   APPLIED», and that was the residual. The true site count was SEVEN, not
   the six this comment used to name: `Admin/LegalEntities/New` carries an
   ΑΦΜ field too and was missed, which is exactly the failure mode an
   opt-in class has and a type rule does not. The seven:

     --field-w-amka  «12345678910»  Children/New, Customers/Children/New
     --field-w-afm   «123456789»    Customers/CustomerForm, Branches/New,
                                    Branches/Edit, Admin/LegalEntities/New,
                                    Admin/LegalEntities/Edit

   `--field-w-choice` covers the three fields on `Children/New` a type
   rule cannot see — Φύλο, Ενεργό μέλος, Τάξη — which were on the numeric
   `.field-sm` / `.field-md` overrides retired below. It exists because
   `--field-w-short` was tried there first and MEASURED to clip two of the
   three; see the note on its declaration. `--field-w-short` keeps no
   opt-in class of its own: every field that means «a count» is already
   `type="number"`, so the class had no honest site and a class with no
   site is the defect this ticket is about.

   ⚠️ THE SPECIFICITY FIGHT, which is why this was not a one-liner.
   `.intake-fields .form-field.field-sm input` is (0,3,1); every rule here
   is (0,2,1), so the intake override WON and `Children/New`'s ΑΜΚΑ stayed
   at 16rem no matter what these declared. Retiring that override was the
   prerequisite, not a tidy-up — see `.intake-fields` further down. */
.form-field.field-w-afm input { max-width: var(--field-w-afm); }
.form-field.field-w-amka input { max-width: var(--field-w-amka); }
.form-field.field-w-choice input,
.form-field.field-w-choice select { max-width: var(--field-w-choice); }
/* `datetime-local` carries a date AND a time, so neither token fits it;
   it gets the sum rather than a seventh name nobody would remember. */
.form-field input[type="datetime-local"] {
    max-width: calc(var(--field-w-date) + var(--field-w-time));
}

/* ---------- ONE action-row convention (#762) --------------------------
   «Revisit and group the buttons also better e.g. logically.» — Elias.

   The convention, in one sentence: **an action row has one affirmative
   action, it is last, and a destructive action is separated from the
   rest and coloured like one.**

   Rendered out:
     1. ONE row per surface. Not two, not buttons scattered through the
        body.
     2. The AFFIRMATIVE action — the thing the surface exists to do — is
        LAST, where the pointer already is. It is the row's only
        `.btn-primary`; when the purpose is itself destructive it takes
        that slot and keeps `.btn-danger`'s colour («Ακύρωση … συνεδριών»
        in the delete-range dialog is the example).
     3. «Άκυρο» / «Κλείσιμο» sits immediately to its left.
     4. A DESTRUCTIVE action that is not the surface's purpose is pushed
        to the far side by an auto margin, so it can never be hit by
        aiming at the confirm button, and is always `.btn-danger`.

   MEASURED before this block: of the five dialogs under the visual gate,
   two ended their row with «Ακύρωση» sitting to the RIGHT of the confirm
   («Νέα συνεδρία» read [Αποθήκευση | Ακύρωση], «Αλλαγή τιμής» read
   [Έλεγχος | Ακύρωση]) and three did not. There was no convention; there
   was whichever order each author happened to type.

   Rule 4 is two declarations rather than one because «separated» means
   opposite directions in the two row alignments the app uses: a dialog
   footer is `justify-content: flex-end`, so away-from-the-rest is left;
   a hub's action row is left-aligned, so it is right. Same principle,
   both arrangements.

   `ModalActionRowConventionTests` fails the build on a new dialog that
   does not follow it — the convention is written down in
   `Components/README.md` AND enforced, because #762 was filed about a
   convention that existed in neither place. */
.modal-action-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    align-items: center;
    margin-top: var(--space-3);
}
/* `:not(…-child)` is rule 4's «that is not the surface's purpose», said in
   CSS: a destructive action in the LAST slot of a footer (or the FIRST of
   a left-aligned hub row) is the affirmative action, and pushing it away
   from the row would be pushing the confirm button away from the pointer.
   Everything else gets the gap. */
.modal-action-row > .btn-danger:not(:first-child) { margin-inline-start: auto; }
.modal-footer > .btn-danger:not(:last-child),
.form-actions > .btn-danger:not(:last-child) { margin-inline-end: auto; }

/* ---------- Compact multi-column form body (#655) ----------
   A form of fifteen single-column fields is taller than any screen, so the
   operator scrolls past half of it to reach «Αποθήκευση» and never sees
   related fields together. `auto-fit` + `minmax` pairs them up when there is
   room and collapses to one column when there is not, so the same markup
   serves a desktop and a phone without a breakpoint to maintain.
   The row spacing moves to the grid gap; leaving the field's own bottom
   margin at full size would double-space every row. */
.form-columns {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
    gap: 0 var(--space-4);
    align-items: start;
}
.form-columns > .form-field { margin-bottom: var(--space-3); }
/* A field carrying a long explanation or a textarea reads badly at half
   width — opt it back to the whole row. */
.form-columns > .form-field-wide { grid-column: 1 / -1; }

/* ---------- Field width follows the VALUE, not the column (2026-08-15) ----
   Elias: «Check every field in that page that only uses as much space as it
   should.»

   Every control here is a flex child of `.form-field`, so it stretched to the
   whole grid cell: «Συνεδρίες / εβδομάδα», which holds one digit between 1 and
   7, was rendered the same 16rem wide as a goals box. That is not a cosmetic
   complaint — a 16rem box for one digit LOOKS like it wants a sentence, and on
   a screen built for people who are not computer users (REQ-F29-007) the size
   of a box is a claim about what belongs in it.

   The rule is one rule rather than a shrink of whichever field looked worst:
   a control's width is bounded by the longest value it can legally hold.
   `--field-chars` states that length in characters at the field, next to the
   Range/format that justifies it, and the arithmetic lives here once. Fields
   that do not set it are unchanged, so this cannot quietly narrow a control
   nobody measured.

   The padding term is the input's own horizontal padding plus a select's
   arrow; without it the last character sits under the chevron. `max-width`,
   not `width`, so a narrow column still wins and the one-column phone layout
   is untouched. */
/* 🔴 #870 / W1 — the same ceiling, applied to a control that declares
   `--field-chars` on ITSELF rather than on a `.form-field` wrapper.
   `SessionInlineEditPanel` has set `style="--field-chars: 7"` on its
   «Τιμή (€)» input since v0.141; the input sits in a `<dd>` of a
   `dl.detail-grid`, not in a `.form-field`, so the selector below never
   matched it and the declaration did nothing. Measured: 202 px in a
   1024 px card, against a 128.5 px ceiling — the failing case in
   ModalFieldWidthTests.W1. Stating the length at the field and having it
   ignored is worse than not stating it, because it reads as done. */
input[style*="--field-chars"],
select[style*="--field-chars"],
.form-field[style*="--field-chars"] input,
.form-field[style*="--field-chars"] select {
    max-width: calc(var(--field-chars) * 1ch + 3.2rem);
}

/* ---------- Auto-resizing textareas (child history user report) ----------
   Multi-line note / reason / evaluation fields grew a fixed height and
   forced the user to scroll inside a tiny box. `field-sizing: content`
   lets the textarea size itself to its content — natively, both for
   content present on load and as the user types — so no JS interop or
   @ref/OnAfterRenderAsync plumbing is needed. Chromium (this app's
   target per the demo UA) supports it. `min-height` keeps an empty box
   comfortably tappable; `max-height` caps runaway growth on a huge note
   and re-introduces an inner scrollbar past the cap so a single field can
   never eat the whole viewport. It stays an ordinary, accessible
   <textarea>. Browsers without field-sizing simply fall back to the
   existing `rows`-based height — no regression.

   Applied by the `.auto-resize` class on the History page's inline
   diagnosis-notes field and on the shared note/reason/section textareas
   in the detail panels the page opens (diagnosis, evaluation, evaluation
   sections, session reschedule) and the child contact log, so the
   improvement follows those components everywhere they are reused. */
textarea.auto-resize {
    field-sizing: content;
    min-height: 3.5rem;
    max-height: 60vh;
    overflow-y: auto;
}

/* ---------- Inline filter bar (Sessions/Rooms list pages) ----------
   Lightweight row of labels + controls that sit above a data table.
   Styles its direct <input>, <select>, and .btn-* children so they
   inherit the design-system tokens and don't fall back to browser
   defaults in dark mode. */
.filters {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2) var(--space-3);
    margin-bottom: var(--space-4);
}
.filters > label { font-weight: 600; color: var(--ink); }
.filters > input,
.filters > select {
    padding: var(--space-2) var(--space-3);
    border: 1.5px solid var(--line);
    border-radius: var(--radius-sm);
    font-size: var(--text-lg);
    background: var(--cream);
    color: var(--ink);
}
.filters > input:focus,
.filters > select:focus {
    border-color: var(--teal-800);
}
/* REQ-F29-NFR-001 — visible focus ring on the inline filter bar controls. */
.filters > input:focus-visible,
.filters > select:focus-visible {
    outline: var(--focus-ring-width) solid var(--focus-ring);
    outline-offset: 1px;
}
.checkbox-inline {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--ink);
    cursor: pointer;
    user-select: none;
}
.checkbox-inline input[type="checkbox"] { accent-color: var(--teal-800); }

/* ---------- Typed-search selectors (ParentTypeahead, ChildTypeahead,
   TherapistTypeahead). All three use the .search-select scope; the
   legacy .parent-typeahead selectors below are kept as aliases so the
   existing component renders unchanged. New consumers should use
   .search-select. */
.search-select,
.parent-typeahead { position: relative; }
.search-select__input-wrap,
.parent-typeahead__input-wrap { position: relative; }
.search-select__clear,
.parent-typeahead__clear {
    position: absolute;
    right: 0.35rem;
    top: 50%;
    transform: translateY(-50%);
    background: transparent;
    border: none;
    color: var(--ink-soft);
    font-size: var(--text-3xl);
    line-height: 1;
    padding: var(--space-05) var(--space-2);
    cursor: pointer;
    border-radius: var(--radius-sm);
}
.search-select__clear:hover,
.parent-typeahead__clear:hover { color: var(--ink); background: var(--teal-100); }
.search-select__hint,
.parent-typeahead__hint { font-size: var(--text-base); color: var(--ink-soft); margin: var(--space-1) 0 0; }
.search-select__list,
.parent-typeahead__list {
    position: absolute;
    z-index: 20;
    left: 0;
    right: 0;
    top: calc(100% + 0.25rem);
    list-style: none;
    margin: 0;
    padding: var(--space-1) 0;
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    box-shadow: 0 10px 30px -15px oklch(0.22 0.05 215 / 0.35);
    /* Cap the dropdown so a long result list doesn't run past the
       viewport on small screens — items below the fold become
       unreachable when the page can't scroll the absolutely-positioned
       <ul> back into view. min(60vh, 320px) keeps phones usable while
       still showing several rows on a desktop. */
    max-height: min(60vh, 320px);
    overflow-y: auto;
}
.search-select__list li,
.parent-typeahead__list li { margin: 0; }
.search-select__empty,
.parent-typeahead__empty {
    padding: var(--space-3) var(--space-4);
    color: var(--ink-soft);
    font-size: var(--text-md);
}
.search-select__item,
.parent-typeahead__item {
    display: flex;
    flex-direction: column;
    width: 100%;
    text-align: left;
    background: transparent;
    border: none;
    padding: 0.55rem 0.9rem;
    color: var(--ink);
    cursor: pointer;
    font: inherit;
}
.search-select__item:hover,
.search-select__item.is-highlighted,
.parent-typeahead__item:hover,
.parent-typeahead__item.is-highlighted { background: var(--teal-100); }
.search-select__name,
.parent-typeahead__name { font-weight: 600; font-size: var(--text-lg); }
.search-select__meta,
.parent-typeahead__meta { font-size: var(--text-sm); color: var(--ink-soft); margin-top: var(--space-05); }

/* ---------- IcdCodePicker component (REQ-F01-016) ---------- */
.icd-picker { display: flex; flex-direction: column; gap: var(--space-1); }
.icd-picker__input-wrap {
    display: flex;
    gap: var(--space-2);
    align-items: stretch;
}
.icd-picker__input { flex: 1 1 auto; min-width: 0; }
.icd-picker__open { white-space: nowrap; }
.icd-picker__hint { font-size: var(--text-base); color: var(--ink-soft); margin: 0; }
.icd-picker__selected-desc { color: var(--ink); font-weight: 500; }
.icd-picker__unknown { color: var(--ink-soft); font-style: italic; }
.icd-picker__badge {
    display: inline-block;
    margin-left: var(--space-2);
    padding: 0.05rem 0.45rem;
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--teal-900);
    background: var(--teal-100);
    border-radius: var(--radius-sm);
    vertical-align: middle;
}
.icd-picker__modal { width: min(40rem, 100%); }
.icd-picker__header-actions {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}
.icd-picker__cancel { padding: 0.3rem var(--space-3); font-size: var(--text-base); }
.icd-picker__search {
    width: 100%;
    padding: var(--space-2) 0.65rem;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    font: inherit;
    color: var(--ink);
    background: var(--card);
}
.icd-picker__search:focus {
    outline: 2px solid var(--teal-700);
    outline-offset: 1px;
}
/* Virtualized list: bounded height + scroll so <Virtualize> can
   measure overflow and only mount the visible rows. The fixed row
   height (44 px) matches the `ItemSize` parameter on the
   <Virtualize> tag — keep them in lockstep. */
.icd-picker__list {
    list-style: none;
    margin: var(--space-2) 0 0;
    padding: 0;
    max-height: 24rem;
    overflow-y: auto;
}
.icd-picker__list > li,
.icd-picker__list .icd-picker__item {
    height: 44px;
    box-sizing: border-box;
}
.icd-picker__group-title {
    display: flex;
    align-items: center;
    height: 44px;
    box-sizing: border-box;
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--ink-soft);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin: 0;
    padding: 0 var(--space-2);
}
.icd-picker__item {
    display: grid;
    grid-template-columns: minmax(4.5rem, auto) 1fr auto;
    gap: var(--space-3);
    align-items: center;
    width: 100%;
    text-align: left;
    background: transparent;
    border: none;
    padding: 0 var(--space-2);
    border-radius: var(--radius-sm);
    color: var(--ink);
    cursor: pointer;
    font: inherit;
}
.icd-picker__item:hover,
.icd-picker__item.is-highlighted { background: var(--teal-100); }
.icd-picker__code { font-family: ui-monospace, monospace; font-weight: 600; }
.icd-picker__desc { font-size: var(--text-lg); }

.form-error {
    color: var(--danger);
    background: oklch(0.97 0.04 25);
    border: 1px solid oklch(0.85 0.08 25);
    padding: 0.55rem var(--space-3);
    border-radius: var(--radius-sm);
    margin: 0 0 var(--space-4);
    font-size: var(--text-base);
}

.btn-primary, .btn-secondary {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    padding: 0.65rem 1.1rem;
    border-radius: var(--radius-sm);
    border: 1px solid transparent;
    font-size: var(--text-lg);
    font-weight: 600;
    cursor: pointer;
    transition: background-color 120ms ease, border-color 120ms ease;
    text-decoration: none;
}
/* REQ-F23-003 — primary CTA picks up the tenant brand colour. */
.btn-primary { background: var(--rheasys-primary, var(--teal-800)); color: #ffffff; }
.btn-primary:hover:not(:disabled) { background: var(--teal-900); }
.btn-primary:disabled { opacity: 0.6; cursor: not-allowed; }

.btn-secondary { background: var(--card); color: var(--ink); border-color: var(--line); }
.btn-secondary:hover:not(:disabled) { background: var(--cream); }
.btn-secondary:disabled { opacity: 0.6; cursor: not-allowed; }

/* REQ-F29-NFR-001 — buttons had no explicit focus-visible ring and relied on
   the (often suppressed) UA default. Give both variants a high-contrast ring. */
.btn-primary:focus-visible,
.btn-secondary:focus-visible {
    outline: var(--focus-ring-width) solid var(--focus-ring);
    outline-offset: 2px;
}

/* REQ-F29-001 — destructive-action button. Distinct danger weight (--danger)
   so a delete/cancel is never confused with the safe choice. Shares the
   .btn-primary/.btn-secondary box model. */
.btn-danger {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    padding: 0.65rem 1.1rem;
    border-radius: var(--radius-sm);
    border: 1px solid transparent;
    font-size: var(--text-lg);
    font-weight: 600;
    cursor: pointer;
    text-decoration: none;
    background: var(--danger);
    color: #ffffff;
    transition: background-color 120ms ease, border-color 120ms ease;
}
.btn-danger:hover:not(:disabled) { background: oklch(0.48 0.16 25); }
.btn-danger:disabled { opacity: 0.6; cursor: not-allowed; }
.btn-danger:focus-visible {
    outline: var(--focus-ring-width) solid var(--focus-ring);
    outline-offset: 2px;
}

.btn-block { width: 100%; }

.data-table {
    width: 100%;
    border-collapse: collapse;
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    overflow: hidden;
}
.data-table th, .data-table td {
    padding: 0.65rem 0.875rem;
    text-align: left;
    border-bottom: 1px solid var(--line);
    font-size: var(--text-md);
}
.data-table th { background: var(--teal-50); font-weight: 700; color: var(--teal-900); }
.data-table tr:last-child td { border-bottom: none; }
.data-table tr.row-clickable { cursor: pointer; transition: background-color 120ms ease, box-shadow 120ms ease; }
.data-table tr.row-clickable:hover { background: var(--teal-50); }
.data-table tr.row-selectable td { transition: border-color 120ms ease; }
.data-table tr.row-selectable:hover td { border-bottom-color: var(--teal-700); }
.data-table tr.row-selectable:hover td:first-child { box-shadow: inset 3px 0 0 0 var(--teal-700); }
.data-table tr.row-selectable td:hover { background: var(--teal-100); }
.data-table th.num, .data-table td.num { text-align: right; font-variant-numeric: tabular-nums; }
.data-table tfoot td { border-top: 1px solid var(--line); background: var(--teal-50); }

/* Inline link inside table cells (e.g. mailto/tel). Uses teal-900 on
   the white card surface for WCAG-AA contrast (≥ 4.5:1) and only
   underlines on hover/focus to keep busy lists readable. */
.text-link {
    color: var(--teal-900);
    text-decoration: none;
    font-weight: 500;
}
.text-link:hover,
.text-link:focus-visible { text-decoration: underline; }
.text-link:focus-visible { outline: 2px solid var(--teal-700); outline-offset: 2px; border-radius: 2px; }

/* Column-header button used to toggle sort direction. Inherits the
   header's color/weight so it visually reads as a header label, not
   a button. */
.data-table th .sort-header {
    background: transparent;
    border: 0;
    padding: 0;
    margin: 0;
    font: inherit;
    color: inherit;
    cursor: pointer;
}
.data-table th .sort-header:hover { text-decoration: underline; }
.data-table th .sort-header:focus-visible {
    outline: 2px solid var(--teal-700);
    outline-offset: 2px;
    border-radius: 2px;
}

/* DataTable.razor — Excel-like wrapper around .data-table.
   Adds sticky header for vertical scrolling, per-column drag
   resize handles, and a filter row beneath the headers. */
/* Outer scroll container ensures wide tables stay reachable on
   narrow viewports — without this rule the table either truncates
   or pushes the layout horizontally. The mobile cards rule below
   (max-width: 768px) drops the horizontal scroll because rows
   stack as cards instead.

   #548 — APP-WIDE CONVENTION: every `<table class="data-table">` is
   wrapped in a `<div class="data-table-scroll">`. Phones are safe
   already (rows stack as cards under 768 px); the bug bites at tablet
   widths (~768–1080 px) where the table still renders as a table with
   nothing to contain it, so the page scrolls sideways instead of the
   table.

   Why a wrapper element and not a pure-CSS rule on `.data-table`
   itself — both alternatives were rejected:
     • `table { display: block; overflow-x: auto }` makes the browser
       wrap thead/tbody in an *anonymous* table box that shrink-wraps
       its content, so every table narrower than its container would
       stop stretching to 100%. That silently restyles every list in
       the app.
     • `*:has(> table.data-table) { overflow-x: auto }` needs no markup
       but hangs `overflow` on whatever element happens to be the
       parent — usually a padded `.card` holding headers, buttons and
       popovers. Per spec `overflow-x: auto` forces `overflow-y` to
       auto too, so it would clip anything escaping that card
       (dropdowns, the `.audit-tooltip` bubble, sticky bars).
   A dedicated wrapper guarantees the scroll box is exactly the table's
   box and nothing else, and it is a no-op when the table already fits.

   This convention is enforced by DataTableScrollWrapperTests in
   Rheasys.Therapy.Admin.Tests — add the wrapper on new tables or that
   test fails. Do NOT reintroduce per-page inline `overflow-x` styles. */
.data-table-scroll { width: 100%; overflow-x: auto; }

/* Fieldset groups (e.g. «Πράξεις» on the approval forms): a clean outer
   border with breathing room so it never touches or overlaps the inner
   editor table's own border (R-20260722 owner feedback: "the outer border
   line should be outside the Πράξεις table — no overlapping"). */
fieldset.form-field {
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    padding: var(--space-2) var(--space-4) var(--space-4);
    margin: 0 0 var(--space-4);
}
fieldset.form-field legend {
    padding: 0 var(--space-2);
    font-weight: 600;
}
fieldset.form-field .data-table-scroll {
    margin-top: var(--space-1);
}
fieldset.form-field .data-table {
    /* the fieldset provides the outer frame — avoid double outer borders */
    border-width: 1px;
}
.data-table-wrap { width: 100%; }
.data-table-wrap--scrolling {
    overflow: auto;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
}
.data-table-wrap--scrolling .data-table--advanced {
    border: 0;
    border-radius: 0;
}
.data-table--advanced { table-layout: auto; }
.data-table--advanced th {
    position: sticky;
    top: 0;
    z-index: 1;
    /* Re-state the background on sticky headers so rows scrolling
       underneath don't bleed through. */
    background: var(--teal-50);
}
.data-table--advanced th.data-table-th--resizable {
    position: sticky;
    padding-right: var(--space-5);
}
.data-table--advanced .data-table-resize-handle {
    position: absolute;
    top: 0;
    right: 0;
    width: 6px;
    height: 100%;
    cursor: col-resize;
    user-select: none;
    /* Subtle visual cue on hover so users discover the affordance. */
    background: transparent;
    transition: background-color 120ms ease;
}
.data-table--advanced .data-table-resize-handle:hover,
.data-table--advanced .data-table-resize-handle:focus-visible {
    background: var(--teal-700);
    outline: none;
}
body.data-table-resizing { cursor: col-resize; user-select: none; }
.data-table-filter-row th {
    background: var(--card);
    padding: 0.4rem var(--space-2);
    top: 2.6rem;
    z-index: 1;
}
.data-table-filter-input {
    width: 100%;
    box-sizing: border-box;
    padding: 0.3rem var(--space-2);
    font-size: var(--text-base);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--card);
    color: var(--ink);
}
.data-table-filter-input:focus-visible {
    outline: 2px solid var(--teal-700);
    outline-offset: 1px;
    border-color: var(--teal-700);
}
.data-table-empty {
    text-align: center;
    color: var(--ink-soft);
    padding: 1.25rem !important;
}
/* Excel-style multi-select column filter. The trigger sits in the
   filter row in place of a text input.

   The panel is a `position: fixed` FLOATING panel, anchored to its
   trigger by wwwroot/js/data-table.js (openMultiFilter). It used to be
   `position: absolute`, which left it inside the `overflow: auto` of
   .data-table-wrap--scrolling — on a short table (few or zero rows) the
   option list and the «Καθαρισμός» button were clipped away and
   unreachable (R-20260726-03 follow-up). `fixed` takes it out of every
   ancestor's overflow in both axes.

   `left` / `top` / `max-height` are written as INLINE styles by the JS.
   The Blazor markup deliberately emits no `style` attribute on the
   panel so those survive component re-renders (same contract as the
   column-resize widths). With the JS absent the panel still lands under
   its trigger: a fixed box with `top: auto; left: auto` falls back to
   its static position. */
.data-table-multi-filter {
    position: relative;
    display: block;
    width: 100%;
    box-sizing: border-box;
}
.data-table-multi-filter > button.data-table-multi-filter__trigger {
    display: block;
    text-align: start;
    font: inherit;
    cursor: pointer;
    padding: 0.3rem 1.4rem 0.3rem var(--space-2);
    font-size: var(--text-base);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--card);
    color: var(--ink);
    box-sizing: border-box;
    width: 100%;
    user-select: none;
    position: relative;
}
.data-table-multi-filter > button.data-table-multi-filter__trigger::after {
    content: '▾';
    position: absolute;
    right: 0.45rem;
    top: 50%;
    transform: translateY(-50%);
    color: var(--ink-soft);
    font-size: var(--text-xs);
}
.data-table-multi-filter--open > button.data-table-multi-filter__trigger,
.data-table-multi-filter > button.data-table-multi-filter__trigger:focus-visible {
    border-color: var(--teal-700);
    outline: 2px solid var(--teal-700);
    outline-offset: 1px;
}
.data-table-multi-filter__panel {
    position: fixed;
    top: auto;
    left: auto;
    z-index: 20;
    min-width: 12rem;
    max-width: 18rem;
    /* Fallback cap for the JS-less static-position case; the JS narrows
       this to the space actually available above/below the trigger. */
    max-height: min(20rem, calc(100vh - 2rem));
    /* Column layout + the scrolling list below keep the «Καθαρισμός»
       action pinned and visible however tight the max-height gets. */
    display: flex;
    flex-direction: column;
    overflow: hidden;
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.18);
    padding: 0.4rem var(--space-2);
}
.data-table-multi-filter__panel[hidden] { display: none; }
/* Filter-row <th>s are `position: sticky`, which makes each header cell
   its own stacking context — without this bump a neighbouring header
   cell paints over the top edge of the open panel. Stays under the
   sticky .app-header (z-index 30) and every modal. */
.data-table-filter-row th.data-table-filter-th--open { z-index: 20; }
.data-table-multi-filter__actions {
    display: flex;
    justify-content: flex-end;
    margin-bottom: var(--space-1);
    flex: 0 0 auto;
}
.data-table-multi-filter__list {
    list-style: none;
    margin: 0;
    padding: 0;
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}
.data-table-multi-filter__list li { margin: 0; padding: 0; }
.data-table-multi-filter__option {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-1) 0.3rem;
    cursor: pointer;
    border-radius: var(--radius-sm);
    font-size: var(--text-base);
    color: var(--ink);
    line-height: 1.3;
}
.data-table-multi-filter__option:hover { background: var(--teal-50); }
.data-table-multi-filter__option input { margin: 0; }
/* Inline "Καθαρισμός φίλτρων" action shown when filters reduce the
   table to zero rows. Plain text-link affordance — sits next to the
   message so the user can recover without leaving the table. */
.data-table-clear-filters {
    display: inline-block;
    margin-left: var(--space-2);
    padding: 0;
    background: none;
    border: 0;
    color: var(--teal-700);
    font-weight: 600;
    text-decoration: underline;
    cursor: pointer;
}
.data-table-clear-filters:hover { color: var(--teal-900); }
.data-table-clear-filters:focus-visible {
    outline: 2px solid var(--teal-700);
    outline-offset: 2px;
    border-radius: var(--radius-sm);
}
/* Placeholder rows emitted by <Virtualize> while the visible window
   is being mounted. Keeping them transparent (background only) keeps
   the alternating-row look from flickering during fast scrolls. */
.data-table-row-placeholder td { background: var(--card); }

/* Standard screen-reader-only utility (lifted from the WAI-ARIA
   authoring practices). Used by DataTable's aria-live region to
   announce filter result counts without occupying layout space. */
.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.details-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); gap: var(--space-5); margin-top: var(--space-4); }
.details-card { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); padding: 1.25rem var(--space-5); }
.details-card h2 { font-family: Verdana, 'DejaVu Sans', Geneva, sans-serif; color: var(--teal-900); margin: 0 0 var(--space-3); }
/* minmax(0, 1fr): with a bare `1fr` the value column's auto floor was the
   widest <dd>'s min-content — on /customers/{id} that made the card 1038 px
   and pushed both iPads sideways (the Παιδιά table's own scroll wrapper was
   then outside the viewport, so «Πληρωτής» was simply cut off). */
.details-card dl { display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 0.35rem 0.85rem; margin: 0; font-size: var(--text-md); }
.details-card dt { color: var(--ink-soft); }
.details-card dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
.details-empty { color: var(--ink-soft); font-size: var(--text-md); padding: var(--space-2) 0; }
/* gap + wrap: at 320 px «Καταγραφή πληρωμής» and its «Πληρωμή στον
   λογαριασμό» button collided (space-between with no gap leaves nothing
   between two items that can no longer shrink). Never wraps on desktop. */
.details-card-header { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: var(--space-2) var(--space-3); margin-bottom: var(--space-3); }
.details-card-header h2 { margin: 0; }
@media (max-width: 900px) {
    .details-grid { grid-template-columns: minmax(0, 1fr); }
}
.details-grid > * { min-width: 0; }

.page-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--space-4); gap: var(--space-4); }
.page-header h1 { margin: 0; font-family: Verdana, 'DejaVu Sans', Geneva, sans-serif; color: var(--teal-900); }

/* Payments summary badges shown next to the customer name. */
.parent-summary-badges { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-2); }
.summary-badge {
    display: inline-flex;
    align-items: center;
    font-size: var(--text-sm);
    padding: 0.15rem var(--space-2);
    border-radius: 999px;
    border: 1px solid var(--line);
    background: var(--card);
    color: var(--ink);
    line-height: 1.4;
}
.summary-badge-warn { border-color: var(--danger, #c0392b); color: var(--danger, #c0392b); background: oklch(0.98 0.02 25); }
/* R-20260726-02: red notice on the «Αποδείξεις ημέρας» dashboard card — replaces
   the inline list of (often overdue) receipts with a count + link to the worklist. */
.due-receipts-notice {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3, 1rem);
    flex-wrap: wrap;
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--danger, #c0392b);
    border-left: 4px solid var(--danger, #c0392b);
    border-radius: var(--radius-sm, 6px);
    background: oklch(0.98 0.02 25);
    color: var(--danger, #c0392b);
}
.due-receipts-notice strong { font-size: var(--text-xl); }
.due-receipts-notice .btn-secondary { flex: 0 0 auto; }
.summary-badge-ok { border-color: var(--success, #2e8b57); color: var(--success, #2e8b57); background: oklch(0.98 0.03 150); }
.summary-badge-info { border-color: var(--teal-400, var(--line)); color: var(--teal-900, var(--ink)); background: oklch(0.97 0.02 200); }
.summary-badge-muted { color: var(--ink-soft); }

/* #619 «ώρα να ζητήσετε πληρωμή» cue. Amber for «ask when convenient»,
   the existing red -warn for «ask today» — a third hue would be a third
   thing to decode mid-conversation. The badge is a <button> because it
   opens the record-a-reminder panel, so the button chrome is reset back
   to the badge look. */
.summary-badge-attention {
    border-color: var(--warn, #b8860b);
    color: oklch(0.42 0.11 70);
    background: oklch(0.96 0.08 85);
}
button.summary-badge.payment-prompt {
    cursor: pointer;
    font: inherit;
    font-size: var(--text-sm);
}
button.summary-badge.payment-prompt:hover { filter: brightness(0.97); }
.payment-prompt-headline { font-weight: 600; margin: 0 0 var(--space-3, 1rem); }
.payment-prompt-facts { display: grid; grid-template-columns: auto 1fr; gap: var(--space-1) var(--space-4); margin: 0 0 var(--space-3, 1rem); }
.payment-prompt-facts dt { color: var(--ink-soft); }
.payment-prompt-facts dd { margin: 0; }
.payment-prompt-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.payment-prompt-editor { margin-top: var(--space-3, 1rem); }

/* Inline editing inside the parent detail card. The trigger keeps the
   look of plain text (so the dl reads naturally) but flips to an editor
   row on click. */
.inline-edit-list dt { color: var(--ink-soft); padding-top: var(--space-05); }
.inline-edit-list dd { padding-bottom: var(--space-05); }
/* Compact variant — label and value share one row, like a table.
   Used on /customers/{id}/children/{id}/edit so the info pane mirrors
   the linked-parents grid next to it. */
.inline-edit-list--compact {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    column-gap: var(--space-4);
    row-gap: var(--space-2);
    align-items: baseline;
    margin: 0;
}
.inline-edit-list--compact dt { padding-top: 0; }
.inline-edit-list--compact dd { margin: 0; padding-bottom: 0; }
.inline-edit-trigger {
    background: transparent;
    border: 1px dashed transparent;
    padding: 0 var(--space-1);
    margin: -1px -0.25rem;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: text;
    border-radius: 4px;
}
.inline-edit-trigger:hover,
.inline-edit-trigger:focus-visible {
    border-color: var(--line);
    background: oklch(0.98 0.01 215);
    outline: none;
}
.inline-edit-row { display: flex; flex-wrap: wrap; gap: var(--space-1); align-items: center; }
.inline-edit-row input,
.inline-edit-row select { padding: var(--space-1) var(--space-2); }
.inline-edit-row-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-1);
    align-items: center;
}
.inline-edit-row-grid > input { min-width: 0; }
.btn-icon {
    border: 1px solid var(--line);
    background: var(--card);
    padding: var(--space-05) var(--space-2);
    border-radius: 4px;
    cursor: pointer;
    font-size: var(--text-md);
    line-height: 1.2;
}
.btn-icon:hover:not(:disabled) { background: var(--teal-50); }
.btn-icon:disabled { opacity: 0.5; cursor: not-allowed; }
/* REQ-F29-NFR-001 — visible focus ring for icon-only buttons. */
.btn-icon:focus-visible {
    outline: var(--focus-ring-width) solid var(--focus-ring);
    outline-offset: 1px;
}

/* Παιδιά table: keep the inline-edit row's inputs compact so the
   six-column layout still fits inside the parent details card. */
.children-table .children-actions-col { width: 1%; white-space: nowrap; }
.children-table .children-row-editing input,
.children-table .children-row-editing select {
    width: 100%;
    min-width: 0;
    box-sizing: border-box;
    padding: var(--space-1) 0.4rem;
    font-size: var(--text-base);
}
.children-edit-actions { display: inline-flex; gap: var(--space-1); }
.children-row-error td { padding-top: 0; }

/* flex-wrap: «Προηγούμενη» + «Σελίδα x από y» + «Επόμενη» is 492 px, wider
   than any phone. Wrapping keeps both buttons reachable; on desktop the row
   fits and nothing moves. */
.pager { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; margin-top: var(--space-4); justify-content: flex-end; }

.login-card {
    max-width: 32rem;
    margin: var(--space-4) 0 var(--space-6);
    background: var(--card);
    padding: var(--space-5) var(--space-5) 1.75rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: 0 10px 30px -15px oklch(0.22 0.05 215 / 0.25);
}

/* =============================================================
   Toasts (REQ-F29-006). ToastHost renders a fixed, bottom-right
   .toast-stack; each .toast inside it is a chip (success/error/
   info level). Legacy per-page toasts still emit a bare
   `<div class="toast">` on pages not yet migrated — a bare .toast
   stays individually fixed (the default below); once inside a
   .toast-stack it becomes static and the stack owns positioning.
   ============================================================= */
.toast {
    position: fixed;
    bottom: 1.25rem;
    right: 1.25rem;
    z-index: 50;
    display: flex;
    align-items: center;
    gap: var(--space-2);
    background: var(--success);
    color: #fff;
    padding: var(--space-2) var(--space-4);
    border-radius: var(--radius-sm);
    box-shadow: 0 10px 25px -12px oklch(0.35 0.08 145 / 0.6);
    font-size: var(--text-md);
    animation: toast-in var(--transition-base);
}

.toast-stack {
    position: fixed;
    bottom: 1.25rem;
    right: 1.25rem;
    z-index: 50;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    max-width: min(24rem, calc(100vw - 2rem));
}
.toast-stack .toast {
    position: static;
    bottom: auto;
    right: auto;
}

.toast-success { background: var(--success); }
.toast-error   { background: var(--danger); box-shadow: 0 10px 25px -12px oklch(0.45 0.12 25 / 0.6); }
.toast-info    { background: var(--teal-800); color: #fff; box-shadow: 0 10px 25px -12px oklch(0.30 0.06 210 / 0.6); }

.toast-icon {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.25rem;
    height: 1.25rem;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.22);
    font-size: var(--text-sm);
    font-weight: 700;
    line-height: 1;
}
.toast-text { flex: 1 1 auto; }
.toast-action {
    flex: 0 0 auto;
    background: rgba(255, 255, 255, 0.22);
    border: 1px solid rgba(255, 255, 255, 0.45);
    color: inherit;
    font-size: var(--text-sm);
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
    padding: var(--space-1) var(--space-2);
    border-radius: 999px;
    min-width: auto;
    min-height: auto;
    white-space: nowrap;
}
.toast-action:hover { background: rgba(255, 255, 255, 0.34); }
.toast-close {
    flex: 0 0 auto;
    background: transparent;
    border: none;
    color: inherit;
    font-size: var(--text-xl);
    line-height: 1;
    cursor: pointer;
    padding: 0 var(--space-05);
    opacity: 0.8;
    min-width: auto;
    min-height: auto;
}
.toast-close:hover { opacity: 1; }

@keyframes toast-in {
    from { opacity: 0; transform: translateY(0.5rem); }
    to   { opacity: 1; transform: translateY(0); }
}

/* =============================================================
   Loading skeletons (REQ-F29-006). Token-built shimmer placeholders
   sized to the incoming layout — table-rows or card. Replaces bare
   «Φόρτωση…» text so a load reads as "content is coming".
   ============================================================= */
.skeleton { display: flex; flex-direction: column; gap: var(--space-2); }

.skeleton-row {
    display: flex;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-1);
    border-bottom: 1px solid var(--line);
}
.skeleton-row-head { border-bottom: 2px solid var(--line); }
.skeleton-cell {
    flex: 1 1 0;
    height: 0.9rem;
    border-radius: var(--radius-sm);
    background: var(--teal-100);
}
.skeleton-row-head .skeleton-cell { height: 0.75rem; opacity: 0.8; }

.skeleton-card-item {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    padding: var(--space-4);
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: var(--radius);
}
.skeleton-line {
    height: 0.85rem;
    border-radius: var(--radius-sm);
    background: var(--teal-100);
    width: 100%;
}
.skeleton-line-title { height: 1.15rem; width: 45%; }
.skeleton-line-short { width: 30%; }

/* Subtle left-to-right shimmer. Gated behind prefers-reduced-motion below. */
.shimmer {
    position: relative;
    overflow: hidden;
}
.shimmer::after {
    content: "";
    position: absolute;
    inset: 0;
    transform: translateX(-100%);
    background: linear-gradient(
        90deg,
        transparent,
        oklch(1 0 0 / 0.55),
        transparent);
    animation: skeleton-shimmer 1.4s ease-in-out infinite;
}

@keyframes skeleton-shimmer {
    100% { transform: translateX(100%); }
}

/* =============================================================
   Empty states (REQ-F29-006). Shared block: icon + one Greek
   sentence + optional primary CTA. Replaces ad-hoc muted <p>.
   ============================================================= */
.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-3);
    padding: var(--space-7) var(--space-4);
    text-align: center;
    color: var(--ink-soft);
}
.empty-state-icon { font-size: var(--text-5xl); line-height: 1; opacity: 0.85; }
.empty-state-message { margin: 0; font-size: var(--text-md); max-width: 32rem; }
.empty-state-cta { margin-top: var(--space-1); }

/* =============================================================
   Modal dialog (#142 slot suggester). Minimal backdrop + card;
   no third-party lib per CLAUDE.md. Click-outside closes via the
   component's OnClose handler.
   ============================================================= */
/* =============================================================
   #749 part 3 — COMPACTNESS, and it lives here rather than in the
   45 call sites.

   v0.91.0 set the target in one line: «συμπαγές παράθυρο πάνω από τη
   λίστα, χωρίς scroll». Four rules deliver it, and each one is
   asserted as GEOMETRY in ModalCompactnessTests — measured in a real
   browser, not eyeballed and not snapshotted:

     R1  the page never scrolls while a dialog is open
     R2  the card always fits the viewport; the BODY is the only scroller
     R3  header and footer are pinned, so the primary action is always
         reachable without scrolling
     R4  short fields pair into two columns; long ones keep the full row

   Two of the four were measurably broken before this block existed.
   See each rule for its number.
   ============================================================= */

/* R1 — the page never scrolls behind an open dialog.
   Until #749 part 3 this lock existed ONLY inside @media (max-width:
   1024px), so on every desktop the list behind the dialog scrolled
   under the wheel while the dialog stayed put — measured at 1 031 px of
   travel, under all four dialogs the gate covers. :has() is the only
   lever that reaches <html> from a component that renders no JS, and it
   is already how the drawer does it. */
html:has(.modal-backdrop) { overflow: hidden; }

.modal-backdrop {
    position: fixed;
    inset: 0;
    background: oklch(0.22 0.05 215 / 0.45);
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding: var(--space-8) var(--space-4) var(--space-4);
    z-index: 100;
}
/* R2 — the height budget is the backdrop's own content box, not a
   number the card has to know.
   It used to be `calc(100vh - 5rem)`, i.e. the backdrop's padding
   restated as a constant — and restating it is exactly how it drifts:
   `.record-detail-modal` and `.new-receipt-modal` each carried their
   own `calc(100vh - 4rem)`. By arithmetic that is one rem MORE than the
   backdrop leaves (64 px top + 16 px bottom), so at full height those
   two cards ran to the very bottom edge of the screen with no gap —
   derived from the padding values, not measured in a browser, because
   neither variant is on the dialog gate. `100%` resolves against the
   flex container's content box, so a variant that widens the card can
   no longer get its height wrong, and the mobile breakpoint's different
   padding is followed for free. */
.modal-card {
    background: var(--card);
    border-radius: var(--radius);
    /* UI RULE (Elias, 2026-08-19): EVERY pop-up and dialog carries a border.
       A shadow alone stops separating the dialog from the page the moment the
       page behind it is pale or the screen is bright — and this product is
       used on whatever monitor the front desk has. The border is the thing
       that always states «this is a separate surface»; the shadow only
       suggests it. Set here, on the ONE shared card, so no dialog can be
       built without it. */
    border: 1px solid var(--line);
    box-shadow: 0 30px 60px -25px oklch(0.22 0.05 215 / 0.55);
    width: min(48rem, 100%);
    max-height: 100%;
    display: flex;
    flex-direction: column;
    color: var(--ink);
}
/* The header doubles as the window's drag handle (#575, js/modal-drag.js):
   clinicians need to shove a dialog aside to read the row or calendar block
   behind it instead of closing and reopening it.

   `cursor: move` is the whole affordance — a handle with no pointer feedback
   reads as broken. `user-select: none` stops the title being highlighted as
   the pointer sweeps across it, and `touch-action: none` is what makes the
   drag work on the iPad at all: without it the browser claims the gesture as
   a pan before any pointermove reaches us. Both are scoped to the header, so
   selecting text and scrolling in `.modal-body` are untouched — which is the
   header-only half of the requirement, expressed in CSS as well as in JS. */
.modal-header {
    display: flex;
    align-items: center;
    /* 🔴 flex-start, NOT space-between. Elias, 2026-08-16: «The ⓘ should be
       at the end of the title with one char space from the title.» It was
       sitting far right because space-between pushed everything after the
       <h2> to the opposite edge — the ⓘ was beside the CLOSE button, not
       beside the title it describes. The × is pushed right by its own auto
       margin below, which keeps the title and its ⓘ together as one group. */
    justify-content: flex-start;
    padding: var(--space-4) 1.25rem;
    border-bottom: 1px solid var(--line);
    cursor: move;
    touch-action: none;
    user-select: none;
    -webkit-user-select: none;
}
.modal-header h2 { margin: 0; font-size: var(--text-xl); }
/* One character of space, as asked — a rem value would drift away from the
   title's own type size, and this badge is read as part of the heading. */
.modal-header .audit-tooltip { margin-left: 1ch; }
/* The × is the only thing that belongs on the far side. */
.modal-header .modal-close { margin-left: auto; }
/* Controls inside the header keep their own pointer semantics; the × is a
   button, not a grab point, and modal-drag.js declines to start a drag on it. */
.modal-header button,
.modal-header a,
.modal-header input,
.modal-header select { cursor: pointer; }
.modal-card.is-modal-dragging { user-select: none; }
.modal-card.is-modal-dragging .modal-header { cursor: grabbing; }
.modal-close {
    background: transparent;
    border: none;
    font-size: var(--text-xl);
    line-height: 1;
    color: var(--ink-soft);
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-sm);
}
.modal-close:hover { color: var(--ink); background: var(--teal-100); }
/* R2 / R3 — the body is the ONE scroller and the chrome is pinned.
   `min-height: 0` is what actually lets it scroll: a flex item's
   automatic minimum size is its content, so without this a long body
   pushes the card past its own max-height instead of scrolling inside
   it. `flex: 0 0 auto` on the header and the footer says the action
   row is never the thing that gives way when the body is too tall —
   «Αποθήκευση» stays on screen at any content length. */
.modal-body {
    padding: var(--space-4) 1.25rem;
    overflow: auto;
    flex: 1 1 auto;
    min-height: 0;
}
.modal-header { flex: 0 0 auto; }
.modal-footer {
    padding: var(--space-3) 1.25rem;
    border-top: 1px solid var(--line);
    display: flex;
    justify-content: flex-end;
    gap: var(--space-2);
    flex: 0 0 auto;
}

/* R3, second half — the action row a dialog puts INSIDE its body.
   MEASURED, and it is the reason this rule exists rather than a
   tidiness argument: with 200 rows in «Νέα συνεδρία», «Αποθήκευση»
   sat at y=893.6 in an 800 px viewport — 109 px below the bottom of
   the screen, reachable only by scrolling the body to its end. Pinning
   `.modal-footer` does nothing for it, because that dialog has no
   `.modal-footer`: it passes HideDefaultFooter and puts its buttons in
   the form, so the shell's footer slot is empty and the buttons are
   part of what scrolls.

   This is the treatment `.form-actions` already gets under 640 px, for
   exactly the same reason: a short scrollport whose save button would
   otherwise be somewhere below the fold. A dialog body IS a short
   scrollport, at every width. So the rule is the phone rule, applied
   where the same condition holds.

   Scoped by structure to the two shapes the dialogs actually use —
   directly in the body, or inside the `<form>` that `<EditForm>`
   renders. The chain starts at `.modal-card > .modal-body` so that a
   dialog opened OVER another dialog (a confirm over a form) keeps its
   own chrome untouched: its footer is a child of its own card, not of
   anyone's body. */
.modal-card > .modal-body > .form-actions,
.modal-card > .modal-body > form > .form-actions,
.modal-card > .modal-body > .modal-footer,
.modal-card > .modal-body > form > .modal-footer {
    position: sticky;
    bottom: 0;
    z-index: 1;
    background: var(--card);
    padding-top: var(--space-3);
    border-top: 1px solid var(--line);
}

/* R4 — two columns for short fields, the whole row for long ones.
   #655 proved this on two full-page forms (`.form-columns`); a dialog
   is where it matters most, because a dialog cannot grow past R2's
   height budget — every row it saves is a row the operator does not
   scroll to reach «Αποθήκευση».

   THREE things make it safe to switch on for all 45 dialogs at once
   rather than annotating them one at a time:

   1. It only engages where there is something to pair. The `:has(>
      .form-field ~ .form-field)` guard means a dialog whose body is a
      paragraph, a `<dl>`, a table or an `<iframe>` — most of them —
      stays in ordinary block layout, byte for byte. A confirm dialog
      is not quietly turned into a grid container, and no free-content
      dialog pays the margin-collapsing difference between block and
      grid.
   2. It reaches both shapes the dialogs actually use. Fields sit
      either directly in the body or one level down inside the `<form>`
      that `<EditForm>` renders; the child-combinator rule that only
      handles the first would have silently done nothing to
      NewSessionModal, NewReceiptModal and ReportDialog — the three
      biggest forms in the app.
   3. «Long» is detected structurally, not declared. A field carrying a
      textarea, a table or a nested fieldset takes the whole row on its
      own, so nobody has to remember `.form-field-wide` (which still
      works, and still wins).

   `BodyColumns="false"` on ModalShell opts a dialog out. */
.modal-body--columns:has(> .form-field ~ .form-field),
.modal-body--columns > form:has(> .form-field ~ .form-field) {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
    gap: 0 var(--space-4);
    align-items: start;
}
/* Anything that is not a short field spans the row — including the
   `<form>` itself, the validation summary and the action row. */
.modal-body--columns > :not(.form-field),
.modal-body--columns > form > :not(.form-field),
.modal-body--columns .form-field-wide,
.modal-body--columns .form-field:has(textarea),
.modal-body--columns .form-field:has(table),
.modal-body--columns .form-field:has(fieldset) {
    grid-column: 1 / -1;
}
/* The row rhythm moves to the grid; the field's own full margin would
   double-space every pair. Same trade as `.form-columns` (#655). */
.modal-body--columns > .form-field,
.modal-body--columns > form > .form-field {
    margin-bottom: var(--space-3);
}

.blazor-error-ui {
    background: #FFF8D8;
    border-top: 1px solid #FFE69C;
    color: #664D03;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.6rem 3.75rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}
.blazor-error-ui .dismiss { cursor: pointer; position: absolute; right: 0.75rem; top: 0.5rem; }

.address-autocomplete { position: relative; }
.address-autocomplete__hint { font-size: var(--text-base); color: var(--ink-soft); margin: var(--space-1) 0 0; }
/* #611 — the ΤΚ-first instruction. Sits between the label and the input so
   it is read before the user starts typing, not after they have already
   led with a street name. Muted but not tiny: it is functional advice. */
.address-autocomplete__guide { font-size: var(--text-base); color: var(--ink-soft); margin: var(--space-05) 0 var(--space-2); }
.address-autocomplete__list {
    list-style: none;
    margin: var(--space-1) 0 0;
    padding: var(--space-1) 0;
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
    max-height: 14rem;
    overflow-y: auto;
}
.address-autocomplete__item {
    width: 100%;
    text-align: left;
    background: transparent;
    border: 0;
    padding: var(--space-2) var(--space-3);
    font-size: var(--text-md);
    cursor: pointer;
    color: var(--ink);
}
.address-autocomplete__item:hover,
.address-autocomplete__item:focus { background: var(--teal-50); outline: none; }
.address-autocomplete__empty { padding: var(--space-2) var(--space-3); font-size: var(--text-md); color: var(--ink-soft); }

/* F15 user management / audit */
.badge {
    display: inline-block;
    padding: 0.15rem var(--space-2);
    border-radius: 999px;
    font-size: var(--text-sm);
    font-weight: 600;
    letter-spacing: 0.01em;
}
.badge-active { background: oklch(0.93 0.07 145); color: oklch(0.35 0.08 145); }
.badge-inactive { background: oklch(0.94 0.02 255); color: var(--ink-soft); }
/* #904 — «Εκκρεμής»: invited, has not accepted yet. Its own pill because it
   is neither of the two above: the person is not working (not green) and
   nobody shut them out (not the grey of «Ανενεργός», which is what this
   state was painted as until the two were told apart in the data). Blue-
   violet, distinct from the amber «Σε αναμονή» membership pill, which is a
   different concept on a different entity. */
.badge-pending { background: oklch(0.93 0.05 275); color: oklch(0.40 0.11 275); }
/* REQ-F01-019: «Σε αναμονή» membership status — an amber pill that
   distinguishes a waiting child from active/inactive at a glance. */
.badge-waiting { background: oklch(0.94 0.08 85); color: oklch(0.42 0.11 70); }
/* #874 — «Ελλιπή στοιχεία»: the record is incomplete (e.g. no ΑΜΚΑ on
   file yet), NOT an error the operator just caused — a standing red pill
   beside the child's name wherever it is shown, distinct from the amber
   «Σε αναμονή» membership badge above. */
.badge-incomplete { background: oklch(0.94 0.06 25); color: oklch(0.4 0.16 25); margin-left: var(--space-2); }
/* #874 — a field shown as incomplete on the «Στοιχεία» tab (e.g. no ΑΜΚΑ on
   file). Red like .form-error so the operator reads it the same way at a
   glance, but not the class itself: this is a standing state of the
   record, not a validation failure from something the operator just typed. */
.child-field-incomplete { color: var(--red-600, #b91c1c); font-style: italic; }
/* Status badge that doubles as a click-to-toggle button on the
   /users list. Strips the default <button> chrome so it still
   reads as a pill while hinting clickability with a hover ring. */
.badge.badge-clickable {
    border: 0;
    cursor: pointer;
    font: inherit;
    font-size: var(--text-sm);
    font-weight: 600;
    letter-spacing: 0.01em;
}
.badge.badge-clickable:hover:not(:disabled),
.badge.badge-clickable:focus-visible {
    outline: 2px solid var(--teal-700);
    outline-offset: 1px;
}
.badge.badge-clickable:disabled { opacity: 0.6; cursor: not-allowed; }
/* #829 / Elias 2026-08-12 — «Χωρίς συνδεδεμένο πρόσωπο». Amber like the
   other «needs attention, not broken» pills, never red: a child whose
   paperwork is unfinished is a normal intake state, not an error somebody
   made. The 1px border is what keeps the pill visible when the colour is
   not — greyscale print, forced-colours mode, a washed-out screen — and
   REQ-F29-007 forbids colour from being the only signal, so the words say
   it too. white-space: nowrap because the tag sits in a table cell next
   to names and a three-word label broken over two lines stops reading as
   one tag. */
.badge-no-linked-person {
    background: #FFF3CD;
    color: #664D03;
    border: 1px solid #E0A800;
    white-space: nowrap;
}

/* REQ-F18-006: warn badge for cross-branch delivery on sessions. */
.badge-warn {
    background: #FFF3CD;
    color: #664D03;
    border: 1px solid #FFE69C;
    margin-left: var(--space-1);
}

/* F02 — invoice lifecycle pill rendered next to the page title on the
   invoice detail view so admins scan Draft / Issued / Cancelled at a
   glance. Font-size is deliberately smaller than the h1 and
   vertical-align keeps it centered on the cap-height line. */
.status-pill {
    display: inline-block;
    margin-left: var(--space-3);
    padding: 0.2rem var(--space-3);
    border-radius: 999px;
    font-size: var(--text-md);
    font-weight: 600;
    letter-spacing: 0.01em;
    vertical-align: middle;
    border: 1px solid transparent;
}
.status-pill-draft {
    background: oklch(0.94 0.02 255);
    color: var(--ink-soft);
    border-color: oklch(0.88 0.02 255);
}
.status-pill-issued {
    background: oklch(0.93 0.07 145);
    color: oklch(0.35 0.08 145);
    border-color: oklch(0.85 0.09 145);
}
.status-pill-cancelled {
    background: oklch(0.94 0.06 25);
    color: oklch(0.38 0.15 25);
    border-color: oklch(0.85 0.1 25);
}
.status-pill-partiallypaid {
    background: oklch(0.95 0.08 80);
    color: oklch(0.40 0.13 80);
    border-color: oklch(0.85 0.10 80);
}
.status-pill-paid {
    background: oklch(0.93 0.10 145);
    color: oklch(0.32 0.10 145);
    border-color: oklch(0.82 0.12 145);
}

/* Section subheads for grouped lists (e.g. rooms by branch). */
.section-subhead {
    font-size: var(--text-xl);
    margin: 1.25rem 0 var(--space-2) 0;
    color: var(--ink-soft);
}

/* REQ-F18-006: diagnostic callout shown on cross-branch session detail. */
.callout {
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-sm);
    margin-bottom: var(--space-4);
    font-size: var(--text-lg);
}
.callout-warn {
    background: #FFF8D8;
    border: 1px solid #FFE69C;
    color: #664D03;
}

.btn-sm { padding: var(--space-1) 0.55rem; font-size: var(--text-base); }
.btn-sm + .btn-sm { margin-left: var(--space-1); }

/* Calendar grid view: therapist × weekday (or room × weekday) matrix. */
.calendar-view-toggle {
    display: inline-flex;
    /* 335 px of segmented control (Ημέρα/Εβδομάδα/Μήνας + Σήμερα + caret)
       does not fit a 288 px phone column. Wrapping keeps every segment
       reachable instead of pushing the page sideways; above the phone
       widths it fits on one line and this never engages. */
    flex-wrap: wrap;
    max-width: 100%;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    overflow: hidden;
    background: var(--card);
}
.calendar-view-toggle .btn-toggle {
    background: transparent;
    border: 0;
    padding: var(--space-2) 0.85rem;
    font-size: var(--text-md);
    font-weight: 600;
    color: var(--ink);
    cursor: pointer;
}
.calendar-view-toggle .btn-toggle + .btn-toggle { border-left: 1px solid var(--line); }
.calendar-view-toggle .btn-toggle:hover { background: var(--cream); }
.calendar-view-toggle .btn-toggle.is-active {
    background: var(--teal-800);
    color: #fff;
}
/* Outlook-style caret that reveals the "jump to date" picker. Narrower
   than the text buttons so it reads as an adornment on «Σήμερα». */
.calendar-view-toggle .btn-toggle-caret {
    padding-left: var(--space-2);
    padding-right: var(--space-2);
    font-size: var(--text-xs);
    /* a11y (axe target-size): guarantee a ≥24×24px touch target. */
    min-width: 1.75rem;
    min-height: 1.75rem;
}
.calendar-view-toggle .btn-toggle-caret[aria-expanded="true"] {
    background: var(--cream);
}
/* Inline native date picker for the "jump to date" navigator. Native
   <input type="date"> keeps the browser's keyboard-accessible calendar. */
.calendar-datepicker {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-1) var(--space-2);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--card);
}
.calendar-datepicker-label {
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--ink-soft);
}
.calendar-datepicker input[type="date"] {
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    padding: var(--space-1) var(--space-2);
    font: inherit;
    color: var(--ink);
    background: var(--card);
}

.calendar-week-grid {
    display: grid;
    grid-template-columns: minmax(10rem, 12rem) repeat(7, minmax(0, 1fr));
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--card);
    overflow: hidden;
}
.calendar-week-grid-head {
    display: contents;
}
.calendar-week-grid-corner,
.calendar-week-grid-daycol {
    background: var(--teal-50);
    color: var(--teal-900);
    padding: var(--space-2) 0.6rem;
    font-weight: 700;
    font-size: var(--text-md);
    border-bottom: 1px solid var(--line);
}
.calendar-week-grid-daycol {
    display: flex;
    gap: var(--space-1);
    align-items: baseline;
    border-left: 1px solid var(--line);
}
.calendar-week-grid-daycol span {
    color: var(--ink-soft);
    font-weight: 500;
    font-size: var(--text-base);
}
.calendar-week-grid-row { display: contents; }
.calendar-week-grid-rowlabel {
    padding: 0.55rem 0.7rem;
    font-weight: 600;
    font-size: var(--text-md);
    color: var(--ink);
    background: var(--card);
    border-top: 1px solid var(--line);
    word-break: break-word;
}
.calendar-week-grid-cell {
    padding: var(--space-1);
    border-top: 1px solid var(--line);
    border-left: 1px solid var(--line);
    min-height: 3.5rem;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}
.calendar-week-grid-block {
    display: flex;
    flex-direction: column;
    gap: var(--space-05);
    padding: var(--space-1) var(--space-2);
    background: var(--cream);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    color: var(--ink);
    font-size: var(--text-sm);
    text-decoration: none;
    line-height: 1.15;
}
.calendar-week-grid-block:hover { background: var(--teal-50); }
.calendar-week-grid-block .time { font-weight: 700; font-variant-numeric: tabular-nums; }
.calendar-week-grid-block .initials {
    color: var(--ink-soft);
    font-size: var(--text-xs);
    letter-spacing: 0.02em;
}
.calendar-week-grid-block.is-cancelled { opacity: 0.55; text-decoration: line-through; }
.calendar-week-grid-block.is-noshow { background: #FFF4EE; border-color: #F3C2A4; }
.calendar-week-grid-block.is-completed { background: #EAF6EE; border-color: #B9DDC4; }
.calendar-week-grid-block.is-pending {
    background: #FFFCE8;
    border-color: #E6CE5C;
    box-shadow: 0 0 0 2px #F1E08A inset;
}
/* Status blocks hard-code light background tints, so pin the text to a
   stable dark ink — otherwise the inherited var(--ink) flips to white
   in dark mode and the labels become invisible on the light fills. */
.calendar-week-grid-block.is-noshow,
.calendar-week-grid-block.is-completed,
.calendar-week-grid-block.is-pending {
    color: oklch(0.22 0.03 215);
}
.calendar-week-grid-block.is-noshow .initials,
.calendar-week-grid-block.is-completed .initials,
.calendar-week-grid-block.is-pending .initials {
    color: oklch(0.44 0.04 215);
}
.calendar-week-grid-block.is-conflict {
    border-color: #C63030;
    box-shadow: 0 0 0 2px #F3A0A0 inset;
}
.calendar-week-grid-block[draggable="true"] { cursor: grab; }
.calendar-week-grid-block[draggable="true"]:active { cursor: grabbing; }
.calendar-week-grid-cell.is-drop-target {
    background: #FFFCE8;
    outline: 2px dashed #E6CE5C;
    outline-offset: -2px;
}
.calendar-pending-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    margin: var(--space-2) 0 var(--space-3);
    background: #FFFCE8;
    border: 1px solid #E6CE5C;
    border-radius: 6px;
    color: oklch(0.22 0.03 215);
}
.calendar-pending-bar .form-error { margin: 0; }
.calendar-week-grid-empty {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.6rem;
    height: 1.6rem;
    border-radius: 999px;
    color: var(--ink-soft);
    text-decoration: none;
    font-weight: 700;
    opacity: 0;
    transition: opacity 120ms ease, background-color 120ms ease;
}
.calendar-week-grid-cell:hover .calendar-week-grid-empty { opacity: 0.8; }
.calendar-week-grid-empty:hover {
    background: var(--teal-50);
    opacity: 1;
}

@media (max-width: 768px) {
    .calendar-week-grid {
        grid-template-columns: minmax(7rem, 9rem) repeat(7, minmax(3.5rem, 1fr));
        overflow-x: auto;
    }
    .calendar-week-grid-block { font-size: var(--text-xs); }
}

/* #147 slice 6: hour grid — rows are 30-minute buckets. */
.calendar-hour-grid {
    display: grid;
    grid-template-columns: minmax(3.5rem, 4rem) repeat(7, minmax(5rem, 1fr));
    border: 1px solid var(--line);
    border-radius: 6px;
    background: var(--card);
    overflow: hidden;
}
.calendar-hour-grid-head,
.calendar-hour-grid-row {
    display: contents;
}
.calendar-hour-grid-corner,
.calendar-hour-grid-daycol {
    padding: 0.4rem var(--space-2);
    background: var(--teal-50);
    border-bottom: 1px solid var(--line);
    border-right: 1px solid var(--line);
    font-size: var(--text-sm);
    color: var(--ink-soft);
}
.calendar-hour-grid-daycol strong { display: block; color: var(--ink); }
.calendar-hour-grid-timelabel {
    padding: 0.2rem var(--space-2) 0;
    font-size: var(--text-xs);
    color: var(--ink-soft);
    border-right: 1px solid var(--line);
    min-height: 2rem;
    font-variant-numeric: tabular-nums;
}
.calendar-hour-grid-cell {
    position: relative;
    min-height: 2rem;
    border-top: 1px solid var(--line);
    border-right: 1px solid var(--line);
    padding: 0;
}
.calendar-hour-grid-cell.is-drop-target {
    background: #FFFCE8;
    outline: 2px dashed #E6CE5C;
    outline-offset: -2px;
    z-index: 2;
}
.calendar-hour-grid .calendar-week-grid-block {
    /* Hour-mode blocks absolutely position themselves inside
       calendar-hour-grid-cell; reuse the is-pending / is-cancelled
       / is-noshow / is-completed decorations from the week grid
       block so colours stay consistent across modes. */
    display: flex;
    flex-direction: column;
    gap: var(--space-05);
    padding: var(--space-05) var(--space-1);
    font-size: var(--text-xs);
    text-decoration: none;
    color: var(--ink);
}
@media (max-width: 768px) {
    .calendar-hour-grid {
        grid-template-columns: minmax(3rem, 3.5rem) repeat(7, minmax(3.5rem, 1fr));
        overflow-x: auto;
    }
}

/* APP-001 day grid: rows = therapists/rooms, cols = 30-min slots,
   sections grouped by branch / Online / OffSite. The grid template is
   set inline on the wrapping element because the column count is
   data-driven (slot range auto-fits the day's earliest/latest
   sessions). */
.calendar-day-grid-wrap {
    overflow-x: auto;
    /* Edge-fade affordance for horizontal scroll: a soft shadow on the
       far edges hints that more content sits beyond the viewport on
       iPad / mobile. The "local" gradients sit on the inner card so
       they scroll with the content; the "scroll" shadows stay pinned
       to the wrapper edges and shrink to zero when the grid is
       fully revealed (classic "scroll shadow" pattern). */
    background:
        linear-gradient(to right, var(--card), transparent 40px) 0 0 / 100% 100% no-repeat local,
        linear-gradient(to left, var(--card), transparent 40px) 100% 0 / 100% 100% no-repeat local,
        linear-gradient(to right, rgba(0, 0, 0, 0.08), transparent 14px) 0 0 / 100% 100% no-repeat scroll,
        linear-gradient(to left, rgba(0, 0, 0, 0.08), transparent 14px) 100% 0 / 100% 100% no-repeat scroll;
}
.calendar-day-grid {
    display: grid;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--card);
    /* min-width keeps the grid horizontally scrollable instead of
       collapsing slot columns into unreadable widths. */
    min-width: max-content;
}
.calendar-day-grid-head { display: contents; }
.calendar-day-grid-corner,
.calendar-day-grid-slothead {
    background: var(--teal-50);
    color: var(--teal-900);
    padding: 0.4rem var(--space-2);
    font-weight: 700;
    font-size: var(--text-base);
    border-bottom: 1px solid var(--line);
    font-variant-numeric: tabular-nums;
}
.calendar-day-grid-slothead { border-left: 1px solid var(--line); text-align: center; }
.calendar-day-grid-section {
    grid-column: 1 / -1;
    padding: var(--space-2) var(--space-3);
    background: var(--cream);
    color: var(--ink);
    font-size: var(--text-md);
    border-top: 1px solid var(--line);
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
}
.calendar-day-grid-section .muted { font-size: var(--text-sm); }
.calendar-day-grid-row { display: contents; }
.calendar-day-grid-rowlabel {
    padding: 0.55rem 0.7rem;
    font-weight: 600;
    font-size: var(--text-base);
    color: var(--ink);
    background: var(--card);
    border-top: 1px solid var(--line);
    word-break: break-word;
}
.calendar-day-grid-cell {
    border-top: 1px solid var(--line);
    border-left: 1px solid var(--line);
    min-height: 3rem;
    background: var(--card);
    padding: var(--space-1);
    display: flex;
    align-items: stretch;
}
/* APP-015 in-hours: slot inside a therapist's working window. Light
   green tint borrowed from is-completed so the visual language stays
   consistent (green = good / available). The cell is still tappable
   when ShowEmptyAddLinks is true. */
.calendar-day-grid-cell.is-available {
    background: #EAF6EE;
}
/* APP-015 out-of-hours: slot outside any working window for this
   therapist + branch. Subtle dark fill makes the cell look
   "blocked" without overpowering the grid. */
.calendar-day-grid-cell.is-out-of-hours {
    background: oklch(0.94 0.01 215);
    color: var(--ink-soft);
}
/* REQ-F04-010 holiday banner sits on top of the day-grid wrap. Amber
   palette so it reads as advisory ("the day is closed, sessions on it
   are blocked at validation") and stays distinct from connectivity
   (red) and licence-utilisation (also amber but at the page header,
   not above the grid). */
.calendar-day-grid-holiday-banner {
    margin: 0 0 var(--space-2) 0;
    padding: var(--space-2) var(--space-3);
    border: 1px solid oklch(0.78 0.13 80);
    background: oklch(0.96 0.06 90);
    color: oklch(0.32 0.07 60);
    border-radius: 0.375rem;
    font-size: var(--text-lg);
}
.calendar-day-grid-marker {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--ink-soft);
    font-weight: 600;
    font-size: var(--text-base);
    user-select: none;
}
.calendar-day-grid-marker-available {
    color: oklch(0.40 0.08 145);
    font-size: var(--text-xs);
    letter-spacing: 0.02em;
    text-transform: uppercase;
}
.calendar-day-grid-empty {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--ink-soft);
    text-decoration: none;
    font-weight: 700;
    /* Touch devices (no hover, coarse pointer) get a faint always-on
       "+" so the affordance is reachable on iPad / phones without
       requiring a "hover" tap. Pointer devices keep the quieter
       hover-to-reveal behaviour. */
    opacity: 0.4;
    transition: opacity 120ms ease, background-color 120ms ease;
    border-radius: var(--radius-sm);
}
@media (hover: hover) and (pointer: fine) {
    .calendar-day-grid-empty { opacity: 0; }
    .calendar-day-grid-cell:hover .calendar-day-grid-empty { opacity: 0.8; }
}
.calendar-day-grid-empty:hover,
.calendar-day-grid-empty:focus-visible {
    background: var(--teal-50);
    opacity: 1;
}
.calendar-day-grid-block {
    display: flex;
    flex-direction: column;
    gap: var(--space-05);
    padding: var(--space-1) var(--space-2);
    margin: 0.05rem;
    background: var(--cream);
    border: 1px solid var(--line);
    border-top: 1px solid var(--line);
    border-left: 1px solid var(--line);
    border-radius: var(--radius-sm);
    color: var(--ink);
    font-size: var(--text-sm);
    text-decoration: none;
    line-height: 1.15;
    /* Block lives directly inside the grid as its own gridcell, so it
       carries the same border treatment as the empty cells to keep
       column lines consistent. */
}
.calendar-day-grid-block:hover { background: var(--teal-50); }
.calendar-day-grid-block .time {
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}
.calendar-day-grid-block .child {
    font-size: var(--text-base);
    color: var(--ink);
}
.calendar-day-grid-block .muted {
    color: var(--ink-soft);
    font-size: var(--text-xs);
}
.calendar-day-grid-block.is-cancelled { opacity: 0.55; text-decoration: line-through; }
.calendar-day-grid-block.is-noshow { background: #FFF4EE; border-color: #F3C2A4; }
.calendar-day-grid-block.is-completed { background: #EAF6EE; border-color: #B9DDC4; }
.calendar-day-grid-block.is-noshow,
.calendar-day-grid-block.is-completed {
    color: oklch(0.22 0.03 215);
}
.calendar-day-grid-block.is-noshow .muted,
.calendar-day-grid-block.is-completed .muted {
    color: oklch(0.44 0.04 215);
}

/* APP-001 + user request: when the displayed date is today, render
   a transparent vertical bar at the current local time, spanning all
   rows. The component sets grid-column to the "now" slot and
   --now-frac (0..1) for sub-slot precision; we draw the line via a
   pseudo-element positioned at calc(--now-frac * 100%) inside that
   column. pointer-events: none keeps the underlying session blocks
   clickable. z-index 4 puts the bar above blocks (z-index 1) and
   section headers (no explicit z-index) but below modals. */
.calendar-day-grid-now {
    position: relative;
    pointer-events: none;
    z-index: 4;
}
.calendar-day-grid-now::before {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    left: calc(var(--now-frac, 0) * 100%);
    width: 2px;
    background: rgba(220, 38, 38, 0.55);
    box-shadow: 0 0 4px rgba(220, 38, 38, 0.35);
}
.calendar-day-grid-now::after {
    /* Small dot at the top so the bar reads as "current time"
       even when the column underneath has no session blocks
       (visual cue mirroring Google Calendar's pattern). */
    content: '';
    position: absolute;
    top: 0.1rem;
    left: calc(var(--now-frac, 0) * 100% - 4px);
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: rgba(220, 38, 38, 0.75);
}

.calendar-day-datepicker {
    padding: var(--space-2) var(--space-2);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--card);
    color: var(--ink);
    font: inherit;
}

@media (max-width: 768px) {
    .calendar-day-grid-block { font-size: var(--text-xs); }
    .calendar-day-grid-rowlabel { font-size: var(--text-sm); }
    .calendar-day-grid-slothead { font-size: var(--text-xs); padding: var(--space-1) var(--space-1); }
}

.form-warning {
    background: #FFF8D8;
    border: 1px solid #FFE69C;
    color: #664D03;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    font-size: var(--text-md);
    margin-top: var(--space-4);
}

.invite-success { display: flex; flex-direction: column; gap: var(--space-2); }
.invite-success input[type="text"] { font-family: monospace; }

/* #889 — .audit-tabs / .audit-tab retired: Pages/Audit/List.razor now
   renders through the shared TabStrip component
   (src/Rheasys.Therapy.UI/Components/TabStrip.razor.css). */
/* The per-view explanatory paragraph, now that the page header is shared. */
.audit-view-intro {
    margin-top: 0;
    margin-bottom: var(--space-3);
}

.audit-filters {
    display: grid;
    /* min(12rem, 100%) is the standard auto-fit guard: without it the 12 rem
       floor survives on a viewport narrower than 12 rem. The min-width:0 on
       the children stops the «Εφαρμογή»+«Καθαρισμός» row's min-content from
       re-inflating the track (it was pushing /audit 62 px wide at 430 px). */
    grid-template-columns: repeat(auto-fit, minmax(min(12rem, 100%), 1fr));
    gap: var(--space-3);
    margin-bottom: var(--space-4);
    align-items: end;
}
.audit-filters > * { min-width: 0; }
.audit-filters-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    align-items: flex-end;
}

/* R-20260727-05 — the location that goes with an IP address, rendered as
   secondary text directly under the IP in both «Ιστορικό ενεργειών» and
   «Ιστορικό συνδέσεων». Smaller than the row text so the address itself stays
   the primary value of the column; nothing is rendered at all when the
   location could not be resolved. */
.audit-ip-location {
    font-size: var(--text-xs);
    line-height: 1.3;
}

/* R-20260721-06 — the «Μεταβολή» one-row summary names the field and its
   old→new value; keep it to a single line with an ellipsis so a long value
   can never blow up the row height. The full list lives in the popup. */
.audit-change-summary {
    display: inline-block;
    max-width: 32rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    vertical-align: bottom;
}

/* R-20260728-05 — the audit detail modal shows names, not UUIDs. An id
   that could not be resolved still prints verbatim (traceability), just
   visually demoted; the verbatim payload lives in a collapsed
   «Τεχνικά στοιχεία» block underneath. */
.audit-value-id {
    font-size: var(--text-sm);
    color: var(--ink-soft);
    word-break: break-all;
}

.audit-raw {
    font-size: var(--text-base);
}

.audit-raw > summary {
    cursor: pointer;
    color: var(--ink-soft);
}

.audit-raw code {
    word-break: break-all;
}

/* F14 Incident management — red accent for the data-breach dashboard
   reuses the existing --danger token (same oklch as .form-error border). */
.incident-breach-title {
    color: var(--danger);
    border-left: 4px solid var(--danger);
    padding-left: var(--space-2);
}

/* =============================================================
   Layout primitives (REQ #87). Small opt-in utility classes —
   adding one to a Razor component should be enough to get a
   reliable, responsive layout without a framework. Borrowed from
   every-layout.dev patterns; each primitive is ≤ 5 lines.
   ============================================================= */

/* Vertical rhythm. Children get consistent gap; override the
   `--stack-gap` custom property on the parent to tune. */
.stack {
    display: flex;
    flex-direction: column;
    gap: var(--stack-gap, var(--space-4));
}

/* Horizontal group that wraps when it runs out of room. */
.cluster {
    display: flex;
    flex-wrap: wrap;
    gap: var(--cluster-gap, var(--space-3));
    align-items: center;
}

/* Content container with a sensible max width. */
.container {
    width: 100%;
    max-width: 72rem;
    margin-inline: auto;
    padding-inline: var(--space-4);
}

/* Sidebar layout: fixed-width side + fluid content, collapses on phones.
   Inherits `--sidebar-w` from the consumer (default 260 px). */
.sidebar {
    display: grid;
    gap: var(--space-5);
    /* minmax(0, 1fr) + min-width:0 children: the content column hosts
       tables and other scrollers whose min-content would otherwise widen
       the whole page layout instead of scrolling inside their own box. */
    grid-template-columns: minmax(var(--sidebar-w, 260px), 20%) minmax(0, 1fr);
}
.sidebar > * { min-width: 0; }
@media (max-width: 640px) {
    .sidebar { grid-template-columns: minmax(0, 1fr); }
}

/* Auto-fit grid of equal-width columns. Caller tunes the minimum
   column width via `--grid-min` (default 16 rem). */
.grid-auto {
    display: grid;
    gap: var(--space-4);
    grid-template-columns: repeat(auto-fit, minmax(var(--grid-min, 16rem), 1fr));
}

/* =============================================================
   Motion safety. Keyframes defined at the top of this file
   (spin / rise / floatY) are only activated when the user has
   not requested reduced motion. Any component that animates
   tokens defined above (--transition-*) should gate itself with
   this media query too.
   ============================================================= */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
        scroll-behavior: auto !important;
    }
}
.incident-breach-row-critical td {
    background: oklch(0.97 0.04 25);
}

/* =============================================================
   Billing reconciliation (REQ-F02-002 dashboard).
   ============================================================= */
.recon-filter {
    display: flex;
    gap: var(--space-3);
    flex-wrap: wrap;
    align-items: end;
}
.recon-filter .form-field { min-width: 12rem; }

/* ---------- «Νέα τιμή» as a full-width per-specialty list (#858) ----------
   Elias, 2026-08-15: «in the price tab "Νέα τιμή" should be speciality price.
   it should not be a column, instead it should use all availale horizontal
   spce. the prices speciality list should list all specialities that are
   pre-assigned using the center price as default.»

   The editor was four `.form-field`s in a `.recon-filter` flex row — a column
   of controls that asked «which therapy? what price? from when?» one field at
   a time, so the operator had to hold the answer to the first in their head
   while filling the rest, and could see only the therapy they had selected.

   A therapy per ROW inverts that: every therapy the centre offers is on screen
   with what this child pays for it and where that came from, and changing one
   is editing the line you are already looking at. The grid is defined once
   here rather than per row, so the columns line up down the list — which is
   the whole reason a table beats four repeated fields.

   `auto` on the price column keeps the input at its content width (the
   `--field-chars` rule from v0.141), while the ROW spans the full width. Ask 3
   is about the section, not about stretching a 6-character money field to fill
   a screen. */
.rate-specialty-list {
    display: grid;
    grid-template-columns: minmax(9rem, 1fr) auto auto minmax(8rem, 1.5fr) auto;
    gap: var(--space-2) var(--space-3);
    align-items: center;
    width: 100%;
}
.rate-specialty-list > .rate-row-head {
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--ink-soft);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}
.rate-specialty-list .rate-therapy { font-weight: 600; }

/* A source badge that wraps to two lines («Τιμή / παιδιού») makes its whole
   row twice as tall, five times over, for no information gained. */
.rate-specialty-list .status-badge { white-space: nowrap; }

/* 🔴 The row separator MUST span the whole row. Without this it is a sixth
   child in a five-column grid, so it consumes the first cell of the next row
   and every subsequent row is shifted one column left — the list renders
   diagonally. It looked correct in the markup and in every bUnit assertion,
   because those check ELEMENTS, not the boxes they land in; only looking at
   the rendered screen shows it.

   Zero height on desktop: the alignment comes from the columns, and a visible
   rule between every therapy would be five more lines of ink on a short list.
   The mobile override below gives it a border, where rows genuinely need
   separating because each becomes its own block. */
.rate-specialty-list > .rate-row-sep {
    grid-column: 1 / -1;
    height: 0;
}
.rate-specialty-list input { margin: 0; }

/* One column on a phone would interleave five values per therapy with no way
   to tell which belongs to which, so each row becomes its own labelled block
   instead — the same trade the responsive data tables already make. */
@media (max-width: 48rem) {
    .rate-specialty-list {
        grid-template-columns: 1fr;
        gap: var(--space-1);
    }
    .rate-specialty-list > .rate-row-head { display: none; }
    .rate-specialty-list > .rate-row-sep {
        border-top: 1px solid var(--line);
        margin: var(--space-2) 0 0;
    }
}

.recon-badge {
    display: inline-block;
    margin-left: var(--space-2);
    padding: var(--space-05) var(--space-2);
    background: var(--teal-100);
    color: var(--teal-900);
    border-radius: 999px;
    font-size: var(--text-base);
    font-weight: 700;
}

.recon-delta { font-weight: 600; text-align: right; font-variant-numeric: tabular-nums; }
.recon-delta-positive { color: var(--danger); }
.recon-delta-negative { color: oklch(0.55 0.13 145); }

.recon-actions { display: flex; flex-wrap: wrap; gap: var(--space-1); }
.recon-ids { display: flex; flex-wrap: wrap; gap: var(--space-1); font-size: var(--text-base); }
.recon-id {
    background: var(--teal-50);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    padding: var(--space-05) var(--space-2);
    color: var(--ink-soft);
    font-family: 'Consolas', 'Menlo', monospace;
}

.recon-link-form {
    display: flex;
    gap: var(--space-2);
    flex-wrap: wrap;
    align-items: center;
}
.recon-link-form input {
    flex: 1;
    min-width: 14rem;
    padding: 0.35rem var(--space-2);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
}

/* Invoice status-breakdown chips (REQ-F02-MVP-002 surface). Clickable
   filter chips above the invoice list. The active chip picks up the
   teal accent so the currently-applied filter is always obvious. */
.invoice-chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin: var(--space-4) 0;
}
.invoice-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-1) var(--space-3);
    border: 1px solid var(--line);
    border-radius: 999px;
    background: var(--card);
    color: var(--ink);
    font-size: var(--text-md);
    cursor: pointer;
}
.invoice-chip:hover:not([disabled]) {
    border-color: var(--teal-600);
}
.invoice-chip[disabled] {
    opacity: 0.6;
    cursor: default;
}
.invoice-chip--active {
    background: var(--teal-100);
    color: var(--teal-900);
    border-color: var(--teal-600);
    font-weight: 600;
}
.invoice-chip-count {
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.form-success {
    color: var(--success);
    background: oklch(0.97 0.04 145);
    border: 1px solid oklch(0.85 0.08 145);
    border-radius: var(--radius-sm);
    padding: var(--space-2) var(--space-3);
    font-weight: 600;
}

/* =============================================================
   Responsive polish (REQ #89). Consumes the tokens from #87 and
   the mobile shell breakpoints from #88.

   1. Under 768 px, .data-table rows stack as cards with column
      labels read from each <td data-label="…">.
   2. .form-actions (button row) becomes sticky at the bottom of
      the viewport on phones so the save button is always visible.
   ============================================================= */

/* ---- Tables → cards under 768 px ---- */
@media (max-width: 768px) {
    .data-table,
    .data-table thead,
    .data-table tbody,
    .data-table tr,
    .data-table td {
        display: block;
        width: 100%;
    }
    /* `thead { display: none }` used to take the FILTER row down with the
       label row: the column filters stayed in the DOM at zero size, so
       /children and /customers lost filtering entirely on a phone (/users
       and /therapists only degraded because they carry a page-level
       «Ρόλος» select). Keep the label row hidden — each cell reprints its
       own label via td::before below — and surface the filter row as a
       stacked panel above the cards. */
    .data-table thead { display: block; }
    .data-table thead tr:not(.data-table-filter-row) { display: none; }
    .data-table tr.data-table-filter-row {
        display: flex;
        flex-wrap: wrap;
        gap: var(--space-3);
        padding: var(--space-3);
        margin-bottom: var(--space-3);
        border: 1px solid var(--line);
        border-radius: var(--radius);
        background: var(--card);
        box-shadow: none;
    }
    .data-table-filter-row th {
        display: block;
        position: static;
        top: auto;
        flex: 1 1 11rem;
        min-width: 0;
        padding: 0;
        background: transparent;
        text-align: start;
    }
    /* Columns with no filter contribute nothing here — an empty cell would
       still claim a flex slot and print a stray caption. */
    .data-table-filter-row th.data-table-filter-th--none { display: none; }
    .data-table-filter-row th::before {
        content: attr(data-label);
        display: block;
        margin-bottom: var(--space-1);
        font-size: var(--text-xs);
        text-transform: uppercase;
        letter-spacing: 0.06em;
        color: var(--ink-soft);
        font-weight: 600;
    }
    .data-table-filter-row .data-table-multi-filter__trigger,
    .data-table-filter-row .data-table-filter-input {
        display: flex;
        align-items: center;
        min-height: var(--tap-min, 44px);
    }
    .data-table-filter-row .data-table-multi-filter__option { min-height: var(--tap-min, 44px); }

    .data-table tr {
        background: var(--card);
        border: 1px solid var(--line);
        border-radius: var(--radius);
        padding: var(--space-3, 12px) var(--space-4, 16px);
        margin-bottom: var(--space-3, 12px);
        box-shadow: 0 1px 2px oklch(0.22 0.05 215 / 0.04);
    }
    .data-table tr.row-clickable { cursor: pointer; }
    .data-table tr.row-clickable:hover { background: var(--teal-50); }

    .data-table td {
        border: none;
        padding: var(--space-2, 8px) 0;
        display: grid;
        grid-template-columns: 40% minmax(0, 1fr);
        column-gap: var(--space-3, 12px);
        align-items: baseline;
        word-break: break-word;
    }
    .data-table td::before {
        content: attr(data-label);
        font-size: var(--text-xs);
        text-transform: uppercase;
        letter-spacing: 0.06em;
        color: var(--ink-soft);
        font-weight: 600;
    }
    /* A <td data-label=""> (e.g. action column) drops the label column. */
    .data-table td[data-label=""] { grid-template-columns: 1fr; }
    .data-table td[data-label=""]::before { display: none; }
}

/* ---- Sticky form action bar on phone ---- */
.form-actions {
    display: flex;
    gap: var(--space-2);
    flex-wrap: wrap;
    margin-top: var(--space-4);
}
@media (max-width: 640px) {
    .form-actions {
        position: sticky;
        bottom: 0;
        left: 0;
        right: 0;
        background: var(--card);
        padding: var(--space-3, 12px) var(--space-4, 16px);
        margin-left: calc(var(--space-4, 16px) * -1);
        margin-right: calc(var(--space-4, 16px) * -1);
        border-top: 1px solid var(--line);
        box-shadow: 0 -2px 8px oklch(0.22 0.05 215 / 0.08);
        z-index: 5;
    }
    .form-actions .btn-primary,
    .form-actions .btn-secondary {
        flex: 1;
        min-height: var(--tap-min, 44px);
    }
}

/* ---------- Billing exceptions dashboard (REQ-F02-003) ---------- */
.exception-summary {
    display: flex; gap: var(--space-4); font-size: var(--text-md); color: var(--ink-soft);
}
.exception-summary strong { color: var(--ink); }

.exception-list { list-style: none; padding: 0; margin: var(--space-2) 0 0; }
.exception-card {
    border: 1px solid var(--line);
    border-left-width: 4px;
    border-radius: var(--radius-sm);
    padding: var(--space-3) var(--space-4);
    margin: var(--space-2) 0;
    background: var(--teal-50);
}
.exception-card-missing   { border-left-color: var(--danger); }
.exception-card-orphan    { border-left-color: oklch(0.70 0.14 60); }
.exception-card-mismatch  { border-left-color: var(--teal-800); }
.exception-card-diagnosis { border-left-color: oklch(0.55 0.16 295); }

.exception-head { display: flex; gap: var(--space-2); align-items: baseline; flex-wrap: wrap; }
.exception-type { font-weight: 700; color: var(--ink); }
.exception-parent { color: var(--ink); font-weight: 600; }
.exception-ike { color: var(--ink-soft); }
.exception-body { margin: 0.35rem 0 var(--space-2); color: var(--ink); }
.exception-actions {
    display: flex; flex-wrap: wrap; gap: var(--space-2);
}
.exception-dismiss-form {
    margin-top: var(--space-3);
    padding: var(--space-3);
    border-radius: var(--radius-sm);
    background: var(--card);
    border: 1px solid var(--line);
}
.exception-dismiss-form label {
    display: block;
    font-size: var(--text-base);
    font-weight: 600;
    margin-bottom: var(--space-1);
}
.exception-dismiss-form textarea {
    width: 100%;
    padding: 0.55rem var(--space-3);
    border: 1.5px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--cream);
    color: var(--ink);
    font: inherit;
    resize: vertical;
}
.exception-dismiss-actions { display: flex; gap: var(--space-2); margin-top: var(--space-2); }

/* =============================================================
   #173 Home dashboard — quick-action tiles, metric cards, and
   the "next sessions today" list rendered on `/`. Cards are
   anchor elements so keyboard + screen-reader users can tab
   through the same surface the mouse-user clicks on.
   ============================================================= */
.dashboard-quick-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    margin: 0 0 var(--space-5);
}
.dashboard-quick-action {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: 0.55rem 0.95rem;
    border-radius: var(--radius-sm);
    background: var(--lime-300);
    color: var(--ink);
    text-decoration: none;
    font-weight: 600;
    border: 1px solid var(--line);
    min-height: var(--tap-min);
    transition: background-color var(--transition-fast);
}
.dashboard-quick-action:hover { background: var(--lime-500); }
.dashboard-quick-action-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.5rem; height: 1.5rem;
    border-radius: 999px;
    background: var(--teal-900);
    color: #fff;
    font-weight: 700;
    font-size: var(--text-md);
    line-height: 1;
}

.dashboard-metrics {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
    gap: var(--space-4);
    margin: 0 0 var(--space-5);
}
.dashboard-card {
    display: block;
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 1.1rem 1.25rem;
    color: var(--ink);
    text-decoration: none;
    box-shadow: var(--shadow-plaque);
    transition: transform var(--transition-fast), box-shadow var(--transition-fast);
}
.dashboard-card:hover {
    transform: translateY(-2px);
    box-shadow:
        0 4px 10px -4px oklch(0.25 0.05 215 / 0.3),
        0 12px 24px -12px oklch(0.25 0.05 215 / 0.45);
}
.dashboard-card h3 {
    margin: 0 0 var(--space-1);
    font-family: Verdana, 'DejaVu Sans', Geneva, sans-serif;
    color: var(--teal-900);
    font-size: var(--text-xl);
}
.dashboard-card-count {
    margin: 0.1rem 0 var(--space-1);
    font-size: var(--text-4xl);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--teal-900);
    line-height: 1.1;
}
.dashboard-card .muted { margin: 0; }
.dashboard-card-warn { border-left: 4px solid var(--danger); }

/* =============================================================
   Phase 3.9 — REQ-F25-005 / REQ-LEX-NOT-011 12-card admin KPI
   landing grid. 4 columns on desktop, 2 on tablet, 1 on phone.
   Cards are anchor elements so the whole tile is keyboard- and
   screen-reader-navigable. Threshold-flagged cards add a coloured
   left rail PLUS the inline ✱ icon + «Προσοχή» label so colour is
   never the only signal (WCAG 2.1 AA).
   ============================================================= */
.dashboard-kpi-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--space-4);
    margin: 0 0 var(--space-5);
}
@media (max-width: 1024px) {
    .dashboard-kpi-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
    .dashboard-kpi-grid { grid-template-columns: minmax(0, 1fr); }
}

.dashboard-kpi-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 0.95rem 1.1rem;
    color: var(--ink);
    text-decoration: none;
    box-shadow: var(--shadow-plaque);
    transition: transform var(--transition-fast), box-shadow var(--transition-fast);
    min-height: 120px;
}
.dashboard-kpi-card:hover {
    transform: translateY(-2px);
    box-shadow:
        0 4px 10px -4px oklch(0.25 0.05 215 / 0.3),
        0 12px 24px -12px oklch(0.25 0.05 215 / 0.45);
}
/* #698: a card with no screen behind its number is rendered as a plain
   <div> instead of an <a>. Suppress the lift-on-hover so it does not
   still advertise itself as clickable — the affordance was the whole
   reason a dead link read as a working one. */
.dashboard-kpi-card.is-static,
.dashboard-kpi-card.is-static:hover {
    transform: none;
    box-shadow: var(--shadow-plaque);
    cursor: default;
}
.dashboard-kpi-card:focus-visible {
    outline: 3px solid var(--teal-700);
    outline-offset: 2px;
}
.dashboard-kpi-card.is-warn {
    border-left: 4px solid var(--danger);
    background: oklch(0.98 0.04 25 / 0.5);
}
.dashboard-kpi-card-head {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
}
.dashboard-kpi-category {
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--ink-soft);
    font-weight: 600;
}
.dashboard-kpi-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.15rem;
    height: 1.15rem;
    border-radius: 999px;
    background: var(--danger);
    color: #fff;
    font-weight: 700;
    font-size: var(--text-sm);
    line-height: 1;
    margin-left: auto;
}
.dashboard-kpi-warn-label {
    font-size: var(--text-xs);
    font-weight: 700;
    color: var(--danger);
    text-transform: uppercase;
    letter-spacing: 0.05em;
}
.dashboard-kpi-card h3 {
    margin: var(--space-1) 0 0;
    font-family: Verdana, 'DejaVu Sans', Geneva, sans-serif;
    color: var(--teal-900);
    font-size: var(--text-lg);
    line-height: 1.2;
}
.dashboard-kpi-card-count {
    margin: var(--space-05) 0 var(--space-05);
    font-size: var(--text-4xl);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--teal-900);
    line-height: 1.1;
}
.dashboard-kpi-secondary {
    margin: 0;
    font-size: var(--text-base);
}
.dashboard-kpi-breakdown {
    list-style: none;
    margin: var(--space-2) 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-05);
    font-size: var(--text-base);
}
.dashboard-kpi-breakdown li {
    display: flex;
    justify-content: space-between;
    gap: var(--space-2);
    color: var(--ink-soft);
}
.dashboard-kpi-breakdown-count {
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    color: var(--teal-900);
}
.dashboard-kpi-stamp {
    margin: -0.5rem 0 var(--space-4);
    font-size: var(--text-base);
}

@media (prefers-reduced-motion: reduce) {
    .dashboard-kpi-card { transition: none; }
    .dashboard-kpi-card:hover { transform: none; }
}

.dashboard-upcoming {
    list-style: none;
    padding: 0;
    margin: var(--space-2) 0 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}
.dashboard-upcoming li a {
    display: grid;
    grid-template-columns: 4.5rem 1fr auto;
    gap: var(--space-3);
    align-items: baseline;
    padding: 0.55rem var(--space-3);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--teal-50);
    color: var(--ink);
    text-decoration: none;
    transition: background-color var(--transition-fast);
}
.dashboard-upcoming li a:hover { background: var(--teal-100); }
.dashboard-upcoming .time {
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    color: var(--teal-900);
}
.dashboard-upcoming .therapist { color: var(--ink); }
.dashboard-upcoming .status { color: var(--ink-soft); font-size: var(--text-md); }

/* =============================================================
   F20 — In-app help (REQ-F20-001).
   Round `?` button in the header opens a side panel that slides
   in from the right, dimming the rest of the page with a scrim.
   The panel becomes a full-screen modal under 640px so phones
   don't render an unreadable squeeze. All colours come from the
   teal/lime token set so the control sits naturally with the
   rest of the chrome.
   ============================================================= */
.help-button {
    width: 2.25rem;
    height: 2.25rem;
    min-width: 2.25rem;
    border-radius: 999px;
    border: 1px solid rgba(255, 255, 255, 0.45);
    background: rgba(255, 255, 255, 0.08);
    color: #ffffff;
    font-weight: 700;
    font-size: var(--text-xl);
    line-height: 1;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background-color var(--transition-fast),
                border-color var(--transition-fast);
}
.help-button:hover {
    background: var(--teal-900);
    border-color: rgba(255, 255, 255, 0.75);
}
.help-button:focus-visible {
    outline: 2px solid var(--lime-500);
    outline-offset: 2px;
}

.help-panel-scrim {
    position: fixed;
    inset: 0;
    background: oklch(0.22 0.05 215 / 0.45);
    z-index: 1000;
    animation: fade-in var(--transition-fast) ease-out;
}

.help-panel {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    width: min(420px, 92vw);
    background: var(--card);
    color: var(--ink);
    z-index: 1001;
    display: flex;
    flex-direction: column;
    border-left: 1px solid var(--line);
    box-shadow: -12px 0 40px -16px oklch(0.22 0.05 215 / 0.55);
    animation: help-panel-in var(--transition-base) ease-out;
}
.help-panel:focus { outline: none; }

.help-panel-header {
    padding: var(--space-4) 1.25rem;
    border-bottom: 1px solid var(--line);
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--space-3);
    align-items: center;
    background: var(--teal-50);
}
.help-panel-header h2 {
    margin: 0;
    font-family: Verdana, 'DejaVu Sans', Geneva, sans-serif;
    font-size: var(--text-xl);
    font-weight: 600;
    color: var(--teal-900);
}
.help-panel-close {
    width: 2.25rem;
    height: 2.25rem;
    border-radius: var(--radius-sm);
    border: 1px solid var(--line);
    background: transparent;
    color: var(--ink-soft);
    font-size: var(--text-3xl);
    line-height: 1;
    cursor: pointer;
    transition: background-color var(--transition-fast);
}
.help-panel-close:hover { background: var(--teal-100); color: var(--ink); }
.help-panel-close:focus-visible {
    outline: 2px solid var(--teal-700);
    outline-offset: 2px;
}

.help-panel-body {
    padding: 1.25rem var(--space-5) var(--space-5);
    overflow-y: auto;
    flex: 1;
    line-height: 1.6;
}
.help-panel-body h2 {
    font-family: Verdana, 'DejaVu Sans', Geneva, sans-serif;
    font-size: var(--text-xl);
    font-weight: 600;
    color: var(--teal-900);
    margin: var(--space-5) 0 var(--space-2);
}
.help-panel-body h2:first-child { margin-top: 0; }
.help-panel-body h3 {
    font-size: var(--text-lg);
    font-weight: 600;
    color: var(--ink);
    margin: var(--space-4) 0 0.4rem;
}
.help-panel-body p { margin: 0 0 var(--space-3); }
.help-panel-body ul, .help-panel-body ol {
    padding-left: var(--space-5);
    margin: 0 0 0.85rem;
}
.help-panel-body li { margin: var(--space-1) 0; }
.help-panel-body code {
    font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.88em;
    background: var(--teal-50);
    padding: 0.05rem 0.3rem;
    border-radius: 4px;
    color: var(--teal-900);
}
.help-panel-body a {
    color: var(--teal-800);
    text-decoration: underline;
    text-underline-offset: 2px;
}
.help-panel-body a:hover { color: var(--teal-900); }
.help-panel-body table {
    border-collapse: collapse;
    width: 100%;
    margin: var(--space-2) 0 var(--space-4);
    font-size: 0.92em;
}
.help-panel-body th, .help-panel-body td {
    border: 1px solid var(--line);
    padding: var(--space-2) var(--space-2);
    text-align: left;
}
.help-panel-body th { background: var(--teal-50); font-weight: 600; }

.help-panel-footer {
    margin-top: var(--space-5);
    padding-top: var(--space-3);
    border-top: 1px dashed var(--line);
    font-size: var(--text-sm);
}

@keyframes help-panel-in {
    from { transform: translateX(100%); }
    to   { transform: translateX(0); }
}
@keyframes fade-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* Phone: take the whole viewport; sliding from the right is
   disorienting on a 360-px screen. */
@media (max-width: 640px) {
    .help-panel {
        width: 100vw;
        border-left: none;
    }
}

/* Reduced-motion: skip the slide-in / fade so people who set the
   preference don't get a transition on every Help open. */
@media (prefers-reduced-motion: reduce) {
    .help-panel,
    .help-panel-scrim {
        animation: none;
    }
}

/* =============================================================
   REQ-F24-004 — soft feature gate.
   Rendered by <FeatureGated> when the licence flag is off.
   The locked state is a small notice with an upgrade CTA;
   gated child markup is intentionally not emitted at all.
   ============================================================= */
.feature-gated-locked {
    display: inline-flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-4) 1.25rem;
    background: var(--teal-50);
    border: 1px dashed var(--teal-700);
    border-radius: var(--radius-sm);
    color: var(--ink-soft);
    max-width: 32rem;
}
.feature-gated-badge {
    font-size: var(--text-base);
    font-weight: 600;
    color: var(--teal-900);
    letter-spacing: 0.02em;
}
.feature-gated-cta {
    color: var(--rheasys-primary, var(--teal-800));
    font-weight: 500;
    text-decoration: underline;
    width: fit-content;
}
.feature-gated-cta:hover { color: var(--teal-900); }

/* Connection-loss banner — single instance in MainLayout / EmptyLayout,
   driven by ConnectivityService. Red palette to signal "your edits
   may be lost" rather than the amber soft-warning tone used for
   licence cap utilisation. */
.connectivity-banner {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    background: oklch(0.93 0.08 28);
    border: 1px solid oklch(0.62 0.20 28);
    color: oklch(0.30 0.10 28);
    padding: 0.6rem var(--space-4);
    border-radius: var(--radius-sm);
    font-size: var(--text-md);
    margin-bottom: var(--space-3);
}
.connectivity-banner-icon { font-size: var(--text-xl); line-height: 1; }

/* F27 session-recording notice — GDPR Art. 13 transparency banner shown
   while the current session is being captured. Neutral/informational tone
   (not an error), unobtrusive, with a dismiss button. */
.session-recording-banner {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    background: oklch(0.95 0.03 250);
    border: 1px solid oklch(0.78 0.06 250);
    color: oklch(0.38 0.05 250);
    padding: var(--space-2) var(--space-4);
    border-radius: var(--radius-sm);
    font-size: var(--text-base);
    margin-bottom: var(--space-3);
}
.session-recording-banner-icon { font-size: var(--text-xs); line-height: 1; color: oklch(0.58 0.20 28); }
.session-recording-banner-dismiss {
    margin-left: auto;
    background: none;
    border: none;
    color: inherit;
    cursor: pointer;
    font-size: var(--text-md);
    line-height: 1;
    padding: var(--space-05) var(--space-1);
    border-radius: var(--radius-sm);
}
.session-recording-banner-dismiss:hover { background: oklch(0.88 0.04 250); }

/* #732 — fictitious-data warning. Strongest tone in the shell, because it
   is the only thing that distinguishes a δοκιμαστικό dataset from a real
   one now that the seeded names read like real names.

   Not dismissible and not colour-only: the ⚠ mark and the «Προσοχή» text
   carry the meaning on their own, so it survives a colour-blind reader and a
   monochrome print. It sits in the flow at the top of <main>, so it pushes
   content down rather than overlaying it — the side nav, the header and the
   mobile tabbar are never covered. */
.demo-data-banner {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    background: oklch(0.92 0.09 28);
    border: 1px solid oklch(0.55 0.21 28);
    border-left-width: 4px;
    color: oklch(0.28 0.10 28);
    padding: 0.6rem var(--space-4);
    border-radius: var(--radius-sm);
    font-size: var(--text-md);
    line-height: 1.35;
    margin-bottom: var(--space-3);
}
.demo-data-banner-icon { font-size: var(--text-xl); line-height: 1.2; }

/* REQ-F24-006 soft warning banner — surfaces when seats / storage are
   ≥ 80% of licence cap. */
.metering-warning {
    background: oklch(0.95 0.06 80);
    border: 1px solid oklch(0.78 0.14 78);
    color: oklch(0.32 0.07 80);
    padding: 0.6rem var(--space-4);
    border-radius: var(--radius-sm);
    font-size: var(--text-md);
}
.metering-warning .metering-warning-dismiss {
    background: transparent;
    border: none;
    color: inherit;
    cursor: pointer;
    margin-left: var(--space-2);
    font-weight: 600;
}

/* =============================================================
   REQ-F16-004 / LEXIA AUTH-008 — inline audit attribution tooltip.
   Pure CSS hover/focus tooltip; no JS interop. Reuses the global
   palette tokens (--teal-*, --line, --ink) so it inherits any tenant
   branding overrides automatically.
   ============================================================= */
.audit-tooltip {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-left: var(--space-1);
    width: 1.15rem;
    height: 1.15rem;
    border-radius: 999px;
    background: var(--teal-50);
    border: 1px solid var(--line);
    color: var(--ink-soft);
    font-size: var(--text-xs);
    font-weight: 700;
    line-height: 1;
    cursor: help;
    /* Accessibility: keep the focus ring visible — the affordance is
       primarily keyboard-reachable for non-pointer users. */
    outline-offset: 2px;
    user-select: none;
    vertical-align: middle;
}
.audit-tooltip:focus-visible {
    outline: 2px solid var(--rheasys-primary, var(--teal-800));
}
.audit-tooltip-icon {
    font-family: Verdana, 'DejaVu Sans', Geneva, sans-serif;
    font-style: italic;
    color: var(--teal-800);
    pointer-events: none;
}
.audit-tooltip-bubble {
    position: absolute;
    bottom: calc(100% + 0.4rem);
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    min-width: 18rem;
    max-width: 28rem;
    padding: 0.55rem var(--space-3);
    background: var(--ink);
    color: var(--cream);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-plaque);
    font-size: var(--text-sm);
    font-weight: 400;
    line-height: 1.35;
    text-align: left;
    white-space: normal;
    /* Off by default; toggled by hover/focus on the parent. */
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition: opacity var(--transition-fast), visibility var(--transition-fast);
    z-index: 5;
}
.audit-tooltip:hover .audit-tooltip-bubble,
.audit-tooltip:focus-visible .audit-tooltip-bubble,
.audit-tooltip:focus-within .audit-tooltip-bubble {
    opacity: 1;
    visibility: visible;
}
.audit-tooltip-line {
    display: block;
}
.audit-tooltip-label {
    color: oklch(0.78 0.02 215);
}
.audit-tooltip-actor {
    color: var(--cream);
    font-weight: 600;
    margin: 0 var(--space-1);
    word-break: break-word;
}
.audit-tooltip-date {
    color: var(--cream);
    margin-left: var(--space-1);
    font-variant-numeric: tabular-nums;
}
@media (prefers-reduced-motion: reduce) {
    .audit-tooltip-bubble { transition: none; }
}

/* LEXIA REQ-LEX-SES-005 / -015 — SessionNote completion badge.
   Three states (Missing / Draft / Locked) rendered as colour + glyph
   so screen readers + colour-blind users still parse the row. The
   colour tokens hit WCAG-AA contrast (>= 4.5:1) against their
   background. .calendar-day-grid-block-note positions the badge in
   the upper-right of a session cell on the day grid. */
.note-state-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.25rem;
    height: 1.25rem;
    padding: 0 var(--space-1);
    border-radius: 999px;
    font-size: var(--text-base);
    font-weight: 700;
    line-height: 1;
    border: 1px solid transparent;
}
.note-state-missing {
    background: #FCE8E8;
    color: #842029;
    border-color: #F1AEB5;
}
.note-state-draft {
    background: #E2F0D9;
    color: #0F5132;
    border-color: #A3CFBB;
}
.note-state-locked {
    background: #E2E3E5;
    color: #41464B;
    border-color: #C4C8CB;
}

.calendar-day-grid-block-note {
    position: absolute;
    top: 0.2rem;
    right: 0.2rem;
    z-index: 1;
}
.calendar-day-grid-block { position: relative; }

/* Child detail patient header — keeps the child's name visible
   above the tabs so context is never lost when switching between
   Info / Evaluation / Diagnoses / Services / Absences / Receipts /
   Financials (per Child Edit design handoff). */
.patient-header {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    margin: 0 0 var(--space-3);
    padding: var(--space-4) 1.25rem;
    background: var(--teal-100);
    border: 1px solid var(--teal-100);
    border-left: 4px solid var(--rheasys-accent, var(--lime-500, #84cc16));
    border-radius: 0.6rem;
    position: relative;
    overflow: hidden;
}
.patient-avatar {
    width: 3rem;
    height: 3rem;
    border-radius: 50%;
    flex-shrink: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--rheasys-primary, var(--teal-800));
    color: #fff;
    font-size: var(--text-xl);
    font-weight: 700;
    letter-spacing: 0.02em;
}
.patient-info { flex: 1; min-width: 0; }
.patient-eyebrow {
    font-size: var(--text-xs);
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--ink);
    opacity: 0.65;
    margin-bottom: var(--space-05);
}
.patient-name {
    font-size: var(--text-3xl);
    font-weight: 600;
    line-height: 1.15;
    color: var(--ink);
    margin-bottom: var(--space-2);
}
.patient-meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1) 0;
    font-size: var(--text-base);
    color: var(--ink);
}
.patient-meta .meta-item {
    display: inline-flex;
    align-items: baseline;
    gap: var(--space-1);
    padding: 0 var(--space-3);
}
.patient-meta .meta-item + .meta-item {
    border-left: 1px solid var(--teal-100);
}
.patient-meta .meta-item:first-child { padding-left: 0; }
.patient-meta .meta-key { opacity: 0.7; }
.patient-meta .meta-value { font-weight: 600; }
.patient-meta .meta-aux { opacity: 0.55; }
.patient-meta .meta-error { color: oklch(0.55 0.18 25); }
.patient-meta .meta-link {
    color: var(--rheasys-primary, var(--teal-800));
    text-decoration: underline;
    text-decoration-color: color-mix(in oklch, currentColor 30%, transparent);
    text-underline-offset: 0.15em;
}
.patient-meta .meta-link:hover,
.patient-meta .meta-link:focus-visible {
    text-decoration-color: currentColor;
}

/* Two-column layout for the child edit page: the main edit form on the
   left and the «Συνδεδεμένα πρόσωπα» panel on the right. Collapses to a
   single column under 900px so it stays usable on tablets / phones. */
.child-edit-grid {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    gap: var(--space-2);
    align-items: start;
}
.child-edit-grid > .login-card { margin-top: 0; }
.child-edit-parents { min-width: 0; }
.child-edit-parents > h2 { margin-bottom: var(--space-05); }
/* The linked-parents table sizes to its CONTENT, not the full panel width,
   so the ~5 short columns sit together with no far-right column and no big
   empty gap (repeat owner report v0.48→v0.52). The load-bearing per-column
   sizing + name no-wrap lives in the component-scoped Edit.razor.css using
   ::deep; keep the global rules here in agreement (do NOT re-add width:100%
   or word-break here — that was the cause of the wrap + spread that kept
   coming back). */
.child-edit-parents .linked-parents-table { table-layout: auto; width: auto; max-width: 100%; }
.child-edit-parents .linked-parents-table th,
.child-edit-parents .linked-parents-table td {
    padding: 0.4rem var(--space-2);
    white-space: nowrap;
}
@media (max-width: 900px) {
    .child-edit-grid { grid-template-columns: 1fr; }
}

/* =============================================================
   «Νέα εγγραφή» child-intake compact layout. Page-scoped under
   .new-registration so it never perturbs the global .form-field /
   .login-card used across the app. Reuses the .child-edit-grid
   two-panel split (child fields left, guardians right; collapses
   under 900px). Inside the child panel, .intake-fields pairs the
   8 fields into a 2-column grid (collapses under 700px). Vertical
   rhythm is tightened via the scoped .form-field override only.
   ============================================================= */
.new-registration { max-width: 76rem; }
/* Single-column (guardians-under, i.e. below 900px) readable cap. */
@media (max-width: 900px) {
    .new-registration { max-width: 52rem; }
}
.new-registration .intake-grid > .login-card { margin-top: 0; }

/* 2-column grid for the child fields; collapse to one column so
   mobile is unaffected. */
.intake-fields {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0 var(--space-4);
    align-items: start;
}
@media (max-width: 700px) {
    .intake-fields { grid-template-columns: 1fr; }
}

/* Tighter vertical rhythm for this dense intake — scoped, so the
   global .form-field spacing is untouched. */
.intake-fields .form-field { margin-bottom: var(--space-3); }

/* Short fields size to their content via the SEMANTIC tokens near the top
   of this file, not from here. Name / Σχολείο keep the default flexible
   width.

   🔴 RETIRED (#762): this block used to be

       .intake-fields .form-field.field-sm input,
       .intake-fields .form-field.field-sm select { max-width: 12rem; }
       .intake-fields .form-field.field-md input,
       .intake-fields .form-field.field-md select { max-width: 16rem; }

   and it is the reason the ΑΜΚΑ / ΑΦΜ tokens sat declared-and-unapplied
   for three releases. `.field-sm` / `.field-md` are NUMBERS WEARING A
   NAME: «sm» and «md» say how wide, never what the value is, so nothing
   about them survives a redesign and nothing tells the next author which
   to pick. Worse, being page-scoped they are (0,3,1) against the tokens'
   (0,2,1) — so this page silently WON, and `Children/New`'s ΑΜΚΑ rendered
   at 16rem while the stylesheet declared 12rem for it. A reviewer reading
   the token block would have concluded the opposite of what shipped.

   Each of the five sites moved to the token for what it MEANS:
     Ημ. γέννησης  → no class at all; `input[type="date"]` already caps it
     ΑΜΚΑ          → .field-w-amka   (eleven digits)
     Φύλο          → .field-w-choice (one of a short list)
     Ενεργό μέλος  → .field-w-choice (one of a short list)
     Τάξη          → .field-w-choice («Γ' Δημοτικού»)
   The date needing NO class is the point of the whole design: the meaning
   was already in the markup, so the stylesheet could read it. */

/* Guardians panel sits beside the child fields on wide screens. */
.intake-guardians { min-width: 0; }
.intake-guardians > h2:first-child { margin-top: 0; }

/* «Σύνδεση υπάρχοντος κηδεμόνα» typeahead + «+ Νέος κηδεμόνας»
   button on one row (wraps under its own width when cramped). */
.intake-add-guardian {
    display: flex;
    gap: var(--space-3);
    align-items: flex-end;
    flex-wrap: wrap;
}
.intake-add-guardian .form-field { flex: 1 1 16rem; margin-bottom: 0; }
.intake-add-guardian > button { flex: 0 0 auto; }

/* Linked-parents inline editing table on the child edit page. The
   second column is narrow on a tablet, so the dropdowns shrink to fit
   their content and the payer radio gets its own centred column. */
.linked-parents-table th,
.linked-parents-table td {
    vertical-align: middle;
}
.linked-parents-table .payer-col { text-align: center; }
.linked-parents-table .link-edit-select {
    padding: var(--space-1) 0.4rem;
    border: 1px solid var(--line);
    border-radius: 0.35rem;
    background: var(--card);
    color: var(--ink);
    font-size: var(--text-base);
    /* Size to the widest option (its content), not the whole column, so the
       Σχέση / Επιμέλεια columns don't stretch and shove the later columns to
       the far right. */
    width: auto;
    max-width: 100%;
}
.linked-parents-table .payer-radio {
    display: inline-flex;
    align-items: center;
    justify-content: center;
}
.linked-parents-table .payer-radio input { transform: scale(1.15); cursor: pointer; }

/* #889 — .child-tabs / .child-tab retired: ChildTabs.razor and
   ComplianceTabs.razor now render through the shared TabStrip component
   (src/Rheasys.Therapy.UI/Components/TabStrip.razor.css), which carries
   the sticky behaviour via TabStrip's Sticky="true" opt-in. */

/* #889 — .customer-tabs / .customer-tab retired from here too: every
   remaining consumer (Customers/Detail, MyData/Export, Rooms/Edit) now
   renders through TabStrip.razor.css, which is where the same rules live
   now (as ::deep selectors, since TabStripItem is a separate component). */

/* CustomerSessionsTab — totals strip, view-mode toggle, child-grouped
   sections. Read-only; reused status-pill classes for the bucket
   badges. */
.sessions-tab-summary { margin-top: var(--space-4); }
.sessions-tab-totals {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    gap: var(--space-3);
}
.sessions-tab-total-item {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: var(--space-3) var(--space-4);
    background: var(--cream);
    display: flex;
    flex-direction: column;
    gap: var(--space-05);
}
.sessions-tab-total-label {
    font-size: var(--text-base);
    color: var(--ink-soft);
    font-weight: 600;
}
.sessions-tab-total-value {
    font-size: var(--text-2xl);
    font-weight: 700;
    color: var(--ink);
}
.sessions-tab-total-count {
    font-size: var(--text-base);
    color: var(--ink-soft);
}
.sessions-tab-total-uncovered { border-left: 4px solid oklch(0.70 0.18 25); }
.sessions-tab-total-billed    { border-left: 4px solid oklch(0.78 0.16 80); }
.sessions-tab-total-paid      { border-left: 4px solid oklch(0.70 0.16 145); }
.sessions-tab-no-charge-note {
    margin: var(--space-3) 0 0;
    font-size: var(--text-base);
    color: var(--ink-soft);
}
.sessions-tab-view-toggle { display: flex; gap: var(--space-2); }
.sessions-tab-view-toggle .btn-secondary.active {
    background: var(--teal-100);
    border-color: var(--teal-300, var(--teal-100));
}
.sessions-tab-table tbody tr.sessions-tab-row-muted {
    opacity: 0.55;
}
.sessions-tab-table tbody tr.sessions-tab-row-cross-parent {
    opacity: 0.8;
    background: oklch(0.97 0.02 60);
}
.sessions-tab-cross-parent-badge {
    display: inline-block;
    margin-left: var(--space-2);
    padding: 0.05rem 0.45rem;
    font-size: var(--text-xs);
    font-weight: 600;
    line-height: 1.3;
    color: oklch(0.40 0.12 50);
    background: oklch(0.95 0.05 60);
    border: 1px solid oklch(0.85 0.08 60);
    border-radius: 999px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    white-space: nowrap;
}
.sessions-tab-cross-parent-migrate {
    margin-left: var(--space-2);
}
.sessions-tab-child-group {
    margin: var(--space-3) 0;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--card);
}
.sessions-tab-child-group > summary {
    padding: var(--space-3) var(--space-4);
    cursor: pointer;
    display: flex;
    align-items: baseline;
    gap: var(--space-4);
    font-weight: 600;
    list-style: revert;
}
.sessions-tab-child-group-name {
    color: var(--ink);
}
.sessions-tab-child-group-stats {
    color: var(--ink-soft);
    font-weight: 500;
    font-size: var(--text-md);
}
.sessions-tab-child-group .sessions-tab-table { margin: 0 var(--space-4) var(--space-4); width: calc(100% - 2rem); }
.sessions-tab-checkbox-col { width: 2rem; text-align: center; }
.sessions-tab-selection-bar {
    margin-top: var(--space-4);
    background: var(--cream);
    border-color: var(--rheasys-accent, var(--lime-500, #84cc16));
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-4);
    align-items: flex-end;
    justify-content: space-between;
}
.sessions-tab-selection-summary {
    display: flex;
    flex-direction: column;
    gap: var(--space-05);
}
.sessions-tab-selection-actions {
    display: flex;
    gap: var(--space-2);
    align-items: flex-end;
    flex-wrap: wrap;
}
.sessions-tab-issue-outcome { margin-top: var(--space-4); }

/* CustomerSessionsTab — APY-grouped layout, drag-drop, inline edits.
   The grouped view replaces the old chronological/per-child toggle;
   each group is a drop target sized as a card. */
.sessions-tab-newapy-bar {
    margin-top: var(--space-4);
    background: var(--cream);
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-4);
    align-items: flex-end;
    justify-content: space-between;
}
.sessions-tab-apy-group {
    margin: var(--space-3) 0;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--card);
    transition: border-color 120ms ease, background 120ms ease;
}
.sessions-tab-apy-group.sessions-tab-apy-draft {
    border-left: 4px solid oklch(0.78 0.16 80);
}
.sessions-tab-apy-group.sessions-tab-apy-issued {
    border-left: 4px solid oklch(0.70 0.16 145);
}
.sessions-tab-apy-group.sessions-tab-apy-partial {
    border-left: 4px solid oklch(0.78 0.16 80);
}
.sessions-tab-apy-group.sessions-tab-apy-paid {
    border-left: 4px solid oklch(0.70 0.16 145);
    opacity: 0.95;
}
.sessions-tab-apy-group.sessions-tab-apy-unassigned {
    border-left: 4px solid oklch(0.70 0.18 25);
}
.sessions-tab-apy-group.sessions-tab-apy-nocharge {
    border-left: 4px solid var(--line);
    opacity: 0.7;
}
.sessions-tab-apy-group.sessions-tab-apy-cancelled {
    border-left: 4px solid var(--line);
    opacity: 0.6;
    background: repeating-linear-gradient(
        45deg,
        var(--card),
        var(--card) 12px,
        var(--cream) 12px,
        var(--cream) 14px);
}
.sessions-tab-apy-group.sessions-tab-apy-cancelled h3 { text-decoration: line-through; }
.sessions-tab-list-toolbar {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    flex-wrap: wrap;
}
.sessions-tab-show-cancelled {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-md);
    color: var(--ink-soft);
    cursor: pointer;
    user-select: none;
}
.sessions-tab-show-cancelled input[type="checkbox"] { margin: 0; }
.sessions-tab-apy-group.sessions-tab-apy-drop-active {
    border-color: var(--rheasys-accent, var(--lime-500, #84cc16));
    background: var(--cream);
    box-shadow: 0 0 0 2px var(--rheasys-accent, var(--lime-500, #84cc16)) inset;
}
.sessions-tab-apy-header {
    padding: var(--space-3) var(--space-4);
    display: grid;
    grid-template-columns: minmax(12rem, 1fr) minmax(12rem, 1fr) auto;
    gap: var(--space-4);
    align-items: center;
}
.sessions-tab-apy-header-unassigned h3,
.sessions-tab-apy-header-nocharge h3,
.sessions-tab-apy-header h3 {
    margin: 0;
    font-size: var(--text-lg);
    color: var(--ink);
}
.sessions-tab-apy-title {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
}
.sessions-tab-apy-meta {
    display: flex;
    align-items: center;
    gap: var(--space-4);
    color: var(--ink-soft);
    font-size: var(--text-md);
    flex-wrap: wrap;
}
.sessions-tab-apy-total {
    font-weight: 700;
    color: var(--ink);
}
.sessions-tab-apy-actions {
    display: flex;
    gap: var(--space-2);
    flex-wrap: wrap;
    justify-content: flex-end;
}
.sessions-tab-apy-stats {
    display: flex;
    gap: var(--space-4);
    color: var(--ink-soft);
    font-size: var(--text-md);
    justify-self: end;
}
.sessions-tab-apy-subtitle {
    font-size: var(--text-base);
    color: var(--ink-soft);
    font-weight: 500;
}
.sessions-tab-apy-empty {
    margin: 0;
    padding: var(--space-3) var(--space-4) var(--space-4);
    color: var(--ink-soft);
    font-style: italic;
    font-size: var(--text-md);
}
.sessions-tab-apy-edit {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    align-items: flex-end;
}
.sessions-tab-apy-edit label {
    display: flex;
    flex-direction: column;
    gap: var(--space-05);
    font-size: var(--text-sm);
    color: var(--ink-soft);
}
.sessions-tab-apy-edit-notes input { min-width: 14rem; }
.sessions-tab-apy-group .sessions-tab-table {
    margin: 0 var(--space-4) var(--space-4);
    width: calc(100% - 2rem);
}
.sessions-tab-drag-col { width: 2.2rem; text-align: center; padding-left: var(--space-1); padding-right: var(--space-1); }
.sessions-tab-drag-handle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.6rem;
    height: 1.6rem;
    padding: 0 var(--space-1);
    border-radius: 4px;
    cursor: grab;
    color: var(--ink-soft);
    font-weight: 700;
    line-height: 1;
    user-select: none;
    /* Touch / pen drags must not scroll the surrounding page —
       sessions-drag.js owns the gesture from pointerdown onwards. */
    touch-action: none;
}
.sessions-tab-drag-handle:hover { background: var(--teal-50); color: var(--ink); }
.sessions-tab-drag-handle:active { cursor: grabbing; }
.sessions-tab-table tbody tr.sessions-tab-dragging {
    opacity: 0.55;
    background: var(--teal-50);
}
body.sessions-tab-drag-cursor,
body.sessions-tab-drag-cursor * { cursor: grabbing !important; }
.sessions-tab-inline-trigger {
    background: transparent;
    border: 1px dashed transparent;
    border-radius: 4px;
    padding: var(--space-05) var(--space-2);
    color: inherit;
    text-align: left;
    cursor: pointer;
    font: inherit;
}
.sessions-tab-inline-trigger:hover:not(:disabled) {
    border-color: var(--line);
    background: var(--cream);
}
.sessions-tab-inline-trigger:disabled {
    cursor: not-allowed;
    opacity: 0.7;
}
/* Projected (tariff-estimated, not-yet-charged) amount — muted +
   parenthesised so it never reads as a posted charge. */
.sessions-tab-amount-projected {
    color: var(--ink-soft);
    font-style: italic;
}
.sessions-tab-inline-edit {
    display: flex;
    gap: var(--space-1);
    align-items: center;
}
.sessions-tab-inline-edit input,
.sessions-tab-inline-select {
    font: inherit;
    padding: var(--space-05) var(--space-1);
    border: 1px solid var(--line);
    border-radius: 4px;
    background: var(--card);
}
/* The amount + reason editor needs more horizontal space than the
   plain attendance / time editors — wrap onto two rows in the narrow
   Ποσό cell so the textarea-style reason input does not get clipped. */
.sessions-tab-inline-amount {
    flex-wrap: wrap;
    min-width: 16rem;
}
.sessions-tab-inline-amount input[type="number"] {
    width: 6rem;
}
.sessions-tab-inline-amount input[type="text"] {
    flex: 1 1 12rem;
    min-width: 10rem;
}
.btn-xs {
    padding: var(--space-05) var(--space-2);
    font-size: var(--text-sm);
    line-height: 1.2;
}

/* REQ-F27-002 — global search bar in the MainLayout header. The
   container is positioned: relative so the dropdown can absolutely
   position beneath the input. Hidden on very narrow screens; the
   v1 mobile-overlay TODO is documented in the F27 PR. */
.global-search {
    position: relative;
    flex: 0 1 24rem;
    min-width: 14rem;
    max-width: 28rem;
    display: flex;
    align-items: center;
    background: oklch(1 0 0 / 0.12);
    border: 1px solid oklch(1 0 0 / 0.25);
    border-radius: 0.5rem;
    padding: var(--space-1) var(--space-2);
    color: #ffffff;
    transition: background 120ms ease, border-color 120ms ease;
}
.global-search:focus-within {
    background: oklch(1 0 0 / 0.18);
    border-color: oklch(1 0 0 / 0.55);
}
.global-search__icon {
    flex: 0 0 auto;
    margin-right: var(--space-2);
    opacity: 0.85;
    font-size: var(--text-lg);
}
.global-search__input {
    flex: 1 1 auto;
    background: transparent;
    border: 0;
    color: #ffffff;
    font-size: var(--text-md);
    padding: 0.35rem var(--space-1);
    min-width: 0;
}
.global-search__input::placeholder { color: oklch(1 0 0 / 0.6); }
.global-search__input:focus { outline: none; }
.global-search__hint {
    flex: 0 0 auto;
    /* a11y (axe color-contrast): 0.55 white on the header failed WCAG AA;
       0.9 clears 4.5:1 for this small text. */
    color: oklch(1 0 0 / 0.9);
    font-size: var(--text-xs);
    border: 1px solid oklch(1 0 0 / 0.3);
    border-radius: 0.25rem;
    padding: 0.05rem 0.35rem;
    margin-left: var(--space-2);
    white-space: nowrap;
}
.global-search__spinner {
    flex: 0 0 auto;
    margin-left: var(--space-2);
}

.global-search__dropdown {
    position: absolute;
    top: calc(100% + 0.35rem);
    left: 0;
    right: 0;
    background: var(--card, #ffffff);
    color: var(--ink, #111);
    border: 1px solid var(--line, oklch(0.88 0.01 210));
    border-radius: 0.5rem;
    box-shadow: 0 8px 24px oklch(0 0 0 / 0.18);
    padding: var(--space-1) 0;
    z-index: 60;
    max-height: 70vh;
    overflow: auto;
}
.global-search__empty {
    padding: var(--space-2) var(--space-4);
    margin: 0;
    color: var(--ink-soft, #555);
    font-size: var(--text-md);
}
.global-search__more-hint {
    padding: var(--space-1) 0.9rem var(--space-2);
    margin: 0;
    color: var(--ink-soft, #555);
    font-size: var(--text-xs);
    font-style: italic;
}
.global-search__group-header {
    padding: var(--space-1) var(--space-4) var(--space-1);
    font-size: var(--text-xs);
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--ink-soft, #555);
}
.global-search__row {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--space-2);
    width: 100%;
    text-align: left;
    background: transparent;
    border: 0;
    padding: var(--space-2) 0.9rem;
    cursor: pointer;
    color: inherit;
    font-size: var(--text-md);
}
.global-search__row.is-highlighted,
.global-search__row:hover { background: var(--teal-50, oklch(0.96 0.03 210)); }
/* The row is a three-column grid and only the middle column can shrink, so
   the name — not the ειδικότητες — is what gives way when a therapist holds
   all six. Ellipsis on the name; the secondary stays on one line. */
.global-search__primary {
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.global-search__secondary {
    color: var(--ink-soft, #555);
    font-size: var(--text-sm);
    white-space: nowrap;
}
.global-search__badge {
    display: inline-block;
    padding: var(--space-05) var(--space-2);
    border-radius: 0.35rem;
    font-size: var(--text-xs);
    font-weight: 700;
    color: #ffffff;
    letter-spacing: 0.03em;
}
.global-search__badge--parent { background: oklch(0.45 0.12 155); }
.global-search__badge--child { background: oklch(0.50 0.13 230); }
.global-search__badge--therapist { background: oklch(0.45 0.15 295); }

/* Mobile trigger — only visible on small screens. The inline bar
   collapses to this icon-only button; tapping it sets
   .global-search--mobile-open which promotes the same UI to a
   full-screen overlay. */
.global-search__mobile-trigger {
    display: none;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    background: transparent;
    color: #ffffff;
    border: 1px solid oklch(1 0 0 / 0.3);
    border-radius: 0.5rem;
    cursor: pointer;
    padding: 0;
}
.global-search__mobile-trigger:hover { background: oklch(1 0 0 / 0.1); }
.global-search__mobile-trigger:active { background: oklch(1 0 0 / 0.18); }

.global-search__close {
    display: none;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    background: transparent;
    color: var(--ink, #111);
    border: 1px solid var(--line, oklch(0.85 0.02 210));
    border-radius: 0.5rem;
    cursor: pointer;
    font-size: var(--text-2xl);
    line-height: 1;
    padding: 0;
}

@media (max-width: 768px) {
    .global-search__mobile-trigger { display: inline-flex; }
    /* Hide the inline bar; .global-search--mobile-open below promotes
       it back into a full-screen overlay. */
    .global-search:not(.global-search--mobile-open) { display: none; }

    .global-search.global-search--mobile-open {
        display: flex;
        flex-direction: column;
        align-items: stretch;
        position: fixed;
        inset: 0;
        z-index: 1000;
        background: var(--card, #ffffff);
        color: var(--ink, #111);
        border: 0;
        border-radius: 0;
        padding: var(--space-3);
        gap: var(--space-2);
        max-width: none;
    }
    .global-search--mobile-open .global-search__close { display: inline-flex; }
    .global-search--mobile-open .global-search__icon {
        position: absolute;
        top: 1.25rem;
        left: 4rem;
        color: var(--ink-soft, #555);
        font-size: var(--text-lg);
    }
    .global-search--mobile-open .global-search__input {
        order: 1;
        background: transparent;
        color: var(--ink, #111);
        font-size: var(--text-xl);
        padding: var(--space-3) var(--space-3) var(--space-3) 2.75rem;
        border: 1px solid var(--line, oklch(0.85 0.02 210));
        border-radius: 0.5rem;
    }
    .global-search--mobile-open .global-search__input::placeholder {
        color: var(--ink-soft, #555);
    }
    .global-search--mobile-open .global-search__close { order: 0; align-self: flex-end; }
    .global-search--mobile-open .global-search__spinner { order: 2; }
    .global-search--mobile-open .global-search__dropdown {
        order: 3;
        position: static;
        border: 0;
        box-shadow: none;
        border-radius: 0;
        max-height: none;
        flex: 1 1 auto;
        padding: 0;
    }
}

/* =============================================================
   Home page (Phase A) — operational landing.
   See docs/design/home-dashboard.md for the visual spec.
   ============================================================= */

/* --- Top status strip ------------------------------------------------ */

.home-strip {
    display: grid;
    grid-template-columns: 8rem minmax(0, 2fr) minmax(0, 2fr) auto;
    gap: 0;
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    overflow: hidden;
    margin-bottom: var(--space-4);
}

.home-strip-cell {
    padding: var(--space-3) var(--space-4);
    border-right: 1px solid var(--line);
    min-width: 0;
}
.home-strip-cell:last-child { border-right: 0; }

.home-strip-cell-head {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-bottom: var(--space-2);
}
.home-strip-cell-title {
    font-size: var(--text-xs);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-soft);
    font-weight: 700;
}
.home-strip-cell-count { font-size: var(--text-xs); color: var(--ink-soft); }

.home-strip-pip {
    display: inline-block; width: 0.55rem; height: 0.55rem; border-radius: 999px;
    background: var(--ink-soft);
}
.home-strip-pip.is-live { background: var(--lime-600); box-shadow: 0 0 0 3px oklch(0.85 0.18 130 / 0.35); }

.home-strip-now {
    background: linear-gradient(180deg, var(--teal-50), var(--card));
}
.home-strip-clock {
    font-family: Verdana, 'DejaVu Sans', Geneva, sans-serif;
    font-size: var(--text-4xl);
    line-height: 1;
    color: var(--teal-900);
}
.home-strip-clock-sub {
    margin-top: var(--space-1);
    font-size: var(--text-xs);
    color: var(--ink-soft);
}

.home-strip-empty {
    margin: 0;
    color: var(--ink-soft);
    font-size: var(--text-3xl);
    line-height: 1;
}

.home-strip-cards { list-style: none; margin: 0; padding: 0; display: flex; gap: var(--space-2); flex-wrap: wrap; }
.home-strip-card {
    display: grid;
    grid-template-columns: auto auto 1fr;
    grid-template-rows: auto auto;
    column-gap: var(--space-2);
    align-items: baseline;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    border: 1px solid #B9DDC4;
    background: #EAF6EE;
    color: var(--ink);
    text-decoration: none;
    font-size: var(--text-md);
    min-width: 14rem;
}
.home-strip-card:hover { background: #DCEFE2; }
.home-strip-card-time { font-weight: 700; color: oklch(0.42 0.10 145); }
.home-strip-card-room { color: var(--ink-soft); font-size: var(--text-base); }
.home-strip-card-name { grid-column: 3; font-weight: 600; }
.home-strip-card-meta { grid-column: 1 / -1; font-size: var(--text-xs); color: var(--ink-soft); }

.home-strip-next-rows { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-1); }
.home-strip-next-row {
    display: grid;
    grid-template-columns: 3.4rem 4rem 1fr auto auto;
    column-gap: var(--space-2);
    align-items: baseline;
    padding: var(--space-1) var(--space-2);
    border-radius: 6px;
    text-decoration: none;
    color: var(--ink);
    font-size: var(--text-base);
}
.home-strip-next-row:hover { background: var(--teal-50); }
.home-strip-next-row .t { font-weight: 700; }
.home-strip-next-row .r { color: var(--ink-soft); font-size: var(--text-sm); }
.home-strip-next-row .n { font-weight: 500; }
.home-strip-next-row .th { color: var(--ink-soft); font-size: var(--text-sm); }
.home-strip-next-row .ty { color: var(--ink-soft); font-size: var(--text-xs); }

.home-strip-actions {
    display: flex;
    gap: var(--space-2);
    align-items: center;
    background: var(--teal-50);
}
.home-strip-action {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: 0.55rem 0.9rem;
    border-radius: var(--radius-sm);
    font-weight: 600;
    text-decoration: none;
    border: 1px solid var(--line);
    background: var(--card);
    color: var(--ink);
    cursor: pointer;
    min-height: var(--tap-min);
}
.home-strip-action.is-primary {
    background: var(--lime-500);
    border-color: var(--lime-600);
    color: var(--ink);
}
.home-strip-action.is-primary:hover { background: var(--lime-600); }

/* --- Home grid (calendar + sidebar) --------------------------------- */

.home-grid {
    display: grid;
    /* minmax(0, …), not a bare `1fr`. A bare `1fr` is minmax(auto, 1fr),
       and that `auto` floor resolves to the day calendar's min-content —
       4.5 rem + 8.5 rem per room, i.e. 1432 px at the demo's ten rooms and
       ~1976 px at fourteen. The floor widened the track, the card, and with
       it the entire document on every device, so .day-cal-v's own
       `overflow: auto` never had a narrower box to scroll inside. */
    grid-template-columns: minmax(0, 1fr) minmax(18rem, 22rem);
    gap: var(--space-4);
    align-items: start;
}
/* Grid ITEMS carry `min-width: auto` too, so the same min-content would
   push the card straight back out of a correctly-sized track. */
.home-grid > * { min-width: 0; }
@media (max-width: 1024px) {
    .home-grid { grid-template-columns: minmax(0, 1fr); }
    .home-strip { grid-template-columns: minmax(0, 1fr); }
    .home-strip-cell { border-right: 0; border-bottom: 1px solid var(--line); }
    .home-strip-cell:last-child { border-bottom: 0; }
}

.home-calendar-card {
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: var(--space-4) 1.25rem var(--space-3);
}
.home-calendar-head {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: var(--space-4); flex-wrap: wrap;
    margin-bottom: var(--space-3);
}
.home-calendar-title {
    font-family: Verdana, 'DejaVu Sans', Geneva, sans-serif;
    color: var(--teal-900);
    margin: 0;
    font-size: var(--text-3xl);
}
.home-calendar-sub { margin: var(--space-05) 0 0; color: var(--ink-soft); font-size: var(--text-base); }
.home-calendar-toolbar { display: flex; gap: var(--space-2); flex-wrap: wrap; }

.home-calendar-legend {
    display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4);
    margin-top: var(--space-3); padding-top: var(--space-3);
    border-top: 1px dashed var(--line);
    font-size: var(--text-sm); color: var(--ink-soft);
}
.legend-chip { display: inline-flex; align-items: center; gap: var(--space-1); }
.legend-chip .sw {
    display: inline-block; width: 0.85rem; height: 0.85rem; border-radius: 3px;
    border: 1px solid transparent;
}
.legend-chip.is-live    .sw { background: #DAF1E1; border-color: #7FC596; }
.legend-chip.is-confirmed .sw { background: #EAF6EE; border-color: #B9DDC4; }
.legend-chip.is-pending .sw { background: #FFF6DA; border-color: #E5C36A; }
.legend-chip.is-noshow  .sw { background: #FBE0D6; border-color: #DA8062; }
.legend-chip.is-therapist .sw {
    background: oklch(0.92 0.07 290); border-color: oklch(0.66 0.12 290); width: 0.85rem;
}
.legend-chip.is-amount .sw {
    background: oklch(0.95 0.04 25); border-color: oklch(0.55 0.16 25);
    color: oklch(0.40 0.16 25); font-size: var(--text-xs); font-weight: 700;
    display: inline-flex; align-items: center; justify-content: center;
    width: auto; min-width: 1.2rem; padding: 0 var(--space-1);
}

/* --- Right sidebar -------------------------------------------------- */

.home-sidebar { display: flex; flex-direction: column; gap: var(--space-4); }
.home-rooms-card { padding: 0.85rem var(--space-4); }
.home-rooms-card h2 {
    font-family: Verdana, 'DejaVu Sans', Geneva, sans-serif;
    font-size: var(--text-xl);
    margin: 0;
    display: inline-flex; align-items: center; gap: var(--space-2);
}
.home-rooms-bullet { color: var(--lime-500); }
.btn-link {
    background: transparent; border: 0; color: var(--ink-soft);
    cursor: pointer; font-size: var(--text-sm); padding: 0;
}
.btn-link:hover { color: var(--ink); text-decoration: underline; }

.home-rooms-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.home-rooms-row {
    display: grid;
    grid-template-columns: 1.5rem minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) 0.6rem;
    border-radius: var(--radius-sm);
    border: 1px solid var(--line);
    background: var(--card);
}
.home-rooms-row.is-in-use {
    border-color: #B9DDC4;
    background: oklch(0.985 0.025 145);
}
.home-rooms-badge {
    display: inline-flex; align-items: center; justify-content: center;
    width: 1.5rem; height: 1.5rem; border-radius: 4px;
    background: var(--teal-50);
    color: var(--ink-soft);
    font-size: var(--text-xs); font-weight: 700;
}
.home-rooms-row.is-in-use .home-rooms-badge { background: var(--lime-500); color: var(--ink); }
.home-rooms-body { display: flex; flex-direction: column; min-width: 0; }
.home-rooms-name { font-weight: 600; }
.home-rooms-status { font-size: var(--text-sm); color: var(--ink-soft); }
.home-rooms-tag {
    font-size: var(--text-xs); letter-spacing: 0.05em; font-weight: 700;
    border: 1px solid var(--line);
    border-radius: 999px;
    padding: 0.15rem 0.55rem;
    color: var(--ink-soft);
}
.home-rooms-tag.is-in-use {
    background: var(--lime-500);
    color: var(--ink);
    border-color: var(--lime-600);
}

.home-kpi-shortcut { margin: 0; font-size: var(--text-base); }
.home-kpi-shortcut a { color: var(--ink-soft); text-decoration: none; }
.home-kpi-shortcut a:hover { color: var(--ink); text-decoration: underline; }

/* --- DayCalendarVertical -------------------------------------------- */

/* The room grid is intentionally wider than a phone; it scrolls inside
   this box. `overscroll-behavior-x: contain` keeps that swipe from turning
   into a browser back-gesture on iOS/Android. */
.day-cal-v { position: relative; overflow: auto; overscroll-behavior-x: contain; max-width: 100%; }
.day-cal-v-empty { padding: var(--space-4); }
.day-cal-v-grid {
    display: grid;
    grid-auto-rows: minmax(2rem, auto);
    position: relative;
    /* Header row + N hour rows. The :nth row is computed via inline
       grid-row spans so the column wrappers can absolutely position
       blocks without breaking the implicit row track. */
    grid-template-rows: 2.75rem repeat(var(--hour-rows, 9), var(--row-h, 4.5rem));
    background: var(--card);
}
.day-cal-v-corner {
    position: sticky; left: 0; top: 0; z-index: 3;
    background: var(--card);
    border-bottom: 1px solid var(--line);
    border-right: 1px solid var(--line);
}
.day-cal-v-colhead {
    border-bottom: 1px solid var(--line);
    border-right: 1px solid var(--line);
    padding: 0.4rem 0.55rem;
    background: var(--card);
    position: sticky; top: 0; z-index: 2;
}
.day-cal-v-colhead-row {
    display: flex; align-items: center; gap: var(--space-2);
    font-size: var(--text-base);
}
.day-cal-v-room-badge {
    display: inline-flex; align-items: center; justify-content: center;
    width: 1.4rem; height: 1.4rem; border-radius: 4px;
    background: var(--teal-50); color: var(--ink-soft);
    font-weight: 700; font-size: var(--text-xs);
}
.day-cal-v-room-name { font-weight: 600; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.day-cal-v-room-occ { color: var(--ink-soft); font-size: var(--text-xs); font-variant-numeric: tabular-nums; }
.day-cal-v-occ-bar {
    margin-top: var(--space-1);
    height: 3px;
    background: var(--teal-50);
    border-radius: 999px;
    overflow: hidden;
}
.day-cal-v-occ-bar > span {
    display: block; height: 100%;
    background: var(--lime-500);
}

.day-cal-v-time-axis {
    grid-column: 1;
    position: relative;
    border-right: 1px solid var(--line);
    background: var(--card);
}
.day-cal-v-time-label {
    position: absolute;
    left: 0; right: 0;
    text-align: right;
    padding-right: var(--space-2);
    font-size: var(--text-xs);
    color: var(--ink-soft);
    transform: translateY(-0.5em);
}

.day-cal-v-col {
    position: relative;
    border-right: 1px solid var(--line);
    background: var(--card);
}
.day-cal-v-row-bg, .day-cal-v-row-half {
    position: absolute; left: 0; right: 0;
    pointer-events: none;
}
.day-cal-v-row-bg { border-top: 1px solid var(--line); height: 0; }
.day-cal-v-row-half {
    border-top: 1px dashed var(--line);
    height: 0;
    opacity: 0.45;
}
/* REQ-F04-009 / -010 — mirror of Rheasys.Therapy.UI/wwwroot/calendar.css
   (source of truth). Keep both copies in sync in the same PR. */
.day-cal-v-ooh {
    position: absolute; left: 0; right: 0;
    background: color-mix(in oklch, var(--ink) 12%, transparent);
    pointer-events: none;
    z-index: 0;
}
.day-cal-v-holiday-banner {
    margin: 0 0 var(--space-2) 0;
    padding: var(--space-2) var(--space-3);
    border: 1px solid oklch(0.78 0.13 80);
    background: oklch(0.96 0.06 90);
    color: oklch(0.32 0.07 60);
    border-radius: 0.375rem;
    font-size: var(--text-lg);
}

.day-cal-v-block {
    position: absolute;
    left: 0.25rem; right: 0.25rem;
    /* Above the empty-slot "+" links (z-index 1) so blocks reliably
       receive click / right-click / drag. Without this, sparse views
       (e.g. Αίθουσες) stack the empty links over blocks and the
       context menu never opens. */
    z-index: 2;
    border-radius: var(--radius-sm);
    border: 1px solid #B9DDC4;
    background: #EAF6EE;
    padding: 0.35rem var(--space-2);
    /* Pinned dark ink — block backgrounds are pastel-light in both
       light and dark mode, so the text colour can't follow --ink
       (which flips light in dark mode and disappears against the
       pastel surface). */
    color: oklch(0.22 0.03 215);
    text-decoration: none;
    overflow: hidden;
    display: flex; flex-direction: column; gap: var(--space-05);
    font-size: var(--text-sm);
    box-shadow: 0 1px 0 oklch(1 0 0 / 0.6) inset;
}
.day-cal-v-block:hover { filter: brightness(0.97); }
.day-cal-v-block.is-live {
    border-color: oklch(0.62 0.16 145);
    background: #DAF1E1;
}
.day-cal-v-block.is-noshow {
    background: #FBE0D6;
    border-color: #DA8062;
}
.day-cal-v-block.is-cancelled {
    opacity: 0.5; text-decoration: line-through;
}
.day-cal-v-block.is-completed {
    background: oklch(0.96 0.025 145);
    border-color: #B9DDC4;
}

/* AppointmentContextMenu — right-click menu on calendar blocks.
   The backdrop is invisible but covers the viewport so any click
   outside the menu closes it; the menu itself is positioned with
   client coordinates from the MouseEventArgs. */
.appt-ctx-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1100;
    background: transparent;
}
.appt-ctx-menu {
    position: absolute;
    min-width: 14rem;
    background: var(--surface, #fff);
    border: 1px solid var(--border, #cfd8dc);
    border-radius: var(--radius-sm, 4px);
    box-shadow: 0 4px 16px oklch(0 0 0 / 0.18);
    padding: var(--space-1) 0;
    display: flex;
    flex-direction: column;
}
.appt-ctx-item {
    background: none;
    border: 0;
    text-align: left;
    padding: var(--space-2) 0.85rem;
    font: inherit;
    color: var(--ink, #1c2a33);
    cursor: pointer;
}
.appt-ctx-item:hover:not(:disabled),
.appt-ctx-item:focus-visible {
    background: var(--teal-100, #e6f3f0);
    outline: none;
}
.appt-ctx-item:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}
.appt-ctx-error {
    margin: var(--space-1) 0.85rem var(--space-2);
    color: var(--danger, #b3261e);
    font-size: var(--text-sm);
}
.day-cal-v-block-head {
    display: flex; align-items: baseline; gap: var(--space-1);
    color: oklch(0.42 0.10 145);
    font-weight: 700;
    font-size: var(--text-xs);
}
.day-cal-v-tag-live { color: oklch(0.42 0.10 145); font-size: var(--text-xs); letter-spacing: 0.06em; }
/* User report (page /): completed / paid status icons on session tiles
   (mirror of Rheasys.Therapy.UI/wwwroot/calendar.css — keep both in sync).
   Compact, colour-coded round badges pinned to the right of the block
   head; green «✓» for completed, teal «€» for paid, each aria-labelled. */
.day-cal-v-status-badges {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    line-height: 1;
}
.day-cal-v-status-badges.has-concurrent {
    margin-left: var(--space-1);
}
.day-cal-v-status-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.05rem;
    height: 1.05rem;
    border-radius: 999px;
    font-size: var(--text-xs);
    font-weight: 700;
    line-height: 1;
    border: 1px solid transparent;
}
.day-cal-v-status-badge.is-completed {
    background: var(--success);
    border-color: oklch(0.52 0.130 145);
    color: #fff;
}
.day-cal-v-status-badge.is-paid {
    background: var(--teal-800);
    border-color: oklch(0.44 0.10 212);
    color: #fff;
}
.day-cal-v-status-badge.is-owed {
    /* User report (page /): «money owed» badge — warm red-orange (hue 25,
       matching the «εκκρεμές υπόλοιπο» pill) so it warns and stays apart
       from the teal paid badge. Never co-occurs with is-paid. */
    background: oklch(0.55 0.16 25);
    border-color: oklch(0.44 0.16 25);
    color: #fff;
}
.day-cal-v-block-child { font-weight: 700; }
.day-cal-v-block-type {
    /* Pinned soft ink — see .day-cal-v-block above for the rationale. */
    color: oklch(0.44 0.04 215);
    font-size: var(--text-xs);
}
.day-cal-v-block-foot { display: flex; gap: var(--space-1); flex-wrap: wrap; margin-top: auto; }
.day-cal-v-pill {
    display: inline-flex; align-items: center;
    padding: 0.05rem 0.4rem;
    border-radius: 999px;
    font-size: var(--text-xs);
    border: 1px solid transparent;
    line-height: 1.4;
}
.day-cal-v-pill-therapist {
    background: oklch(0.92 0.07 290);
    border-color: oklch(0.78 0.10 290);
    color: oklch(0.34 0.12 290);
}

.day-cal-v-now-line {
    position: absolute;
    height: 0;
    border-top: 2px solid var(--lime-500);
    pointer-events: none;
    z-index: 2;
}
.day-cal-v-now-label {
    position: absolute;
    left: 0;
    background: var(--lime-500);
    color: var(--ink);
    padding: var(--space-05) var(--space-2);
    border-radius: 4px;
    font-size: var(--text-xs);
    font-weight: 700;
    letter-spacing: 0.04em;
    transform: translateY(-50%);
    z-index: 3;
    pointer-events: none;
    grid-column: 1;
    width: 4.5rem;
    text-align: center;
}

/* Phase D — extra pill flavours on calendar blocks. The block sits on
   a pinned-light background that doesn't flip in dark mode, so pill
   text colours are also pinned (var(--ink-soft) flips light and
   disappears against the pill background). */
.day-cal-v-pill-progress {
    background: oklch(0.94 0.02 210);
    border-color: oklch(0.88 0.025 210);
    color: oklch(0.44 0.04 215);
    font-variant-numeric: tabular-nums;
}
.day-cal-v-pill-amount {
    background: oklch(0.95 0.04 25);
    border-color: oklch(0.55 0.16 25);
    color: oklch(0.40 0.16 25);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

/* Phase C1 — therapist-column mode pills + adjusted column-header
   badge so the inline colour-hint stays readable on dark accents.
   Pinned colours; see .day-cal-v-pill-progress for why. */
.day-cal-v-pill-room {
    background: oklch(0.94 0.02 210);
    border-color: oklch(0.88 0.025 210);
    color: oklch(0.44 0.04 215);
}
.day-cal-v-colhead-row .day-cal-v-room-badge[style*="background"] {
    /* Inline-style takes over background+colour for therapist mode. */
    border: 1px solid oklch(0.30 0.03 210 / 0.4);
}

/* Phase C2 — week-view tweaks. Day columns get a subtle "today" tint
   on the column header + body, anchored against the same grid the
   day calendar uses. */
.week-cal-v .day-cal-v-colhead.is-today {
    background: oklch(0.97 0.04 130);
    border-bottom-color: var(--lime-500);
}
.week-cal-v .day-cal-v-col.is-today {
    background: oklch(0.99 0.015 130);
}
.week-cal-v-day-label {
    font-weight: 700;
    color: var(--teal-900);
    font-size: var(--text-base);
}
.week-cal-v-date-label {
    margin-left: var(--space-2);
    color: var(--ink-soft);
    font-size: var(--text-sm);
    font-variant-numeric: tabular-nums;
    flex: 1;
}

/* Phase C3 — keyboard focus ring on calendar session blocks. The
   block is rendered as <a> so :focus-visible already triggers the
   browser default; we lift it slightly so it sits above the
   neighbouring blocks at column boundaries. */
.day-cal-v-block:focus-visible {
    outline: 2px solid var(--lime-500);
    outline-offset: 2px;
    z-index: 4;
}

/* Phase C4 — click-empty-to-create overlay. Empty half-hour slots
   render a faint "+" that fades in on column hover; the link is
   absolutely positioned so it sits above the row dividers but below
   any session block. */
.day-cal-v-empty {
    position: absolute;
    left: 0.25rem;
    right: 0.25rem;
    border-radius: var(--radius-sm);
    color: var(--ink-soft);
    text-decoration: none;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 700;
    font-size: var(--text-lg);
    opacity: 0;
    transition: opacity var(--transition-fast), background var(--transition-fast);
    z-index: 1;
}
.day-cal-v-col:hover .day-cal-v-empty {
    opacity: 0.5;
}
.day-cal-v-empty:hover,
.day-cal-v-empty:focus-visible {
    opacity: 1 !important;
    background: var(--teal-50);
    outline: 2px dashed var(--lime-500);
    outline-offset: -2px;
}
@media (hover: none) {
    /* Mobile: no hover layer, render at half opacity always so users
       can still see the affordance. */
    .day-cal-v-empty { opacity: 0.4; }
}

/* Phase C5 — drag/resize affordances on calendar session blocks. */
.day-cal-v-block { cursor: grab; touch-action: none; }
/* #724 — a `data-` attribute, not a class: Blazor renders `class` on this
   element (BlockClass) and clears only what it rendered, so a class written
   from JS is a second writer of an attribute the renderer owns. It never
   renders `data-dragging`, so the two cannot reach each other. */
.day-cal-v-block[data-dragging] {
    cursor: grabbing;
    opacity: 0.8;
    outline: 2px solid var(--lime-500);
    outline-offset: 0;
    z-index: 5;
}

/* Drop-target indicator — owner report R-20260731-07 (#573 §6, iPad):
   «When I drag and drop on different date I don't see the square. Eg the
   target date slot.» A move repositions the block inside its OWN column,
   so a cross-column (in week view: cross-DATE) drag gave no feedback in
   the column being dropped on. calendar-drag.js now tints the column
   under the pointer and appends a ghost sized to the dragged block at the
   snapped drop position. Presentational only — the ghost is aria-hidden
   and takes no pointer events, so it can never intercept the drop. */
/* #724 — attribute, not class, for the same reason as [data-dragging]
   above; in the week view the column's class is not even constant
   (`is-today` / `is-nonworking`), so JS and Blazor were writing the same
   string from both ends. */
.day-cal-v-col[data-drop-target] {
    background: var(--teal-50);
}

/* #724 — the ghost is no longer a foreign child of the Blazor-managed
   column. calendar-drag.js creates this layer and parents it to
   `document.body`, which Blazor never diffs (the app roots at #app), and
   positions the ghost `fixed` from the column's own bounding rect.

   z-index sits below `.app-header` (30) and every modal, so an overlay the
   user cannot dismiss can never cover the chrome. It is above the calendar,
   which is a deliberate change from the old in-column z-index 4: a body
   child cannot be pushed underneath page content. In practice the ghost only
   overlaps the dragged block during a SAME-column move, where the drop
   position is already obvious; the cross-column case the ghost exists for
   (R-20260731-07) never overlaps it at all. */
.cal-drop-layer {
    position: fixed;
    inset: 0;
    pointer-events: none;
    z-index: 25;
}
.cal-drop-ghost {
    position: fixed;
    border: 2px dashed var(--lime-500);
    border-radius: var(--radius-sm);
    background: color-mix(in oklch, var(--lime-500) 14%, transparent);
    pointer-events: none;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}

/* #832 — the target says «not here» WHILE the pointer is over it, instead of
   after a confirmation dialog and a round trip to a Postgres exclusion
   constraint.

   REQ-F29-007: THE COLOUR IS NEVER THE ONLY MESSAGE. Three things change at
   once and only one of them is hue — the border goes solid, the ghost carries
   the word «Κατειλημμένο», and the cursor says no. On a greyscale screen, or
   to an operator who cannot separate red from lime, the word and the solid
   border still read. */
.cal-drop-ghost[data-blocked] {
    border-style: solid;
    border-color: #b91c1c;
    background: color-mix(in oklch, #b91c1c 16%, transparent);
    cursor: not-allowed;
}
.cal-drop-ghost__label {
    font-size: var(--text-xs);
    font-weight: 700;
    line-height: 1.1;
    text-align: center;
    padding: 0 var(--space-1);
    color: #7f1d1d;
    background: #fff;
    border-radius: var(--radius-sm);
}
/* Empty when the target is free — no stray chip on an ordinary drag. */
.cal-drop-ghost__label:empty {
    display: none;
}
.day-cal-v-col[data-drop-blocked],
.week-cal-v .day-cal-v-col.is-today[data-drop-blocked] {
    background: #fef2f2;
}
/* The «today» tint would otherwise win over the drop tint in week view
   (same specificity, defined later in the file). */
.week-cal-v .day-cal-v-col.is-today[data-drop-target] {
    background: var(--teal-50);
}
.day-cal-v-resize-handle {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: 6px;
    cursor: ns-resize;
    opacity: 0;
    background: linear-gradient(to bottom, transparent, oklch(0.30 0.04 210 / 0.25));
    border-bottom-left-radius: var(--radius-sm);
    border-bottom-right-radius: var(--radius-sm);
    transition: opacity var(--transition-fast);
    touch-action: none;
}
.day-cal-v-block:hover .day-cal-v-resize-handle,
.day-cal-v-block:focus-within .day-cal-v-resize-handle {
    opacity: 0.9;
}

/* Demo-seed progress dialog (#demo-seed-progress).
   Uses .modal-backdrop + .modal-card + .modal-body / .modal-footer
   from the slot-suggester block above; only the bar primitives are
   bespoke. */
.demo-seed-progress-card { width: min(28rem, 100%); }
.demo-seed-progress-track {
    width: 100%;
    height: 0.75rem;
    background: var(--teal-100);
    border-radius: 999px;
    overflow: hidden;
    margin: var(--space-3) 0 var(--space-1);
}
.demo-seed-progress-fill {
    height: 100%;
    background: var(--teal-500, #14b8a6);
    transition: width 0.25s ease-out;
}
.demo-seed-progress-percent {
    margin: 0 0 var(--space-3);
    font-weight: 600;
    text-align: right;
    color: var(--ink);
}

/* Desktop sidebar collapse — pairs with #side-nav-collapsed-toggle in
   MainLayout. The toggle button now lives inside <NavMenu> so it
   travels with the side nav visually instead of cluttering the header.
   Hidden on tablet/phone where the slide-in drawer is the right
   pattern. The :checked sibling state is the same idiom #88 used for
   the mobile drawer; no JS state. */
.side-nav-collapsed-toggle {
    position: absolute;
    left: -9999px;
    width: 0;
    height: 0;
    opacity: 0;
}
.side-nav .nav-collapse-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    margin: 0 0 var(--space-2) auto;
    border-radius: var(--radius-sm);
    cursor: pointer;
    border: 1px solid var(--line);
    background: transparent;
    padding: 0;
    transition: background var(--transition-fast, 120ms ease-out);
    color: var(--ink-soft);
}
.side-nav .nav-collapse-btn:hover { background: var(--teal-50); color: var(--ink); }
.side-nav .nav-collapse-btn:active { background: var(--teal-100, var(--teal-50)); }
/* Two-stroke chevron drawn via the ::before / ::after pseudos so
   we don't need an icon font. Points left when the sidebar is open,
   right when collapsed (handled below). */
.nav-collapse-glyph {
    position: relative;
    width: 12px;
    height: 12px;
}
.nav-collapse-glyph::before,
.nav-collapse-glyph::after {
    content: '';
    position: absolute;
    width: 7px;
    height: 2px;
    background: currentColor;
    border-radius: 1px;
    left: 2px;
    transform-origin: 1px 50%;
    transition: transform var(--transition-fast, 120ms ease-out);
}
.nav-collapse-glyph::before { top: 3px; transform: rotate(-45deg); }
.nav-collapse-glyph::after  { top: 7px; transform: rotate(45deg); }
.side-nav-collapsed-toggle:checked ~ .app-shell .nav-collapse-glyph::before { transform: rotate(135deg); }
.side-nav-collapsed-toggle:checked ~ .app-shell .nav-collapse-glyph::after  { transform: rotate(-135deg); }

/* The actual collapse: shrink the side-nav to a narrow rail so the
   day grid gets most of the freed space while the toggle button stays
   reachable to expand again. Only above the tablet breakpoint —
   under 1024 px the drawer pattern already handles "hide for more
   space". */
@media (min-width: 1025px) {
    .side-nav { transition: width var(--transition-base, 200ms ease-out); }
    .side-nav-collapsed-toggle:checked ~ .app-shell .side-nav {
        width: 2.5rem;
        padding-left: 0.375rem;
        padding-right: 0.375rem;
    }
    .side-nav-collapsed-toggle:checked ~ .app-shell .side-nav > ul {
        display: none;
    }
}
@media (max-width: 1024px) {
    .side-nav .nav-collapse-btn { display: none; }
}

/* Customer ΑΠΥ tab — collapsible row toggle + nested sessions table. */
.receipt-toggle {
    min-width: 2rem;
    padding: var(--space-05) var(--space-2);
    font-family: monospace;
    line-height: 1;
}
.receipt-sessions-row > td {
    /* var(--teal-50) flips correctly between light/dark; the old
       hardcoded near-white fallback left light text on a light row. */
    background: var(--teal-50);
    padding: var(--space-2) var(--space-3);
}
.data-table-nested {
    width: 100%;
    margin: 0;
    background: transparent;
    box-shadow: none;
}
.data-table-nested th,
.data-table-nested td {
    font-size: var(--text-md);
}

/* CustomerFinancialsTab — ledger totals strip + inline payment form +
   chronological event timeline. Reuses the sessions-tab totals layout
   visually but kept in a separate scope so the two tabs can diverge. */
.financials-totals-grid {
    /* Two columns, three rows. Left column stacks the title, «Πληρωμένο»
       and «Πίστωση γονέα»; right column carries «Σύνολο ΑΠΥ» and
       «Υπόλοιπο προς εξόφληση». The right column starts on row 2 so its
       first value pill («Σύνολο ΑΠΥ») lines up with the first left-column
       value pill («Πληρωμένο») rather than with the title (user report —
       «Σύνολο ΑΠΥ» sat one row too high). Columns are content-sized so the
       pills don't stretch across the card. */
    display: grid;
    grid-template-columns: max-content max-content;
    align-items: baseline;
    justify-content: start;
    gap: var(--space-2) 1.25rem;
    margin: 0;
}
.financials-total {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 0.3rem var(--space-3);
    background: var(--cream);
    display: flex;
    flex-direction: row;
    align-items: baseline;
    gap: var(--space-2);
    /* Auto-size each pill to its content so the amount sits right next to
       its label instead of being stretched across the grid cell (user
       report — balance-summary value pills left a huge empty gap). */
    justify-self: start;
    width: fit-content;
    min-width: 0;
}
/* Explicit placement so the right column drops down one row and its value
   pills align with the left column's value pills. */
.financials-totals-title { grid-column: 1; grid-row: 1; }
.financials-total-paid   { grid-column: 1; grid-row: 2; }
.financials-total-credit { grid-column: 1; grid-row: 3; }
.financials-total-receipted  { grid-column: 2; grid-row: 2; }
.financials-total-outstanding { grid-column: 2; grid-row: 3; }
.financials-total dt {
    font-size: var(--text-base);
    color: var(--ink-soft);
    font-weight: 600;
    margin: 0;
    white-space: nowrap;
}
.financials-total dd {
    font-size: var(--text-lg);
    font-weight: 700;
    color: var(--ink);
    /* Keep the amount next to its label (small gap comes from the flex
       gap above) rather than shoving it to the row's right edge. */
    margin: 0;
    white-space: nowrap;
}
.financials-totals-title {
    display: flex;
    align-items: center;
}
.financials-totals-title dt {
    font-family: Verdana, 'DejaVu Sans', Geneva, sans-serif;
    font-size: var(--text-xl);
    font-weight: 700;
    color: var(--teal-900);
    margin: 0;
    white-space: nowrap;
}
.financials-total-ok   { border-left: 4px solid oklch(0.70 0.16 145); }
.financials-total-warn { border-left: 4px solid oklch(0.70 0.18 25); }

/* Child edit — "Προσθήκη γονέα" parent-search block. The default h3 +
   .form-field spacing made this single label + search box occupy a lot
   of vertical space (user report). Tighten the heading and the field so
   it reads as a compact inline lookup. Scoped to this block only. */
.add-parent-compact { margin-top: var(--space-3); }
.add-parent-compact h3 { margin: 0 0 var(--space-1); }
.add-parent-compact .form-field { margin-bottom: 0; gap: var(--space-1); }
.add-parent-compact .form-field label { font-size: var(--text-sm); }
.add-parent-compact .search-select { max-width: 22rem; }

/* .add-parent-2col reuses the child panel's 2-column idea (see
   .intake-fields) so the shared CustomerForm's field stack no longer
   stretches down the page (user report on PR #476). The fields inside
   its <form> flow into a 2-column grid; section headings (<h2>) and the
   action row (.form-actions) span the full width so they still read as
   dividers. Collapses to one column under 640px. Vertical rhythm comes
   from .add-parent-compact above. */
.add-parent-2col > form {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-2) var(--space-4);
    align-items: start;
}
.add-parent-2col > form > h2,
.add-parent-2col > form > .form-actions,
.add-parent-2col > form > .form-error,
/* #618 — the address search must span both columns. Being first in DOM it
   otherwise lands in the top-LEFT cell with «Οδός και αριθμός» beside it, so
   it reads as one field among many rather than as the thing you use before
   the others. That defeats the ΤΚ-first rule: the whole point is that the
   user types the postal code FIRST and the suggestions collapse to one
   locality, which only happens if the search is what they reach before the
   address fields. Reported by Christina on /children/new, where this wrapper
   is applied — /customers/new was unaffected because it does not use the
   2-column grid, which is why the Playwright pass on that route missed it. */
.add-parent-2col > form > .address-autocomplete {
    grid-column: 1 / -1;
}
.add-parent-2col > form > h2 { margin-top: var(--space-2); }
@media (max-width: 640px) {
    .add-parent-2col > form { grid-template-columns: 1fr; }
}

/* Each staged guardian row: name + «Σχέση με το παιδί» select + remove,
   on one line, wrapping when cramped. */
.guardian-list { list-style: none; }
.guardian-row {
    display: flex;
    gap: var(--space-3);
    align-items: flex-end;
    flex-wrap: wrap;
    padding: var(--space-1) 0;
}
.guardian-row .guardian-name { flex: 1 1 10rem; align-self: center; }
.guardian-row .guardian-relationship {
    flex: 0 0 auto;
    margin-bottom: 0;
    gap: var(--space-1);
}
.guardian-row .guardian-relationship label { font-size: var(--text-sm); }
.guardian-row .guardian-relationship select { max-width: 11rem; }

.financials-pay-form {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
    gap: var(--space-3);
    margin-top: var(--space-2);
}
.financials-pay-form-wide {
    grid-column: 1 / -1;
}
.financials-pay-actions {
    display: flex;
    gap: var(--space-2);
    margin-top: var(--space-3);
}

.financials-timeline tbody tr.financials-row-reversed td {
    text-decoration: line-through;
    color: var(--ink-soft);
}

.financials-toggle {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    background: none;
    border: none;
    padding: 0;
    margin: 0;
    font: inherit;
    color: inherit;
    cursor: pointer;
}
.financials-toggle:hover { text-decoration: underline; }
.financials-toggle:focus-visible {
    outline: 2px solid var(--teal-700);
    outline-offset: 2px;
    border-radius: 3px;
}
.financials-toggle-icon {
    display: inline-block;
    width: 0.85rem;
    color: var(--ink-soft);
    font-size: var(--text-xs);
}
.financials-child-count {
    font-size: var(--text-base);
}

/* Child charge rows keep the normal table text colour and only tint
   the row so they read on both light and dark themes (the th-style
   var(--teal-50) flips contrast in dark mode). */
.financials-timeline tbody tr.financials-child-row td {
    background: color-mix(in oklab, var(--teal-700) 7%, transparent);
}
.financials-timeline tbody tr.financials-child-row td:first-child {
    box-shadow: inset 3px 0 0 0 var(--teal-700);
}
.financials-child-cell { padding-left: 1.75rem !important; }

/* Phase B2 — Εξοπλισμός sidebar panel. Per-room row with comma-style
   chip list, status-coloured chips. */
.home-equipment-card { padding: 0.85rem var(--space-4); }
.home-equipment-card h2 {
    font-family: Verdana, 'DejaVu Sans', Geneva, sans-serif;
    font-size: var(--text-xl);
    margin: 0;
    display: inline-flex; align-items: center; gap: var(--space-2);
}
.home-equipment-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
.home-equipment-row {
    display: grid;
    grid-template-columns: 1.5rem 1fr;
    align-items: start;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-2);
    border-radius: var(--radius-sm);
    border: 1px solid var(--line);
    background: var(--card);
}
.home-equipment-body { display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; }
.home-equipment-room { font-weight: 600; font-size: var(--text-base); }
.home-equipment-items { display: flex; flex-wrap: wrap; gap: var(--space-1); align-items: center; font-size: var(--text-xs); }
.home-equipment-chip {
    display: inline-flex;
    padding: 0.05rem 0.4rem;
    border-radius: 999px;
    border: 1px solid transparent;
    color: var(--ink-soft);
    background: var(--teal-50);
}
.home-equipment-chip.is-ok { background: var(--teal-50); border-color: var(--line); }
.home-equipment-chip.is-maintenance {
    background: oklch(0.95 0.05 80);
    border-color: oklch(0.78 0.10 80);
    color: oklch(0.40 0.10 80);
}
.home-equipment-chip.is-missing {
    background: oklch(0.95 0.04 25);
    border-color: oklch(0.55 0.16 25);
    color: oklch(0.40 0.16 25);
    text-decoration: line-through;
}
.home-equipment-sep { color: var(--ink-soft); opacity: 0.5; }

/* Rich hover preview + drag-reschedule confirmation (mirrors
   Rheasys.Therapy.UI/wwwroot/calendar.css — keep both in sync). */
.day-cal-v-hovercard {
    position: fixed;
    z-index: 1200;
    pointer-events: none;
    max-width: 20rem;
    background: var(--card, #fff);
    color: var(--ink);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: 0 8px 24px oklch(0.2 0.03 215 / 0.22);
    padding: 0.6rem var(--space-3);
    font-size: var(--text-sm);
}
.day-cal-v-hovercard-time {
    display: flex; justify-content: space-between; align-items: baseline;
    gap: var(--space-3); font-weight: 700;
}
.day-cal-v-hovercard-status {
    font-weight: 600; color: var(--ink-soft); font-size: var(--text-xs);
}
.day-cal-v-hovercard-child {
    font-size: var(--text-lg); font-weight: 700; margin: var(--space-05) 0 var(--space-2);
}
.day-cal-v-hovercard-rows { margin: 0; display: grid; gap: var(--space-05); }
.day-cal-v-hovercard-rows > div { display: flex; gap: var(--space-2); }
.day-cal-v-hovercard-rows dt {
    color: var(--ink-soft); min-width: 5.5rem; margin: 0;
}
.day-cal-v-hovercard-rows dd { margin: 0; font-weight: 600; }
.day-cal-v-hovercard-owed {
    display: flex; justify-content: space-between; align-items: baseline;
    margin-top: var(--space-2); padding-top: var(--space-2);
    border-top: 1px solid var(--line);
}
.day-cal-v-hovercard-owed strong {
    font-size: var(--text-xl); color: oklch(0.55 0.18 25);
}

.day-cal-v-confirm-backdrop {
    position: fixed; inset: 0; z-index: 1300;
    background: oklch(0.2 0.03 215 / 0.35);
    display: flex; align-items: center; justify-content: center;
}
.day-cal-v-confirm {
    background: var(--card, #fff); color: var(--ink);
    border-radius: var(--radius);
    box-shadow: 0 12px 40px oklch(0.2 0.03 215 / 0.35);
    padding: var(--space-5); width: min(24rem, calc(100vw - 2rem));
}
.day-cal-v-confirm h2 { margin: 0 0 var(--space-1); font-size: var(--text-xl); }
.day-cal-v-confirm-child { margin: 0 0 var(--space-3); font-weight: 700; }
.day-cal-v-confirm-rows { margin: 0 0 var(--space-4); display: grid; gap: var(--space-1); }
.day-cal-v-confirm-rows > div { display: flex; gap: var(--space-3); }
.day-cal-v-confirm-rows dt {
    color: var(--ink-soft); min-width: 3rem; margin: 0;
}
.day-cal-v-confirm-rows dd { margin: 0; font-weight: 600; }
.day-cal-v-confirm-actions {
    display: flex; gap: var(--space-2); justify-content: flex-end;
}

/* =============================================================
   Evaluation detail panel — used by both /evaluations/{id} and the
   history modal. The iframe viewer renders the browser's native PDF
   viewer inline (no JS dependency) and resizes to a comfortable
   reading height; users open in a new tab for full-screen.
   ============================================================= */
.evaluation-detail-actions {
    display: flex;
    gap: var(--space-2);
    flex-wrap: wrap;
    margin-bottom: var(--space-4);
}
.evaluation-detail-grid {
    align-items: baseline;
}
.evaluation-status {
    display: inline-block;
    padding: 0.1rem var(--space-2);
    border-radius: 999px;
    font-size: var(--text-base);
    background: var(--teal-100);
    color: var(--ink);
    border: 1px solid var(--line);
}
.evaluation-status--draft    { background: oklch(0.95 0.02 90); }
.evaluation-status--pending  { background: oklch(0.92 0.04 60); }
.evaluation-status--approved { background: oklch(0.92 0.05 150); }
.evaluation-status--sent     { background: oklch(0.92 0.04 215); }
.evaluation-status-actions {
    display: inline-flex;
    gap: var(--space-2);
    margin-left: var(--space-2);
    flex-wrap: wrap;
}
.inline-edit-input {
    padding: var(--space-1) var(--space-2);
    min-width: 18rem;
    max-width: 100%;
}
/* Evaluation body-section editor (EvaluationSectionsEditor.razor,
   REQ-F22-007). A list of section cards; each is either a read-only
   view (header + content + row controls) or an inline edit form. */
.evaluation-sections-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}
.evaluation-section-row {
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    padding: 0.75rem var(--space-4);
    background: var(--card);
}
.evaluation-section-view {
    display: flex;
    gap: var(--space-3);
    justify-content: space-between;
    align-items: flex-start;
    flex-wrap: wrap;
}
.evaluation-section-body { flex: 1 1 20rem; min-width: 0; }
.evaluation-section-body h3 { margin: 0 0 var(--space-2); }
.evaluation-section-body p { margin: 0; }
.evaluation-section-controls {
    display: inline-flex;
    gap: var(--space-2);
    flex-wrap: wrap;
    align-items: flex-start;
}
.evaluation-section-form { display: flex; flex-direction: column; gap: var(--space-2); }
/* Inline document preview (DocumentInlineViewer.razor). Shared by the
   consent / diagnosis / evaluation attachment surfaces — a bordered
   card with a filename + actions header above an inline PDF/image
   viewer. The viewer height is supplied inline by the component. */
.document-inline-viewer {
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    padding: var(--space-2) var(--space-3);
    margin-top: var(--space-2);
    background: var(--card);
}
.document-inline-viewer + .document-inline-viewer {
    margin-top: var(--space-3);
}
.document-inline-viewer__header {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    justify-content: space-between;
    align-items: center;
    margin-bottom: var(--space-2);
}
.document-inline-viewer__actions {
    display: inline-flex;
    gap: var(--space-2);
    flex-wrap: wrap;
}
.document-inline-viewer__frame {
    width: 100%;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: oklch(0.97 0.005 215);
}
.document-inline-viewer__image {
    max-width: 100%;
    height: auto;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
}

/* Modal hosting a detail panel from a list (history, etc.). Wider
   than the default .modal-card so the inline PDF viewer fits. */
/* Width only. The height budget is `.modal-card`'s `max-height: 100%`
   (#749 part 3) — this variant used to restate it as
   `calc(100vh - 4rem)`, one rem more than the backdrop actually
   leaves, and sat flush against the bottom edge of the screen. */
.record-detail-modal {
    width: min(64rem, 100%);
}

/* «Νέα ΑΠΥ» creation modal — roomy enough for the receipt form + line
   editor while staying scrollable on short viewports. */
.new-receipt-modal {
    width: min(44rem, 100%);
}

.new-receipt-line {
    margin-top: var(--space-3);
}

/* Diagnosis detail panel form card. The γνωμάτευση PDF preview itself
   is rendered by the shared DocumentInlineViewer (.document-inline-viewer). */
.diagnosis-detail-card {
    background: var(--card);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: var(--space-5);
    margin-bottom: var(--space-4);
}

/* Session detail panel header strip — replaces the page-level button
   row when SessionDetailPanel is embedded in a modal. */
.session-detail-actions {
    display: flex;
    gap: var(--space-2);
    flex-wrap: wrap;
    align-items: center;
    margin-bottom: var(--space-3);
}

/* ── Label/value detail lists (<dl class="detail-grid">) ──────────────
   Owner report R-20260731-06 (#573 §5, reported from an iPad):
   «Λεπτομέρειες ραντεβού Modal should be more compact. It is using lots
   of space unnecessarily.»

   Root cause: `.detail-grid` had NO stylesheet rule at all, so every
   <dl> carrying it fell back to the browser default — the <dt> on its
   own line and the <dd> on the next one, indented 40px. «Λεπτομέρειες
   ραντεβού» has ~10 fields, i.e. ~20 lines plus the default <dl>
   margins: most of an iPad viewport for a handful of short values.

   Lay label and value out as a two-column grid instead: one line per
   field, the label column sized to the longest label, the value column
   taking the rest. Roughly halves the height with no markup change.
   Used by SessionDetailPanel, EvaluationDetailPanel, the myDATA export
   page and the demo-seed dialogs — all the same label/value shape.
   Collapses back to a stack under 480px, where a two-column split would
   squeeze the values too hard. */
.detail-grid {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    column-gap: var(--space-3);
    row-gap: var(--space-1);
    align-items: baseline;
    margin: 0 0 var(--space-3);
}
.detail-grid > dt {
    margin: 0;
    font-size: var(--text-base);
    font-weight: 600;
    color: var(--ink-soft);
}
.detail-grid > dd {
    margin: 0;
    min-width: 0;
}
@media (max-width: 480px) {
    .detail-grid {
        grid-template-columns: minmax(0, 1fr);
        row-gap: var(--space-05);
    }
    .detail-grid > dd {
        margin-bottom: var(--space-1);
    }
}

/* Elias, 2026-08-15, with a mock-up of «Λεπτομέρειες συνεδρίας»: the read
   -only summary and the edit form become ONE label/value list, and the
   controls sit in the value column beside their labels rather than under
   them.

   🔴 THE LAYOUT PRIMITIVE ALREADY EXISTED — this is `.detail-grid` with
   controls in the <dd>, not a new grid. The old dialog stacked
   label-above-control for the editable half while the read-only half used
   this grid two inches higher, so the same screen answered «where is the
   label?» two different ways, and the editable half spent a whole row per
   field on a label of two words.

   It also removes a genuine duplication: Θεραπευτής, Πρόγραμμα and
   Τοποθεσία were shown TWICE — once as read-only text in the summary and
   again as a control in the editor below it. One list, one answer.

   `align-items: center` rather than the base rule's `baseline`, because a
   row whose value is an <input> should centre on the control; baseline
   alignment against a bordered box puts the label visibly high. */
.detail-grid--form {
    align-items: center;
    row-gap: var(--space-2);
}
.detail-grid--form > dt {
    /* The label sits beside a control, so it needs the control's own
       vertical centre rather than a text baseline. */
    align-self: center;
}
.detail-grid--form > dd > .form-field {
    /* The <dd> IS the field wrapper here; a nested .form-field would add
       its own label spacing for a label that has moved to the <dt>. */
    margin: 0;
}
/* Any label INSIDE the value column, at any depth: the label lives in the
   <dt>. Scoped to descendants because a component may nest its own —
   SessionTimeRangeEditor prints «Έναρξη» and «Διάρκεια» two levels down, so
   a direct-child rule left the row showing its label twice. */
.detail-grid--form > dd label {
    /* The label lives in the <dt>. A component that renders its own
       (TherapistTypeahead does) would otherwise print it twice. */
    display: none;
}

/* Two controls under one label — «Έναρξη · Διάρκεια» in the mock. They
   are one decision («when, and for how long»), and splitting them across
   two rows made the operator read two labels to answer it. */
.detail-grid--form .field-pair {
    display: flex;
    gap: var(--space-2);
    align-items: center;
    flex-wrap: wrap;
}

/* The clear «×» belongs INSIDE the field it clears. It used to sit at the
   far right of a full-width row — measured at ~880 px from the input on
   the exported dialog — where nothing connected the two. */
.detail-grid--form .field-clear {
    position: absolute;
    right: var(--space-2);
    top: 50%;
    transform: translateY(-50%);
    border: 0;
    background: none;
    color: var(--ink-soft);
    cursor: pointer;
    line-height: 1;
    padding: var(--space-1);
}
.detail-grid--form .field-with-clear {
    position: relative;
    display: inline-block;
}
.detail-grid--form .field-with-clear > input {
    /* Room for the button, so a long therapist name does not run under it. */
    padding-right: var(--space-6);
}
/* #762 — TWO label/value pairs per line in a wide dialog.
   #573 §5 halved this list's height by giving `.detail-grid` a rule at
   all; this halves it again where there is room, and it is the same
   argument #655 and #749 R4 made about form fields, applied to the shape
   that carries the same problem. A dialog is where it matters most,
   because a dialog cannot grow past its height budget: every row saved is
   a row the operator does not scroll past to reach the actions.

   MEASURED, «Λεπτομέρειες συνεδρίας» at 1280 px before this rule: 7
   label/value pairs on 7 rows, and a body showing 592 px of 1 359 —
   767 px, more than half the panel, below the fold.

   Scoped to `.record-detail-modal`, i.e. the wide card, and gated at
   980 px so the pairs only appear when each value still gets a usable
   column. The standalone `/sessions/{id}` page is untouched: it has a
   whole page to be tall in, and it is the dialog that Elias reported. */
@media (min-width: 980px) {
    /* 🔴 …but NOT the form variant. #762 pairs label/value TWO PER LINE in a
       wide dialog, which is a real win for READ-ONLY pairs — two short values
       per line, half the height. A row whose value is a CONTROL is the
       opposite case: it needs the width, and pairing it clipped «Διάρκεια»
       at the card edge, pushed the therapist's clear «×» outside its own
       field and wrapped the action row onto two lines. Elias's mock is a
       single column for exactly this reason.

       Scoped as an exclusion rather than by deleting #762, which still
       serves every other read-only .detail-grid in a dialog. */
    .record-detail-modal .modal-body .detail-grid:not(.detail-grid--form) {
        grid-template-columns:
            max-content minmax(0, 1fr) max-content minmax(0, 1fr);
        column-gap: var(--space-4);
    }
    /* A value that needs the room — notes, a cancellation reason — takes
       the rest of the line rather than being squeezed into a quarter of
       it. Marked at the call site, because only the author knows which
       values are prose. */
    .record-detail-modal .modal-body .detail-grid:not(.detail-grid--form) > dd.detail-grid-wide {
        grid-column: 2 / -1;
    }
}

/* The sub-cards of the session panel («Συνδεδεμένη ΑΠΥ», «Υπενθυμίσεις»).
   Was an inline `margin-top:1rem` on each; as a class it can also drop the
   card padding when the panel is inside a modal, where the modal already
   supplies the frame and 1.25rem of nested padding is pure waste (#573 §5). */
.session-detail-section {
    margin-top: var(--space-3);
}
.modal-body .session-detail-section {
    padding: var(--space-3);
}
.modal-body .session-detail-section h2 {
    font-size: var(--text-lg);
    margin-bottom: var(--space-2);
}

/* ── Bulk session-cost re-adjustment (gap #10 / PDF p. 14) ───────────── */
.reprice-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-top: var(--space-3); }
.reprice-chip {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-1) var(--space-2);
    background: var(--teal-50);
    border: 1px solid var(--teal-100);
    border-radius: 0.4rem;
}
.reprice-autocomplete {
    list-style: none;
    margin: var(--space-1) 0 0;
    padding: var(--space-1);
    border: 1px solid var(--border, #d0d7de);
    border-radius: 0.4rem;
    background: var(--surface, #fff);
    max-height: 14rem;
    overflow-y: auto;
}
.reprice-autocomplete li { margin: 0; }
.reprice-autocomplete button {
    display: block;
    width: 100%;
    text-align: left;
    padding: 0.4rem var(--space-2);
    background: transparent;
    border: 0;
    cursor: pointer;
    border-radius: 0.3rem;
}
.reprice-autocomplete button:hover { background: var(--teal-50); }

.reprice-totals {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-5);
    margin: var(--space-2) 0 var(--space-4);
}
.reprice-total { display: flex; flex-direction: column; }
.reprice-total-label { font-size: var(--text-sm); color: var(--muted, #57606a); }
.reprice-total-value { font-size: var(--text-xl); font-weight: 700; font-variant-numeric: tabular-nums; }

.reprice-delta { font-variant-numeric: tabular-nums; }
.reprice-delta-up { color: var(--danger); }
.reprice-delta-down { color: oklch(0.55 0.13 145); }

.reprice-row-billed { opacity: 0.65; }
.reprice-tag {
    display: inline-block;
    padding: 0.1rem var(--space-2);
    border-radius: 999px;
    font-size: var(--text-sm);
    font-weight: 600;
    background: var(--teal-50);
    color: var(--teal-900);
}
.reprice-tag-billed { background: var(--warn-100, #fdf1cf); color: var(--warn-900, #7a5c00); }
/* ============================================================
   REQ-F04-011 — «Πρόγραμμα ημέρας» (day-programme view + A4 print).
   Gap-analysis #7 / competitor PDF pp. 12–13. An agenda-style day
   sheet: time slots down the page, each listing its sessions with
   child + therapist + room + status (colour-coded like the calendar
   blocks). The @media print block turns it into a clean A4 hand-out.
   ============================================================ */

.day-programme {
    display: block;
    margin-top: var(--space-2);
}

/* Screen-only long date under the toolbar; the print header carries
   its own dated line so this is hidden when printing. */
.day-programme-screen-date {
    margin-top: 0;
}

/* Non-working-day banner — mirrors the calendar's amber holiday /
   closed strip so the two surfaces read consistently. */
.day-programme-daytag {
    display: inline-block;
    padding: 0.35rem var(--space-3);
    border-radius: var(--radius-sm);
    font-weight: 600;
    font-size: var(--text-md);
    margin: 0 0 var(--space-4);
}

.day-programme-daytag.is-holiday {
    background: oklch(0.95 0.06 75);
    border: 1px solid oklch(0.80 0.10 75);
    color: oklch(0.42 0.10 60);
}

.day-programme-daytag.is-closed {
    background: oklch(0.95 0.01 210);
    border: 1px solid var(--line);
    color: var(--ink-soft);
}

.day-programme-empty {
    padding: var(--space-5) 0;
}

/* Slot list: a time gutter on the left, session cards to its right. */
.day-programme-slots {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
}

.day-programme-slot {
    display: grid;
    grid-template-columns: 5rem 1fr;
    gap: var(--space-3);
    align-items: start;
    border-top: 1px solid var(--line);
    padding-top: var(--space-3);
}

.day-programme-slot:first-child {
    border-top: 0;
    padding-top: 0;
}

.day-programme-slot-time {
    font-weight: 700;
    font-size: var(--text-xl);
    color: var(--ink);
    padding-top: var(--space-1);
    font-variant-numeric: tabular-nums;
}

.day-programme-slot-items {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.day-programme-item {
    border: 1px solid var(--line);
    border-left: 4px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--card);
}

/* Colour-coded left border by status — same intent as the calendar
   block classes (PDF p. 12: in-centre / cancelled colour-coding). */
.day-programme-item.is-scheduled { border-left-color: var(--teal-600); }
.day-programme-item.is-completed { border-left-color: var(--success); }
.day-programme-item.is-noshow    { border-left-color: oklch(0.70 0.14 55); }
.day-programme-item.is-cancelled { border-left-color: var(--danger); }
.day-programme-item.is-cancelled .day-programme-item-child { text-decoration: line-through; }

.day-programme-item-link {
    display: grid;
    grid-template-columns: 6.5rem 1.6fr 1.4fr 1fr auto;
    gap: var(--space-2) var(--space-4);
    align-items: baseline;
    padding: 0.6rem 0.85rem;
    text-decoration: none;
    color: var(--ink);
}

.day-programme-item-link:hover {
    background: oklch(0.975 0.01 205);
}

.day-programme-item-time {
    font-variant-numeric: tabular-nums;
    color: var(--ink-soft);
    font-size: var(--text-md);
}

.day-programme-item-child {
    font-weight: 600;
}

.day-programme-item-therapist,
.day-programme-item-venue {
    color: var(--ink-soft);
    font-size: var(--text-md);
}

.day-programme-item-status {
    justify-self: end;
    font-size: var(--text-sm);
    font-weight: 600;
    padding: 0.1rem var(--space-2);
    border-radius: 999px;
    border: 1px solid var(--line);
    white-space: nowrap;
}

.day-programme-item-status.is-scheduled { color: var(--teal-600); }
.day-programme-item-status.is-completed { color: var(--success); }
.day-programme-item-status.is-noshow    { color: oklch(0.55 0.14 55); }
.day-programme-item-status.is-cancelled { color: var(--danger); }

/* Print header is invisible on screen; the @media print block below
   reveals it at the top of the A4 sheet. */
.day-programme-print-header {
    display: none;
}

@media (max-width: 640px) {
    .day-programme-item-link {
        grid-template-columns: 1fr auto;
        grid-auto-rows: auto;
    }
    .day-programme-item-time { grid-column: 1; }
    .day-programme-item-status { grid-column: 2; grid-row: 1; }
    .day-programme-item-child,
    .day-programme-item-therapist,
    .day-programme-item-venue { grid-column: 1 / -1; }
}

/* ------------------------------------------------------------
   A4 print layout. Hides the app chrome (header / side-nav /
   footer / mobile tabbar / toolbar) and lays the day-sheet out
   on portrait A4 with a dated, clinic-branded header that repeats
   sensibly across page breaks.
   ------------------------------------------------------------ */
@media print {
    @page {
        size: A4 portrait;
        margin: 14mm 12mm;
    }

    /* Kill the shell chrome so only the day-sheet prints. */
    .app-header,
    .app-footer,
    .side-nav,
    .nav-drawer-scrim,
    .mobile-tabbar,
    .connectivity-banner,
    .license-utilization-banner,
    .session-recording-banner,
    .day-programme-toolbar,
    .day-programme-screen-date,
    .day-programme-print-btn {
        display: none !important;
    }

    html, body,
    .app-shell,
    .app-body,
    .app-main {
        background: #fff !important;
        margin: 0 !important;
        padding: 0 !important;
        display: block !important;
    }

    .app-main {
        overflow: visible !important;
    }

    /* Reveal the branded, dated print header. */
    .day-programme-print-header {
        display: flex !important;
        justify-content: space-between;
        align-items: flex-end;
        gap: var(--space-4);
        border-bottom: 2px solid #222;
        padding-bottom: var(--space-2);
        margin-bottom: var(--space-4);
    }

    .day-programme-print-brand strong {
        display: block;
        font-size: 15pt;
    }

    .day-programme-print-branch {
        font-size: 10pt;
        color: #333;
    }

    .day-programme-print-meta {
        text-align: right;
        font-size: 10pt;
    }

    .day-programme-print-date {
        display: block;
        font-weight: 600;
        font-size: 11pt;
    }

    .day-programme-print-count {
        color: #333;
    }

    .day-programme,
    .day-programme-slots,
    .day-programme-item {
        color: #000 !important;
    }

    .day-programme-slots { gap: 0; }

    /* Keep a slot's sessions together across a page break, and never
       break inside a single session row. */
    .day-programme-slot {
        break-inside: avoid;
        page-break-inside: avoid;
        border-top: 1px solid #bbb;
        padding: var(--space-1) 0;
    }

    .day-programme-item {
        break-inside: avoid;
        page-break-inside: avoid;
        border: 1px solid #ccc;
        border-left: 4px solid #888;
        margin: var(--space-05) 0;
    }

    .day-programme-item-link {
        color: #000 !important;
        padding: var(--space-1) var(--space-2);
    }

    .day-programme-item-link:hover { background: transparent; }

    .day-programme-item-status {
        border-color: #999;
        color: #000 !important;
    }

    /* Amber loses meaning in mono print — keep the banners legible. */
    .day-programme-daytag {
        border: 1px solid #333 !important;
        color: #000 !important;
        background: #f2f2f2 !important;
    }

    a[href] { text-decoration: none; }
}


/* R-20260722 «make compact»: side-by-side form fields on wide screens,
   stacking naturally on narrow ones. */
.form-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
    gap: 0 var(--space-4);
    align-items: start;
}

/* Child Οικονομικά account (2026-07-23): the account summary, the
   +/− ledger colours, and the session-fold selection bar. */
.ledger-summary {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    gap: var(--space-3) var(--space-5);
    margin: 0;
}
.ledger-summary > div { display: flex; flex-direction: column; }
.ledger-summary dt { font-size: var(--text-base); color: var(--muted, #64748b); }
.ledger-summary dd { margin: 0; font-size: var(--text-xl); font-weight: 600; }
.ledger-plus { color: #15803d; }
.ledger-minus { color: #b91c1c; }

.fold-bar {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3) var(--space-5);
    align-items: end;
    justify-content: space-between;
    margin-top: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--border, #e2e8f0);
    border-radius: 0.5rem;
    background: var(--surface-2, #f8fafc);
}
.fold-bar-figures { display: flex; flex-wrap: wrap; gap: var(--space-1) var(--space-5); align-items: center; }
.fold-bar-controls { display: flex; flex-wrap: wrap; gap: var(--space-3); align-items: end; }

/* Child Οικονομικά (2026-07-24): one compact spreadsheet-style ledger —
   ΑΠΥ receipts with their billed sessions nested beneath, an
   «Αχρέωτες συνεδρίες» group, the ΕΟΠΥΥ αποδείξεις, and a totals footer. */
.ledger-scroll { width: 100%; overflow-x: auto; overscroll-behavior-x: contain; margin-top: var(--space-3); }
/* Phone-only affordance; see the media block at the end of this section. */
.ledger-scroll-hint { display: none; margin: var(--space-3) 0 0; font-size: var(--text-sm); color: var(--ink-soft); }
.ledger-sheet {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--text-md);
    font-variant-numeric: tabular-nums;
}
.ledger-sheet th, .ledger-sheet td {
    padding: var(--space-1) var(--space-2);
    border-bottom: 1px solid var(--line, #e2e8f0);
    text-align: left;
    vertical-align: middle;
    white-space: nowrap;
}
.ledger-sheet th {
    background: var(--teal-50, #f0fdfa);
    font-weight: 700;
    color: var(--teal-900, #134e4a);
    /* NB: no `position: sticky` here. The ledger is not inside a
       vertical-scroll container (`.ledger-scroll` only scrolls on X),
       so a sticky thead would stick to the page viewport and slide
       under the 84px sticky `.app-header` (z-index 30) — hiding the
       first body rows (owner report 2026-07-24, «οι πρώτες τρεις
       γραμμές δεν φαίνονται»). A static header keeps every row visible. */
}
.ledger-sheet td.num, .ledger-sheet th.num { text-align: right; }
.ledger-sheet .ledger-sel { width: 2rem; text-align: center; }
.ledger-sheet .ledger-act { width: 1%; text-align: right; }
.ledger-sheet .ledger-indent { padding-left: 1.75rem; color: var(--muted, #64748b); }
/* Report R-20260727-02 — the ΑΠΥ folded (nested) under its παραπεμπτικό sits a
   level deeper than the referral row it continues. */
.ledger-sheet .ledger-indent-2 { padding-left: 3.25rem; }
/* Report R-20260727-03 — the ΑΠΥ's own sessions nest one level deeper still,
   beneath the ΑΠΥ that is itself nested under the παραπεμπτικό. */
.ledger-sheet .ledger-indent-3 { padding-left: 4.75rem; }
.ledger-sheet tr.ledger-eopyy-apy > td { background: #f7fdf9; }
.ledger-sheet tr.ledger-eopyy-apy-session > td { background: #fbfefc; }
/* Payer line on each ΑΠΥ group row (owner report 2026-07-24 «ποιος πληρώνει»). */
.ledger-sheet .ledger-payer {
    display: block;
    font-size: var(--text-sm);
    color: var(--muted, #64748b);
    white-space: normal;
}
/* Group header rows (ΑΠΥ / Αχρέωτες / ΕΟΠΥΥ). */
.ledger-sheet tr.ledger-group > td { background: var(--surface-2, #f8fafc); border-top: 2px solid var(--line, #e2e8f0); }
.ledger-sheet tr.ledger-group-unbilled > td { background: #fffbeb; }
.ledger-sheet tr.ledger-group-eopyy > td { background: #f0fdf4; }
/* Nested session/detail rows. */
.ledger-sheet tr.ledger-child > td { color: var(--ink, #1e293b); }
.ledger-sheet tr.ledger-empty > td { padding: 0; border-bottom: none; }
/* Totals footer. */
.ledger-sheet tfoot td { border-bottom: none; padding-top: var(--space-1); padding-bottom: var(--space-1); }
.ledger-sheet tr.ledger-total td { color: var(--muted, #64748b); }
.ledger-sheet tr.ledger-total:first-child td { border-top: 2px solid var(--line, #e2e8f0); padding-top: var(--space-2); }
.ledger-sheet tr.ledger-balance td {
    border-top: 1px solid var(--line, #e2e8f0);
    font-size: var(--text-xl);
    padding-top: var(--space-2);
    padding-bottom: var(--space-2);
}

/* ── Child «Οικονομικά» on phones ─────────────────────────────────────
   Seven money columns cannot fit 320 px, so the sheet scrolls sideways —
   but at the baseline only 28 % of it was reachable (238 px of clientWidth
   against an 844 px table), because the card's own 24 px side padding ate
   a sixth of the viewport before the table even started. Full-bleed the
   scroller back out to the card edges, tighten the cells, and say out loud
   that it scrolls. This is the screen where money is settled.

   The <thead> deliberately stays `position: static` — see the note on
   `.ledger-sheet th` above; making it sticky slid it under the 84 px
   sticky .app-header and hid the first rows (owner report 2026-07-24). */
@media (max-width: 640px) {
    /* Applies to every .details-card, not just this one: 24 px of side
       padding is 15 % of a 320 px viewport on a card that is already
       inside .app-main's own 16 px. */
    .details-card { padding-left: var(--space-3); padding-right: var(--space-3); }
    .ledger-scroll {
        margin-left: calc(var(--space-3) * -1);
        margin-right: calc(var(--space-3) * -1);
        width: auto;
    }
    .ledger-scroll-hint { display: block; }
    .ledger-sheet { font-size: var(--text-base); }
    .ledger-sheet th, .ledger-sheet td { padding: var(--space-1) var(--space-2); }
    .ledger-sheet .ledger-indent { padding-left: var(--space-4); }
    .ledger-sheet .ledger-indent-2 { padding-left: 1.9rem; }
    .ledger-sheet .ledger-indent-3 { padding-left: 2.8rem; }
}

/* ── Tap targets on phones (WCAG 2.2 «Target Size (Minimum)») ─────────
   Deliberately at the END of the sheet: every control below is declared
   thousands of lines after the phone breakpoint block near the top, so an
   override placed up there loses on source order at equal specificity.
   Height only where the control's width already carries a label — nothing
   here widens a row that has to fit 320 px. */
@media (max-width: 640px) {
    .app-header .help-button,
    .app-header .global-search__mobile-trigger {
        width: var(--tap-min, 44px);
        height: var(--tap-min, 44px);
        min-width: var(--tap-min, 44px);
    }
    /* Ημέρα / Εβδομάδα / Μήνας / Σήμερα — the calendar's primary control. */
    .calendar-view-toggle .btn-toggle { min-height: var(--tap-min, 44px); }
    /* Password reveal on /login. */
    .eye-btn { min-width: var(--tap-min, 44px); min-height: var(--tap-min, 44px); }
}

/* #622 — planning grid entry point on the child card, and the dialog it
   opens. The modal is deliberately wide and tall: a grid squeezed into a
   default dialog defeats the point of a horizontal time axis. */
.patient-header-actions {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    margin-left: auto;
}

.modal-card.planning-grid-modal {
    width: min(96vw, 1500px);
    max-width: 96vw;
    height: min(88vh, 900px);
    display: flex;
    flex-direction: column;
}

.modal-card.planning-grid-modal .modal-body {
    display: flex;
    flex-direction: column;
    min-height: 0;
    flex: 1 1 auto;
    /* #675 — `auto`, not `hidden`. The dialog has a FIXED height, so with
       `hidden` anything the grid could not fit was silently sliced: when
       the overview wrapped below the grid its «Προγραμματισμένες
       συνεδρίες» heading came out cut in half across the bottom edge,
       with no scrollbar to say anything was there. The scroller inside
       still does the horizontal work and normally nothing overflows here
       at all — this is the difference between degrading and lying. */
    overflow: auto;
}
/* =============================================================
   Δοκιμαστικά δεδομένα — compact seeder screens.

   User report (Elias, from the preview): «Make seeder page compact. It
   uses too much space.» The configure screen stacks 47 fields one per
   line inside eight <fieldset>s, and .form-fieldset had no rule at all,
   so it rendered on browser defaults — roughly two and a half screens of
   scrolling to reach the submit button.

   Same treatment as .intake-fields (Children/New) and .add-parent-2col
   (Children/Edit): a scoped multi-column grid plus a tighter vertical
   rhythm, collapsing to one column on narrow viewports so mobile is
   unaffected. Scoped to .seed-config so the four OTHER pages using
   .form-fieldset (Branches/New, Branches/Edit, Campaigns/New,
   BulkAttendancePanel) keep their current layout.

   Compact is not cryptic (REQ-F29-007): every label, its «(όρια: …)»
   hint and the field order are untouched — only the box they sit in got
   smaller.
   ============================================================= */
.seed-config .form-fieldset {
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    padding: var(--space-2) var(--space-3) var(--space-1);
    margin: 0 0 var(--space-3);
    min-width: 0;
}
.seed-config .form-fieldset > legend {
    font-size: var(--text-base);
    font-weight: 700;
    color: var(--ink);
    padding: 0 var(--space-2);
}

/* The fields — and ONLY the fields — are the grid. The <legend> is
   deliberately left outside it: #618 was exactly this mistake, a
   full-width heading dropped into a half-width cell beside an unrelated
   field because a grid lays its children out in DOM order. Keeping the
   legend a sibling of the grid rather than a member of it means there is
   no grid-column span to forget. */
.seed-fields {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0 var(--space-4);
    align-items: start;
}
.seed-fields .form-field { margin-bottom: var(--space-2); min-width: 0; }
.seed-fields .form-field label { font-size: var(--text-sm); }
.seed-fields .form-field input,
.seed-fields .form-field select { padding: 0.4rem 0.55rem; font-size: var(--text-md); }

/* #696 UI follow-up — the cost notice above the density checkbox. It is a
   sibling of .seed-fields, never a member: a paragraph dropped into the grid
   would render inside one third-width cell, which is the #618 mistake. It is
   styled as a notice rather than as `.muted` on purpose — «this takes 80
   seconds instead of 5» is the one line on the screen the operator must not
   skim past. */
.seed-config .seed-density-note {
    margin: 0 0 var(--space-2);
    padding: var(--space-2) var(--space-3);
    border-left: 3px solid var(--warn-border, #b8860b);
    background: var(--surface-muted, rgba(0, 0, 0, 0.03));
    font-size: var(--text-sm);
    line-height: 1.5;
}

@media (max-width: 1100px) {
    .seed-fields { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 700px) {
    .seed-fields { grid-template-columns: 1fr; }
}

/* Button rows on both seeder screens. Previously inline styles repeated
   in two files; identical rendering, one definition. */
.seed-actions,
.seed-buttons {
    display: flex;
    gap: var(--space-2);
    flex-wrap: wrap;
}
.seed-actions { margin-top: var(--space-2); }

/* ---------- «Επεξεργασία προγράμματος θεραπείας» panel (#684) ----------
   Live-demo report: the screen is «too tall — too much empty space, that
   forces you to scroll». Two of the three causes are markup (the fields
   moved to `.form-columns`, matching Programs/New.razor per #655); the
   third is here. The programme's read-only identity — code, end date,
   status — was a plain <dl>, which stacks each dt/dd pair on its own
   line: six rows of chrome before the first thing the operator came to
   change. Laid out as columns it is one row, and it reads better besides,
   because the three facts belong together. */
.program-edit-meta {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-5);
    margin: 0 0 var(--space-3);
}
.program-edit-meta > div { display: flex; flex-direction: column; }
.program-edit-meta dt {
    font-size: var(--text-sm);
    color: var(--ink-soft);
}
.program-edit-meta dd { margin: 0; font-weight: 500; }

/* v0.147 — the row now carries two CONTROLS as well as four facts: the
   ειδικότητα picker and the start date moved into it, so the whole of what a
   programme IS reads on one line. `.form-field` supplies a bottom margin that
   would push the two control cells out of line with the plain ones, and the
   labels in this row are <dt>s, so neither is wanted here. */
.program-edit-meta dt > label { font: inherit; color: inherit; margin: 0; }
.program-edit-meta dd > .form-field,
.program-edit-meta dd input,
.program-edit-meta dd select { margin-bottom: 0; }
/* The row must stay ONE line at the modal's width, so the cells hold their
   own content rather than stretching to the widest. */
.program-edit-meta > div { flex: 0 0 auto; }

/* 🔴 «Ημ/νία λήξης» is the end of the programme's CYCLE, and a programme is
   explicitly renewed or ended (Elias, 2026-08-16). A cycle end that has
   passed is therefore a decision waiting to be made, on a programme that is
   still «Ενεργό» and still billing — the status badge beside it cannot say
   this, which is why the date says it itself.

   `color` and not a background: this is one cell of a dense row, and a filled
   badge here would out-shout the status badge two cells over, which is the
   one thing on the row that IS a badge. */
.program-cycle-end--expired {
    color: var(--danger);
    font-weight: 700;
}

/* Hosted in a modal the panel drops the card chrome (the modal supplies
   its own padding and background), so only the standalone page carries
   `.login-card`. Nothing else differs between the two hosts. */
.program-edit-panel > .form-actions { margin-bottom: 0; }

/* ---------- #806 — the programme form, compacted --------------------
   Elias, in one sitting: «Θεραπευτής (προαιρετικό override) and Διάρκεια
   (λεπτά) should show value»; «The repeated sessions take too much space
   and can be more compact»; «Διακανονισμός πληρωμής: — can be more
   compact».

   🔴 THE RULES, written down here so the next panel does not need a
   fourth report. Every one of them is a rule about VERTICAL space, which
   is what «compact» always turns out to mean:

     R1. A short value takes the width it MEANS, never the width of its
         container — #762's --field-w-* tokens. A field that cannot share
         its line pushes every field below it down, so a horizontal
         complaint is a scrolling one.
     R2. A form that is not a login is not a login card. `.login-card`
         caps at 32 rem, and at 32 rem `.form-columns` resolves to ONE
         column: the create and edit pages were a single stack of
         full-width fields, which is Elias's list of «fields that sprawl»
         exactly. All THREE hosts — create page, standalone edit, modal —
         get the same 76 rem, so they read alike.
     R3. Repeating rows are a GRID, not a stack of cards. Chrome per
         repetition multiplies.
     R4. An inherited value is DISPLAYED, not implied. «— Ίδια με
         πρόγραμμα —» names the source and hides the value; the two facts
         «nothing is set» and «the programme's value applies» need to be
         distinguishable (REQ-F29-007).
     R5. Where a section genuinely needs its space, it keeps it — the
         OffSite address block below, and the «Διακανονισμός πληρωμής»
         dialog, are both left alone deliberately. Compactness is not the
         goal; readable in one glance is.

   R2. `min()` rather than a flat 76rem so the narrow-screen behaviour is
   unchanged; this only lifts a ceiling. The number is not free-hand: it is
   what the WHEN row needs to hold a therapist's name at the same time as a
   weekday, MEASURED rather than guessed — see the note below. */
.program-form-card { max-width: min(76rem, 100%); }

/* R1 + R3 — a repeating session as grids of meaning-sized cells rather
   than a card of full-width fields. The chrome goes first: `.details-card`
   spent 20 px of padding top and bottom plus 12 px of margin — 52 px per
   repeat — framing content the panel already frames. */
.program-slot {
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--card);
    padding: var(--space-3);
    margin-bottom: var(--space-2);
}
.program-slot-grid {
    display: grid;
    gap: var(--space-2) var(--space-3);
    /* `start`, not `end`. Two of these fields carry a hint line under the
       control, so bottom-aligning the BOXES lifts their controls ~19 px
       above their row-mates' — the labels and inputs stop lining up and
       the row reads as two. Top-aligned, every label starts on the same
       line, which is what «one row» has to mean to a reader. */
    align-items: start;
}
/* R2 again, for the modal host: the panel is the same form in a dialog, so
   it gets the same width. Scoped to its own class rather than widening
   `.record-detail-modal`, which the session panel also uses and which
   carries a committed baseline. */
.program-edit-modal { width: min(76rem, 100%); }
/* 🔴 NOT one auto-flowing grid for all seven controls, and NOT six equal
   columns. Both were tried and both were MEASURED clipping a value.

   At `repeat(6, 1fr)` the therapist track came to 317 px and «— Ανάθεση
   προγράμματος: χωρίς ανάθεση —» rendered as «— Ανάθεση προγράμματος:
   χωρί»: the fix for a field that hid its value shipping a field that
   shows half of it. Widening that one track to 1.5fr left it 14.7 px
   short and the duration 6.3 px short — numbers from the browser, not
   from arithmetic, because a <select> clips with no ellipsis and no
   scrollbar and there is nothing to see.

   The reason no single track set works is that the row holds two kinds of
   value. Tracks wide enough for a therapist's name are wasted on
   «Δευτέρα»; tracks that fit «Δευτέρα» cut the name. So the repeat is TWO
   grids — WHEN and WHERE — each sized for its own content. That is also
   how it reads: when the session is and who takes it, then where. */
.program-slot-when {
    grid-template-columns:
        minmax(6rem, 0.7fr)     /* Ημέρα — «Παρασκευή» */
        minmax(6rem, 0.7fr)     /* Ώρα — capped at --field-w-time anyway */
        minmax(0, 2.2fr)        /* Θεραπευτής + the name it inherits */
        minmax(0, 1.4fr);       /* Διάρκεια + the value it inherits */
}
.program-slot-where {
    grid-template-columns:
        minmax(9rem, 1fr)       /* Τύπος τοποθεσίας — «Εκτός κέντρου» */
        minmax(0, 2fr)          /* Αίθουσα / σύνδεσμος */
        auto;                   /* Αφαίρεση */
    margin-top: var(--space-2);
}
/* Below the breakpoint both collapse to the same content-driven flow, so
   a phone needs no third rule. */
@media (max-width: 59.99rem) {
    .program-slot-when,
    .program-slot-where {
        grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
    }
}
/* The row owns the spacing now (the grid gap), so the field's own bottom
   margin would double it — the same trade `.form-columns` makes. */
.program-slot-grid > .form-field { margin-bottom: 0; }
/* The button has no label above it, so it is pushed down to sit on the
   controls' line rather than floating at the top of the row. */
.program-slot-actions { display: flex; align-items: end; padding-top: 1.55rem; }
.program-slot-actions > button { width: 100%; }

/* v0.147 — «Επαναλαμβανόμενες συνεδρίες», the dialog the recurrence editor
   moved into (Elias, 2026-08-16 item 1). Wide on purpose: a repeat tiles
   seven controls across two grid rows (#806), and at the default dialog
   width every one of them would claim a line of its own — which is exactly
   the tall block #806 spent a release flattening. Capped at 96vw so it
   still fits a laptop, and the height is left to the content: unlike the
   πλέγμα this body has no scrolling surface of its own to size. */
.modal-card.program-slots-modal {
    width: min(96vw, 1100px);
    max-width: 96vw;
}

/* «Διάρκεια» — a <fieldset> of two radios plus one derived field, sharing a
   `.form-columns` row with plain one-line fields. The radios stacked, so
   this cell was two and a half times the height of its neighbours and the
   whole row grew to match it. They are a pair; they read as a pair.

   ⚠️ WRITTEN AS `fieldset.program-duration`, AND THAT IS NOT DECORATION.
   `fieldset.form-field` above is (0,1,1) and a bare `.program-duration` is
   (0,1,0), so the first version of this block lost the cascade in silence
   and shipped into an after-capture that looked exactly like the before
   one. That is the same specificity fight #762 fought with
   `.intake-fields .form-field.field-sm`, one ticket later.

   The border STAYS. It is what says these three controls are one decision,
   and «compact» is not a licence to dissolve a grouping — only to stop it
   costing two lines for a choice between two words. */
fieldset.program-duration {
    /* Two columns of `.form-columns`, because «Σε εβδομάδες» and «Σε
       αριθμό συνεδριών» do not fit one. It costs no height — this cell was
       already the tallest on its row — and it buys the radios a single
       line plus room for the derived field beside them. */
    grid-column: span 2;
    padding: var(--space-1) var(--space-3) var(--space-2);
    margin: 0 0 var(--space-3);
    /* A grid/flex item's automatic minimum size is its content, and a
       <fieldset> additionally refuses to shrink below its contents unless
       told; without this the cell can push its whole row wider. */
    min-width: 0;
    /* NOT the inherited `.form-field` flex column: in a column every label
       is a block-level item, so `display: inline-flex` on the radios would
       do nothing at all. */
    display: block;
}
fieldset.program-duration > legend {
    padding: 0 var(--space-1);
    font-size: var(--text-base);
    font-weight: 600;
    color: var(--ink);
}
fieldset.program-duration > label {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    margin-inline-end: var(--space-3);
    font-size: var(--text-md);
}
fieldset.program-duration > .form-field { margin: var(--space-1) 0 0; }

/* R4's other half — the figure the two pricing controls never showed.
   A read-only fact, so it is a label + value rather than a field: styling
   it like an input would invite the operator to type in it. */
.program-applied-price {
    gap: var(--space-05);
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    background: var(--cream);
}
.program-applied-price-label {
    font-size: var(--text-base);
    font-weight: 600;
    color: var(--ink);
}
.program-applied-price-value { font-size: var(--text-xl); }

/* #709 — «Έλεγχος» panels. The consequence check that precedes a bulk
   write: what will change, what will not, and what will be skipped.
   Shared by the tariff price adjustment and the batch programme renewal
   so the two read as the same step, because they are. */
.preview-panel {
    margin-top: var(--space-4);
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--border, #d6d9de);
    border-left: 3px solid var(--accent, #2f6fed);
    border-radius: 6px;
    background: var(--surface-subtle, #f6f8fa);
}

.preview-panel h3,
.preview-panel h4 {
    margin: 0 0 var(--space-2);
    font-size: var(--text-lg);
}

.preview-panel p { margin: var(--space-1) 0; }

/* Bounded, because a batch can name hundreds of rows and the confirm
   button has to stay reachable without scrolling past all of them. */
.preview-list {
    margin: var(--space-1) 0 var(--space-2);
    padding-left: var(--space-5);
    max-height: 12rem;
    overflow-y: auto;
}

.preview-list li { margin: var(--space-05) 0; }

/* What will NOT happen. Stated first and marked, per #587: a check that
   hides its skips is worse than no check, because the operator reads a
   number that will not arrive. */
.preview-warn { color: var(--warn-text, #8a5a00); }

/* ============================================================
   #743 — «Εκτύπωση» για το ΠΛΕΓΜΑ ΠΡΟΓΡΑΜΜΑΤΙΣΜΟΥ.

   Κέντρα still put the week on the wall. The πλέγμα is the artefact a
   centre gathers around in September and, until this block existed, the
   only route to paper was a screenshot. The button is one line of JS
   (window.print); EVERYTHING that makes the result usable is here.

   Three things this block has to get right, and each is a way the
   feature fails silently if it is missed:

   1. LANDSCAPE, ONE PAGE. A week that wraps onto a second sheet is
      useless on a wall — you cannot read Friday if it is behind
      Monday. The fit is structural rather than a guessed scale factor:
      .planning-grid-table already carries `table-layout: fixed`, so
      giving it `width: 100%` and clearing every cell's fixed rem width
      makes the browser divide the printable width across the columns
      that are actually there. Three days fit comfortably; seven fit
      narrowly; either way it is ONE page by construction, at whatever
      span the operator left on screen.

      ⚠️ `@page { size: … landscape }` is honoured by Chromium and
      ignored by Firefox, which prints whatever orientation the dialog
      is set to. The width rules above still hold there — the sheet
      stays one page wide, just a narrower one.

   2. THE PATTERNS HAVE TO SURVIVE THE PRINTER. This is the one that
      bites. #730 gave the four cell states hatching (−45° for
      «Προτεινόμενη θέση», +45° for «Τοποθέτηση») precisely so they
      would survive a mono printer — but every one of those patterns is
      a `background-image`, and browsers DO NOT PRINT background images
      unless the user ticks «Background graphics» in the dialog. Left
      alone, the whole πλέγμα comes out as a page of empty white boxes:
      not degraded, BLANK. `print-color-adjust: exact` is what forces
      them, and it is load-bearing, not a nicety.

      Belt and braces on top: the borders. A border is foreground, so it
      prints under any setting, and #730's own note already names the
      dashed edge as the signal that outlives a coarse hatch. In print
      they are pushed to near-black, so «Κατειλημμένο», «Προτεινόμενη
      θέση» and «Τοποθέτηση» stay three different things even if the
      gradients flatten to one grey.

   3. WHAT IS ON SCREEN, AND NOTHING ELSE. The operator has already
      arranged the span and the filters they want. Nothing here
      re-derives a week or re-queries anything; the chrome is dropped
      and the same DOM is laid out for paper. The legend («Τι δείχνει
      το πλέγμα», #730) STAYS — it is what makes the sheet readable to
      someone who was not at the screen — and the print header
      (REQ-F29-007) is revealed to say what the sheet covers, over what
      days, and when it came off the printer.
   ============================================================ */

/* Invisible on screen; the @media print block reveals it. Kept out of the
   scoped PlanningGrid.razor.css together with the rest of the print rules
   so the whole print contract reads in one place. */
.planning-grid-print-header {
    display: none;
}

@media print {
    @page {
        size: A4 landscape;
        margin: 10mm 8mm;
    }

    /* --- app chrome off ------------------------------------------------ */
    /* Same list the «Πρόγραμμα ημέρας» sheet drops, plus the πλέγμα's own
       controls. The toolbar, the skip link and the search notes are all
       ways of CHANGING the view; on paper the view is already fixed. */
    body:has(.planning-grid) .app-header,
    body:has(.planning-grid) .app-footer,
    body:has(.planning-grid) .side-nav,
    body:has(.planning-grid) .nav-drawer-scrim,
    body:has(.planning-grid) .mobile-tabbar,
    body:has(.planning-grid) .connectivity-banner,
    body:has(.planning-grid) .license-utilization-banner,
    body:has(.planning-grid) .session-recording-banner,
    body:has(.planning-grid) .demo-data-banner,
    body:has(.planning-grid) .new-build-banner,
    body:has(.planning-grid) .page-header,
    body:has(.planning-grid) .planning-page-intro,
    .planning-grid-nav,
    .planning-grid-nav-note,
    .planning-grid-skip-link,
    .planning-host-controls,
    .planning-page-scope,
    .planning-host-apply,
    .planning-grid-print-btn {
        display: none !important;
    }

    /* The «Προγραμματισμένες συνεδρίες» column is a working list, not part
       of the wall chart, and it is the one element wide enough to force the
       table off the page. The sessions it names are drawn in the grid. */
    .planning-grid-overview {
        display: none !important;
    }

    html:has(.planning-grid),
    body:has(.planning-grid),
    body:has(.planning-grid) .app-shell,
    body:has(.planning-grid) .app-body,
    body:has(.planning-grid) .app-main {
        background: #fff !important;
        margin: 0 !important;
        padding: 0 !important;
        display: block !important;
        overflow: visible !important;
        height: auto !important;
        max-height: none !important;
    }

    /* --- the modal entry points ---------------------------------------- */
    /* The πλέγμα opens as a dialog from a child's card, from the programme
       editor and from a session panel — three of its four entry points. A
       dialog is a viewport-sized box with its own scroller, so printed
       untouched it hands the sheet a 88vh window with the grid clipped
       inside it. Scoped with :has() so no OTHER modal in the app is
       reshaped by a rule meant for this one. */
    /* 🔴 THE PAGE BEHIND THE DIALOG.

       Neutralising the dialog is only half of it. The modal is rendered
       INSIDE the page that opened it — mid-document on a child's card,
       between the patient header and the tab content — so once the backdrop
       stops being `position: fixed` the wall chart prints sandwiched between
       whatever else that page was showing. Three of the four entry points are
       dialogs, so this is the majority of routes to the button.

       The visibility recipe rather than `display: none` on the surroundings:
       `visibility` is inherited and can be turned back ON in a descendant, so
       one pair of rules blanks the whole page and then un-blanks exactly the
       dialog — no list of a page's sections to keep in step, and nothing
       breaks the first time a card grows another panel.

       Both rules are gated on a planning modal ACTUALLY being open. On the
       hosted page at /scheduling/planning there is no backdrop, `:has()`
       matches nothing, and none of this applies. */
    body:has(.modal-backdrop .planning-grid) .app-main {
        visibility: hidden !important;
    }

    .modal-backdrop:has(.planning-grid),
    .modal-backdrop:has(.planning-grid) * {
        visibility: visible !important;
    }

    /* The blanked page keeps its BOXES — visibility hides ink, not layout —
       so without this the sheet is pushed down the page by the height of a
       child's card and trails blank sheets behind it. Collapsing the emptied
       main is what brings it back to one page. Measured on a real child card:
       1649px of page behind, and one A4 landscape sheet out. */
    body:has(.modal-backdrop .planning-grid) .app-main {
        height: 0 !important;
        min-height: 0 !important;
        overflow: visible !important;
    }

    .modal-backdrop:has(.planning-grid) {
        position: static !important;
        display: block !important;
        background: none !important;
        padding: 0 !important;
        inset: auto !important;
    }

    .modal-card:has(.planning-grid) {
        width: 100% !important;
        max-width: none !important;
        height: auto !important;
        max-height: none !important;
        box-shadow: none !important;
        border-radius: 0 !important;
        display: block !important;
    }

    .modal-card:has(.planning-grid) .modal-body {
        overflow: visible !important;
        height: auto !important;
        max-height: none !important;
        padding: 0 !important;
        display: block !important;
    }

    /* The dialog's own title bar and buttons. The print header below says
       the same thing and says it dated, so this is duplication that costs
       vertical millimetres the grid needs. */
    .modal-card:has(.planning-grid) .modal-header,
    .modal-card:has(.planning-grid) .modal-footer,
    .modal-card:has(.planning-grid) .modal-close {
        display: none !important;
    }

    /* --- the sheet ------------------------------------------------------ */
    .planning-grid {
        display: block !important;
        overflow: visible !important;
        color: #000 !important;
    }

    /* Revealed, and first on the page: REQ-F29-007's «this sheet explains
       itself» — the παιδί or the room overview, the day span, and the
       moment it was printed. An undated week on a wall is a hazard. */
    .planning-grid-print-header {
        display: flex !important;
        justify-content: space-between;
        align-items: flex-end;
        gap: 6mm;
        border-bottom: 1.5pt solid #000;
        padding-bottom: 1.5mm;
        margin-bottom: 2mm;
    }

    .planning-grid-print-scope strong {
        display: block;
        font-size: 12pt;
    }

    .planning-grid-print-span {
        display: block;
        font-size: 9pt;
    }

    .planning-grid-print-meta {
        text-align: right;
        font-size: 9pt;
    }

    /* #730's ΥΠΟΜΝΗΜΑ stays. Without it the hatching is a distinction
       nobody named, and the sheet is read by people who were not at the
       screen — which is the entire reason it goes on a wall. */
    .planning-grid-legend {
        display: flex !important;
        font-size: 7.5pt !important;
        color: #000 !important;
        margin-bottom: 2mm !important;
        break-inside: avoid;
        page-break-inside: avoid;
    }

    /* The scroller is the screen's answer to a table wider than the
       viewport. On paper there is no scrolling, so it stops being a window
       and becomes a plain block — otherwise everything past the visible
       strip is simply cut off. */
    .planning-grid-scroll {
        overflow: visible !important;
        border: 0 !important;
        flex: none !important;
        min-width: 0 !important;
        width: 100% !important;
    }

    /* THE FIT. `table-layout: fixed` is already on the table; `width: 100%`
       plus cleared cell widths is what turns it into «divide the printable
       width across the columns that exist» instead of «1.15rem each and
       overflow». This is why the sheet is one page wide at 1, 3, 5 or 7
       days without a scale factor anywhere. */
    .planning-grid-table {
        width: 100% !important;
        max-width: 100% !important;
        break-inside: avoid;
        page-break-inside: avoid;
    }

    .planning-grid-cell,
    .planning-grid-slot,
    .planning-grid-skip-cell {
        width: auto !important;
        min-width: 0 !important;
        max-width: none !important;
    }

    /* ⚠️ The swatches carry .planning-grid-cell TOO — that is what stops the
       legend and the grid drifting apart (#730) — so the reset above
       collapses them to hairlines and the υπόμνημα becomes five unreadable
       slivers. Found by eye in a Chromium PDF, not by any assertion. Sized
       back explicitly, and in mm because on paper a swatch is a physical
       thing rather than a fraction of a table. */
    .planning-grid-legend .planning-grid-legend-swatch {
        width: 6mm !important;
        min-width: 6mm !important;
        height: 3mm !important;
        display: inline-block !important;
    }

    /* The one column that keeps a stated width: a room or therapist name
       squeezed to 1/144th of the page is not a label. Everything else
       shares what is left. */
    .planning-grid-corner,
    .planning-grid-resource {
        width: 26mm !important;
        min-width: 26mm !important;
        max-width: 26mm !important;
        position: static !important;
        background: #fff !important;
    }

    /* Sticky is a scrolling affordance and there is no scrolling on paper;
       left as-is the headers stack at the top of the flow. */
    .planning-grid-days th,
    .planning-grid-times th {
        position: static !important;
    }

    /* `overflow: hidden` because the day label is `white-space: nowrap` and a
       day whose quarter-hours are ALL folded (a closed Sunday, #675) is one
       9px column — so «Κυρ 09/08» ran straight off the right edge of the
       sheet and printed into the margin. Clipped to its own column instead:
       the day is shut, the fold already says so, and text bleeding past the
       page edge reads as a broken printout. */
    .planning-grid-day {
        font-size: 7.5pt !important;
        padding: 0.4mm 0.8mm !important;
        overflow: hidden !important;
    }

    .planning-grid-times th {
        font-size: 5pt !important;
    }

    .planning-grid-kind {
        font-size: 6pt !important;
    }

    .planning-grid-name {
        font-size: 7.5pt !important;
    }

    .planning-grid-cell {
        height: 5mm !important;
    }

    /* --- the crux: patterns that survive a mono printer ----------------- */
    /* Every cell state #730 drew is a background-image, and background
       images are OFF by default in every print dialog. Without this the
       sheet is a page of empty boxes — the hatching does not degrade, it
       disappears. The legend swatches are listed too: they paint with the
       SAME classes, so an unforced legend would explain patterns the grid
       below it no longer shows. */
    .planning-grid-cell,
    .planning-grid-skip-cell,
    .planning-grid-legend-swatch {
        -webkit-print-color-adjust: exact !important;
        print-color-adjust: exact !important;
    }

    /* Borders are FOREGROUND. They print whatever the dialog is set to, so
       they are the floor under the hatching: even if a printer flattens
       every gradient to one grey, «Κατειλημμένο», «Προτεινόμενη θέση» and
       «Τοποθέτηση» still differ by their edge. #730's own note calls the
       dashed border the signal that outlives a coarse hatch. */
    .planning-grid-cell {
        border: 0.25pt solid #999 !important;
    }

    .planning-grid-cell.is-day-start {
        border-left: 1pt solid #000 !important;
    }

    .planning-grid-cell.is-occupied {
        border: 0.25pt solid #444 !important;
    }

    .planning-grid-cell.is-occupied.is-therapist-hold {
        border: 0.5pt solid #000 !important;
    }

    .planning-grid-cell.is-highlighted {
        outline: 0.75pt solid #000 !important;
        outline-offset: -0.75pt;
        border: 0.5pt dashed #000 !important;
    }

    .planning-grid-cell.is-draft {
        border: 0.5pt solid #000 !important;
    }

    /* #802 — a προτεινόμενη θέση the allocator has already dropped a πρόταση
       on. On the child entry that is the NORMAL outcome of «Επόμενη ελεύθερη
       θέση», so on paper it must not read as an ordinary proposal: it takes
       the outline and the dashed edge the free suggestion above gets, over
       the draft's own border — the same composition the screen rule makes.

       It beats .planning-grid-cell.is-draft's `!important` border on
       SPECIFICITY (three classes against two), not on order; it is declared
       after it as well, so neither half of the cascade has to be argued. */
    .planning-grid-cell.is-draft.is-found {
        outline: 0.75pt solid #000 !important;
        outline-offset: -0.75pt;
        border: 0.5pt dashed #000 !important;
    }

    /* The pick button is a transparent hit target on screen; on paper it
       must not paint over the cell's pattern. */
    .planning-grid-pick {
        background: none !important;
        border: 0 !important;
    }
}

/* ---------------------------------------------------------------------
   «Τι εκκρεμεί σήμερα» — the day's worklist on the landing page (#737).
   Deliberately thin: the card chrome, the count pill and the empty state
   are the existing .details-card / .recon-badge / EmptyState idioms, so
   this section reads as part of the same screen rather than as a widget
   bolted onto it. Only the lane list is new.
   --------------------------------------------------------------------- */
.today-worklist { margin-bottom: var(--space-4); }
.worklist-scope { color: var(--ink-soft); font-size: var(--text-md); }
.worklist-lede { color: var(--ink-soft); font-size: var(--text-md); margin: 0 0 var(--space-3); }
.worklist-lanes { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-2); }
.worklist-lane { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0 var(--space-3); }
/* The whole label+count is one target: a count you can see but not click
   is a number with nowhere to go (#698). */
.worklist-lane-link { display: inline-flex; align-items: baseline; text-decoration: none; }
.worklist-lane-link:hover .worklist-lane-label { text-decoration: underline; }
.worklist-lane-label { font-weight: 600; }
/* min-width:100% forces the hint onto its own line under the label at
   every width, so the sentence that explains the number never wraps into
   the number itself. */
.worklist-lane-hint { min-width: 100%; color: var(--ink-soft); font-size: var(--text-base); }
.worklist-lane-unknown {
    margin-left: var(--space-2);
    color: var(--ink-soft);
    font-size: var(--text-base);
    font-style: italic;
}

/* #829 — the standing «nobody to charge» warning on a child with no
   κηδεμόνας. Deliberately NOT .form-error: it is a permanent condition of
   the record rather than a failed submit, and it renders on every tab of
   the child's card for as long as it is true. Same red as .form-error so
   it reads with the same weight, without inheriting a selector that other
   screens' tests use to detect load failures. */
.child-guardian-warning {
    color: oklch(0.55 0.18 25);
    margin: var(--space-2) 0 var(--space-3);
    font-weight: 600;
}

/* #876 — the reason an ΕΟΠΥΥ document cannot be issued, rendered beside the
   action it disables. Same red weight as .child-guardian-warning (and for
   the same reason: a standing condition of the record, not a failed
   submit), but tighter margins because it sits inside an action row rather
   than under a page header. */
.child-document-guard {
    color: oklch(0.55 0.18 25);
    margin: var(--space-1) 0 0;
    font-weight: 600;
    flex-basis: 100%;
}

/* #876 — an <a> cannot carry the `disabled` attribute, so a blocked
   document action drops its href (which is what actually makes it
   unclickable and unnavigable) and wears this to look the part. The
   aria-disabled attribute carries the meaning for assistive tech; the
   colour only repeats it. */
.btn-secondary.is-disabled,
a[aria-disabled="true"].btn-secondary {
    opacity: 0.55;
    cursor: not-allowed;
    pointer-events: none;
}

/* #851 — the programme editor's «Στόχοι». It reserved four rows of height on
   an optional field, filled or not, which was the largest single block in a
   modal Elias reported as «too much space, especially the empty boxes».
   Two rows by default, and it grows on focus so writing a long goal is not
   made worse by the compaction. Layout only. */
.program-goals-grow {
    min-height: 3.25rem;
    resize: vertical;
    transition: min-height 120ms ease;
}
.program-goals-grow:focus {
    min-height: 7rem;
}

/* #851 — Elias: «the modal uses too much space, especially the empty boxes
   used to enter values.»

   The panel carries fourteen `.form-field`s, and the global rule gives every
   one of them `margin-bottom: var(--space-4)` — 16px × 14 is over 200px of
   gap before a single control is drawn, and an empty optional field pays it
   exactly like a filled required one. Halving it inside THIS panel is the
   single highest-leverage change and touches no other screen.

   Scoped to .program-edit-panel deliberately: .form-field is used across the
   whole app and other forms are not the complaint. */
.program-edit-panel .form-field { margin-bottom: var(--space-2); }
.program-edit-panel fieldset.form-field { margin-bottom: var(--space-3); }

/* «Εκτιμώμενο ποσό» is a value the form computes, not a box to fill in, so it
   reads as one line instead of a label stacked above a control. */
.program-applied-price--inline {
    flex-direction: row;
    align-items: baseline;
    gap: var(--space-2);
    margin-bottom: 0;
}

/* #853 — the child's price tag.
   🔴 Added after the release check found the component shipping with NO rules
   at all: every .child-price-tag* class existed only in the markup. That is
   the same defect as the guessed .calendar-view-btn earlier the same night,
   and no unit test can see either — a component with no stylesheet renders,
   passes, and looks broken. The published app.css is what has to be grepped. */
.child-price-tag {
    margin-top: var(--space-4);
    padding-top: var(--space-3);
    border-top: 1px solid var(--line, #e5e7eb);
}
.child-price-tag__title {
    font-size: var(--text-base);
    font-weight: 600;
    color: var(--ink);
    margin: 0 0 var(--space-2);
}
.child-price-tag__list {
    list-style: none;
    margin: 0 0 var(--space-2);
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
}
/* Row rather than a table: four short values that should read on one line and
   stack on a narrow screen without a horizontal scroller. */
.child-price-tag__row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-2);
}
.child-price-tag__specialty { font-weight: 600; }
.child-price-tag__amount { font-variant-numeric: tabular-nums; }
.child-price-tag__from { color: var(--muted, #6b7280); font-size: var(--text-sm); }

/* ------------------------------------------------------------------
   #854 — .status-badge, app-wide.

   🔴 It was defined ONLY in CustomerContactsTab.razor.css and
   CustomerConsentsTab.razor.css, which are SCOPED stylesheets: Blazor
   rewrites their selectors with the owning component's [b-xxxxx]
   attribute, so the rules apply inside those two components and
   nowhere else. Sixteen other .razor files render `status-badge` and
   got no styling at all from it.

   ChildPriceTag is one of them. #853 shipped it with no stylesheet
   rules whatsoever, that was caught by grepping the published app.css
   inside the preview image, and the guard test added afterwards listed
   only the `child-price-tag*` names — so the badge INSIDE it stayed
   unstyled through the fix and the test that was meant to prevent
   exactly this. A guard that enumerates a subset of what a component
   renders proves only that the subset is fine.

   The scoped copies keep winning where they exist: `.status-badge[b-x]`
   outranks `.status-badge` on specificity, so the two tabs are
   untouched. This is the fallback for everybody else.
   ------------------------------------------------------------------ */
.status-badge {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: 0.15rem 0.55rem;
    border-radius: 999px;
    font-size: var(--text-sm);
    font-weight: 600;
    line-height: 1.4;
    border: 1px solid transparent;
    background: var(--line, oklch(0.90 0.01 210));
    color: var(--ink-soft, oklch(0.44 0.04 215));
}

/* A price somebody set deliberately — it will NOT move when the
   τιμοκατάλογος moves, and that is the whole distinction the badge
   exists to draw. */
.status-badge.status-active {
    background: oklch(0.62 0.13 145 / 0.15);
    color: oklch(0.42 0.13 145);
    border-color: oklch(0.62 0.13 145 / 0.35);
}

/* Inherited from a tier above: correct, ordinary, and quieter than the
   deliberate one so a scan of the column separates the two. */
.status-badge.status-neutral {
    background: var(--teal-100, oklch(0.93 0.035 205));
    color: var(--teal-900, oklch(0.38 0.08 215));
    border-color: oklch(0.62 0.11 208 / 0.35);
}

/* No price resolves at all — #831 made this a refusal at creation
   precisely because a completed session that resolves nothing raises no
   charge and nobody notices. It must not read as ordinary. */
.status-badge.status-withdrawn {
    background: oklch(0.55 0.16 25 / 0.15);
    color: oklch(0.45 0.16 25);
    border-color: oklch(0.55 0.16 25 / 0.35);
}


/* Σημειώσεις gets its own card (it is a textarea, not a one-line value), and a
   card is not a width: without this it collapsed to the browser's default
   ~20-character box in the middle of a full-width panel. */
.session-inline-edit__notes textarea {
    width: 100%;
    box-sizing: border-box;
}
.session-inline-edit__notes > label {
    display: block;
    margin-bottom: var(--space-1);
}

/* The one action row carries FIVE controls, one of them «Ακύρωση + εύρεση
   αντικατάστασης» — a 29-character label. At 1280px they do not fit on a
   line, and the overflow pushed «Αποθήκευση αλλαγών» under the sticky
   footer where it could not be reached.

   So: wrap deliberately, keep «Ακύρωση» hard left (it is the destructive
   one and #762 separated it on purpose), and let the row grow rather than
   overlap. */
.session-inline-edit__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
}
.session-inline-edit__actions .session-status-actions__cancel {
    margin-right: auto;
}

/* The typeahead's clear «×» is ALREADY positioned correctly — absolutely,
   against `.search-select__input-wrap`. What broke it here is that the wrap
   is a block: in the merged list it stretched the full width of the value
   column while the <input> inside kept its own `--field-chars` width, so the
   × parked ~880px away from the field it clears, with nothing connecting the
   two.

   Shrink the wrap to its content inside this grid and the existing rule puts
   the × back where it belongs — no new positioning, no override of the
   component's own styles. */
.detail-grid--form .search-select__input-wrap,
.detail-grid--form .parent-typeahead__input-wrap {
    display: inline-block;
    width: fit-content;
}

/* 🔴 The action row has to be sticky HERE too, and it was not.
   The existing rule is `.modal-card > .modal-body > .form-actions` — a
   DIRECT child. This row lives one level deeper, inside the editor's own
   `.details-card`, so it never matched: it simply scrolled with the content
   and «Αποθήκευση αλλαγών» sat below the fold, looking cut off by the
   dialog's footer.

   ⚠️ `flex-wrap` alone did NOT fix that — it changed where the overflow
   went, not whether the row was reachable. Five controls (one of them the
   29-character «Ακύρωση + εύρεση αντικατάστασης») do not fit 1280px, so the
   row WILL be two lines; what matters is that both lines stay pinned. */
.modal-card .modal-body .session-inline-edit__actions {
    position: sticky;
    bottom: 0;
    z-index: 1;
    background: var(--card);
    padding-top: var(--space-3);
    border-top: 1px solid var(--line);
}

/* Elias, 2026-08-16: «Part of notes box is covered.»
   The action row is sticky at the bottom of the scrollport, so while there
   is content below it, it floats OVER whatever it is scrolled past — and
   Σημειώσεις is the last card above it. Reserve the row's height beneath
   the card so the last thing on the page is never underneath the thing
   pinned on top of it. */
.session-inline-edit__notes {
    margin-bottom: var(--space-7);
}

/* Elias, 2026-08-17: «the session details dialog should have a list of the
   kids when a group session takes place». A group is one appointment with a
   Session row per child, so this list is the only place the dialog answers
   «who else is in this slot?».

   Unstyled <ul> markers are dropped and the row spacing matched to the rest
   of the detail grid: this sits INSIDE that grid as a wide <dd>, so bullets
   would be the only ones on the screen. */
.session-group-members {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.session-group-members li {
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
    /* The name can be long and the badge must not be pushed off the row. */
    flex-wrap: wrap;
}

.session-group-members .status-badge {
    margin-inline-start: auto;
}


/* ---------- #870 · «Παιδιά της συνεδρίας» ----------------------------
   One bordered row per child, carrying the child's name, their own status
   toggle and their own price. It is a list and not a `.data-table` because
   each row holds interactive controls of three different shapes, and a
   table row that wraps its cells is harder to read than a flex row that
   was designed to. */
.session-children .session-group-members {
    list-style: none;
    margin: 0;
    padding: 0;
}

.session-child-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) 0;
    border-bottom: 1px solid var(--border-subtle, rgba(128, 128, 128, 0.25));
}

.session-child-row:last-child { border-bottom: 0; }

.session-child-row__name {
    flex: 1 1 12rem;
    min-width: 0;
}

.session-child-row__status {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1, 0.25rem);
}

.session-child-row__price {
    display: flex;
    align-items: center;
    gap: 0.25rem;
}

/* The price input states its own length (#870 / W1 above applies the
   ceiling); this keeps it from stretching in the flex row as well. */
.session-child-row__price input { width: auto; }

/* A refusal belongs to its row and takes the whole width of it, so a long
   Greek sentence naming a charge does not squeeze the controls beside it.
   #834: the message is the point — it must not be the thing that wraps
   into unreadability. */
.session-child-row__error { flex: 1 0 100%; }

/* #887 — «Αφαίρεση» sits at the END of the row, after the price, and is
   deliberately NOT inside .session-child-row__status: that container is a
   role="group" of toggles whose every button must carry a literal
   aria-pressed, and a one-shot action is not a toggle. Ordering here is by
   CSS and markup only; the control never moves across an AuthorizeView. */
.session-child-row__actions {
    display: flex;
    align-items: center;
    margin-inline-start: auto;
}

/* The «Προσθήκη παιδιού» picker, one row under the heading it belongs to. */
.session-join-form {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--space-2);
    padding: var(--space-2) 0;
}

.session-status-toggle__hint { margin: 0.35rem 0 0; }

/* #900 — compacting «Λεπτομέρειες συνεδρίας»: the status toggles (top
   Κατάσταση group and the per-child row) mark their CURRENT position with
   `.is-current` on a `.btn-secondary`, not `.btn-primary`. The dialog has
   exactly one primary action — «Αποθήκευση» — and a toggle's pressed state
   is not that action; reusing the filled CTA colour for it gave every
   status group its own "primary-looking" button, which is the opposite of
   MD3's "one primary button per view". `aria-pressed` still carries the
   real state for assistive tech; this is the sighted-only echo of it, as a
   filled-TONAL treatment (soft background, not the loud filled button). */
.session-status-toggle .is-current,
.session-child-row__status .is-current {
    background: var(--teal-100);
    color: var(--teal-900);
    border-color: var(--teal-700);
}
.session-status-toggle .is-current:hover:not(:disabled),
.session-child-row__status .is-current:hover:not(:disabled) {
    background: var(--teal-100);
}

/* #900 — «Υπενθυμίσεις» becomes a native disclosure: closed by default
   (it is a delivery-audit trail an operator checks occasionally, not on
   every open of the dialog), reopened in one click/Enter/Space and always
   keyboard-reachable because `<details>`/`<summary>` need no script. It
   opens itself when a reminder actually failed — that is the #834 shape
   (a fact rendered nowhere) and collapsing it by default must never be how
   that regresses; the summary line also names the failure so it does not
   depend on the operator noticing the disclosure at all. */
.reminders-disclosure > summary {
    cursor: pointer;
    list-style-position: outside;
}
.reminders-disclosure > summary > h2 {
    display: inline;
    margin: 0;
}
.reminders-disclosure[open] > summary {
    margin-bottom: var(--space-2);
}
