/* Custom Matex Styles & Theme Overrides
 *
 * Three concerns live here:
 *
 *   1. mx-* responsive layout shortcuts (.mx-page, .mx-row, .mx-stack,
 *      .mx-cluster, .mx-toolbar, .mx-grid, .mx-list-row, .mx-table-stack,
 *      .mx-log-table).
 *
 *   2. Brand tokens + theme: --mtx-* shades, the .bg-mtx gradient, and
 *      DaisyUI override blocks under [data-theme="matex"]. Self-hosted
 *      Open Sans @font-face declarations. Brand-specific .login-card
 *      border treatment.
 *
 *   3. Bespoke widgets that no daisyUI/mx-* primitive covers:
 *        • .matex-submit-status — PWA submit lifecycle panel.
 *        • .matex-nav-pop — mobile-edge override for navbar dropdowns.
 *        • .matex-required-marker + .matex-field-error* — inline form
 *          validation states paired with matex-validate.js.
 *        • .toast-* — bespoke toast queue UI driven by window.matexNotify.
 *        • .animate-fade-in — keyframe Tailwind doesn't ship.
 *        • .matex-wrap / .matex-truncate / .matex-min-w-0 — overflow
 *          safety-net utilities.
 *
 * Frontend & branding reference: infrastructure/docs/branding-style.md.
 */

/* ────────────────────────────────────────────────────────────────────────
 * Responsive layout shortcuts (mx-* primitives)
 *
 * Reusable, named, opinionated layout patterns used across all services.
 * Templates compose these instead of repeating long Tailwind utility chains
 * and ad-hoc bespoke classes. See infrastructure/docs/branding-style.md §3.
 *
 * Breakpoint vocabulary (matches Tailwind defaults):
 *   sm = 640px  •  md = 768px  •  lg = 1024px  •  xl = 1280px
 * ────────────────────────────────────────────────────────────────────────*/

/* Page wrapper — consistent max-width + responsive padding. Use as the
 * outer <div> of every page's content block. */
.mx-page {
    width: 100%;
    max-width: 72rem;
    margin-inline: auto;
    padding: 1rem;
}
@media (min-width: 640px) { .mx-page { padding: 1.5rem; } }
.mx-page__header { margin-bottom: 1rem; }
@media (min-width: 640px) { .mx-page__header { margin-bottom: 1.5rem; } }

/* Vertical stack — children flow top-down with consistent gap. Default 1rem;
 * --sm/--lg modifiers tighten or loosen. */
.mx-stack { display: flex; flex-direction: column; gap: 1rem; }
.mx-stack--sm { gap: 0.5rem; }
.mx-stack--lg { gap: 1.5rem; }

/* Row that auto-stacks below md. Use for "two columns on tablet+, stacked
 * on phone" layouts (form + sidebar, content + aside, etc.). Children
 * stretch on mobile, top-align on row mode. */
.mx-row {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    align-items: stretch;
}
@media (min-width: 768px) {
    .mx-row {
        flex-direction: row;
        align-items: flex-start;
        gap: 1.5rem;
    }
}

/* Cluster — wrapping inline group. Pills/buttons/badges that should sit
 * on one line if they fit, wrap onto multiple lines otherwise. Default
 * gap suits chip-sized children. */
.mx-cluster {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}
@media (min-width: 640px) { .mx-cluster { gap: 0.75rem; } }

/* Toolbar — page-header action row. Wraps when it can't fit on one line.
 * Justify-between pushes title cluster left, action cluster right. */
.mx-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
}

/* Auto-fit responsive grid — the Bootstrap row/col equivalent.
 *
 * Each child is at LEAST `floor` wide. As many as fit on a row appear on
 * one row; the rest wrap to the next line. min(100%, floor) lets a single
 * column collapse to full width on viewports narrower than `floor`, so
 * children can never overflow horizontally.
 *
 * Pick the variant by tile size:
 *   .mx-grid--mini   — tiny tiles (~5rem floor), apps dropdown, badge rows
 *   .mx-grid--tight  — compact cards (~9rem floor)
 *   .mx-grid         — regular cards (11rem mobile, 13rem sm+)
 *   .mx-grid--cards  — large cards (~16rem floor)
 *
 * All variants are standalone; you don't need to pair them with `.mx-grid`
 * (each sets its own `display: grid`). */
