/* phpframework-v2 — public/assets/css/app.css
   A single, cacheable stylesheet. Supplement to the critical CSS
   inlined in layout/header.php. Add custom overrides here.    */

/* ── Custom Properties ─────────────────────────────────────── */
:root {
    --color-primary:      #4f46e5;
    --color-primary-dark: #3730a3;
    --color-secondary:    #0ea5e9;
    --color-success:      #10b981;
    --color-danger:       #ef4444;
    --color-warning:      #f59e0b;
    --color-info:         #3b82f6;
    --color-purple:       #7c3aed;
    /* Neutral gray ramp (--color-gray-50…900): now emitted per-mode by
       ThemeService's generic layer (aliased onto the default-palette neutral
       sources), so it follows the configured palette and mode-flips. NOT
       defined here as static :root aliases — a `--x: var(--y)` at :root freezes
       to the light value and won't re-resolve under body.theme-dark. */
    /* Semantic tints — derived via color-mix from the (mode-flipping) base
       semantic colors + the active surface/ink, so they dark-adapt for free.
       Every var(--color-*-bg/-fg) usage now flips with the theme; the bg sits
       lightly over the panel, the fg pulls the hue toward the current ink. */
    --color-success-bg:   color-mix(in srgb, var(--color-success) 14%, var(--bg-panel));
    --color-success-fg:   color-mix(in srgb, var(--color-success) 70%, var(--text-default));
    --color-warning-bg:   color-mix(in srgb, var(--color-warning) 16%, var(--bg-panel));
    --color-warning-fg:   color-mix(in srgb, var(--color-warning) 72%, var(--text-default));
    --color-danger-bg:    color-mix(in srgb, var(--color-danger) 13%, var(--bg-panel));
    --color-danger-fg:    color-mix(in srgb, var(--color-danger) 72%, var(--text-default));
    --color-info-bg:      color-mix(in srgb, var(--color-info) 14%, var(--bg-panel));
    --color-info-fg:      color-mix(in srgb, var(--color-info) 72%, var(--text-default));
    --color-purple-bg:    var(--accent-subtle);
    --color-purple-fg:    var(--color-primary-dark);
    --font:               'Inter', system-ui, -apple-system, sans-serif;
    --radius:             8px;
    --radius-lg:          12px;
    --shadow:             0 1px 3px rgba(0,0,0,.08), 0 1px 2px rgba(0,0,0,.04);
    --shadow-md:          0 4px 6px -1px rgba(0,0,0,.08);
    --sidebar-width:      240px;
    --transition:         .15s ease;

    /* Phase 43.11 — non-color page-style tokens. Defaults below match
       the framework's pre-existing hardcoded values so unset projects
       render identically. Admin overrides come in via
       ThemeService::renderOverrideStyle() (which fetches saved
       project_settings rows and emits them as --style-* CSS vars on
       :root). Consumers below this :root block reference these via
       var(--style-*) so admin overrides propagate site-wide. */
    --style-border-card-radius:    12px;
    --style-border-card-width:     1px;
    --style-border-button-radius:  6px;
    --style-spacing-section-padding: 48px;
    --style-spacing-card-padding:    16px;
    --style-spacing-element-gap:     12px;
    --style-font-h1-size:    24px;
    --style-font-h2-size:    18px;
    --style-font-body-size:  14px;
    --style-font-heading-weight: 700;
    /* (theming v2 Phase 4: the --surface-* helper vars were retired — sections
       use data-palette + the ordinal palettes now; nothing references them.) */
}

/* ── Neutral-palette FLOOR ──────────────────────────────────────────────────
   ThemeService normally emits the gray ramp + surface/ink/border vars per-mode
   into an inline <style> (aliased onto the active palette's neutral sources),
   so the ramp follows the theme and dark-flips. But a page/site whose
   ThemeService output is absent or STALE (e.g. an older standalone build that
   shipped before the ramp-emission change) leaves these undefined — and then
   every `border: 1px solid var(--border-strong, var(--color-gray-300))` (admin
   .form-row inputs, .form-control, the global input rule below, …) is INVALID
   AT COMPUTED-VALUE TIME and reverts to `0 none` → invisible inputs/panels.
   This floor gives static light-mode literals so those always resolve. Wrapped
   in :where() → ZERO specificity, so ThemeService's real `:root` (0,1,0) and the
   body.theme-* toggles ALWAYS win, order-independent; this only fills the gap
   where nothing emitted them. Literals (not `var()` aliases) → none of the
   :root-freeze caveat above applies. */
