/* ============================================================================
   Tentary back-end UI framework (ui.css)
   The shared design system for app/admin pages. Three layers:
     1. TOKENS    — :root custom properties (the only place raw values live)
     2. UTILITIES — small composable layout/spacing/text helpers (.u-*)
     3. COMPONENTS— reusable patterns (.ui-*): card, btn, field, segmented…
   Rules of the road (see /ui styleguide + CLAUDE.md):
     • New back-end markup uses tokens + .ui-/.u- classes — never hardcode a
       hex / radius / shadow, never re-create a card or button.
     • Feature-specific styles (ce-, sp-, …) are allowed for genuinely one-off
       bits, but must be built ON these tokens.
     • Purely additive — legacy classes are untouched; pages migrate gradually.
   ============================================================================ */

:root {
    /* — color (accent maps to the existing brand token, #ff4400) — */
    --ui-accent:        var(--color-main, #ff4400);
    --ui-accent-dark:   var(--color-main-dark, #cc3700);
    --ui-accent-soft:   #fff0eb;              /* tint for badges / hover wells */

    --ui-bg:            #f6f6f6;              /* page background */
    --ui-surface:       #ffffff;             /* cards, inputs */
    --ui-surface-2:     #fafafa;             /* wells, table heads, diagrams */

    --ui-text:          #18181b;
    --ui-muted:         #71717a;             /* secondary text */
    --ui-faint:         #a1a1aa;             /* hints, placeholders */

    --ui-border:        #e7e7ea;
    --ui-border-strong: #d4d4d8;

    --ui-success:       #16a34a;
    --ui-success-soft:  #f0fdf4;
    --ui-warn:          #d97706;
    --ui-warn-soft:     #fff7ed;             /* warn wells / notices */
    --ui-danger:        #dc2626;
    --ui-danger-soft:   #fef2f2;
    --ui-info:          #2563eb;
    --ui-info-soft:     #eff6ff;

    /* complementary accent (already used for onboarding-ready, changelog tags, plan badges) */
    --ui-purple:        #7c3aed;
    --ui-purple-soft:   rgba(124,58,237,.10);

    /* — spacing scale — */
    --ui-s1: 4px;  --ui-s2: 8px;  --ui-s3: 12px; --ui-s4: 16px;
    --ui-s5: 24px; --ui-s6: 32px; --ui-s7: 48px; --ui-s8: 64px;

    /* — radius — */
    --ui-r-sm: 6px; --ui-r-md: 10px; --ui-r-lg: 14px; --ui-r-pill: 999px;

    /* — elevation (only two; no 21-shadow zoo) — */
    --ui-shadow-sm: 0 1px 2px rgba(0,0,0,.04), 0 1px 3px rgba(0,0,0,.05);
    --ui-shadow-md: 0 6px 20px rgba(0,0,0,.08);
    --ui-shadow-lg: 0 16px 48px rgba(0,0,0,.18);   /* modals, popovers, menus */

    /* — type — */
    --ui-fs-1: 12px; --ui-fs-2: 13px; --ui-fs-3: 14px;
    --ui-fs-4: 16px; --ui-fs-5: 20px; --ui-fs-6: 26px;

    /* — controls — (matches the 36px button height) */
    --ui-control-h: 36px;
    --ui-focus: 0 0 0 3px rgba(255,68,0,.18);
}

/* ============================================================================
   2. UTILITIES  (.u-*) — curated; compose, don't proliferate
   ============================================================================ */

/* layout primitives */
.u-stack      { display: flex; flex-direction: column; gap: var(--ui-s4); }
.u-stack-sm   { display: flex; flex-direction: column; gap: var(--ui-s2); }
.u-stack-lg   { display: flex; flex-direction: column; gap: var(--ui-s6); }
.u-cluster    { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ui-s3); }
.u-between    { display: flex; align-items: center; justify-content: space-between; gap: var(--ui-s4); }
.u-grid       { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: var(--ui-s4); }
.u-grow       { flex: 1 1 auto; min-width: 0; }
.u-wrap       { flex-wrap: wrap; }

/* spacing (scale-mapped; only the common ones) */
.u-mt-0{margin-top:0}    .u-mt-2{margin-top:var(--ui-s2)} .u-mt-3{margin-top:var(--ui-s3)}
.u-mt-4{margin-top:var(--ui-s4)} .u-mt-5{margin-top:var(--ui-s5)} .u-mt-6{margin-top:var(--ui-s6)} .u-mt-7{margin-top:var(--ui-s7)}
.u-mb-0{margin-bottom:0} .u-mb-2{margin-bottom:var(--ui-s2)} .u-mb-3{margin-bottom:var(--ui-s3)}
.u-mb-4{margin-bottom:var(--ui-s4)} .u-mb-5{margin-bottom:var(--ui-s5)} .u-mb-6{margin-bottom:var(--ui-s6)}

/* text */
.u-h1     { font-size: var(--ui-fs-6); font-weight: 800; line-height: 1.15; letter-spacing: -.01em; color: var(--ui-text); margin: 0; }
.u-h2     { font-size: var(--ui-fs-5); font-weight: 700; line-height: 1.2; color: var(--ui-text); margin: 0; }
.u-h3     { font-size: var(--ui-fs-4); font-weight: 700; line-height: 1.3; color: var(--ui-text); margin: 0; }
.u-small  { font-size: var(--ui-fs-2); }
.u-muted  { color: var(--ui-muted); }
.u-faint  { color: var(--ui-faint); }
.u-strong { font-weight: 600; color: var(--ui-text); }
.u-center { text-align: center; }
.u-narrow { max-width: 700px; }
.u-hidden { display: none !important; }

/* ============================================================================
   3. COMPONENTS (.ui-*)
   ============================================================================ */

/* — page scaffolding — page heading matches the settings pages (20px/700) — */
.ui-page-head { margin: 0 0 var(--ui-s5); }
.ui-page-head h1, .ui-page-head .ui-title { font-size: var(--ui-fs-5); font-weight: 700; margin: 0 0 var(--ui-s2); color: var(--ui-text); }
.ui-page-head .ui-intro { color: var(--ui-muted); font-size: var(--ui-fs-3); margin: 0; max-width: 700px; line-height: 1.6; }

.ui-section { margin-top: var(--ui-s7); }
.ui-section > h2, .ui-section-title { font-size: var(--ui-fs-4); font-weight: 700; margin: 0 0 var(--ui-s2); color: var(--ui-text); }
.ui-section > p, .ui-section-desc { color: var(--ui-muted); font-size: var(--ui-fs-3); margin: 0 0 var(--ui-s4); line-height: 1.55; }
.ui-section > p.ui-hint { font-size: var(--ui-fs-1); color: var(--ui-faint); margin: var(--ui-s2) 0 0; line-height: 1.5; }

/* — editor beside a live preview —————————————————————————————————————
   Settings pages where you change something and watch it change: invoice design, the opt-in
   email. Fills the standard .mm page width; both columns are equal, because a preview that is
   narrower than its form reads as an afterthought. Stacks below ~900px. */
.ui-split { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--ui-s7); }
.ui-split-main, .ui-split-side { flex: 1 1 0; min-width: 0; }
.ui-split-main .ui-section:first-child { margin-top: 0; }
.ui-split-sticky { position: sticky; top: var(--ui-s5); }
@media(max-width:900px){ .ui-split-main, .ui-split-side { flex-basis: 100%; } }

