/* 
   E-Dani Pro Suite | Shared Layout Framework (v1.0)
   Standardized Glassmorphism, Sidebar, and Responsive Navigation
*/

:root {
    --primary-color: #001f54;
    --c-primary: #001f54;       /* Alias used by suite_modals.css */
    --accent-color: #fd7e14;
    --success-color: #28a745;  /* IW green — adopted suite-wide 2026-06-21 */
    --danger-color: #dc3545;   /* IW red — adopted suite-wide 2026-06-21 */
    --bg-light: #f4f6f9;
    --sidebar-width: 260px;
    --max-width-std: 1800px;
}

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

body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    background-color: var(--primary-color);
    background-image: var(--suite-bg-image, url('/shared/images/backgrounds/dashboard-bg.jpeg'));
    background-size: cover;
    background-position: center center;
    background-attachment: fixed;
    display: flex; 
    height: 100vh; 
    color: #0f172a; 
    overflow: hidden; 
}

/* SIDEBAR SKELETON — BUG-782
   Scoped to the sidebar's own class. This was a BARE `aside` selector, which captured every
   <aside> in every app, not just the navigation. Page content wrapped in the semantically correct
   tag silently rendered as a navy, sidebar-width, white-text panel at z-index 1000: measured on
   Schule's Einzelgespräche student pane, which computed to background rgb(0,31,84) / color white /
   width 260px with none of that in its own stylesheet. The owner reported it as "old style
   windows" and it survived a full module rebuild, because the markup is valid, the app's own CSS
   looks right, and the only way to find it is to enumerate which rules match the element.
   The suite has exactly ONE <aside> — shared/navigation_helper.php's `<aside id="sidebar"
   class="desktop-sidebar">`, used by all seven apps — so the class carries every consumer.
   ⚠️ Do not widen this back to a bare element selector. Guard: tests/suite/unit/
   shared_css_no_bare_structural_selectors.unit.js. */
.desktop-sidebar {
    width: var(--sidebar-width);
    background: var(--primary-color);
    color: white;
    display: flex;
    flex-direction: column;
    padding: var(--space-2xl);
    z-index: 1000;
    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
    box-shadow: 4px 0 10px rgba(0,0,0,0.1);
}

.brand { 
    font-weight: 800; 
    font-size: 1.1rem; 
    margin-bottom: var(--space-2xl); 
    display: flex; 
    align-items: center; 
    justify-content: space-between; 
    padding-bottom: var(--space-2xl);
    border-bottom: 1px solid rgba(255,255,255,0.1);
    overflow: hidden; 
    white-space: nowrap; 
    flex-shrink: 0;
}

.brand-logo-wrapper { display: flex; align-items: center; gap: var(--space-md); }
.brand-icon { font-size: 1.2rem; }
.brand-text { font-size: 1.1rem; margin: 0; font-weight: 800; line-height: 1.2; overflow: hidden; text-overflow: ellipsis; }

.sidebar-toggle-btn { 
    background: rgba(255,255,255,0.1); 
    border: none; 
    color: rgba(255,255,255,0.7); 
    cursor: pointer; 
    padding: 5px var(--space-md); 
    border-radius: var(--radius-xs); 
    font-size: 16px; 
    transition: all 0.2s; 
    flex-shrink: 0;
}
.sidebar-toggle-btn:hover { background: rgba(255,255,255,0.2); color: white; }

.sidebar-nav {
    flex: 1;
    overflow-y: auto;
    overflow-x: hidden;
    padding-right: 5px; /* Prevent scrollbar from hugging text */
    margin-bottom: var(--space-md);
}

/* Custom Scrollbar for Sidebar */
.sidebar-nav::-webkit-scrollbar { width: 4px; }
.sidebar-nav::-webkit-scrollbar-track { background: transparent; }
.sidebar-nav::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.2); border-radius: var(--radius-xs); }
.sidebar-nav::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.4); }