:where(:root) {
    --color-gray-50:  #f9fafb;
    --color-gray-100: #f3f4f6;
    --color-gray-200: #e5e7eb;
    --color-gray-300: #d1d5db;
    --color-gray-400: #9ca3af;
    --color-gray-500: #6b7280;
    --color-gray-600: #4b5563;
    --color-gray-700: #374151;
    --color-gray-800: #1f2937;
    --color-gray-900: #111827;
    --bg-page:        #f9fafb;
    --bg-panel:       #ffffff;
    --text-default:   #111827;
    --text-muted:     #6b7280;
    --text-subtle:    #9ca3af;
    --border-subtle:  #f3f4f6;
    --border-default: #e5e7eb;
    --border-strong:  #d1d5db;
    --accent-subtle:  #eef2ff;
    --accent-contrast:#ffffff;
}

/* Theming v2 Phase 5 — re-resolve the semantic tints under the MANUAL theme
   toggles. The :root color-mix defs above already adapt for the OS-preference
   path (ThemeService flips :root's base vars via @media prefers-color-scheme),
   but the manual body.theme-dark / body.theme-light toggles set the base vars
   on <body>, so a :root-declared color-mix would stay frozen at its light
   substitution. Re-declaring the identical mix here lets each var() resolve
   against the toggled <body>'s base values (dark or light respectively). */
body.theme-dark, body.theme-light {
    --color-success-bg:   color-mix(in srgb, var(--color-success) 14%, var(--bg-panel));
    --color-success-fg:   color-mix(in srgb, var(--color-success) 70%, var(--text-default));
    --color-warning-bg:   color-mix(in srgb, var(--color-warning) 16%, var(--bg-panel));
    --color-warning-fg:   color-mix(in srgb, var(--color-warning) 72%, var(--text-default));
    --color-danger-bg:    color-mix(in srgb, var(--color-danger) 13%, var(--bg-panel));
    --color-danger-fg:    color-mix(in srgb, var(--color-danger) 72%, var(--text-default));
    --color-info-bg:      color-mix(in srgb, var(--color-info) 14%, var(--bg-panel));
    --color-info-fg:      color-mix(in srgb, var(--color-info) 72%, var(--text-default));
    --color-purple-bg:    var(--accent-subtle);
    --color-purple-fg:    var(--color-primary-dark);
}

/* ── Reset ──────────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }
body {
    margin: 0;
    font-family: var(--font);
    background: var(--bg-page, var(--color-gray-50));
    color: var(--text-default, var(--color-gray-900));
    font-size: var(--style-font-body-size, 14px);
    /* Phase 43.33 — body weight + italic from --style-font-body-* tokens. */
    font-weight: var(--style-font-body-weight, 400);
    font-style: var(--style-font-body-italic, normal);
    line-height: 1.6;
    -webkit-font-smoothing: antialiased;
}

/* Phase 43.11 — public-page chrome heading scale + weight driven by
   --style-font-* tokens. Admin overrides at /admin/settings/theme (or
   wizard step 6) shift the whole heading ramp without per-rule edits. */
/* Phase 43.31 — h1/h2/h3 use --font-family-heading when set, else
   fall back to body. code/pre/kbd use --font-family-mono. */
/* Phase 43.33 — heading italic + mono weight/italic via dedicated tokens. */
h1 { font-size: var(--style-font-h1-size, 24px); font-weight: var(--style-font-heading-weight, 700); font-style: var(--style-font-heading-italic, normal); font-family: var(--font-family-heading, var(--font-family-body, var(--font))); }
h2 { font-size: var(--style-font-h2-size, 18px); font-weight: var(--style-font-heading-weight, 700); font-style: var(--style-font-heading-italic, normal); font-family: var(--font-family-heading, var(--font-family-body, var(--font))); }
h3 { font-size: calc(var(--style-font-h2-size, 18px) * 0.9); font-weight: var(--style-font-heading-weight, 700); font-style: var(--style-font-heading-italic, normal); font-family: var(--font-family-heading, var(--font-family-body, var(--font))); }
code, pre, kbd, samp {
    font-family: var(--font-family-mono, ui-monospace, 'JetBrains Mono', 'Fira Code', monospace);
    font-weight: var(--style-font-mono-weight, 400);
    font-style: var(--style-font-mono-italic, normal);
}