/* — flow diagram ——————————————————————————————————————————————————————
   Shows a chain the seller does not control end to end: how a payment travels, how a buyer
   becomes a subscriber. Steps read left to right and stack on mobile. A step may be an <a>
   when it links to where that part is set. */
.ui-flow { display: flex; align-items: center; margin: var(--ui-s4) 0 var(--ui-s5); padding: var(--ui-s5);
    border: 1px solid var(--ui-border); border-radius: var(--ui-r-md); background: var(--ui-surface-2); }
.ui-flow-step { flex: 1; text-align: center; background: var(--ui-surface); border: 1px solid var(--ui-border);
    border-radius: var(--ui-r-md); padding: var(--ui-s3) var(--ui-s4); color: var(--ui-text); text-decoration: none; }
a.ui-flow-step { transition: border-color .15s ease; }
a.ui-flow-step:hover { border-color: var(--ui-border-strong); }
.ui-flow-icon { height: 36px; margin: 0 0 var(--ui-s2); font-size: 28px; color: var(--ui-text);
    display: flex; align-items: center; justify-content: center; }
.ui-flow-icon img { height: 18px; }
.ui-flow-label { font-size: var(--ui-fs-2); font-weight: 600; margin: 0 0 2px; }
.ui-flow-sub { font-size: var(--ui-fs-1); color: var(--ui-faint); }
.ui-flow-arrow { display: flex; align-items: center; padding: 0 var(--ui-s2); font-size: 18px; color: var(--ui-faint); }
@media(max-width:640px){ .ui-flow { flex-direction: column; align-items: stretch; gap: var(--ui-s1); }
    .ui-flow-arrow { justify-content: center; transform: rotate(90deg); } }

/* — card — */
.ui-card { background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: var(--ui-r-md); padding: var(--ui-s5); }
.ui-card-pad-sm { padding: var(--ui-s4); }
.ui-card-elevated { box-shadow: var(--ui-shadow-sm); }
a.ui-card, .ui-card-link { display: block; text-decoration: none; color: inherit; transition: border-color .15s ease, transform .15s ease, box-shadow .15s ease; }
a.ui-card:hover, .ui-card-link:hover { border-color: var(--ui-border-strong); }
.ui-card-h { font-size: var(--ui-fs-4); font-weight: 700; margin: 0 0 var(--ui-s2); color: var(--ui-text); }

/* — buttons — */
.ui-btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--ui-s2);
    height: 36px; padding: 0 var(--ui-s4); border-radius: var(--ui-r-sm);
    font-size: var(--ui-fs-3); font-weight: 600; line-height: 1; cursor: pointer; font-family: inherit;
    border: 1px solid transparent; background: var(--ui-surface-2); color: var(--ui-text);
    text-decoration: none; white-space: nowrap; transition: background .15s ease, border-color .15s ease, opacity .15s ease; }