.nav-item { 
    padding: var(--space-md) 15px; 
    margin-bottom: var(--space-2xs); 
    border-radius: var(--radius-lg); 
    cursor: pointer; 
    color: rgba(255,255,255,0.7); 
    font-weight: 500; 
    display: flex; 
    align-items: center; 
    gap: var(--space-lg); 
    text-decoration: none; 
    font-size: 0.85rem; 
    transition: all 0.2s; 
    white-space: nowrap; 
    border: 1px solid transparent;
}
.nav-item:hover { background: rgba(255,255,255,0.1); color: white; }

/* GLOBAL ACTIVE STATE (FORCE ORANGE) */
.nav-item.active { 
    background: var(--accent-color) !important; 
    color: white !important; 
    box-shadow: 0 4px 10px rgba(253, 126, 20, 0.3) !important; 
    font-weight: 600 !important;
}

.nav-icon { font-size: 1.2rem; min-width: 24px; text-align: center; flex-shrink: 0; }

/* .nav-item is a flex row with white-space:nowrap (above) and no ellipsis handling — fine while
   every label was a short app/module name, but dateien's per-folder labels (2026-08-21, TASK-289)
   can run past the 260px sidebar and get hard-clipped by .sidebar-nav's overflow-x:hidden with no
   "…", not truncated cleanly. A flex item's default min-width is auto (= its content width), which
   blocks shrinking below that in the first place — min-width:0 here is what lets overflow/
   text-overflow actually take effect. */
.nav-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* Admin sidebar section headers (grouped nav) */
.nav-section-header {
    font-size: 10px;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    opacity: 0.5;
    padding: 14px 15px 3px;
    color: white;
    font-weight: 700;
    pointer-events: none;
    user-select: none;
}
.nav-section-divider {
    border-top: 1px solid rgba(255,255,255,0.08);
    margin: var(--space-xs) 15px 0;
}
body.sidebar-closed aside#sidebar .nav-section-header { display: none !important; }
body.sidebar-closed aside#sidebar .nav-section-divider { margin: var(--space-2xs) var(--space-sm) 0; }

.footer-nav { 
    margin-top: auto; 
    border-top: 1px solid rgba(255,255,255,0.1); 
    padding-top: 15px; 
    flex-shrink: 0;
}

/* SIDEBAR USER & LOGOUT */
.sidebar-user {
    padding: var(--space-md) 15px; /* Match nav-item padding for alignment */
    background: rgba(255,255,255,0.05);
    border-radius: var(--radius-lg);
    margin-bottom: 15px;
    cursor: pointer;
    transition: all 0.2s;
    overflow: hidden;
}
.sidebar-user:hover { background: rgba(255,255,255,0.1); }

.user-name {
    display: flex;
    align-items: center;
    gap: var(--space-lg); /* Match nav-item gap */
    font-weight: 600;
    font-size: 0.9rem;
    color: white;
}

.user-icon {
    font-size: 1.2rem;
    min-width: 24px;
    text-align: center;
}

.user-role {
    font-size: 0.75rem;
    color: rgba(255,255,255,0.5);
    margin-top: var(--space-3xs);
    padding-left: 36px; /* 24px min-width + 12px gap */
}