/* ── Dark-mode form-input fallback ────────────────────────────
   Any input/select/textarea that doesn't set an explicit
   background inherits the surface color in dark mode. This
   catches view-local conventions like wizard's `.field input`
   that don't go through `.form-row` or `.form-control`. Light
   mode keeps the browser default (white) since we don't override.
   The body.theme-dark variant is for manual toggle.            */
@media (prefers-color-scheme: dark) {
    input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]):not([type=reset]):not([type=color]):not([type=range]):not([type=file]):not([type=image]),
    select,
    textarea {
        background-color: var(--bg-panel);
        color: var(--text-default);
        border-color: var(--border-strong);
    }
}
body.theme-dark input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]):not([type=reset]):not([type=color]):not([type=range]):not([type=file]):not([type=image]),
body.theme-dark select,
body.theme-dark textarea {
    background-color: var(--bg-panel);
    color: var(--text-default);
    border-color: var(--border-strong);
}

/* Global dark-mode overrides for inline-styled light-tinted rows
   commonly used across admin tables (danger highlight = #fef2f2,
   warning = #fffbeb, etc.). Without these, dark-mode users see
   light-tinted rows with white text inheritance = invisible. */
body.theme-dark [style*="background:#fef2f2"],
body.theme-dark [style*="background: #fef2f2"] { background: rgba(185, 28, 28, .15) !important; color: var(--text-default) !important; }
body.theme-dark [style*="background:#fffbeb"],
body.theme-dark [style*="background: #fffbeb"] { background: rgba(180, 83, 9, .15) !important; color: var(--text-default) !important; }
body.theme-dark [style*="background:var(--color-warning-bg)"],
body.theme-dark [style*="background: var(--color-warning-bg)"] { background: rgba(180, 83, 9, .15) !important; color: var(--text-default) !important; }
body.theme-dark [style*="background:#fef9c3"],
body.theme-dark [style*="background: #fef9c3"] { background: rgba(202, 138, 4, .15) !important; color: var(--text-default) !important; }
body.theme-dark [style*="background:#eef2ff"],
body.theme-dark [style*="background: #eef2ff"] { background: rgba(79, 70, 229, .15) !important; color: var(--text-default) !important; }
@media (prefers-color-scheme: dark) {
    body:not(.theme-light) [style*="background:#fef2f2"],
    body:not(.theme-light) [style*="background: #fef2f2"] { background: rgba(185, 28, 28, .15) !important; color: var(--text-default) !important; }
    body:not(.theme-light) [style*="background:#fffbeb"],
    body:not(.theme-light) [style*="background: #fffbeb"] { background: rgba(180, 83, 9, .15) !important; color: var(--text-default) !important; }
    body:not(.theme-light) [style*="background:var(--color-warning-bg)"],
    body:not(.theme-light) [style*="background: var(--color-warning-bg)"] { background: rgba(180, 83, 9, .15) !important; color: var(--text-default) !important; }
    body:not(.theme-light) [style*="background:#fef9c3"],
    body:not(.theme-light) [style*="background: #fef9c3"] { background: rgba(202, 138, 4, .15) !important; color: var(--text-default) !important; }
    body:not(.theme-light) [style*="background:#eef2ff"],
    body:not(.theme-light) [style*="background: #eef2ff"] { background: rgba(79, 70, 229, .15) !important; color: var(--text-default) !important; }
}

a { color: var(--color-primary); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 2px; }
/* Content-area links (article bodies, rendered markdown, composer blocks)
   get a richer treatment: visible underline + teal "visited" so readers can
   tell what they've already opened. Chrome/nav links above stay clean. */
.page-body a, .prose a, .builder-block a, .markdown-body a {
    text-decoration: underline;
    text-decoration-color: color-mix(in srgb, var(--color-primary) 38%, transparent);
    text-underline-offset: 2px;
    transition: color .12s, text-decoration-color .12s;
}
.page-body a:hover, .prose a:hover, .builder-block a:hover, .markdown-body a:hover {
    color: var(--color-primary-dark); text-decoration-color: currentColor;
}
.page-body a:visited, .prose a:visited, .builder-block a:visited, .markdown-body a:visited {
    color: var(--color-secondary);
}
img { max-width: 100%; height: auto; }