.mx-grid,
.mx-grid--mini,
.mx-grid--tight,
.mx-grid--cards {
    display: grid;
    gap: 0.75rem;
}
.mx-grid {
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 11rem), 1fr));
}
@media (min-width: 640px) {
    .mx-grid {
        gap: 1rem;
        grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
    }
}
.mx-grid--mini {
    /* The floor is breakpoint-scaled because the responsive root font
     * size makes a fixed `rem` value mean different pixel sizes per
     * viewport. Tuned so a 17–18 rem panel always fits exactly 3 columns:
     *   phone   (root 22px, 3.5rem = 77px): 3 cols × ~80px in a 263px panel
     *   tablet+ (root 20px, 4rem = 80px):   3 cols × ~85px in a 280px panel
     *   desktop (root 18px, 4.5rem = 81px): 3 cols × ~90px in a 290px panel
     * In a wider container (e.g. a content card) it auto-flows to 4+ cols. */
    gap: 0.5rem;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 3.5rem), 1fr));
}
@media (min-width: 640px) {
    .mx-grid--mini {
        grid-template-columns: repeat(auto-fill, minmax(min(100%, 4rem), 1fr));
    }
}
@media (min-width: 1024px) {
    .mx-grid--mini {
        grid-template-columns: repeat(auto-fill, minmax(min(100%, 4.5rem), 1fr));
    }
}
.mx-grid--tight {
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 9rem), 1fr));
}
.mx-grid--cards {
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 16rem), 1fr));
}

/* List row — info on the left, action on the right. When the row gets
 * narrower than its content can fit, the action wraps onto its own line
 * below the info, anchored to the right. Replaces ad-hoc flex-wrap +
 * min-w-0 + ml-auto patterns scattered across services. */
.mx-list-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 0.75rem;
}
/* Beats `.mx-list-row { display: flex }` (equal specificity, declared later)
 * so the Tailwind `.hidden` toggle from paginate.js actually hides rows that
 * are off-page or filtered out by search. Same fix-pattern as
 * .rpt-list-card.hidden in rapportage.css. */
.mx-list-row.hidden { display: none; }
.mx-list-row__info {
    flex: 1 1 10rem;
    min-width: 0;
}
.mx-list-row__action {
    flex: 0 0 auto;
    margin-left: auto;
}
.mx-list-row__info > * { overflow-wrap: anywhere; word-break: break-word; }

/* Responsive table → cards: on < lg (≤1023px), the table renders as a
 * vertical stack of cards (one per row). On lg+ (≥1024px) it stays a regular
 * table. The breakpoint is at lg (not md) because 5-col admin tables stay
 * cramped between 768–1023px and force horizontal scroll. Each <td> should
 * set data-label="..." with the column label so the label shows in the
 * stacked view; the FIRST column's <td> is treated as the card header (no
 * label, underline separator). */
@media (max-width: 1023px) {
    .mx-table-stack { display: block; width: 100%; }
    .mx-table-stack thead { display: none; }
    .mx-table-stack tbody { display: block; }
    .mx-table-stack tr:not([hidden]) { display: block; }
    .mx-table-stack tr {
        background: oklch(var(--b1));
        border: 1px solid oklch(var(--b3) / 0.4);
        border-radius: 0.5rem;
        padding: 0.75rem;
        margin-bottom: 0.5rem;
    }
    /* Mirror daisyUI .table-zebra: alternate row background so card-stacked
     * rows are visually distinguishable on small/medium devices. Page bg is
     * already --b2, so we use --b3 (slightly darker) on even rows for
     * meaningful contrast against the white (--b1) odd rows. */
    .mx-table-stack.table-zebra tr:nth-child(even) {
        background: oklch(var(--b3) / 0.6);
    }
    .mx-table-stack td {
        display: flex;
        justify-content: space-between;
        align-items: center;
        gap: 0.75rem;
        border: 0 !important;
        padding: 0.3rem 0;
        text-align: left;
    }
    .mx-table-stack td[data-label]::before {
        content: attr(data-label);
        font-size: 0.7rem;
        font-weight: 500;
        color: oklch(var(--bc) / 0.55);
        text-transform: uppercase;
        letter-spacing: 0.025em;
        flex-shrink: 0;
    }
    .mx-table-stack td:first-child {
        display: block;
        padding-bottom: 0.5rem;
        margin-bottom: 0.25rem;
        border-bottom: 1px solid oklch(var(--b3) / 0.4) !important;
    }
    .mx-table-stack td.mx-table-stack__actions {
        justify-content: flex-end;
        padding-top: 0.5rem;
        margin-top: 0.25rem;
        border-top: 1px solid oklch(var(--b3) / 0.4) !important;
    }
}