/* hover rules set color too — the legacy global a:hover{color:#ff4400} must not bleed into button links */
.ui-btn:hover { background: #f0f0f1; color: var(--ui-text); }
.ui-btn:focus-visible { outline: none; box-shadow: var(--ui-focus); }
.ui-btn-primary, .ui-btn-primary:hover { color: #fff; }
.ui-btn-primary { background: var(--ui-accent); }
.ui-btn-primary:hover { background: var(--ui-accent-dark); }
.ui-btn-ghost { background: transparent; border-color: var(--ui-border-strong); color: var(--ui-text); }
.ui-btn-ghost:hover { background: var(--ui-surface-2); color: var(--ui-text); }
.ui-btn-danger { background: transparent; border-color: var(--ui-danger); color: var(--ui-danger); }
.ui-btn-danger:hover { background: var(--ui-danger); color: #fff; }
.ui-btn-sm { height: 30px; padding: 0 var(--ui-s3); font-size: var(--ui-fs-2); }
.ui-btn[disabled], .ui-btn.is-disabled { opacity: .5; pointer-events: none; }
/* text / inline "add" action — accent label, optional leading icon (e.g. "+ New lesson") */
.ui-btn-link { display: inline-flex; align-items: center; gap: var(--ui-s2); background: none; border: 0; height: auto; padding: 0; color: var(--ui-accent); font-weight: 600; cursor: pointer; text-decoration: none; }
.ui-btn-link:hover { background: none; color: var(--ui-accent-dark); text-decoration: none; }

/* — inline disclosure (native <details>, framework-styled trigger; for rare/secondary actions) — */
.ui-disclosure > summary { list-style: none; cursor: pointer; user-select: none; -webkit-user-select: none;
    display: inline-flex; align-items: center; gap: var(--ui-s2);
    font-size: var(--ui-fs-2); font-weight: 600; color: var(--ui-muted); }
.ui-disclosure > summary::-webkit-details-marker { display: none; }
.ui-disclosure > summary:hover { color: var(--ui-text); }
/* caret: the leading topical icon says what it is, this says it opens. Points right when
   closed and down when open, like the other collapsibles in the app (.ocf-chevron). */
.ui-disclosure > summary::after { content: ""; width: 6px; height: 6px; margin-left: var(--ui-s2); opacity: .35;
    border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
    transform: rotate(-45deg); transition: transform .2s; }
.ui-disclosure[open] > summary::after { margin-bottom: 4px; transform: rotate(45deg); }

/* — form fields — */
.ui-field { display: flex; flex-direction: column; gap: var(--ui-s2); }
.ui-label { font-size: var(--ui-fs-2); font-weight: 600; color: var(--ui-text); }
.ui-input, .ui-select, .ui-textarea {
    width: 100%; min-height: var(--ui-control-h); padding: 8px 12px; box-sizing: border-box;
    background: var(--ui-surface); border: 1px solid var(--ui-border-strong); border-radius: var(--ui-r-sm);
    font-size: var(--ui-fs-3); color: var(--ui-text); font-family: inherit; transition: border-color .15s ease, box-shadow .15s ease; }
.ui-textarea { min-height: 96px; resize: vertical; line-height: 1.5; }
/* A textarea holding code (custom head/body code, an HTML page): monospace, no wrapping, and a
   tinted ground so it reads as a code field rather than prose. */
.ui-textarea-code { font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, monospace;
    font-size: 13px; line-height: 1.5; min-height: 200px; tab-size: 4; -moz-tab-size: 4;
    white-space: pre; overflow-wrap: normal; overflow-x: auto; background: var(--ui-surface-2); }
.ui-select { appearance: none; -webkit-appearance: none; padding-right: 38px; cursor: pointer;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='none' stroke='%2371717a' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M1 1.5 6 6.5 11 1.5'/%3E%3C/svg%3E");
    background-repeat: no-repeat; background-position: right 13px center; }
.ui-input:focus, .ui-select:focus, .ui-textarea:focus { outline: none; border-color: var(--ui-accent); box-shadow: var(--ui-focus); }
.ui-input::placeholder, .ui-textarea::placeholder { color: var(--ui-faint); }
/* the browser's own "clear search" X follows the system accent (blue) — swap it for a
   neutral one so it matches the field. WebKit/Blink only; Firefox draws no button. */
.ui-input[type="search"]::-webkit-search-cancel-button {
    -webkit-appearance: none; appearance: none; width: 13px; height: 13px; margin-left: 6px; cursor: pointer;
    background-color: var(--ui-muted);
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' d='M3 3 13 13M13 3 3 13'/%3E%3C/svg%3E") center / contain no-repeat;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' d='M3 3 13 13M13 3 3 13'/%3E%3C/svg%3E") center / contain no-repeat; }
.ui-input[type="search"]::-webkit-search-cancel-button:hover { background-color: var(--ui-text); }
/* iOS draws a date field as a native control and centres its value inside
   ::-webkit-date-and-time-value, so a full-width one reads as a button with the date
   floating in the middle. Normalise it to look like every other text field. */
.ui-input[type="date"] { -webkit-appearance: none; appearance: none; text-align: left; }
.ui-input[type="date"]::-webkit-date-and-time-value { text-align: left; margin: 0; min-height: 1.2em; }
.ui-input[type="date"]::-webkit-datetime-edit { text-align: left; padding: 0; }
.ui-hint { font-size: var(--ui-fs-1); color: var(--ui-faint); }
/* — input with a fixed affix ————————————————————————————————————————
   A field that always carries the same text beside it: a domain ending, a unit, a currency.
   The affix keeps its width and never wraps; the field takes what is left. */
.ui-input-group { display: flex; align-items: center; gap: var(--ui-s2); flex-wrap: nowrap; }
.ui-input-group .ui-input { flex: 1 1 auto; width: auto; min-width: 0; }
.ui-input-affix { flex: 0 0 auto; white-space: nowrap; font-weight: 600; color: var(--ui-text); }

/* — color field (Coloris) */
.ui-color { max-width: 280px; }   /* a hex value never needs a wide field */
.ui-color .clr-field { display: flex; width: 100%; }
.ui-color .clr-field button { width: 24px; height: 60%; margin-right: var(--ui-s2); border-radius: var(--ui-r-sm); }

/* — image slot (logo, banner) */
.ui-image { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ui-s3); padding: var(--ui-s4);
    background: var(--ui-surface); border: 1.5px dashed var(--ui-border-strong); border-radius: var(--ui-r-md); }
.ui-image.has-image { border-style: solid; border-color: var(--ui-border); }
.ui-image-show { display: none; }
.ui-image.has-image .ui-image-show { display: block; }
.ui-image-show img { display: block; width: auto; height: auto; max-width: 200px; max-height: 60px; }
.ui-image-wide .ui-image-show img { max-width: 100%; max-height: 90px; }
.ui-image-wide.has-image .ui-image-show { flex-basis: 100%; }
.ui-image-pick { position: relative; }
.ui-image-pick input[type="file"] { position: absolute; opacity: 0; width: 0; height: 0; }
.ui-image .ui-image-remove { display: none; }
.ui-image.has-image .ui-image-remove { display: inline-flex; }
.ui-image-progress { flex-basis: 100%; }
.ui-image-progress:empty { display: none; }
.ui-image-bar { height: 4px; width: 0; border-radius: var(--ui-r-pill); background: var(--ui-accent); transition: width .2s; }
.ui-image-error { color: var(--ui-danger); }
.ui-image-stack { display: grid; grid-template-columns: 1fr auto; grid-template-areas: "show show" "pick remove"; align-items: center; gap: var(--ui-s2) var(--ui-s3); }
.ui-image-stack .ui-image-show { grid-area: show; display: flex; align-items: center; justify-content: center;
    height: 80px; padding: var(--ui-s2); box-sizing: border-box; background: var(--ui-bg); border-radius: var(--ui-r-sm); }
.ui-image-stack .ui-image-show img { max-height: 100%; max-width: 100%; width: auto; height: auto; }
.ui-image-stack .ui-image-pick { grid-area: pick; justify-self: start; }
.ui-image-stack .ui-image-remove { grid-area: remove; justify-self: end; }
.ui-image-stack .ui-image-progress { grid-column: 1 / -1; }

.ui-eyebrow { margin: 0 0 var(--ui-s3); font-size: var(--ui-fs-1); font-weight: 600; letter-spacing: .5px; text-transform: uppercase; color: var(--ui-muted); }
.ui-preview { overflow: hidden; background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: var(--ui-r-md); }
.ui-preview-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--ui-s3); margin: 0 0 var(--ui-s3); }
.ui-preview-head .ui-eyebrow { margin: 0; }
.ui-field-error .ui-input, .ui-field-error .ui-select, .ui-field-error .ui-textarea { border-color: var(--ui-danger); }
.ui-field-error .ui-hint { color: var(--ui-danger); }

/* — segmented control (either/or; replaces radios/selects for small choices) — */
.ui-segmented { display: inline-flex; background: var(--ui-bg); border-radius: var(--ui-r-sm); padding: 3px; gap: 3px; }
.ui-segmented-item { appearance: none; border: 0; background: transparent; cursor: pointer;
    padding: 7px 14px; border-radius: calc(var(--ui-r-sm) - 2px); font-size: var(--ui-fs-2); font-weight: 600;
    color: var(--ui-muted); white-space: nowrap; transition: background .15s ease, color .15s ease; }
.ui-segmented-item:hover { color: var(--ui-text); }
.ui-segmented-item.is-active { background: var(--ui-surface); color: var(--ui-text); box-shadow: var(--ui-shadow-sm); }

/* — checkbox & radio (custom box; native input drives state; check drawn in CSS) — */
/* position:relative anchors the visually hidden input below to its OWN row. Without it the
   absolutely positioned input resolves against some far-up ancestor, and clicking the label
   makes the browser scroll that element into view — which jumps the whole page instead of
   the row. Only obvious inside a scrolling list like .ui-picker-list, but wrong everywhere. */
.ui-check, .ui-radio { position: relative; display: inline-flex; align-items: center; gap: var(--ui-s2); cursor: pointer;
    font-size: var(--ui-fs-3); color: var(--ui-text); user-select: none; -webkit-user-select: none; }
/* Direct child only: an option can carry its own field (.ui-option-extra), and that input
   must stay visible. Every .ui-check/.ui-radio has its control as a direct child anyway,
   because the tick and dot are drawn through "input + .ui-check-box". */
.ui-check > input, .ui-radio > input { position: absolute; opacity: 0; width: 0; height: 0; }
.ui-check-box { flex: 0 0 20px; width: 20px; height: 20px; box-sizing: border-box; background: var(--ui-surface);
    border: 2px solid var(--ui-border-strong); border-radius: 6px; display: inline-flex; align-items: center;
    justify-content: center; position: relative; transition: background .15s ease, border-color .15s ease; }
.ui-check input:checked + .ui-check-box { background: var(--ui-accent); border-color: var(--ui-accent); }
/* crisp checkmark drawn with a rotated border (no icon-font dependency) */
.ui-check .ui-check-box::after { content: ""; width: 5px; height: 10px; margin-bottom: 2px; border: solid #fff;
    border-width: 0 2.5px 2.5px 0; transform: rotate(45deg) scale(0); transition: transform .15s ease; }
.ui-check input:checked + .ui-check-box::after { transform: rotate(45deg) scale(1); }
.ui-radio .ui-check-box { border-radius: 50%; }
.ui-radio .ui-check-box::after { content: ""; width: 10px; height: 10px; border-radius: 50%; background: var(--ui-accent); transform: scale(0); transition: transform .15s ease; }
.ui-radio input:checked + .ui-check-box { border-color: var(--ui-accent); }
.ui-radio input:checked + .ui-check-box::after { transform: scale(1); }
.ui-check input:focus-visible + .ui-check-box, .ui-radio input:focus-visible + .ui-check-box { box-shadow: var(--ui-focus); }
.ui-check.is-disabled, .ui-radio.is-disabled { opacity: .55; cursor: not-allowed; }

/* — toggle switch (on/off; e.g. enabling a limited-time offer) — */
.ui-toggle { position: relative; display: inline-flex; align-items: center; gap: var(--ui-s3); cursor: pointer;
    font-size: var(--ui-fs-3); color: var(--ui-text); user-select: none; -webkit-user-select: none; }
.ui-toggle input { position: absolute; opacity: 0; width: 0; height: 0; }
.ui-toggle-track { flex: 0 0 36px; width: 36px; height: 20px; background: var(--ui-border-strong); border-radius: var(--ui-r-pill);
    position: relative; transition: background .2s ease; }
.ui-toggle-track::before { content: ""; position: absolute; top: 3px; left: 3px; width: 14px; height: 14px; background: #fff;
    border-radius: 50%; box-shadow: var(--ui-shadow-sm); transition: transform .2s ease; }
.ui-toggle input:checked + .ui-toggle-track { background: var(--ui-accent); }
.ui-toggle input:checked + .ui-toggle-track::before { transform: translateX(16px); }
.ui-toggle input:focus-visible + .ui-toggle-track { box-shadow: var(--ui-focus); }

/* — badges / pills (status) — borderless (matches the rest of the app) — */
.ui-badge { display: inline-flex; align-items: center; gap: 5px; padding: 3px 9px; border-radius: var(--ui-r-pill);
    font-size: var(--ui-fs-1); font-weight: 600; background: #eceef1; color: var(--ui-muted); }
.ui-badge-success { background: #ecfdf3; color: var(--ui-success); }
.ui-badge-warn    { background: #fffaeb; color: var(--ui-warn); }
.ui-badge-danger  { background: #fef2f2; color: var(--ui-danger); }
.ui-badge-accent  { background: var(--ui-accent-soft); color: var(--ui-accent-dark); }

/* — accordion (native <details>) — */
.ui-accordion { border: 1px solid var(--ui-border); border-radius: var(--ui-r-md); background: var(--ui-surface); overflow: hidden; }
.ui-accordion + .ui-accordion { margin-top: var(--ui-s2); }
.ui-accordion > summary { list-style: none; cursor: pointer; padding: var(--ui-s4) var(--ui-s5);
    font-weight: 600; color: var(--ui-text); display: flex; align-items: center; justify-content: space-between; gap: var(--ui-s3);
    user-select: none; -webkit-user-select: none; }
.ui-accordion > summary::-webkit-details-marker { display: none; }
.ui-accordion > summary::after { content: "+"; font-size: 18px; color: var(--ui-faint); font-weight: 400; line-height: 1; }
.ui-accordion[open] > summary::after { content: "\2212"; }
.ui-accordion-body { padding: 0 var(--ui-s5) var(--ui-s5); color: var(--ui-muted); font-size: var(--ui-fs-3); line-height: 1.6; }

/* — tabs (data-ui-tabs) — */
.ui-tabs { display: flex; gap: var(--ui-s5); border-bottom: 1px solid var(--ui-border); margin: 0 0 var(--ui-s5); }
.ui-tab { appearance: none; background: none; border: 0; cursor: pointer; padding: var(--ui-s3) 0;
    font-size: var(--ui-fs-3); font-weight: 600; color: var(--ui-muted); border-bottom: 2px solid transparent; margin-bottom: -1px; }
.ui-tab:hover { color: var(--ui-text); }
.ui-tab.is-active { color: var(--ui-accent); border-bottom-color: var(--ui-accent); }
/* panels hold OPEN content (headings + sections, like a normal page) — never wrap a
   panel in a card. Tabs are only for switching distinct contexts of one record. */
.ui-panel { display: none; }
.ui-panel.is-active { display: block; }

/* ============================================================================
   TABLE — responsive, mobile-first.
   • Desktop: edge-to-edge, grey header, full-width row lines. The primary column
     (.ui-td-grow) absorbs leftover width and ellipsises via the max-width:0 trick —
     this adapts to ANY column count / table width (no magic pixel value); the other
     cells stay on one line.
   • Mobile (≤640px): each row reflows into a stacked card; td[data-label] prints its
     column name. No sideways scrolling.
   • Row actions: a kebab whose menu is position:fixed (placed by ui.js) so it never
     gets clipped by the wrap's overflow or the viewport edge.
   ============================================================================ */
.ui-table-wrap { background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: var(--ui-r-md); overflow-x: auto; }
.ui-table { width: 100%; border-collapse: collapse; font-size: var(--ui-fs-3); }
.ui-table thead th { background: var(--ui-surface-2); text-align: left; font-size: var(--ui-fs-2); font-weight: 600;
    color: var(--ui-muted); padding: var(--ui-s3) var(--ui-s4); border-bottom: 1px solid var(--ui-border); white-space: nowrap; }
.ui-table tbody td { padding: var(--ui-s3) var(--ui-s4); border-bottom: 1px solid var(--ui-border); color: var(--ui-text);
    vertical-align: middle; white-space: nowrap; }
.ui-table tbody tr:last-child td { border-bottom: 0; }
.ui-table tbody tr:hover { background: var(--ui-surface-2); }
.ui-table tbody tr[data-href] { cursor: pointer; }
.ui-table-name { font-weight: 600; color: var(--ui-text); text-decoration: none; }
.ui-table-name:hover { color: var(--ui-accent); }
/* the flexible, ellipsising column (usually the name): takes the leftover width, clips overflow */
.ui-td-grow { max-width: 0; width: 100%; overflow: hidden; text-overflow: ellipsis; }
.ui-table tbody td.ui-td-wrap { white-space: normal; min-width: 240px; }   /* long text: wraps instead of widening the table */
.ui-td-actions { text-align: right; white-space: nowrap; width: 1%; }

/* square icon button — toolbars + the kebab trigger. Hover distinct from row hover. */
.ui-icon-btn { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; flex: 0 0 auto;
    border-radius: var(--ui-r-sm); border: 1px solid transparent; background: transparent; color: var(--ui-muted);
    cursor: pointer; text-decoration: none; font-size: 16px; transition: background .15s ease, color .15s ease; }
.ui-icon-btn:hover { background: var(--ui-border); color: var(--ui-text); }
.ui-icon-btn-danger:hover { background: #fef2f2; color: var(--ui-danger); }

/* busy state (ui.js form[data-ui-busy]): a ring replaces the button's icon and the
   surrounding table row dims while a slow full-page POST (e.g. course duplication) runs */
.ui-busy { position: relative; pointer-events: none; }
.ui-busy i { visibility: hidden; }
.ui-busy::after { content: ""; position: absolute; top: 50%; left: 50%; width: 14px; height: 14px; margin: -8px 0 0 -8px;
    border: 2px solid var(--ui-border); border-top-color: var(--ui-accent); border-radius: 50%; animation: ui-spin .7s linear infinite; }
tr.ui-busy-row > td { opacity: .55; pointer-events: none; }
@keyframes ui-spin { to { transform: rotate(360deg); } }

/* kebab dropdown — position:fixed, placed by ui.js, so it floats above the table's
   overflow clip and flips at the viewport edge. No hover tooltips (touch-unfriendly). */
.ui-menu-wrap { position: relative; display: inline-block; }
.ui-menu { position: fixed; min-width: 176px; z-index: 1000; display: none;
    background: var(--ui-surface); border: 1px solid var(--ui-border); border-radius: var(--ui-r-md); box-shadow: var(--ui-shadow-md); padding: 5px; }
.ui-menu.is-open { display: block; }
.ui-menu-item { display: flex; align-items: center; gap: var(--ui-s2); width: 100%; box-sizing: border-box; padding: 8px 10px;
    border-radius: var(--ui-r-sm); font-size: var(--ui-fs-3); color: var(--ui-text); text-decoration: none; background: none;
    border: 0; cursor: pointer; text-align: left; white-space: nowrap; }
.ui-menu-item:hover { background: var(--ui-bg); color: var(--ui-text); }
.ui-menu-item i { font-size: 16px; color: var(--ui-muted); width: 18px; }
.ui-menu-item-danger, .ui-menu-item-danger i { color: var(--ui-danger); }
.ui-menu-item-danger:hover { background: #fef2f2; color: var(--ui-danger); }

/* generic single-line ellipsis (non-table). Needs a constrained parent (flex/grid),
   hence min-width:0 — no magic max-width. */
.ui-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }

/* — mobile: tables reflow into stacked cards (one row = one card) — */
@media (max-width: 640px) {
    .ui-table-wrap { overflow-x: visible; }
    .ui-table, .ui-table tbody, .ui-table tr, .ui-table td { display: block; width: 100%; box-sizing: border-box; }
    .ui-table thead { position: absolute; left: -9999px; }   /* hide header; labels move inline */
    /* card = 2-column grid: title spans the top, field blocks flow left AND right */
    .ui-table tbody tr { position: relative; display: grid; grid-template-columns: 1fr 1fr;
        gap: var(--ui-s4) var(--ui-s5); padding: var(--ui-s4); border-bottom: 1px solid var(--ui-border); }
    .ui-table tbody tr:last-child { border-bottom: 0; }
    /* each field: a small grey label stacked ABOVE its (slightly larger) value */
    .ui-table tbody td { border: 0; padding: 0; margin: 0; min-width: 0; white-space: normal; font-size: var(--ui-fs-2); color: var(--ui-text); }
    .ui-table tbody td[data-label]::before { content: attr(data-label); display: block; font-size: 10px; font-weight: 600;
        letter-spacing: .02em; color: var(--ui-muted); margin-bottom: 1px; }
    /* primary cell = card title: spans both columns, bold, no label, clear of the kebab */
    .ui-table tbody td.ui-td-grow { grid-column: 1 / -1; padding-right: 40px; max-width: none; width: auto; overflow: visible; text-overflow: clip;
        font-weight: 700; font-size: var(--ui-fs-4); }
    .ui-table tbody td.ui-td-grow::before { content: none; }
    /* long text gets the full card width instead of half */
    .ui-table tbody td.ui-td-wrap { grid-column: 1 / -1; min-width: 0; }
    /* icon-only actions (kebab / edit pencil) pin to the card's top-right corner */
    .ui-table tbody td.ui-td-actions { position: absolute; top: 10px; right: 10px; margin: 0; padding: 0; }
    .ui-table tbody td.ui-td-actions::before { content: none; }
    /* a LABELED button ("explainable" action) is too wide for the corner → drop it to a
       full-width row at the bottom of the card instead. */
    .ui-table tbody td.ui-td-actions:has(.ui-btn) { position: static; grid-column: 1 / -1; top: auto; right: auto; margin-top: var(--ui-s3); }
    .ui-table tbody td.ui-td-actions:has(.ui-btn) .ui-btn { width: 100%; }
}

/* ============================================================================
   TABLE TOOLBAR — search + filters above a list. Everything here is OPTIONAL, but
   when present use this standard: a .ui-toolbar row holding a .ui-search box and any
   filter controls (.ui-segmented / .ui-select), a .ui-toolbar-spacer to push the
   primary action to the right. Active filters can show as removable .ui-chip pills.
   Stacks to full-width on mobile. JS: data-ui-filter for client-side row filtering.
   ============================================================================ */
.ui-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ui-s3); margin: 0 0 var(--ui-s4); }
.ui-toolbar-spacer { flex: 1 1 auto; }
.ui-toolbar .ui-input, .ui-toolbar .ui-select { width: auto; }   /* auto-width inside a toolbar */

/* search box (leading icon + input) */
.ui-search { position: relative; display: inline-flex; align-items: center; }
.ui-search > i { position: absolute; left: 11px; color: var(--ui-faint); font-size: 16px; pointer-events: none; }
.ui-search .ui-input { padding-left: 36px; min-width: 240px; }

/* active-filter chip (removable) */
.ui-chip { display: inline-flex; align-items: center; gap: 5px; height: 30px; padding: 0 5px 0 11px; border-radius: var(--ui-r-pill);
    background: var(--ui-accent-soft); color: var(--ui-accent-dark); font-size: var(--ui-fs-2); font-weight: 600; }
.ui-chip button { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; border: 0;
    background: none; cursor: pointer; color: inherit; border-radius: 50%; font-size: 14px; line-height: 1; }
.ui-chip button:hover { background: rgba(0,0,0,.08); }

@media (max-width: 640px) {
    /* keep the toolbar wrapping: only the search takes a full row; buttons, segmented
       controls and selects keep their natural width and wrap below it. */
    .ui-toolbar-spacer { display: none; }
    .ui-search, .ui-search .ui-input { width: 100%; min-width: 0; }
}

/* — empty state — */
.ui-empty { text-align: center; padding: var(--ui-s7) var(--ui-s5); color: var(--ui-muted); }
.ui-empty i { font-size: 32px; color: #d4d4d8; display: block; margin-bottom: var(--ui-s3); }

/* — misc — */
.ui-divider { height: 1px; background: var(--ui-border); border: 0; margin: var(--ui-s5) 0; }
.ui-well { background: var(--ui-surface-2); border: 1px solid var(--ui-border); border-radius: var(--ui-r-md); padding: var(--ui-s4); }
/* neutralize leading/trailing margins of arbitrary content (e.g. a default <p>) inside
   containers, so it sits flush within the padding — overrides the UA p{margin:1em 0}. */
/* — notice — a short piece of state or advice, tinted to its tone.
   Not .ui-well: that is a neutral grey panel, so a coloured message inside one reads as a
   mismatch (grey box, orange text). Background, border, icon and text move together here. */
.ui-notice { display: flex; align-items: flex-start; gap: var(--ui-s2); width: 100%; box-sizing: border-box;
    padding: var(--ui-s3) var(--ui-s4); border-radius: var(--ui-r-md); border: 1px solid transparent;
    font-size: var(--ui-fs-2); line-height: 1.5; }
.ui-notice > i { flex: 0 0 auto; font-size: 16px; line-height: 1.35; }
.ui-notice-info    { background: var(--ui-info-soft);    border-color: #dbeafe; color: #1e40af; }
.ui-notice-warn    { background: var(--ui-warn-soft);    border-color: #fed7aa; color: #9a3412; }
.ui-notice-danger  { background: var(--ui-danger-soft);  border-color: #fecaca; color: #991b1b; }
.ui-notice-success { background: var(--ui-success-soft); border-color: #bbf7d0; color: #166534; }

.ui-card > :first-child, .ui-well > :first-child { margin-top: 0; }
.ui-card > :last-child,  .ui-well > :last-child  { margin-bottom: 0; }

/* — option card (card + radio/checkbox) —————————————————————————————
   A selectable card for a small set of mutually exclusive choices that each need
   a line of explanation (import method, delivery option). Compose with .ui-radio
   or .ui-check so the box visuals above are reused; this only restyles the frame.
   Selected state is pure CSS via :has(), so no JS is required. */
.ui-option { display: flex; align-items: flex-start; gap: var(--ui-s3); width: 100%; box-sizing: border-box;
    padding: var(--ui-s4); background: var(--ui-surface); border: 1px solid var(--ui-border);
    border-radius: var(--ui-r-md); cursor: pointer; text-align: left;
    transition: border-color .15s ease, background .15s ease; }
.ui-option + .ui-option { margin-top: var(--ui-s3); }
/* Two or three options that each need only a short line (draft vs live): side by side, so a
   binary choice does not cost a screen of height and does not leave the row half empty.
   Wraps back to a column when the row gets tight, which is also what a phone gets. */
.ui-options-row { display: flex; flex-wrap: wrap; gap: var(--ui-s3); }
.ui-options-row > .ui-option { flex: 1 1 260px; width: auto; }
.ui-options-row > .ui-option + .ui-option { margin-top: 0; }
.ui-option:hover { border-color: var(--ui-border-strong); }
.ui-option .ui-check-box { margin-top: 1px; }
.ui-option:has(input:checked) { border-color: var(--ui-accent); background: var(--ui-surface-2); }
.ui-option-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.ui-option-title { font-weight: 600; color: var(--ui-text); }
.ui-option-desc { color: var(--ui-muted); font-size: var(--ui-fs-2); line-height: 1.5; }
/* A control that belongs to one option (a picker, a URL field). Sits in .ui-option-body and
   appears only while that option is selected — again pure CSS, no JS. */
/* The option label sets cursor and user-select for the whole card; a real field inside it
   needs a text cursor and selectable text again. */
.ui-option-extra { display: none; margin-top: var(--ui-s3); cursor: auto;
    user-select: text; -webkit-user-select: text; }
.ui-option:has(input:checked) .ui-option-extra { display: block; }

/* — upload field (click or drag & drop) ————————————————————————————
   Default for any file input. data-ui-upload adds drag highlighting and swaps in
   the chosen filename; without JS it still works as a plain click-to-browse field. */
.ui-upload { position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: var(--ui-s2); width: 100%; box-sizing: border-box; padding: var(--ui-s6) var(--ui-s4);
    background: var(--ui-surface); border: 1.5px dashed var(--ui-border-strong); border-radius: var(--ui-r-md);
    cursor: pointer; text-align: center; transition: border-color .15s ease, background .15s ease; }
.ui-upload:hover { border-color: var(--ui-accent); background: var(--ui-surface-2); }
.ui-upload.is-dragging { border-color: var(--ui-accent); background: var(--ui-accent-soft); }
.ui-upload input[type="file"] { position: absolute; opacity: 0; width: 0; height: 0; }
.ui-upload > i { font-size: 26px; color: var(--ui-muted); }
.ui-upload.is-dragging > i, .ui-upload.has-file > i { color: var(--ui-accent); }
.ui-upload-title { font-weight: 600; color: var(--ui-text); overflow-wrap: anywhere; }
.ui-upload-hint { font-size: var(--ui-fs-1); color: var(--ui-faint); }
/* A file is chosen. The dashed outline and muted text still read as an empty
   dropzone, so people miss that anything happened: go solid, tint the panel and
   switch the icon to a tick (ui.js) so the state is obvious at a glance. */
.ui-upload.has-file { border-style: solid; border-color: var(--ui-accent); background: var(--ui-accent-soft); }
.ui-upload.has-file .ui-upload-title { color: var(--ui-accent); }
.ui-upload.has-file .ui-upload-hint { color: var(--ui-muted); }
.ui-upload.has-file > i { font-size: 30px; }
/* dropped file the accept rejects: stays until a usable one is chosen, so the reason is
   still on screen when the form is submitted */
.ui-upload.is-rejected { border-style: solid; border-color: var(--ui-danger); background: #fef2f2; }
.ui-upload.is-rejected .ui-upload-title { color: var(--ui-danger); }
.ui-upload.is-rejected > i { color: var(--ui-danger); }

/* — form actions row —————————————————————————————————————————————————
   The submit/cancel row at the end of a form. .ui-section only carries a top
   margin, so buttons placed after one would otherwise sit flush against it. */
.ui-form-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ui-s3); margin-top: var(--ui-s7); }
@media (max-width: 640px) {
    .ui-form-actions { flex-direction: column-reverse; align-items: stretch; }
    .ui-form-actions .ui-btn { width: 100%; }
}

/* — picker (searchable single/multi select) ————————————————————————
   For choosing from a list that can get long (offers, products, courses) where a
   native <select multiple> is unusable: no search, cmd-click to multi-select, and
   it renders badly on mobile. Checkboxes for multi, radios for single. */
.ui-picker { border: 1px solid var(--ui-border-strong); border-radius: var(--ui-r-sm);
    background: var(--ui-surface); overflow: hidden; }
.ui-picker-search { position: relative; display: flex; align-items: center; gap: var(--ui-s2);
    padding: 0 var(--ui-s3); border-bottom: 1px solid var(--ui-border); }
.ui-picker-search > i { color: var(--ui-faint); font-size: 15px; flex: 0 0 auto; }
.ui-picker-search input { flex: 1 1 auto; min-width: 0; height: 38px; border: 0; outline: none; background: none;
    font-family: inherit; font-size: var(--ui-fs-3); color: var(--ui-text); }
.ui-picker-search input::placeholder { color: var(--ui-faint); }
.ui-picker-list { max-height: 240px; overflow-y: auto; padding: var(--ui-s2);
    display: flex; flex-direction: column; gap: 2px; }
.ui-picker-item { display: flex; align-items: center; gap: var(--ui-s3); width: 100%; box-sizing: border-box;
    padding: var(--ui-s2) var(--ui-s3); border-radius: var(--ui-r-sm); cursor: pointer; }
.ui-picker-item:hover { background: var(--ui-surface-2); }
.ui-picker-item .ui-picker-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* rows switched off by data-ui-picker-max once the cap is reached */
.ui-picker-item.is-disabled { opacity: .45; cursor: not-allowed; }
.ui-picker-item.is-disabled:hover { background: none; }
.ui-picker-empty { padding: var(--ui-s5); text-align: center; color: var(--ui-faint); font-size: var(--ui-fs-2); }

/* — meter (usage against a plan limit) ————————————————————————————
   "1,240 of 2,500 imported". Turns amber past 80% and red once full, so the
   seller sees a ceiling coming before they hit it. */
.ui-meter { display: flex; flex-direction: column; gap: var(--ui-s2); }
.ui-meter-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--ui-s3);
    font-size: var(--ui-fs-2); color: var(--ui-muted); }
.ui-meter-value { font-weight: 600; color: var(--ui-text); }
.ui-meter-track { height: 6px; border-radius: var(--ui-r-pill); background: var(--ui-border); overflow: hidden; }
.ui-meter-fill { height: 100%; border-radius: var(--ui-r-pill); background: var(--ui-accent);
    transition: width .2s ease, background .2s ease; }
.ui-meter.is-warn .ui-meter-fill { background: var(--ui-warn); }
.ui-meter.is-full .ui-meter-fill { background: var(--ui-danger); }
/* progress toward a goal (e.g. recommended legal texts): green once everything is done */
.ui-meter.is-done .ui-meter-fill { background: var(--ui-success); }

/* — checklist (what is set, what is still open) */
.ui-checklist { display: flex; flex-wrap: wrap; gap: var(--ui-s2) var(--ui-s5); font-size: var(--ui-fs-2); color: var(--ui-muted); }
.ui-checklist-item { display: inline-flex; align-items: center; gap: var(--ui-s1); }
.ui-checklist-item i { font-size: 18px; color: var(--ui-faint); }
.ui-checklist-item.is-done { color: var(--ui-text); }
.ui-checklist-item.is-done i { color: var(--ui-success); }

/* — modal ————————————————————————————————————————————————————————————
   For a short, focused task that shouldn't cost a page load (grant access, rename).
   Opened by [data-ui-modal-open="#id"], closed by [data-ui-modal-close], the
   backdrop or Escape. Keep it to one job; anything longer belongs on a page. */
.ui-modal-overlay { position: fixed; inset: 0; z-index: 10000000; background: rgba(0,0,0,.35);
    display: flex; align-items: flex-start; justify-content: center; overflow-y: auto;
    padding: 12vh var(--ui-s4) var(--ui-s6); }
/* .hidden lives in styles.css, which loads BEFORE ui.css — same specificity, so the
   display:flex above would win and the modal would sit open. Restate it here. */
.ui-modal-overlay.hidden { display: none; }
.ui-modal { position: relative; width: 480px; max-width: 100%; box-sizing: border-box;
    background: var(--ui-surface); border-radius: var(--ui-r-lg); box-shadow: var(--ui-shadow-lg);
    padding: var(--ui-s5); }
.ui-modal-title { margin: 0 var(--ui-s6) var(--ui-s2) 0; font-size: var(--ui-fs-4); font-weight: 700; line-height: 32px; color: var(--ui-text); }
.ui-modal-sub { margin: 0 0 var(--ui-s5); font-size: var(--ui-fs-3); line-height: 1.5; color: var(--ui-muted); }
.ui-modal-x { position: absolute; top: var(--ui-s5); right: var(--ui-s5); }
@media (max-width: 640px) { .ui-modal-overlay { padding-top: var(--ui-s5); } }

/* ---- hide convention -------------------------------------------------------------------
   The app hides things with .hidden, defined in styles.css. That file loads BEFORE this one
   (_head.php), and both selectors are a single class, so ANY component here that sets
   `display` silently outranks it on source order: .ui-notice.hidden stayed on screen at
   display:flex. Restated last, so the convention holds for every component above it.
   No !important on purpose — equal specificity, later in the cascade, is enough. */
.hidden { display: none; }