/* ── Password Strength Meter ────────────────────────────────── */
.password-strength-wrap {
    margin-top: .4rem;
}
.password-strength-bar {
    height: 4px;
    border-radius: 999px;
    background: var(--color-gray-200);
    overflow: hidden;
    margin-bottom: .3rem;
}
.password-strength-fill {
    height: 100%;
    border-radius: 999px;
    transition: width .3s ease, background-color .3s ease;
    width: 0%;
    background: var(--color-gray-300);
}
.password-strength-fill[data-score="1"] { width: 25%; background: var(--color-danger); }
.password-strength-fill[data-score="2"] { width: 50%; background: var(--color-warning); }
.password-strength-fill[data-score="3"] { width: 75%; background: var(--color-info); }
.password-strength-fill[data-score="4"] { width: 100%; background: var(--color-success); }
.password-strength-label {
    font-size: 11px;
    font-weight: 500;
    color: var(--color-gray-500);
}
.password-strength-label[data-score="1"] { color: var(--color-danger); }
.password-strength-label[data-score="2"] { color: var(--color-warning); }
.password-strength-label[data-score="3"] { color: var(--color-info); }
.password-strength-label[data-score="4"] { color: var(--color-success); }
.password-requirements {
    list-style: none;
    padding: 0;
    margin: .5rem 0 0;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: .15rem .75rem;
}
.password-requirements li {
    font-size: 11.5px;
    color: var(--color-gray-500);
    display: flex;
    align-items: center;
    gap: .3rem;
}
.password-requirements li::before {
    content: '○';
    font-size: 9px;
    flex-shrink: 0;
}
.password-requirements li.met { color: var(--color-success); }
.password-requirements li.met::before { content: '●'; }

/* ── Flash message animations ────────────────────────────────── */
.alert {
    animation: slideDown .2s ease;
}
@keyframes slideDown {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* ── Focus-visible rings ─────────────────────────────────────── */
:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: 2px;
}
button:focus-visible,
a:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
    outline: 2px solid var(--color-primary);
    outline-offset: 2px;
    border-radius: 4px;
}

/* ── Reduced motion ─────────────────────────────────────────
   Critical for users with vestibular disorders. Disables alert
   slide-down, banner-emulating pulse, button-hover transitions,
   dropdown opens, modal animations, and any other motion. The
   !important is intentional — view-local transitions should also
   honor user preference.                                       */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .01ms !important;
        scroll-behavior: auto !important;
    }
}

/* ── Skip link for accessibility ────────────────────────────── */
.skip-link {
    position: absolute;
    top: -40px;
    left: 0;
    background: var(--color-primary);
    color: #fff;
    padding: .5rem 1rem;
    z-index: 9999;
    font-size: 13.5px;
    font-weight: 600;
    border-radius: 0 0 6px 0;
    transition: top .15s;
}
.skip-link:focus { top: 0; }

/* ── Print styles ────────────────────────────────────────────── */
@media print {
    .sidebar, .topbar, .btn, form[data-confirm] { display: none !important; }
    .content { padding: 0; }
    body { background: #fff; font-size: 12pt; }
}

/* ── Responsive breakpoints ─────────────────────────────────── */
@media (max-width: 1024px) {
    .grid-4 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 768px) {
    .sidebar { display: none; }
    .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; }
    .topbar { padding: .6rem 1rem; }
    .content { padding: 1rem; }
    .shell { padding: 1.25rem 1rem; }
    table { font-size: 12px; }
    .table th, .table td { padding: .5rem .65rem; }
    /* Tables that aren't wrapped in .table-responsive: clip + scroll. */
    .card-body > table, .tbl-card > table, .tbl-card table { display: block; overflow-x: auto; }
    /* Mobile-friendly button rows: stack when there's no room. */
    .form-actions { flex-direction: column; align-items: stretch; }
    .form-actions > .btn { width: 100%; justify-content: center; }
    .form-actions > .spacer { display: none; }
}
@media (max-width: 640px) {
    /* Tighter table cells on small phones. */
    .table th, .table td { padding: .4rem .55rem; font-size: 11.5px; }
    /* Wizard progress-bar pill labels fall off; numbers remain. */
    /* Already handled in _progress.php via .prog-step-label { display: none } */
    /* Card headers wrap when an action button sits next to the title. */
    .card-header { flex-wrap: wrap; gap: .5rem; }
    /* Modal bodies need full width on small screens. */
    .modal { width: 100%; max-width: 100%; border-radius: 0; }
}
@media (max-width: 480px) {
    .btn { font-size: 13px; }
    .card-header { flex-wrap: wrap; gap: .5rem; }
    /* Topbar nav becomes a horizontal scroll strip on very narrow. */
    .topbar { gap: .5rem; }
    .topbar-nav { overflow-x: auto; flex-wrap: nowrap; }
    .topbar-nav > a, .topbar-nav .topbar-dd-toggle { white-space: nowrap; }
}