/* Compact log-row layout for the admin logs page below lg. The 5-column
 * table flattens to a 2-row entry: meta line (time + level + service +
 * logger) on top, message below. */
@media (max-width: 1023px) {
    .mx-log-table { display: block; width: 100%; }
    .mx-log-table thead { display: none; }
    .mx-log-table tbody, .mx-log-table tr { display: block; }
    .mx-log-table tr {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 0.2rem 0.5rem;
        padding: 0.5rem 0.6rem;
        border-top: 1px solid oklch(var(--b3) / 0.3);
        font-size: 0.75rem;
    }
    .mx-log-table.table-zebra tr:nth-child(even) {
        background: oklch(var(--b3) / 0.4);
    }
    .mx-log-table tr > td { padding: 0; border: 0; display: inline-block; }
    .mx-log-table tr > td:nth-child(4) {
        opacity: 0.55;
        max-width: 12rem;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
    .mx-log-table tr > td:nth-child(5) {
        flex: 0 0 100%;
        word-break: break-word;
        white-space: pre-wrap;
    }
    .mx-log-table tr > td[colspan] {
        flex: 0 0 100%;
        text-align: center;
    }
}

/* Allow `.btn` text to wrap when constrained instead of overflowing.
 * daisyUI sets `flex-shrink: 0`, a fixed `height`, and `flex-wrap: wrap`
 * on .btn — so long text pushes the button wider than its container,
 * and when it does wrap, the icon ends up on its own row. Loosening
 * these (max-width clamps to container, nowrap keeps icon inline,
 * white-space + word-break let text reflow) makes the button grow
 * vertically with text wrapping cleanly beside the icon. Excludes
 * shape buttons (square/circle) and join-items (segmented controls). */
.btn:not(.btn-square):not(.btn-circle):not(.join-item) {
    white-space: normal;
    min-width: 0;
    max-width: 100%;
    height: auto;
    line-height: 1.2;
    word-break: break-word;
    flex-wrap: nowrap;
}

/* daisyUI's .tooltip declares `display: inline-block`, which clobbers
 * .btn's `inline-flex` and breaks center alignment for icon-only buttons.
 * When both classes are present, restore inline-flex so the icon stays
 * centered. */
.btn.tooltip { display: inline-flex; }

/* daisyUI's `.input` is a flex container when used in the icon-prefix
 * pattern (`<label class="input">…<input>…</label>`). Flex children with
 * text (input/select/textarea) default to `min-width: auto` = min-content
 * size, so a long `placeholder` keeps the inner input from shrinking and
 * the whole label overflows its container on narrow screens.
 *
 * Forcing `min-width: 0` on .input itself (so the label can shrink in its
 * own flex parent) and on its direct flex-child fields lets the layout
 * reflow cleanly. Same fix for .select and .join wrappers. */
.input,
.select,
.join {
    min-width: 0;
    max-width: 100%;
}
.input > input,
.input > select,
.input > textarea,
.select > select,
.select > input {
    min-width: 0;
}

/* Long unbreakable strings (URLs, emails, IDs, compound nouns) in headings,
 * card titles, and inline text default to `word-break: normal` which only
 * breaks at whitespace — so a single long token pushes its container wider
 * than its flex/grid track and overflows off-screen. `overflow-wrap:
 * anywhere` lets the renderer break the token at any character when no
 * better wrapping point exists, without affecting normal-text wrapping. */
h1, h2, h3, h4, h5, h6,
.card-title,
.alert,
.badge,
dt, dd,
li, p {
    overflow-wrap: anywhere;
}

/* Form-wrapped menu items inside daisyUI `.menu` — the canonical pattern is
 *   <li><a>…</a></li>
 * which inherits daisyUI's padding + hover styling automatically. When we
 * need a POST form (e.g. role-switcher), the `<form>` becomes the direct
 * child of `<li>` and daisyUI's hover bg attaches to the FORM, not the
 * inner button — so visually the hover is offset from where the button
 * actually sits. These rules:
 *   1. flatten the form box (no padding/bg of its own);
 *   2. style the button to match daisyUI's `.menu li > a` exactly;
 *   3. apply hover/focus to the BUTTON. */
.menu > li.matex-menu-form > form {
    margin: 0;
    padding: 0;
    background: transparent;
    width: 100%;
}
.menu > li.matex-menu-form > form > button {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    padding: 0.5rem 1rem;
    text-align: left;
    color: inherit;
    background: transparent;
    border: 0;
    border-radius: 0.5rem;
    font: inherit;
    cursor: pointer;
    transition: background-color 120ms ease;
}
.menu > li.matex-menu-form > form > button:hover,
.menu > li.matex-menu-form > form > button:focus-visible {
    background-color: oklch(var(--bc) / 0.1);
    outline: none;
}


/* ── Open Sans (self-hosted, GDPR-safe) ─────────────────────
 * Served from our own origin instead of fonts.gstatic.com, so IP
 * addresses never reach Google. The variable font covers weights
 * 300-800 within a single file per subset. */

@font-face {
    font-family: 'Open Sans';
    font-style: normal;
    font-weight: 300 800;
    font-display: swap;
    src: url("../fonts/opensans-latin.23145c714161.woff2") format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: 'Open Sans';
    font-style: normal;
    font-weight: 300 800;
    font-display: swap;
    src: url("../fonts/opensans-latin-ext.7f28d9acd0a8.woff2") format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ── Responsive root font size ────────────────────────────── */
/* Bigger tap targets + larger labels on phones (engineers with gloves
 * or thick fingers). Tailwind/DaisyUI sizes are rem-based, so bumping
 * the root font scales text, spacing, buttons, inputs, and icons in one go.
 * Desktop sits at the conventional 16px; phones get 18px, tablets 17px. */
html {
    font-size: 22px;
    /* Reserve the scrollbar gutter so opening a modal (which locks scroll via
     * body { overflow: hidden }) can't remove the scrollbar and reflow the page
     * sideways. No effect under overlay scrollbars, so headless visual tests
     * are unaffected. */
    scrollbar-gutter: stable;
}

@media (min-width: 640px) {
    html { font-size: 20px; }
}

@media (min-width: 1024px) {
    html { font-size: 18px; }
}

/* ── Typography ───────────────────────────────────────────── */
/* Override Tailwind preflight's sans-serif default with self-hosted
 * Open Sans + brand size scale. */
body {
    font-family: 'Open Sans', system-ui, -apple-system, sans-serif;
    font-size: 1rem;
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    color: oklch(var(--bc));
}

h1, h2, h3, h4, h5, h6 {
    font-family: 'Open Sans', system-ui, -apple-system, sans-serif;
    font-weight: 700;
    color: var(--mtx-blue);
    line-height: 1.25;
}

label {
    font-weight: 600;
    color: oklch(var(--bc));
}

/* ── Brand colour palette ─────────────────────────────────── */
/* Light/dark shade variants of the brand colours feed the `.bg-mtx`
 * gradient + a few one-off accents (login card border, ghost-button
 * hover state on the brand-blue gradient navbar). The flat brand colour
 * goes through daisyUI tokens — `text-primary`, `bg-primary`,
 * `border-primary` — instead of bespoke aliases. */
:root {
    --mtx-blue:         #0C5192;
    --mtx-blue-light:   #1865B0;
    --mtx-blue-dark:    #003B75;
    --mtx-orange:       #FF870F;
    --mtx-orange-light: #FF971E;
    --mtx-orange-dark:  #FF7100;
}

/* DaisyUI emerald theme — remap primary → Matex blue, accent → Matex orange */
[data-theme="matex"] {
    /* Primary: #1865B0 — oklch(46.82% 0.126 243.61) */
    --p:  46.82% 0.126 243.61;
    --pc: 100% 0 0;
    /* Accent: #FF870F — oklch(72% 0.185 47.5) */
    --a:  72% 0.185 47.5;
    --ac: 0% 0 0;
}

/* Gradient helpers */
.bg-mtx {
    background: linear-gradient(to right, var(--mtx-blue-light), var(--mtx-blue-dark));
}

/* DaisyUI btn-primary hover → orange */
[data-theme="matex"] .btn-primary:hover,
[data-theme="matex"] .btn-primary:focus-visible {
    background: linear-gradient(to right, var(--mtx-orange-light), var(--mtx-orange-dark));
    border-color: var(--mtx-orange-dark);
    color: #fff;
}

/* Page bg is grey, so transparent ghost buttons disappear. Give them a
   subtle white surface + outline that lifts off the page, and a clearer
   hover/focus state so they read as actionable. */
[data-theme="matex"] .btn-ghost {
    background-color: #ffffff;
    border: 1px solid rgba(15, 23, 42, 0.12);
    color: rgba(15, 23, 42, 0.78);
    box-shadow: 0 1px 1px rgba(15, 23, 42, 0.04);
    transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease, box-shadow 120ms ease;
}
[data-theme="matex"] .btn-ghost:hover,
[data-theme="matex"] .btn-ghost:focus-visible {
    background-color: #f8fafc;
    border-color: var(--mtx-orange);
    color: var(--mtx-orange-dark);
    box-shadow: 0 2px 4px rgba(15, 23, 42, 0.08);
}
[data-theme="matex"] .btn-ghost:active {
    background-color: #f1f5f9;
    box-shadow: 0 1px 1px rgba(15, 23, 42, 0.06) inset;
}
/* Ghost buttons inside dark surfaces (navbar, modals on dark): keep them
   transparent-on-dark instead of forcing a white pill that would clash. */
.bg-mtx .btn-ghost,
[data-theme="matex"] .navbar .btn-ghost,
[data-theme="matex"] .menu .btn-ghost {
    background-color: transparent;
    border-color: transparent;
    color: inherit;
    box-shadow: none;
}
.bg-mtx .btn-ghost:hover,
[data-theme="matex"] .navbar .btn-ghost:hover,
[data-theme="matex"] .menu .btn-ghost:hover {
    background-color: rgba(255, 255, 255, 0.12);
    border-color: transparent;
    color: inherit;
    box-shadow: none;
}

/* Input focus ring → orange */
[data-theme="matex"] input:focus,
[data-theme="matex"] select:focus,
[data-theme="matex"] textarea:focus {
    outline: 2px solid var(--mtx-orange);
    outline-offset: 1px;
}

/* ── Login page ──────────────────────────────────────────── */
/* Brand-blue ring around the login card. No daisyUI primitive offers a
 * 0.22rem coloured border with this exact treatment. */
.login-card {
    border: 0.22rem solid var(--mtx-blue);
    border-radius: 0.8rem;
}

@media (max-width: 420px) {
    .login-card {
        border-width: 0.16rem;
    }
}

/* ── PWA submit-status panel ─────────────────────────────── */
/* Compressing → uploading → queued → error lifecycle UI rendered above
 * the inert form during submit. Stays in place after the form is
 * replaced for the queued terminal state. Paired with pwa-ui.js. */
/* Rendered above the inert form during submit; stays in place after the
 * form is replaced for the queued terminal state. */
.matex-submit-status {
    display: flex;
    gap: 0.85rem;
    align-items: flex-start;
    background: #eff6ff;
    border: 2px solid var(--mtx-blue, #2563eb);
    border-radius: 0.75rem;
    padding: 0.85rem 1rem;
    margin: 0 0 1rem;
    box-shadow: 0 4px 16px rgba(37, 99, 235, 0.08);
}
.matex-submit-status[data-phase="error"] {
    background: #fef2f2;
    border-color: #dc2626;
    box-shadow: 0 4px 16px rgba(220, 38, 38, 0.10);
}
.matex-submit-status--queued {
    background: #fffbeb;
    border-color: #f59e0b;
    box-shadow: 0 4px 16px rgba(120, 53, 15, 0.10);
}
.matex-submit-status__icon { font-size: 1.4rem; line-height: 1; flex: 0 0 auto; }
.matex-submit-status__body { flex: 1 1 auto; min-width: 0; }
.matex-submit-status__title { font-weight: 800; font-size: 1rem; color: #1e3a8a; }
.matex-submit-status[data-phase="error"] .matex-submit-status__title { color: #7f1d1d; }
.matex-submit-status--queued .matex-submit-status__title { color: #78350f; }
.matex-submit-status__detail {
    margin-top: 0.15rem;
    font-size: 0.875rem;
    line-height: 1.35;
    color: #1f2937;
}
.matex-submit-status__bar {
    height: 6px;
    background: rgba(37, 99, 235, 0.18);
    border-radius: 9999px;
    overflow: hidden;
    margin-top: 0.5rem;
}
.matex-submit-status__fill {
    height: 100%;
    background: var(--mtx-blue, #2563eb);
    transition: width 0.2s ease-out;
}
.matex-submit-status__actions { margin-top: 0.65rem; display: flex; gap: 0.5rem; }
/* Rapportage forms keep them readable mid-submit; inert blocks input but
 * we add a subtle visual cue too. */
form[inert] {
    opacity: 0.7;
    pointer-events: none;
}

/* Mobile-edge override for navbar dropdowns.
 *
 * DaisyUI's dropdown-end anchors a panel's right edge to the trigger
 * button. With a 17-18 rem panel and a trigger near the right edge of a
 * narrow phone (320 px), the panel's left edge would dip past viewport.
 * Below the sm breakpoint we bypass the absolute positioning entirely
 * and pin the panel to both viewport edges. !important wins over
 * daisyUI's component `right: 0` / `position: absolute` rules.
 *
 * Apply via the `.matex-nav-pop` class on any `dropdown-content` that
 * needs this treatment (navbar app switcher + user menu). */
@media (max-width: 639px) {
    .matex-nav-pop {
        position: fixed !important;
        top: 3.4rem;
        right: 0.5rem !important;
        left: 0.5rem !important;
        width: auto !important;
        max-width: none !important;
        margin-top: 0;
    }
    /* Preview-mode banner (`request.session.forced_role`) sits above the
     * navbar inside the sticky header; offset the panel below the combined
     * bar. Keyed off the banner's own class because it is no longer a direct
     * child of <body>. */
    body:has(.matex-preview-banner) .matex-nav-pop {
        top: 5rem;
    }
}

/* ── Sticky header: shrink-on-scroll ─────────────────────────
 * base.html wraps the (optional) preview banner + navbar in a
 * `sticky top-0` container, so the whole top chrome stays pinned while
 * scrolling. matex.js adds `.is-scrolled` to the <header> once the page
 * leaves the top; the bar then trims its height + padding, shrinks the logo
 * and circular triggers, and goes lightly translucent + blurred so it reads
 * as less present while scrolling.
 *
 * The bar height is content-driven (the btn-md triggers + padding hold it at
 * ~4rem), so a smaller `min-height` alone does nothing — the triggers have to
 * come down too. We drop them to their compact mobile size + trim padding.
 *
 * Scoped to ≥640px on purpose: phones already run a short bar, and the
 * fixed-position mobile dropdown (.matex-nav-pop above) is offset to a
 * constant header height — so the mobile bar is kept a stable size. */
.matex-navbar,
.matex-navbar .btn-circle,
.matex-navbar .btn-circle > div,
.matex-navbar__logo {
    transition: min-height 200ms ease, height 200ms ease, width 200ms ease,
                padding 200ms ease, box-shadow 200ms ease,
                background-color 200ms ease;
}

@media (min-width: 640px) {
    .matex-navbar.is-scrolled {
        min-height: 2.75rem;
        padding-top: 0.25rem;
        padding-bottom: 0.25rem;
        /* Same brand gradient as .bg-mtx (--mtx-blue-light → --mtx-blue-dark),
         * dialled translucent so scrolled content shows faintly through. */
        background: linear-gradient(to right,
            rgb(24 101 176 / 0.85), rgb(0 59 117 / 0.85));
        backdrop-filter: blur(8px);
        -webkit-backdrop-filter: blur(8px);
        box-shadow: 0 1px 6px rgb(15 23 42 / 0.18);
    }
    .matex-navbar.is-scrolled .matex-navbar__logo { height: 1.5rem; }
    /* Circular triggers (apps + user avatar) → compact mobile size so they
     * stop holding the bar at full height. The avatar's inner circle is a
     * direct child of its btn-circle, so it shrinks with it. */
    .matex-navbar.is-scrolled .btn-circle {
        height: 2rem;
        min-height: 2rem;
        width: 2rem;
    }
    .matex-navbar.is-scrolled .btn-circle > div { width: 2rem; }
}

@media (prefers-reduced-motion: reduce) {
    .matex-navbar,
    .matex-navbar .btn-circle,
    .matex-navbar .btn-circle > div,
    .matex-navbar__logo {
        transition: none;
    }
}

/* Greeting JS writes its salutation into [data-greeting]. Hide on phones
 * (the avatar trigger already implies who's logged in). The navbar's
 * inline `hidden md:inline` covers its own usage; this rule remains as a
 * safety net for any future [data-greeting] outside the navbar tree. */
[data-greeting]:empty { display: none; }

/* ── Animations ──────────────────────────────────────────── */
/* `fade-in` powers the `.animate-fade-in` utility (Tailwind doesn't
 * ship this animation). Toast slide-in keyframes live further below. */
@keyframes fade-in {
    from { opacity: 0; transform: translateY(-4px); }
    to   { opacity: 1; transform: translateY(0); }
}

.animate-fade-in {
    animation: fade-in 0.2s ease-out;
}

/* Toast queue UI driven by window.matexNotify.
 *   DaisyUI has `alert` but not the slide-in queue/dismiss/auto-fade
 *   behavior; the JS-CSS pairing is intentional. */
/* ── Toast notifications ─────────────────────────────────── */
/* Shared visual language for both Django-messages toasts (rendered in
 * base.html) and JS-triggered toasts from window.matexNotify(). */

@keyframes matex-toast-in {
    from { opacity: 0; transform: translateX(1.5rem); }
    to   { opacity: 1; transform: translateX(0); }
}

@keyframes matex-toast-out {
    from { opacity: 1; transform: translateX(0); }
    to   { opacity: 0; transform: translateX(1.5rem); }
}

.toast-msg {
    animation: matex-toast-in 0.3s ease-out both;
    transform-origin: right center;
}

.toast-msg.toast-closing {
    animation: matex-toast-out 0.3s ease-in forwards;
    pointer-events: none;
}

.toast-msg .toast-close {
    margin-left: 0.25rem;
    padding: 0 0.375rem;
    font-size: 1.25rem;
    line-height: 1;
    border-radius: 0.375rem;
    background: transparent;
    color: inherit;
    border: none;
    opacity: 0.7;
    cursor: pointer;
    flex-shrink: 0;
    align-self: flex-start;
    transition: opacity 0.15s ease, background-color 0.15s ease;
}

.toast-msg .toast-close:hover {
    opacity: 1;
    background: rgba(255, 255, 255, 0.18);
}

/* Warning toasts use dark text on a light background — soften the hover. */
.toast-msg.text-gray-900 .toast-close:hover {
    background: rgba(0, 0, 0, 0.08);
}

/* Hide default <details> marker in all browsers */
details > summary { list-style: none; }
details > summary::-webkit-details-marker { display: none; }
details > summary::marker { display: none; content: ""; }

/* Alpine.js cloak: keep elements hidden until x-data is bound. The attribute
 * is added to the DOM by the author and removed by Alpine on init. */
[x-cloak] { display: none; }

/* ── Required-field marker + inline validation ───────────────
 * Generic UX shared across all services. Templates mark a field unit
 * with `data-matex-field` and embed `<div data-matex-error>` for the
 * error slot. The validator (matex-validate.js) toggles
 * `.matex-field--errored` on the field unit and `.matex-field-error--visible`
 * on the slot; CSS handles the rest. The asterisk (`<span class="matex-required-marker">*</span>`)
 * sits next to a field's label and is purely visual — accessibility
 * relies on native `required` / `aria-required` on the input itself. */

.matex-required-marker {
    color: var(--mtx-orange);
    margin-left: 0.2rem;
    font-weight: 700;
    user-select: none;
}

.matex-field-error {
    display: none;
    color: oklch(var(--er, 65% 0.21 25));
    font-size: 0.75rem;
    line-height: 1.25;
    margin-top: 0.25rem;
}

.matex-field-error--visible {
    display: block;
}

[data-matex-field].matex-field--errored input,
[data-matex-field].matex-field--errored textarea,
[data-matex-field].matex-field--errored select,
[data-matex-field].matex-field--errored .input,
[data-matex-field].matex-field--errored .textarea,
[data-matex-field].matex-field--errored .select {
    border-color: oklch(var(--er, 65% 0.21 25));
    box-shadow: 0 0 0 1px oklch(var(--er, 65% 0.21 25) / 0.35);
}

[data-matex-photo-pair].matex-field--errored .rpt-photo-btn {
    outline: 2px solid oklch(var(--er, 65% 0.21 25));
    outline-offset: 1px;
}

/* ── Responsive overflow safety net ──────────────────────────
 * Prevents long unbroken strings (URLs, IDs, codes, kenmerk numbers)
 * from punching through their parent on narrow viewports. Set as a
 * sane baseline rather than per-component so any future template
 * inherits the behaviour. */

/* Long words break instead of overflowing the body. */
body {
    overflow-wrap: break-word;
    word-wrap: break-word;
}

/* DaisyUI badges + the artikellijst totals pill are inline-flex. DaisyUI
 * ships a fixed `height` + `line-height` (e.g. badge-sm = 1rem) that
 * clips an icon + text combo as soon as the rem scales up or content
 * wraps. Swap the fixed height for a min-height so badges grow with
 * content, cap them at the parent's width, and keep the label on a
 * single line — if multiple badges share a row, `flex-wrap` on the
 * parent moves the next badge onto a new line rather than breaking a
 * word per-character. */
.badge,
.rpt-art-totals__pill {
    max-width: 100%;
    white-space: nowrap;
    overflow-wrap: normal;
    word-break: normal;
    height: auto;
    line-height: 1.25;
}

.badge {
    min-height: 1.25rem;
    padding-top: 0.125rem;
    padding-bottom: 0.125rem;
}

.badge-xs {
    min-height: 0.75rem;
    height: auto;
    line-height: 1.1;
}

.badge-sm {
    min-height: 1rem;
    height: auto;
    line-height: 1.15;
}

.badge-lg {
    min-height: 1.5rem;
    height: auto;
}

/* When badges sit inside a flex row they should NOT shrink (would force
 * the label to break per-character). Instead, parent `flex-wrap` pushes
 * them to a new line as a whole. */
.badge {
    flex: 0 0 auto;
}

/* Utility classes for explicit use where the cascade isn't enough. */
.matex-wrap {
    overflow-wrap: anywhere;
    word-break: break-word;
    white-space: normal;
    min-width: 0;
}

.matex-truncate {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Flex/grid children default to `min-width: auto`, which is the #1 cause
 * of children refusing to shrink below their content. Apply on the
 * problem-area we already use heavily. */
.matex-min-w-0 {
    min-width: 0;
}