.logout-btn {
    width: 100%;
    padding: var(--space-lg);
    background: rgba(239, 68, 68, 0.1);
    border: 1px solid rgba(239, 68, 68, 0.2);
    color: #fca5a5;
    border-radius: var(--radius-lg);
    cursor: pointer;
    font-weight: 600;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-md);
    transition: all 0.2s;
}
.logout-btn:hover { background: #ef4444; color: white; }

/* MAIN CONTENT AREA */
/* SUITE CONTENT GUTTER — single source of truth.
   Both apps wrap page content in <main class="main-content" id="mainContent">,
   so this padding is THE standard for the gap between the sidebar/edge and the
   page cards across the whole suite. Apps must NOT re-declare main padding
   locally (that caused HW↔IW drift). The .main-content selector is included so
   the standard wins even where an app relied on the class. */
main, .main-content {
    flex: 1;
    padding: 25px;
    overflow-y: auto;
    position: relative;
    scroll-behavior: smooth;
    display: flex;
    flex-direction: column;
    align-items: center;
    background: transparent;
}

.section { 
    width: 100%;
    max-width: var(--max-width-std);
    display: none; 
    animation: fadeIn 0.2s ease-out; 
}
.section.active { display: block; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }

/* GLASSMORPHISM CARDS */
.card { 
    background: rgba(255, 255, 255, 0.9); 
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border-radius: var(--radius-3xl); 
    padding: 25px; 
    box-shadow: 0 10px 30px rgba(0,0,0,0.05); 
    border: 1px solid rgba(255,255,255,0.5); 
    margin-bottom: var(--space-2xl); 
}

/* MOBILE BOTTOM NAVIGATION */
.bottom-nav {
    display: none;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    height: auto;
    min-height: 44px;
    padding: var(--space-2xs) var(--space-sm);
    padding-bottom: calc(4px + env(safe-area-inset-bottom));
    background: #ffffff;
    box-shadow: 0 -4px 15px rgba(0,0,0,0.1);
    border-top: 1px solid #eee;
    z-index: 1000;
    flex-direction: row;
    justify-content: safe center; /* centers when items fit; falls back to flex-start when overflowing so first item is not clipped */
    align-items: center;
    gap: var(--space-xs);
    overflow-x: auto;
    flex-wrap: nowrap;
}

/* Bottom Nav Item Reset (to override Sidebar Nav-Item styles) */
.bottom-nav .nav-item {
    flex: 0 0 auto;
    min-width: 52px;
    padding: var(--space-2xs) var(--space-sm);
    font-size: 10px;
    border-radius: 10px;
    flex-direction: column;
    gap: var(--space-2xs);
    box-shadow: none !important;
    background: #f8f9fa !important;
    border: 1px solid #eee !important;
    color: #495057 !important;
    margin: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    text-decoration: none;
    line-height: 1 !important;
}

.bottom-nav .nav-icon { font-size: 1.1rem; min-width: auto; line-height: 1 !important; }
.bottom-nav .nav-item span { line-height: 1 !important; }

/* Bottom Nav Active State (ORANGE) */
.bottom-nav .nav-item.active { 
    background: var(--accent-color) !important; 
    color: white !important; 
    box-shadow: 0 4px 12px rgba(253, 126, 20, 0.2) !important; 
    border-color: var(--accent-color) !important;
    font-weight: 600 !important;
}

/* MOBILE MENU FAB (round button, mirrors the right-side .help-fab) + drawer backdrop.
   Hidden on desktop/tablet (the sidebar handles navigation there); shown ≤1024px. */
.menu-fab {
    display: none;
    position: fixed;
    /* Sit low in the corner. max() (not +) so we DON'T stack 30px on top of the
       iPhone home-indicator inset (~34px) — that pushed the button way too high.
       Desktop/no-notch → 20px; iPhone → ~28px, just clearing the home indicator. */
    bottom: max(20px, calc(env(safe-area-inset-bottom) - 6px));
    left: 30px;
    width: 55px;
    height: 55px;
    background: var(--primary-color, #001f54);
    color: #fff;
    border: 2px solid #fff;
    border-radius: 50%;
    align-items: center;
    justify-content: center;
    font-size: 26px;
    line-height: 1;
    cursor: pointer;
    box-shadow: 0 4px 15px rgba(0,0,0,0.3);
    /* Above every app's help-fab (≤21000) so the backdrop covers it, but below modals (≥50000). */
    z-index: 30002;
    padding: 0;
    transition: transform 0.25s cubic-bezier(0.175, 0.885, 0.32, 1.275), background 0.25s ease;
}
.menu-fab:hover { background: var(--accent-color, #fd7e14); }
body.mobile-menu-open .menu-fab { transform: rotate(90deg); background: var(--accent-color, #fd7e14); }

.mobile-menu-backdrop {
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(0,0,0,0.4);
    -webkit-backdrop-filter: blur(2px);
    backdrop-filter: blur(2px);
    z-index: 30000;
}

/* LAZY LOADING BACKGROUNDS */
body.lazy-bg:not(.bg-loaded) {
    background-image: none !important;
    background-color: var(--primary-color);
}

body.lazy-bg {
    transition: background-image 1s ease-in-out, background-color 1s ease-in-out;
}

body.bg-loaded {
    background-color: transparent;
}

/* SIDEBAR COLLAPSED STATE (Universal logic) */
body.sidebar-closed aside#sidebar { width: 70px !important; flex: 0 0 70px !important; padding: var(--space-md) 5px !important; }
body.sidebar-closed aside#sidebar .nav-label, 
body.sidebar-closed aside#sidebar .brand-text { display: none !important; }
body.sidebar-closed aside#sidebar .nav-item { justify-content: center !important; padding: var(--space-sm) 0 !important; margin-bottom: var(--space-3xs) !important; }
body.sidebar-closed aside#sidebar .brand {
    justify-content: center !important;
    border-bottom: none !important;
    flex-direction: column !important;
    gap: var(--space-sm) !important;
    margin-bottom: var(--space-sm) !important;
    padding-bottom: 0 !important;
}
body.sidebar-closed aside#sidebar .brand-icon { display: block !important; font-size: 1.4rem !important; }
body.sidebar-closed aside#sidebar .sidebar-toggle-btn { transform: rotate(180deg) !important; width: 100% !important; display: block !important; }
body.sidebar-closed aside#sidebar .sidebar-user { padding: var(--space-xs) !important; margin-bottom: var(--space-xs) !important; display: flex !important; justify-content: center !important; }
body.sidebar-closed aside#sidebar .user-name { justify-content: center !important; width: 100% !important; gap: 0 !important; }
body.sidebar-closed aside#sidebar .user-icon { margin: 0 auto !important; text-align: center !important; display: block !important; }
body.sidebar-closed aside#sidebar .footer-nav { padding-top: var(--space-sm) !important; }
body.sidebar-closed aside#sidebar .footer-nav .nav-item { justify-content: center !important; padding: var(--space-sm) 0 !important; margin-bottom: var(--space-3xs) !important; }

/* TABLET BREAKPOINT (Auto-Collapse is now handled via JS in suite_core.js) */
@media (max-width: 1600px) {
    /* No specific overrides needed here anymore as body.sidebar-closed handles the visuals */
}

/* MOBILE BREAKPOINT */
@media (max-width: 1024px) {
    body { flex-direction: column; padding-bottom: calc(95px + env(safe-area-inset-bottom)); height: auto; overflow: visible; }
    aside#sidebar { display: none !important; }
    .menu-fab { display: flex; }
    /* Scoped to this breakpoint only — otherwise a resize past 1024px while the drawer is
       open leaves the backdrop stuck full-screen (FAB/drawer correctly hide via this same
       media query, but the backdrop's old unscoped rule kept it visible, blocking all clicks). */
    body.mobile-menu-open .mobile-menu-backdrop { display: block; }

    /* The role-filtered nav list becomes a slide-up menu panel anchored above the
       .menu-fab (bottom-left). Hidden by default; revealed by body.mobile-menu-open.
       Each item is now a full-width row showing ICON + WORD. */
    .bottom-nav {
        display: flex;
        flex-direction: column;
        align-items: stretch;
        justify-content: flex-start;
        flex-wrap: nowrap;
        gap: var(--space-2xs);
        position: fixed;
        left: 16px;
        right: 16px;
        max-width: 340px;
        top: auto;
        bottom: calc(95px + env(safe-area-inset-bottom));
        /* Grow to fit the whole menu when there's room — from the bottom anchor up to
           just below the notch. Only caps (and scrolls) when the list is taller than
           the screen. */
        max-height: calc(100dvh - 110px - env(safe-area-inset-top) - env(safe-area-inset-bottom));
        overflow-x: hidden;
        overflow-y: auto;
        overscroll-behavior: contain;
        padding: var(--space-md);
        background: #ffffff;
        border: 1px solid #eee;
        border-radius: 18px;
        box-shadow: 0 12px 40px rgba(0,0,0,0.28);
        z-index: 30001;
        transform: translateY(14px) scale(0.96);
        transform-origin: bottom left;
        opacity: 0;
        visibility: hidden;
        pointer-events: none;
        transition: transform 0.22s cubic-bezier(0.175, 0.885, 0.32, 1.275), opacity 0.18s ease, visibility 0.18s ease;
    }
    body.mobile-menu-open .bottom-nav {
        transform: translateY(0) scale(1);
        opacity: 1;
        visibility: visible;
        pointer-events: auto;
    }
    .bottom-nav .nav-item {
        flex: 0 0 auto;
        width: 100%;
        min-width: 0;
        flex-direction: row;
        justify-content: flex-start;
        gap: 14px;
        padding: var(--space-lg) 14px;
        font-size: 15px;
        border-radius: var(--radius-lg);
        /* TOUCH-TARGET FLOOR (TASK-023 Phase 4, 2026-08-05). These two rules ARE the entire
           mobile navigation for all seven apps — aside#sidebar is display:none !important in
           this same block, so there is no other way to navigate below 1024px.

           The row's height is icon (1.35rem = 21.6px, line-height:1) + 2x vertical padding +
           2px border. With --space-lg that was 21.6+24+2 = 47.6px; the responsive tier steps
           --space-lg to 10px, which would give 43.6px — under the 44px minimum (WCAG 2.5.5 /
           Apple HIG). .nav-suite-item below is worse: it uses --space-md and is ALREADY 43.6px
           today, before any of this — a shipped defect this floor also fixes.

           min-height is purely additive here (both are width:100% flex rows with
           align-items:center) and costs nothing on desktop, where .bottom-nav is display:none.
           Density must never be allowed to shrink the only navigation on the device. */
        min-height: 44px;
    }
    .bottom-nav .nav-icon { font-size: 1.35rem; flex-shrink: 0; }
    /* Label span carries no class here (navigation_helper.php's bottom-nav markup — unlike the
       desktop sidebar's <span class="nav-label">, see .nav-label above) — same overflow bug, same
       fix, different selector since there's no class to hang it on. Long dateien folder names
       (TASK-289) were hard-clipped by .bottom-nav's overflow-x:hidden with no "…" otherwise. */
    .bottom-nav .nav-item span:not(.nav-icon) { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    .bottom-nav .bottom-nav-version-label { align-self: center !important; padding: var(--space-sm) 0 var(--space-3xs) !important; }

    /* Scroll hints — only matter when the list is taller than the screen.
       Visible scrollbar + fading edges (toggled by JS classes) so it's obvious
       there's more above/below. */
    .bottom-nav { scrollbar-width: thin; scrollbar-color: rgba(0,0,0,0.22) transparent; }
    .bottom-nav::-webkit-scrollbar { width: 6px; }
    .bottom-nav::-webkit-scrollbar-thumb { background: rgba(0,0,0,0.22); border-radius: 3px; }
    .bottom-nav::before,
    .bottom-nav::after {
        content: "";
        position: sticky;
        z-index: 2;
        height: 28px;
        margin: 0 -10px;            /* bleed over the 10px side padding */
        pointer-events: none;
        opacity: 0;
        transition: opacity 0.15s ease;
    }
    .bottom-nav::before {
        top: 0;
        margin-bottom: -28px;       /* collapse box so it overlays the first row */
        background: linear-gradient(#ffffff, rgba(255,255,255,0));
    }
    .bottom-nav::after {
        bottom: 0;
        margin-top: -28px;          /* overlay the last row */
        background: linear-gradient(rgba(255,255,255,0), #ffffff);
    }
    .bottom-nav.can-scroll-up::before { opacity: 1; }
    .bottom-nav.can-scroll-down::after { opacity: 1; }

    /* Divider + secondary styling for the suite/account group (Hub, Chat, Admin,
       Logout, Exit) so it reads as separate from THIS app's own feature items. */
    .bottom-nav .nav-suite-sep {
        flex: 0 0 auto;
        height: 1px;
        background: #e5e7eb;
        margin: var(--space-lg) var(--space-md) var(--space-sm);
    }
    /* App-feature items above the divider don't get a sep, so hide a leading/trailing one. */
    .bottom-nav .nav-suite-sep:first-child,
    .bottom-nav .nav-suite-sep:last-child { display: none; }

    .bottom-nav .nav-suite-item {
        background: transparent !important;
        border-color: transparent !important;
        color: #6b7280 !important;
        font-weight: 600;
        font-size: 14px;
        padding: var(--space-md) 14px;
        /* Already 43.6px BEFORE the responsive tier — see the note on .nav-item above.
           This is Hub / Chat / Admin / Logout: the way OUT of the current app. */
        min-height: 44px;
    }
    .bottom-nav .nav-suite-item .nav-icon { opacity: 0.9; }
    .bottom-nav .nav-suite-item:active { background: #f1f3f5 !important; }
    /* Keep the meaningful accents, readable on white */
    .bottom-nav .nav-suite-admin  { color: #c77f00 !important; }   /* admin  — amber */
    .bottom-nav .nav-suite-logout { color: #dc3545 !important; }   /* logout — red   */

    /* Minimal outer gutter: 2px horizontal, keep vertical breathing room.
       Top padding adds the device safe-area inset so content clears the notch /
       Dynamic Island when the app runs edge-to-edge (viewport-fit=cover + the
       black-translucent status bar). In a normal browser the inset resolves to 0,
       so there is no double gap. Sides use max() so landscape rounded corners are
       cleared without shrinking the portrait gutter. The background stays full-bleed
       behind the island for the immersive, larger-screen look. */
    main, .main-content {
        padding: calc(8px + env(safe-area-inset-top)) max(2px, env(safe-area-inset-right)) 8px max(2px, env(safe-area-inset-left));
    }
    /* Reduce card padding on mobile to avoid double gutters */
    .card { padding: var(--space-xl); border-radius: var(--radius-2xl); }

    /* ==== THE MOBILE EDGE BUDGET — the golden rule (owner, 2026-08-05) ==============
       OWNER'S RULE, verbatim: "the padding left and right to the edge of the screen …
       on mobile it should be about 2px", and — the half that is easy to miss — "text
       field and button position to card left and right must also be minimal, otherwise
       i have cards with narrow padding but the content is still not taking advantage."

       So the rule is NOT "the card hugs the edge". It is about the WHOLE CHAIN from the
       screen edge to the thing the user actually touches. Measured on dev at 390px:

           screen edge
             +2px   main/.main-content gutter        (the rule above — max(2px, safe-area))
            +12px   .card padding                    (var(--space-xl) at the mobile tier)
             +1px   .card border
           = 13-15px  →  a full-width field is >=92% of the viewport

       THE BUDGET, and what to do about it:
       - Outer gutter: 2px. Do NOT re-declare main/.main-content padding in an app —
         main_gutter_single_source.unit.js fails the build if you do.
       - Card padding: comes from the token tier, never a literal. An app that pins
         `.card { padding: 15px }` opts itself out of the whole system.
       - EVERYTHING BETWEEN THE CARD AND THE FIELD MUST CONTRIBUTE 0. This is the part
         that silently eats the win: a card at 12px means nothing if three nested
         wrapper divs each add 8px. Verified 2026-08-05 that HW's and Schule's wrappers
         are already at 0 — keep it that way. When adding markup inside a card on
         mobile, the wrapper carries layout (flex/grid/gap), NOT horizontal padding.
       - Fields and buttons that are meant to fill the row must actually be width:100%.
         A 266px select inside a 364px card is the defect this rule exists to name.

       HOW TO CHECK IT (do this, do not eyeball it) — at 390px in a browser:
           const f = document.querySelector('.card input, .card select');
           f.getBoundingClientRect().left            // must be ~13-15, never 25+
           f.getBoundingClientRect().width / innerWidth   // must be >= 0.92
           document.documentElement.scrollWidth <= innerWidth   // must be true

       And the rule has a hard companion: NO HORIZONTAL SCROLL, EVER. A single
       nowrap flex row is enough to break it — that exact bug was found and fixed on
       2026-08-05 in HW's `.mobile-bulk-actions` (3 data-driven buttons, 527px of
       content in a 364px card, page scrollWidth 541 at a 390px viewport). Any row
       whose child count comes from DATA must wrap. */

    /* NESTED CARDS MUST NOT CHARGE THE GUTTER TWICE.
       The single biggest, least obvious leak, measured on IW Stammdaten at 390px:

           main 2px → .card.wide-matrix 8px+1px → 11 → .card 8px+1px → 20 → input

       A card inside a card pays the horizontal gutter a SECOND time, so the field
       starts 20px in and spans 90% of the screen instead of ~13px / 94%. The outer
       card already separates the block from the page; the inner one only needs to
       separate itself VERTICALLY. So horizontal padding is dropped on the inner card
       and vertical padding is kept — the visual nesting survives, the wasted width
       does not.

       Horizontal only, and only on mobile: on desktop the nesting reads as intended
       and there is width to spare. */
    .card .card {
        padding-left: 0;
        padding-right: 0;
    }

    /* ==== 44px TOUCH TARGETS — suite-wide floor (owner 2026-08-05: "whole suite") ==========
       Measured across 24 views at 390px on 2026-08-05: the smallest interactive element was
       under 44px on EVERY page that was checked — 17px on HW's dashboard, 19px on Hub profile,
       22px on HW check-in, 31-33px on Schule/IW/Chat/Projekte. It was the single most universal
       failure in the suite, and unlike the spacing work it is a finger-accuracy problem, not an
       aesthetic one.

       SCOPED TO PRIMARY CONTROLS ON PURPOSE. This floors the things a user is meant to press —
       buttons, form fields, nav rows, tab/pill switchers. It deliberately does NOT force 44px
       onto every inline affordance (a table checkbox, a sort caret, a modal ×, an icon inside a
       text row): doing that would re-layout every table and modal in the suite, and WCAG's own
       AAA 2.5.5 target-size rule exempts inline and user-agent-controlled controls for exactly
       that reason. Those residual small targets are listed in the audit for a deliberate,
       visual pass — they are a known remainder, not an oversight.

       min-height (not height) so nothing that is already taller shrinks, and the flex centring
       only applies to elements that opt into flex anyway. Mobile only: a mouse does not need it. */
    .btn,
    button.btn,
    a.btn,
    .form-input,
    .form-select,
    input[type="text"], input[type="email"], input[type="password"], input[type="number"],
    input[type="date"], input[type="time"], input[type="search"], input[type="tel"],
    select,
    textarea,
    .nav-item,
    .tab-btn,
    .view-switch button {
        min-height: 44px;
    }
    /* textarea is multi-line: a floor is right, a fixed height is not. */
    textarea { min-height: 44px; height: auto; }

    /* ==== ISOLATED ICON BUTTONS — 44px TAP AREA, UNCHANGED VISUAL SIZE ==================
       The standard, decided 2026-08-05 (owner: "i dont know you decide for what standard
       we use"):

         TAP AREA must be >=44px. VISUAL SIZE need not be. They are different things, and
         conflating them is what makes the accessible option look like an ugly one.

       These icon buttons are ~22-32px squares — a dialog's x, a card's edit pencil, a file
       row's action. Enlarging them visually would coarsen every card header and toolbar in
       the suite. Instead the ::after pseudo-element extends the CLICKABLE box outward to at
       least 44px in both directions while the button keeps its drawn size. Nothing moves;
       the target simply stops being missable.

       WHY ONLY "ISOLATED" ONES. This trick is only safe where there is empty space around
       the control. Applying it to a dense grid — e.g. the 21 attendance checkboxes on HW's
       check-in screen, which sit ~30px apart — would make adjacent 44px zones OVERLAP, and
       the user would tick the WRONG row. That is worse than a small target. Dense grids get
       the whole ROW made tappable instead, which is a markup change per feature, not a CSS
       rule, and is tracked separately.

       WCAG 2.5.5 (AAA) asks for 44x44 and explicitly exempts inline controls sitting in a
       line of text — those keep their size deliberately, and that is a documented decision,
       not an omission. */
    .btn-icon,
    .sf-btn-icon,
    .modal-close,
    .qh-close {
        position: relative;
    }
    .btn-icon::after,
    .sf-btn-icon::after,
    .modal-close::after,
    .qh-close::after {
        content: "";
        position: absolute;
        /* centre a >=44px box on the control whatever its drawn size */
        top: 50%;
        left: 50%;
        width: max(100%, 44px);
        height: max(100%, 44px);
        transform: translate(-50%, -50%);
        /* purely a hit target — must never paint or intercept scrolling */
        background: transparent;
    }
    /* EXPECTED MEASUREMENT ARTIFACT, do not "fix" it: on a button smaller than 44px the overlay
       deliberately extends past the button box, so that BUTTON reports scrollWidth > clientWidth
       (e.g. 38/32 on a 32px .sf-btn-icon). That is the feature working. It is confined to the
       button itself — verified 2026-08-05 that page-level scrollWidth stays equal to innerWidth on
       every view where it appears — so an overflow scan will flag it and should ignore it. */
}

/* ═══ Expandable nav items (TASK-292) ══════════════════════════════════════════════════════════
   Only rendered for items that set 'expandable' => true, so every other app's sidebar is untouched.
   The twisty is a sibling of the link, never a child — a button inside an anchor is invalid markup
   and its click would also open the folder. */
.nav-item-wrap { position: relative; display: block; }
.nav-item-wrap > .nav-item { padding-right: 34px; }
.nav-twisty {
    position: absolute;
    right: 6px;
    top: 50%;
    transform: translateY(-50%);
    width: 24px;
    height: 24px;
    border: none;
    background: transparent;
    color: rgba(255, 255, 255, 0.55);
    font-size: 11px;
    line-height: 1;
    cursor: pointer;
    border-radius: 5px;
    font-family: inherit;
}
.nav-twisty:hover      { background: rgba(255, 255, 255, 0.12); color: #fff; }
.nav-twisty:focus-visible { outline: 2px solid rgba(255, 255, 255, 0.6); outline-offset: 1px; }

.nav-children { margin: 0 0 4px; }
.nav-children[hidden] { display: none; }
.nav-children .nav-item {
    padding-left: 42px;
    font-size: 13px;
    opacity: 0.85;
}
.nav-children-status {
    /* Tokens, not literals: spacing_scale_integrity.unit.js requires it so the density tiers can
       actually reach this padding. 42px is the indent that lines a child up under its parent's
       label and is genuinely a layout constant rather than spacing, so it stays a literal. */
    padding: var(--space-xs) var(--space-xl) var(--space-xs) 42px;
    font-size: 12px;
    color: rgba(255, 255, 255, 0.45);
}

/* ═══ A sidebar that can hold a folder tree (TASK-292/293) ══════════════════════════════════════
   260px was chosen for a fixed handful of feature names. A file workspace puts REGISTERED FOLDER
   NAMES there — "Helferwerkstatt — Teilnehmerlisten nach Kurs" — and then indents subfolders under
   them, and at 260px every one of them clipped to "Helferwerkstatt — …", which is the same string
   for six different folders. Owner, 2026-08-21, with a screenshot: "the nav bar is too narrow for
   this kind of structure."

   Three changes, and only the first is app-scoped:
     • an app can widen its own rail by setting --sidebar-width on <body>, since it is already the
       variable every rule reads. No app that does not set it moves a pixel.
     • a nav label may wrap to a SECOND line instead of clipping, but no further — two lines is
       enough for these names and a third would push the tree off screen.
     • the label carries a title attribute (navigation_helper.php), so the full name is reachable
       even in the cases that still clip. */
body.sidebar-wide { --sidebar-width: 330px; }

.nav-item-wrap > .nav-item .nav-label,
.nav-children .nav-item .nav-label {
    white-space: normal;
    overflow-wrap: anywhere;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    line-height: 1.3;
}