/* ── Shared components ───────────────────────────────────────
   These were originally only inlined as critical CSS in
   app/Views/layout/header.php, so views that don't include the
   layout (auth, signup, claim, public site shell) couldn't use
   them. Now they're available everywhere via this stylesheet.
   The inline header.php block still defines the same rules with
   theme-token variants — those load AFTER this file via <style>,
   so authed views get the theme-aware versions and unauthed
   views get these baseline versions.                            */

/* Cards — Phase 43.11 routes radius / border-width / inner padding
   through --style-* tokens so admin overrides at /admin/settings/theme
   (or wizard step 6) apply site-wide. Var fallbacks preserve the
   pre-existing visual baseline. */
.card {
    background: var(--bg-panel, #fff);
    border-radius: var(--style-border-card-radius, var(--radius));
    box-shadow: var(--shadow);
    border: var(--style-border-card-width, 1px) solid var(--border-default, var(--color-gray-200));
}
.card-header {
    padding: var(--style-spacing-card-padding, 1rem) 1.25rem;
    border-bottom: 1px solid var(--color-gray-200);
    display: flex; align-items: center; justify-content: space-between;
}
.card-header h2 { margin: 0; font-size: .95rem; font-weight: 600; }
.card-body { padding: var(--style-spacing-card-padding, 1.25rem); }

/* Alerts */
.alert {
    padding: .85rem 1rem; border-radius: var(--radius);
    margin-bottom: 1rem; font-size: 13.5px;
    display: flex; align-items: center; gap: .5rem;
}
.alert-success { background: var(--color-success-bg); color: var(--color-success-fg); border: 1px solid var(--color-success); }
.alert-error   { background: var(--color-danger-bg);  color: var(--color-danger-fg);  border: 1px solid var(--color-danger); }
.alert-warning { background: var(--color-warning-bg); color: var(--color-warning-fg); border: 1px solid var(--color-warning); }
.alert-info    { background: var(--color-info-bg);    color: var(--color-info-fg);    border: 1px solid var(--color-info); }

/* Form-group + form-control (legacy pattern; .form-row in admin.css is preferred for new code) */
.form-group { margin-bottom: 1rem; }
.form-group label {
    display: block; font-weight: 500;
    margin-bottom: .35rem; font-size: 13.5px;
}
.form-control {
    width: 100%; padding: .55rem .75rem;
    border: 1px solid var(--border-strong, var(--color-gray-300));
    border-radius: 6px; font-size: 14px;
    font-family: var(--font);
    background: var(--bg-panel, #fff); color: var(--text-default, var(--color-gray-900));
    transition: border-color .15s, box-shadow .15s;
}
.form-control:focus {
    outline: none;
    border-color: var(--color-primary);
    box-shadow: 0 0 0 3px rgba(79,70,229,.15);
}
.form-control.is-invalid { border-color: var(--color-danger); }
.form-error { color: var(--color-danger); font-size: 12px; margin-top: .25rem; display: block; }
.form-hint  { color: var(--color-gray-500); font-size: 12px; margin-top: .25rem; }
textarea.form-control { resize: vertical; min-height: 100px; }
select.form-control {
    appearance: none; -webkit-appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%236b7280' viewBox='0 0 16 16'%3E%3Cpath d='M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right .75rem center;
    padding-right: 2rem;
}

/* ── Themed form inputs EVERYWHERE — the "everything the builder produces is on
   theme" invariant. ───────────────────────────────────────────────────────
   Apply the .form-control look to ALL bare inputs/selects/textareas so any form
   — generated module views, custom-module forms, hand-written — is on-theme by
   default in light + dark, not just ones that opt into .form-control. (Generated
   forms use bare <input> inside .form-group, so before this they fell back to
   the browser default = unstyled/ugly.) Wrapped in :where() → ZERO specificity,
   so it's a pure gap-filler: anything with a class (.form-control, admin.css's
   .form-row, auth inputs) or an inline style wins, but it still beats the bare
   UA default. Because it's a plain `input` selector (no `>` combinator) it ALSO
   catches inputs nested in a wrapper — e.g. the password field wrapped by
   _password_toggle.php, which the `.form-row > input` direct-child rule misses
   (that's the "invisible password box" bug). No forced width here, so
   inline/compact inputs (search, a table-cell field) keep their size; block
   form fields fill their row via the .form-group rule below. */
:where(input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]):not([type=reset]):not([type=color]):not([type=range]):not([type=file]):not([type=image]):not([type=hidden]),
select,
textarea) {
    padding: .55rem .75rem;
    border: 1px solid var(--border-strong, var(--color-gray-300));
    border-radius: var(--style-border-input-radius, 6px);
    font-size: 14px;
    font-family: inherit;
    background: var(--bg-panel, #fff);
    color: var(--text-default, var(--color-gray-900));
    transition: border-color .15s, box-shadow .15s;
    box-sizing: border-box;
}
:where(input:not([type=checkbox]):not([type=radio]):not([type=submit]):not([type=button]):not([type=reset]):not([type=color]):not([type=range]):not([type=file]):not([type=image]):not([type=hidden]),
select,
textarea):focus {
    outline: none;
    border-color: var(--color-primary);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--color-primary) 18%, transparent);
}
:where(textarea) { resize: vertical; min-height: 90px; }
/* Block form fields fill their container (generated views wrap each in .form-group). */
.form-group input:not([type=checkbox]):not([type=radio]),
.form-group select,
.form-group textarea { width: 100%; }
/* Give a bare <select> the same chevron .form-control gets. */
select:not(.form-control) {
    appearance: none; -webkit-appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%236b7280' viewBox='0 0 16 16'%3E%3Cpath d='M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/%3E%3C/svg%3E");
    background-repeat: no-repeat; background-position: right .75rem center; padding-right: 2rem;
}

/* Buttons — Phase 43.11 button radius driven by --style-* token. */
.btn {
    display: inline-flex; align-items: center; gap: .4rem;
    padding: var(--style-button-padding-y, .5rem) var(--style-button-padding-x, 1rem); border-radius: var(--style-border-button-radius, 6px);
    font-weight: 500; font-size: var(--style-button-font-size, 13.5px);
    cursor: pointer; text-decoration: none;
    border: 1px solid transparent;
    transition: all .15s;
    font-family: var(--font); line-height: 1.4;
}
.btn-primary   { background: var(--color-primary); color: #fff; border-color: var(--color-primary); }
.btn-primary:hover { background: var(--color-primary-dark); border-color: var(--color-primary-dark); }
.btn-secondary { background: var(--bg-panel, #fff); color: var(--text-default, var(--color-gray-700)); border-color: var(--border-strong, var(--color-gray-300)); }
.btn-secondary:hover { background: var(--color-gray-50); }
.btn-danger    { background: var(--color-danger); color: #fff; border-color: var(--color-danger); }
.btn-danger:hover { background: var(--color-danger-fg); }
.btn-success   { background: var(--color-success); color: #fff; border-color: var(--color-success); }
.btn-sm { padding: .3rem .65rem; font-size: 12.5px; }
.btn-xs { padding: .2rem .5rem; font-size: 11.5px; }

/* Tables */
.table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.table th, .table td {
    padding: .65rem 1rem; text-align: left;
    border-bottom: 1px solid var(--color-gray-100);
}
.table th {
    font-weight: 600; color: var(--color-gray-500);
    font-size: 12px; text-transform: uppercase; letter-spacing: .04em;
    background: var(--color-gray-50);
}
.table tbody tr:hover { background: var(--color-gray-50); }
.table-responsive { overflow-x: auto; }

/* Badges */
.badge {
    display: inline-block; padding: .2rem .55rem;
    border-radius: 999px; font-size: 11px; font-weight: 600;
    line-height: 1;
}
.badge-primary { background: var(--color-purple-bg); color: var(--color-primary-dark); }
.badge-success { background: var(--color-success-bg); color: var(--color-success-fg); }
.badge-danger  { background: var(--color-danger-bg);  color: var(--color-danger-fg); }
.badge-warning { background: var(--color-warning-bg); color: var(--color-warning-fg); }
.badge-gray    { background: var(--color-gray-100);   color: var(--color-gray-700); }

/* Grid */
.grid { display: grid; gap: 1rem; }
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 768px) {
    .grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; }
}

/* ── Page shell wrappers ─────────────────────────────────────── */
/* Centered, padded container for full-page views (wizard, dashboard,
   auth, marketing pages). Replaces ad-hoc `.wiz-shell`, `.dash-shell`,
   `.auth-shell` definitions that were inlined per-view.                */
.shell {
    max-width: 1200px;
    margin: 0 auto;
    padding: 2rem 1.5rem;
}
.shell--narrow { max-width: 680px; }
.shell--medium { max-width: 880px; }
.shell--wide   { max-width: 1400px; }
@media (max-width: 640px) {
    .shell { padding: 1.25rem 1rem; }
}

/* ── Utility classes ─────────────────────────────────────────── */
.sr-only {
    position: absolute; width: 1px; height: 1px;
    padding: 0; margin: -1px; overflow: hidden;
    clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
.text-muted   { color: var(--color-gray-500); }
.text-danger  { color: var(--color-danger); }
.text-success { color: var(--color-success); }
.text-center  { text-align: center; }
.text-right   { text-align: right; }
.fs-12 { font-size: 12px; }
.fs-13 { font-size: 13px; }
.mt-0 { margin-top: 0; }
.mb-0 { margin-bottom: 0; }
.d-flex { display: flex; }
.d-inline-flex { display: inline-flex; }
.flex-wrap { flex-wrap: wrap; }
.align-center { align-items: center; }
.justify-between { justify-content: space-between; }
.gap-1 { gap: .5rem; }
.gap-2 { gap: 1rem; }
.gap-3 { gap: 1.5rem; }
.flex-1 { flex: 1; }
.w-100 { width: 100%; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Inline meta line — replaces the `style="color:#6b7280;font-size:12px"`
   pattern that appears 50+ times across admin/wizard views. Uses
   --text-muted in dark mode (via ThemeService) and falls back to
   --color-gray-500 on standalone surfaces.                            */
.muted-meta {
    color: var(--text-muted, var(--color-gray-500));
    font-size: 12px;
    line-height: 1.5;
}

/* ───────────────────────────────────────────────────────────────────────────
 * menus.embed block (modules/menus/module.php)
 *
 * Migrated out of the inline <style> the block used to emit per-instance
 * (Perf H, 2026-04-30). Same selectors, same rules; lives in app.css so
 * multiple menu blocks on one page don't duplicate the CSS payload.
 * ─────────────────────────────────────────────────────────────────────── */
.menu-block { margin: 0; padding: 0; list-style: none; }
.menu-block .menu-block-item { position: relative; }
.menu-block .menu-block-link { color: var(--color-primary); text-decoration: none; padding: .35rem .65rem; border-radius: 4px; display: inline-block; }
.menu-block .menu-block-link:hover { background: var(--accent-subtle); }
.menu-block .menu-block-holder-label { font-weight: 600; color: var(--text-muted); padding: .35rem .65rem; text-transform: uppercase; letter-spacing: .04em; font-size: 11.5px; }
.menu-block .menu-block-sublist { list-style: none; padding-left: 1rem; margin: 0; }
.menu-block--horizontal { display: flex; flex-wrap: wrap; gap: .25rem .5rem; align-items: center; }
.menu-block--horizontal > .menu-block-item { display: inline-flex; align-items: center; }
.menu-block--horizontal .menu-block-sublist { position: absolute; top: 100%; left: 0; background: var(--bg-panel); border: 1px solid var(--border-default); border-radius: 6px; padding: .35rem; min-width: 180px; box-shadow: 0 4px 6px -1px rgba(0,0,0,.1); display: none; z-index: 30; }
.menu-block--horizontal > .menu-block-item:hover > .menu-block-sublist { display: block; }
.menu-block--vertical { display: flex; flex-direction: column; gap: .15rem; }
.menu-block--dropdown { position: relative; display: inline-block; }
.menu-block--dropdown > .menu-block-trigger { background: var(--color-primary); color: var(--accent-contrast); border: none; padding: .5rem 1rem; border-radius: 6px; cursor: pointer; }
.menu-block--dropdown > ul { display: none; position: absolute; top: 100%; left: 0; background: var(--bg-panel); border: 1px solid var(--border-default); border-radius: 6px; padding: .35rem; min-width: 200px; box-shadow: 0 4px 6px -1px rgba(0,0,0,.1); z-index: 30; margin: .35rem 0 0; list-style: none; }
.menu-block--dropdown.is-open > ul { display: block; }
