/* =============================================================
   MSP SIGNATURE BUILDER — style.css  (v2)
   ============================================================= */

/* ── TOKENS ───────────────────────────────────────────────── */
:root {
    --accent:        #2563eb;
    --accent-strong: #1d4ed8;
    --accent-soft:   rgba(37, 99, 235, 0.08);
    --accent-ring:   rgba(37, 99, 235, 0.18);

    --bg:        #f5f6f8;
    --surface:   #ffffff;
    --line:      #e6e8ee;
    --line-soft: #eef0f4;

    --text:       #171a21;
    --muted:      #5d6472;
    --hint:       #878e9d;

    --danger:      #dc2626;
    --danger-soft: #fdf0f0;

    /* ── SCALES ──
       This sheet grew organically into a continuum rather than a scale: 30
       distinct spacing values (every integer 1-16, then most evens to 50), 20
       font sizes of which 7 are fractional, and 8 weights between 550 and 700
       on a system stack where most of those render identically. The tokens
       below are the scale everything snaps to. Migrate view by view, not
       sheet-wide — 1-2px shifts are expected and are the point. */

    /* space */
    --s-1: 4px;  --s-2: 8px;  --s-3: 12px;
    --s-4: 16px; --s-5: 24px; --s-6: 32px;

    /* type — --fs-2 is the body default. Only these two tiers survive: the
       scale was written as 11/12/13/15/18/24 and this design turned out to run
       on half-steps it cannot express (11.5px on 22 rules, 12.5px on 16,
       10.5px on 11), so the other four tiers were never adopted by anything.
       Deleted rather than left as an aspiration — adopting them would change
       rendered sizes, which is a visual decision and not a cleanup. */
    --fs-2: 12px; --fs-3: 13px;

    /* motion — replaces 8 near-identical hover durations */
    --t-fast: 0.12s; --t-base: 0.16s;

    --r-sm: 8px;
    --r-md: 11px;
    --r-lg: 16px;

    /* Space between stacked sections — see the vertical rhythm note below. */
    --stack: 18px;

    /* Semantic success/warning. These were 25 scattered literals, each
       hand-mirrored into body.theme-dark — which is exactly why three were
       missed (see .co-sig-status-ok). Tokenised so dark gets them for free. */
    --ok:   #047857; --ok-soft:   #ecfdf5;
    --warn: #b45309; --warn-soft: #fffbeb;

    /* Interaction fills. These were hard-coded light values with no dark
       counterpart, which is how .btn-soft:hover — 60 usages, the most-used
       button in the app — ended up painting #e3e7ee behind #e8eaef text in
       dark mode: a contrast ratio of 1.03:1, i.e. an invisible label. */
    --hover:         #e3e7ee;
    --line-hover:    #d6dae2;
    --accent-hover:  #1d4ed8;
    --accent-shadow: rgba(37, 99, 235, 0.35);

    --thumb-bar: #c2c9d6;                    /* template-picker rule marks */
    --scrim:     rgba(13, 16, 23, 0.5);      /* modal backdrop */

    --shadow-sm: 0 1px 2px rgba(16, 20, 28, 0.05);
    --shadow-md: 0 6px 20px rgba(16, 20, 28, 0.08);
    --shadow-lg: 0 18px 50px rgba(16, 20, 28, 0.18);

    --font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
}

/* ── DARK THEME ───────────────────────────────────────────────
   Only the tool's own chrome flips; the signature preview canvases stay white
   (they represent a white e-mail background — matching the forced-light export),
   so a dark UI reads like a dark mail client showing a light signature. */
body.theme-dark {
    --accent:        #3b82f6;
    --accent-strong: #93b8fb;
    --accent-soft:   rgba(59, 130, 246, 0.18);
    --accent-ring:   rgba(59, 130, 246, 0.32);

    --bg:        #13151a;
    --surface:   #1b1e25;
    --line:      #2d323d;
    --line-soft: #262a32;

    --text:       #e8eaef;
    --muted:      #a3abba;
    --hint:       #7e8798;

    --danger:      #f87171;
    --danger-soft: #2c1b1b;

    /* Dark counterparts for the semantic pair. The light values (#047857 on
       #ecfdf5) sit at ~3.0:1 on the dark surface — the exact failure
       .co-sig-status-ok shipped with; these give 10.94:1.
       These are deliberately the values the chips ALREADY used in their
       hand-written dark overrides, so tokenising the ~15 light literals and
       deleting those 10 override rules is a pure refactor: nothing moves
       visually, there is simply one definition instead of ten. */
    --ok:   #6ee7b7; --ok-soft:   rgba(4, 120, 87, 0.22);
    --warn: #fcd34d; --warn-soft: rgba(180, 83, 9, 0.22);

    --hover:      #333945;      /* 9.63:1 against --text, was 1.03:1 */
    --line-hover: #3a4151;
    /* Hover goes DARKER here, deliberately. --accent-strong inverts across
       themes (light #1d4ed8 darker, dark #93b8fb lighter), and white on
       #93b8fb is 2.00:1 — hovering made the label HARDER to read than the
       3.68:1 base. #2563eb gives 5.17:1, so hover now improves legibility. */
    --accent-hover:  #2563eb;
    --accent-shadow: rgba(0, 0, 0, 0.45);

    /* The thumbnails' rule marks sat on --line-soft as a fixed mid-grey, so
       the template picker shouted in dark mode. */
    --thumb-bar: #4a5262;
    /* A fixed 50% wash over #13151a reads flat rather than layered. */
    --scrim:     rgba(0, 0, 0, 0.66);

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.45);
    --shadow-md: 0 6px 20px rgba(0, 0, 0, 0.50);
    --shadow-lg: 0 18px 50px rgba(0, 0, 0, 0.65);
}
/* The bulk preview canvas is a signature surface → keep it white in dark mode. */
body.theme-dark .bulk-frame-wrap { background-color: #ffffff; }

/* ── RESET / BASE ─────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; }

html, body {
    margin: 0;
    padding: 0;
    height: 100%;
    font-family: var(--font);
    background-color: var(--bg);
    color: var(--text);
    font-size: 14px;
    -webkit-font-smoothing: antialiased;
}

.app {
    display: flex;
    flex-direction: column;
    height: 100vh;
    overflow: hidden;
}

/* ── TOP BAR ──────────────────────────────────────────────── */
.topbar {
    display: flex;
    align-items: center;
    gap: var(--s-4);
    padding: 0 20px;
    /* min-height, NOT height. .topbar-actions is flex-wrap: wrap, so a fixed
       height meant that the moment its seven buttons needed a second row the
       second row was simply CLIPPED — no scrollbar, no reflow, controls just
       gone. That was always latent (German labels run 20-35% longer than
       English); adding the user chip to this bar brought it within ~35px of the
       column breakpoint, which is how it finally showed up. Growing is the
       correct answer at every width and in every language. */
    min-height: 60px;
    background-color: var(--surface);
    border-bottom: 1px solid var(--line);
    flex-shrink: 0;
    z-index: 20;
}

.brand {
    display: flex;
    align-items: center;
    gap: 11px;
    min-width: 0;
    margin-right: auto;      /* replaces justify-content: space-between — see .topbar */
}
.brand-mark {
    width: 34px;
    height: 34px;
    border-radius: 15px;
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 14px;
    flex-shrink: 0;
}
.brand-icon {
    max-height:110%; 
    max-width:110%;
}
.brand-icon:hover{
    max-height: 120%;
    max-width: 120%;
}
/* The ink splash sits INLINE inside the name element — it flows with the
   text (no pixel-nudging), and inverts on the dark theme to stay visible. */
.brand-splash {
    height: 21px;
    width: auto;
    flex-shrink: 0;
}
body.theme-dark .brand-splash { filter: invert(1); }
.brand-text { display: flex; flex-direction: column; line-height: 1.15; }
.brand-text strong {
    font-size: 14.5px;
    font-weight: 650;
    letter-spacing: -0.01em;
    display: inline-flex;
    align-items: center;
    gap: 5px;
}
.brand-text span   { font-size: 11px; color: var(--hint); }
.version-tag { font-style: normal; font-size: 9.5px; color: var(--hint); opacity: 0.75; cursor: default; }

/* "Profiles live in this browser" note under the Profiles section */
.storage-note {
    display: flex;
    gap: 9px;
    margin: 12px 0 0;
    padding: 10px 12px;
    background-color: var(--line-soft);
    border-radius: var(--r-md);
    font-size: 12px;
    line-height: 1.55;
    color: var(--muted);
}
.storage-note i { margin-top: 2px; color: var(--accent-strong); }
.storage-note b { color: var(--text); font-weight: 600; }

.topbar-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
}

/* These are the Signature Editor's own tools — import, export, copy, reset —
   so every other workspace hides them. The signed-in user below is deliberately
   NOT inside this container: it has to survive all four views. */
body.view-bulk .topbar-actions,
body.view-company .topbar-actions,
body.view-admin .topbar-actions { display: none; }

/* Signed-in identity + sign-out. min-width:0 with the ellipsis below is the
   load-bearing pair: the bar is a fixed 60px, so content that refuses to shrink
   wraps and is then CLIPPED rather than reflowed. The name yields instead. */
.topbar-user { display: flex; align-items: center; gap: var(--s-2); min-width: 0; }
.co-userchip {
    font-size: var(--fs-3);
    font-weight: 560;
    color: var(--text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 22ch;
}

/* ── BUTTONS ──────────────────────────────────────────────── */
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 7px;
    padding: 8px 14px;
    font-size: 13px;
    font-weight: 550;
    font-family: inherit;
    border: none;
    border-radius: var(--r-sm);
    cursor: pointer;
    white-space: nowrap;
    line-height: 1;
    transition: background-color 0.14s ease, color 0.14s ease, box-shadow 0.14s ease, transform 0.08s ease;
}
.btn:active:not(:disabled) { transform: translateY(0.5px); }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }

.btn-primary {
    background-color: var(--accent);
    color: #fff;
    box-shadow: 0 1px 2px var(--accent-shadow);
}
.btn-primary:hover:not(:disabled) { background-color: var(--accent-hover); }

.btn-soft {
    background-color: var(--line-soft);
    color: var(--text);
}
.btn-soft:hover:not(:disabled) { background-color: var(--hover); }

.btn-icon {
    padding: 8px 10px;
    background-color: var(--line-soft);
    color: var(--muted);
}
.btn-icon:hover:not(:disabled) { background-color: var(--hover); color: var(--text); }

.btn-ghost-danger {
    padding: 8px 10px;
    background: none;
    color: var(--muted);
}
.btn-ghost-danger:hover:not(:disabled) { background-color: var(--danger-soft); color: var(--danger); }

.btn-sm { padding: 6px 11px; font-size: 12px; }

.btn-cluster {
    display: flex;
    border-radius: var(--r-sm);
    overflow: hidden;
}
.btn-cluster .btn-icon { border-radius: 0; }
.btn-cluster .btn-icon:first-child { border-radius: var(--r-sm) 0 0 var(--r-sm); }
.btn-cluster .btn-icon:last-child  { border-radius: 0 var(--r-sm) var(--r-sm) 0; border-left: 1px solid var(--line); }

/* ── LAYOUT ───────────────────────────────────────────────── */
.layout {
    display: flex;
    flex: 1;
    min-height: 0;
}

.stage {
    flex: 1;
    overflow-y: auto;
    padding: 26px 30px 40px;
    min-width: 0;
}

/* ── EMAIL PREVIEW CARD ───────────────────────────────────── */
.email-card {
    max-width: 780px;
    margin: 0 auto;
    background-color: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-md);
    overflow: hidden;
}

.email-head {
    display: flex;
    gap: 13px;
    padding: 18px 24px 15px;
    border-bottom: 1px solid var(--line-soft);
}
.email-avatar {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background: linear-gradient(135deg, #284978, #3b6db0);
    color: #fff;
    font-size: 13px;
    font-weight: 650;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.email-meta p { margin: 0 0 2px; font-size: 12.5px; color: var(--muted); }
.email-meta p strong { color: var(--text); font-weight: 600; }
.email-subject { font-weight: 600; color: var(--text) !important; }

.email-body {
    padding: 20px 24px 4px;
}
.email-body p {
    margin: 0 0 11px;
    font-size: 13.5px;
    line-height: 1.6;
    color: var(--text);
}

/* Signature zone — sits directly after the mail body, like a real message.
   The subtle dashed line marks where the editable signature begins. */
.sig-zone {
    padding: 12px 24px 22px;
    margin-top: 8px;
    border-top: 1px dashed var(--line-soft);
}

/* Preview controls — ABOVE the mail, so the card below reads as one
   continuous e-mail: header, body, signature at the end. */
.preview-toolbar {
    display: flex;
    align-items: center;
    gap: 8px;
    max-width: 780px;
    margin: 0 auto 12px;
}
.preview-toolbar .mode-chip { margin-right: auto; }

.mode-chip {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    font-size: 11.5px;
    font-weight: 600;
    color: var(--muted);
    background-color: var(--line-soft);
    border-radius: 999px;
    padding: 5px 12px;
}
.mode-chip.custom {
    color: var(--accent-strong);
    background-color: var(--accent-soft);
}

#preview-frame {
    width: 100%;
    border: none;
    display: block;
    min-height: 90px;
}

/* The "preview as a person" pane. Sized by mountPreviewFrame() the same way
   the template pane's iframe is, so it only needs the chrome removed here.
   It swaps places with #preview-frame; the two are never visible together. */
#ph-preview-frame {
    max-width: 100%;
    border: none;
    display: block;
    min-height: 90px;
}
#ph-preview-frame[hidden] { display: none; }

/* The starter gallery is a horizontal row in the template pane but a single
   column in the 332px side panel — one full-width button per layout. */
#ed-tpl-starters { flex-direction: column; align-items: stretch; gap: 6px; }
#ed-tpl-starters .co-starter-btn { width: 100%; }

/* Save-to: a segmented three-way destination picker. The selected target is
   ambient state the author reads before hitting Ctrl/Cmd+S, so it has to stay
   visible between saves — which is why this is a panel section, not a modal. */
.save-targets {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 4px;
    padding: 3px;
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    margin-bottom: 11px;
}
.save-target {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 7px 4px;
    border: none;
    border-radius: calc(var(--r-md) - 3px);
    background: transparent;
    color: var(--muted);
    font-family: inherit;
    font-size: 11.5px;
    font-weight: 600;
    cursor: pointer;
}
.save-target i { font-size: 12px; }
.save-target:hover:not(:disabled):not(.active) { background: var(--line-soft); color: var(--text); }
.save-target.active {
    background: var(--surface);
    color: var(--accent-strong);
    box-shadow: var(--shadow-sm);
}
.save-target:disabled { opacity: 0.45; cursor: not-allowed; }
.save-fields { display: flex; flex-direction: column; }
.save-fields[hidden] { display: none; }
.save-fields .mini-label:empty { display: none; }

/* Phone bezel around the WHOLE mail (header + body + signature) so the
   narrowed preview reads as a genuine message on a device. Everything is
   border-box: 395×864 minus the 2×10px bezel = a true 375×844 screen inside
   (iPhone-class dimensions). The mail scrolls INSIDE the screen with the
   scrollbars hidden — phones don't show any. */
.email-card.phone-frame {
    width: 395px;
    max-width: 395px;
    height: 864px;
    display: flex;
    flex-direction: column;
    overflow-y: auto;
    overflow-x: hidden;
    border: 10px solid #1f2430;
    border-radius: 30px;
    box-shadow: var(--shadow-lg);
    scrollbar-width: none;
}
.email-card.phone-frame::-webkit-scrollbar { display: none; width: 0; height: 0; }
.email-card.phone-frame > * { flex-shrink: 0; }

/* Phone chrome: status bar + dynamic island + home indicator. Hidden unless
   the phone preview is active. */
.phone-status, .phone-home { display: none; }
.email-card.phone-frame .phone-status {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 13px 24px 4px;
    font-size: 13px;
    font-weight: 650;
    color: var(--text);
}
.phone-island {
    position: absolute;
    left: 50%;
    top: 11px;
    transform: translateX(-50%);
    width: 100px;
    height: 25px;
    background-color: #10131a;
    border-radius: 999px;
}
.phone-status-icons { display: flex; gap: 6px; font-size: 11px; }
.email-card.phone-frame .phone-home {
    display: block;
    position: sticky;
    bottom: 7px;
    width: 125px;
    height: 5px;
    margin: 14px auto 7px;
    margin-top: auto;
    border-radius: 999px;
    background-color: #1f2430;
    opacity: 0.8;
    flex-shrink: 0;
}
/* dark mail: light home bar (like a real phone), light status text */
.email-card.preview-dark .phone-status { color: #e8eaef; }
.email-card.preview-dark .phone-home { background-color: #d6d9df; }

/* Dark preview darkens the whole mail card too — one coherent message, not a
   white mail with a dark signature hole. (The export itself forces light.) */
.email-card.preview-dark { background-color: #1f1f1f; border-color: #3a3f4a; }
.email-card.preview-dark .email-head { border-bottom-color: #383d47; }
.email-card.preview-dark .email-meta p { color: #9aa1ad; }
.email-card.preview-dark .email-meta p strong,
.email-card.preview-dark .email-subject { color: #e8eaef !important; }
.email-card.preview-dark .email-body p { color: #d5d8de; }
.email-card.preview-dark .sig-zone { border-top-color: #383d47; }

/* ── DRAG-TO-IMPORT ───────────────────────────────────────── */
/* Pill + outline shown while an .html file hovers over the editor */
.drop-pill {
    position: fixed;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    z-index: 400;
    display: none;
    align-items: center;
    gap: 10px;
    padding: 14px 22px;
    background-color: #1f2430;
    color: #ffffff;
    font-size: 13.5px;
    font-weight: 600;
    border-radius: 999px;
    box-shadow: var(--shadow-lg);
    pointer-events: none;
}
.drop-pill i { color: #8ab4ff; }
body.dragging-import .drop-pill { display: flex; }
body.dragging-import .email-card {
    outline: 2px dashed var(--accent);
    outline-offset: 5px;
}

/* ── FLOATING FORMAT TOOLBAR ──────────────────────────────── */
.format-toolbar {
    position: fixed;
    z-index: 300;
    display: flex;
    flex-direction: column;
    gap: 5px;
    padding: 4px;
    background-color: #1f2430;
    border-radius: 10px;
    box-shadow: var(--shadow-lg);
}
.ft-row { display: flex; align-items: center; gap: 2px; }
.ft-row-font { gap: 6px; }
.ft-sep { width: 1px; height: 20px; margin: 0 3px; background-color: #3a4151; flex: none; }
/* Font family select + size stepper (per-selection, imported text) */
.ft-select {
    flex: 1 1 auto;
    min-width: 0;
    max-width: 150px;
    height: 28px;
    padding: 0 6px;
    border: 1px solid #3a4151;
    border-radius: 7px;
    background-color: #12151c;
    color: #eef1f7;
    font-size: 12px;
    cursor: pointer;
    outline: none;
}
.ft-select:focus { border-color: var(--accent); }
.ft-size { display: inline-flex; align-items: center; gap: 2px; flex: none; }
.ft-size input {
    width: 40px;
    height: 28px;
    padding: 0 2px;
    border: 1px solid #3a4151;
    border-radius: 7px;
    background-color: #12151c;
    color: #eef1f7;
    font-size: 12px;
    text-align: center;
    outline: none;
    -moz-appearance: textfield;
}
.ft-size input::-webkit-inner-spin-button,
.ft-size input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
.ft-size input:focus { border-color: var(--accent); }
.format-toolbar button.ft-size-btn { width: 24px; height: 28px; font-size: 14px; border: 1px solid #3a4151; }
/* Outlook (points) equivalent of the px size */
.ft-pt {
    flex: none;
    min-width: 34px;
    font-size: 10.5px;
    font-weight: 600;
    color: #8b93a5;
    white-space: nowrap;
    cursor: help;
}
.ft-pt::before { content: "\2248\00a0"; color: #6b7280; font-weight: 400; }
.ft-pt:empty { display: none; }
.format-toolbar button {
    width: 30px;
    height: 30px;
    border: none;
    border-radius: 7px;
    background: none;
    color: #cbd2e0;
    font-size: 12px;
    cursor: pointer;
    transition: background-color 0.12s, color 0.12s;
}
.format-toolbar button:hover { background-color: #323948; color: #fff; }
.format-toolbar button.ft-danger:hover { background-color: #3d222b; color: #ff8fa0; }
/* Colour swatch — native picker overlaid on a palette-icon button */
.ft-color {
    position: relative;
    width: 30px; height: 30px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 7px;
    color: #cbd2e0;
    cursor: pointer;
    transition: background-color 0.12s, color 0.12s;
}
.ft-color:hover { background-color: #323948; color: #fff; }
.ft-color input[type="color"] {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    margin: 0; padding: 0; border: none;
    opacity: 0; cursor: pointer;
}
/* Inline link-URL editor (second row) */
.ft-link-edit { display: flex; align-items: center; gap: 4px; padding: 0 1px 1px; }
.ft-link-edit input {
    width: 224px;
    height: 28px;
    padding: 0 9px;
    border: 1px solid #3a4151;
    border-radius: 7px;
    background-color: #12151c;
    color: #eef1f7;
    font-size: 12px;
    outline: none;
}
.ft-link-edit input:focus { border-color: var(--accent); }
.ft-link-edit .ft-apply {
    width: 30px; height: 28px;
    background-color: var(--accent);
    color: #fff;
}
/* --accent-hover, not --accent-strong: this carries white text, and
   --accent-strong lightens in dark mode (2.00:1). Same fix as .btn-primary. */
.ft-link-edit .ft-apply:hover { background-color: var(--accent-hover); }

/* Column-gap control — appears only while the selection sits inside a table
   built by "Align as columns" (row is toggled via [hidden], same override
   as .ft-link-edit since .ft-row forces display:flex over the UA default). */
.ft-colgap-row { color: #9aa1b0; font-size: 11px; }
.ft-colgap-row i { color: #8ab4ff; width: 14px; text-align: center; }
.ft-colgap-label { white-space: nowrap; }

/* ── SIDE PANEL ───────────────────────────────────────────── */
.panel {
    width: 332px;
    flex-shrink: 0;
    background-color: var(--surface);
    border-left: 1px solid var(--line);
    overflow-y: auto;
    padding: 18px 18px 32px;
}

.panel-section {
    padding: 14px 0 16px;
    border-bottom: 1px solid var(--line-soft);
}
.panel-section:first-child { padding-top: 4px; }
.panel-section:last-child { border-bottom: none; }

.section-title {
    font-size: 10.5px;
    font-weight: 700;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--muted);
    margin-bottom: 12px;
}

details.adv > summary.section-title {
    cursor: pointer;
    list-style: none;
    display: flex;
    align-items: center;
    user-select: none;
    margin-bottom: 0;
}
details.adv > summary.section-title::-webkit-details-marker { display: none; }
details.adv > summary.section-title::after {
    content: '\f078';
    font-family: 'Font Awesome 6 Free';
    font-weight: 900;
    font-size: 9px;
    margin-left: auto;
    transition: transform 0.15s;
}
details.adv[open] > summary.section-title { margin-bottom: 12px; }
details.adv[open] > summary.section-title::after { transform: rotate(180deg); }

/* Fields */
.field { margin-bottom: 11px; }
.field:last-child { margin-bottom: 0; }

.field label {
    display: flex;
    align-items: baseline;
    gap: 7px;
    font-size: 12px;
    font-weight: 560;
    color: var(--text);
    margin-bottom: 5px;
}
.field .hint {
    font-size: 11px;
    font-weight: 450;
    color: var(--muted);
    margin-left: auto;
}

.input {
    width: 100%;
    padding: 8px 11px;
    font-size: 13px;
    font-family: inherit;
    color: var(--text);
    background-color: var(--bg);
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    outline: none;
    transition: border-color 0.14s, background-color 0.14s, box-shadow 0.14s;
    appearance: none;
}
.input:hover { border-color: var(--line-hover); }
.input:focus {
    border-color: var(--accent);
    background-color: var(--surface);
    box-shadow: 0 0 0 3px var(--accent-ring);
}
textarea.input { resize: vertical; min-height: 44px; line-height: 1.45; }

.select {
    background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2210%22%20height%3D%226%22%3E%3Cpath%20fill%3D%22%2369707f%22%20d%3D%22M0%200l5%206%205-6z%22%2F%3E%3C%2Fsvg%3E");
    background-repeat: no-repeat;
    background-position: right 11px center;
    padding-right: 30px;
    cursor: pointer;
}

input[type="range"].range {
    width: 100%;
    margin: 2px 0;
    accent-color: var(--accent);
    cursor: pointer;
}
input[type="range"].range:disabled { opacity: 0.4; cursor: not-allowed; }

input[type="color"].color-swatch {
    width: 100%;
    height: 34px;
    padding: 3px;
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    background-color: var(--bg);
    cursor: pointer;
}

.check {
    display: flex !important;
    align-items: center;
    gap: 8px;
    cursor: pointer;
    margin-bottom: 0 !important;
}
.check input { accent-color: var(--accent); margin: 0; }

/* Segmented controls */
.seg {
    display: flex;
    background-color: var(--bg);
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    padding: 3px;
    gap: 2px;
}
.seg button {
    flex: 1;
    padding: 6px 0;
    border: none;
    border-radius: 6px;
    background: none;
    color: var(--muted);
    font-size: 11.5px;
    cursor: pointer;
    transition: background-color 0.13s, color 0.13s, box-shadow 0.13s;
}
.seg button:hover { color: var(--text); }
.seg button.active {
    background-color: var(--surface);
    color: var(--accent-strong);
    box-shadow: var(--shadow-sm);
}

/* Profiles row */
.profile-row {
    display: flex;
    gap: 7px;
}
.profile-row .select { flex: 1; min-width: 0; }

/* Template tiles */
.template-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 8px;
}
.template-option {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    padding: 9px 6px 8px;
    background-color: var(--surface);
    border: 1.5px solid var(--line);
    border-radius: var(--r-md);
    cursor: pointer;
    font-family: inherit;
    transition: border-color 0.14s, background-color 0.14s, box-shadow 0.14s;
}
.template-option:hover { border-color: var(--accent-ring); }
.template-option.active {
    border-color: var(--accent);
    background-color: var(--accent-soft);
    box-shadow: 0 0 0 3px var(--accent-ring);
}

.template-thumb {
    display: flex;
    align-items: center;
    gap: 5px;
    padding: 7px;
    width: 100%;
    height: 40px;
    background-color: var(--line-soft);
    border-radius: 7px;
}
.t-photo {
    width: 19px; height: 19px;
    background-color: var(--thumb-bar);
    border-radius: 4px;
    flex-shrink: 0;
}
.t-photo.small { width: 15px; height: 15px; }
.t-bar { width: 3px; height: 22px; background-color: var(--thumb-bar); border-radius: 2px; flex-shrink: 0; }
.t-lines { display: flex; flex-direction: column; gap: 3px; flex: 1; min-width: 0; }
.t-line { height: 3px; background-color: var(--thumb-bar); border-radius: 2px; }
.t-line.long  { width: 100%; }
.t-line.med   { width: 70%; }
.t-line.short { width: 48%; }

/* Banner thumb: accent block left + lines right */
.banner-thumb { padding: 0; overflow: hidden; }
.t-side {
    width: 26%;
    height: 100%;
    background-color: var(--thumb-bar);
    border-radius: 7px 0 0 7px;
    flex-shrink: 0;
}
.banner-thumb .t-lines { padding: 7px 7px 7px 0; }

/* Card thumb: accent top edge + body */
.card-thumb { flex-direction: column; align-items: stretch; gap: 0; padding: 0; overflow: hidden; }
.t-topbar {
    height: 4px;
    width: 100%;
    background-color: var(--thumb-bar);
    flex-shrink: 0;
}
.t-card-body {
    display: flex;
    align-items: center;
    gap: 5px;
    padding: 6px 7px;
    flex: 1;
    min-height: 0;
}
.t-card-body .t-photo.small { width: 13px; height: 13px; }

/* Centered thumb: stacked dot + centred lines */
.centered-thumb { flex-direction: column; justify-content: center; gap: 3px; }
.centered-thumb .t-photo.small { width: 12px; height: 12px; border-radius: 50%; }
.centered-thumb .t-line { width: 60%; }
.centered-thumb .t-line.short { width: 38%; }

/* Banner Top / Bottom thumbs: wide image block stacked with lines */
.stacked-thumb { flex-direction: column; align-items: stretch; justify-content: center; gap: 4px; }
.t-wide {
    width: 100%;
    height: 11px;
    background-color: var(--thumb-bar);
    border-radius: 3px;
    flex-shrink: 0;
}
.stacked-thumb .t-lines { padding: 0; flex: 0; }

/* Wide-banner templates: square Size/Anchor controls don't apply */
body.wide-banner-template .photo-geometry {
    opacity: 0.42;
    pointer-events: none;
    user-select: none;
}

/* Social media icon picker */
.social-grid {
    display: grid;
    grid-template-columns: repeat(6, 1fr);
    gap: 7px;
    margin-bottom: 12px;
}
.social-btn {
    height: 38px;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: var(--bg);
    border: 1.5px solid var(--line);
    border-radius: var(--r-sm);
    color: var(--muted);
    font-size: 15px;
    cursor: pointer;
    transition: border-color 0.14s, color 0.14s, background-color 0.14s, box-shadow 0.14s;
}
.social-btn:hover { border-color: var(--accent-ring); color: var(--text); }
.social-btn.active {
    border-color: var(--accent);
    background-color: var(--accent-soft);
    color: var(--accent-strong);
    box-shadow: 0 0 0 3px var(--accent-ring);
}
#social-links .field:first-child { margin-top: 2px; }

/* Per-platform field group (Icon URL + Link) */
.social-group {
    background-color: var(--bg);
    border: 1px solid var(--line-soft);
    border-radius: var(--r-md);
    padding: 10px 11px 11px;
    margin-bottom: 9px;
}
.social-group:last-child { margin-bottom: 0; }
.social-group-title {
    font-size: 11.5px;
    font-weight: 650;
    color: var(--text);
    margin-bottom: 8px;
}
.social-group .field { margin-bottom: 8px; }
.social-group .field:last-child { margin-bottom: 0; }
.social-group .input { background-color: var(--surface); }

/* Reorder / remove controls on each social-group header */
.social-group-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 8px;
}
.social-group-head .social-group-title { margin-bottom: 0; }
.social-group-tools { display: flex; gap: 3px; }
.sg-btn {
    width: 24px;
    height: 24px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--line);
    border-radius: 6px;
    background-color: var(--surface);
    color: var(--muted);
    font-size: 10.5px;
    cursor: pointer;
    transition: background-color 0.12s, color 0.12s, border-color 0.12s;
}
.sg-btn:hover:not(:disabled) { border-color: var(--accent); color: var(--accent-strong); background-color: var(--accent-soft); }
.sg-btn:disabled { opacity: 0.35; cursor: not-allowed; }

/* Footer preset picker sits just above the disclaimer textarea */
.disclaimer-preset { margin-bottom: 7px; }

/* Extra-images panel (reuses .social-group / .sg-btn for each row) */
.images-hint { font-size: 12px; color: var(--muted); line-height: 1.55; margin: -2px 0 10px; }
.images-empty { font-size: 11.5px; color: var(--muted); margin: 0 0 9px; }
.add-image-btn { width: 100%; margin-top: 2px; }

/* Imported-images panel — one editable URL per picture (custom mode only) */
.imported-img-row {
    display: flex;
    gap: 10px;
    align-items: flex-start;
    padding: 9px;
    margin-bottom: 8px;
    background-color: var(--bg);
    border: 1px solid var(--line-soft);
    border-radius: var(--r-md);
    cursor: pointer;
    transition: border-color 0.12s, box-shadow 0.12s, background-color 0.12s;
}
.imported-img-row:last-child { margin-bottom: 0; }
.imported-img-row:hover { border-color: var(--line); }
.imported-img-row.selected {
    border-color: var(--accent);
    box-shadow: inset 0 0 0 1px var(--accent);
    background-color: var(--accent-soft);
}
.imported-img-thumb {
    flex: none;
    width: 46px;
    height: 46px;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: var(--surface);
    border: 1px solid var(--line-soft);
    border-radius: 6px;
    overflow: hidden;
}
.imported-img-thumb img { max-width: 100%; max-height: 100%; display: block; }
.imported-img-main { flex: 1 1 auto; min-width: 0; }
.imported-img-label {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 11.5px;
    font-weight: 650;
    color: var(--text);
    margin-bottom: 5px;
}
.imported-img-badge {
    font-size: 9.5px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--accent-strong);
    background-color: var(--accent-soft);
    padding: 1px 6px;
    border-radius: 999px;
}
.imported-img-url { background-color: var(--surface); }

/* Shared resize control for the selected imported image */
#imported-image-resizer {
    margin-top: 11px;
    padding-top: 11px;
    border-top: 1px solid var(--line-soft);
}
.imported-resizer-title {
    font-size: 11.5px;
    font-weight: 650;
    color: var(--text);
    margin-bottom: 9px;
}
.imported-img-alt { margin-top: 5px; font-size: 12px; }

/* Collapsible panel sections — click the title to fold a section away;
   the chevron shows the state. (Advanced layout is a native <details>.) */
#editor-view .panel .panel-section > .section-title {
    cursor: pointer;
    user-select: none;
    display: flex;
    align-items: center;
}
.section-chevron {
    margin-left: auto;
    font-size: 9px;
    color: var(--hint);
    transition: transform 0.15s;
}
#editor-view .panel .panel-section.collapsed > .section-title { margin-bottom: 0; }
.panel-section.collapsed .section-chevron { transform: rotate(-90deg); }
#editor-view .panel .panel-section.collapsed > :not(.section-title) { display: none !important; }

/* Click-to-type slider values */
.hint-editable { cursor: text; border-bottom: 1px dashed transparent; border-radius: 2px; }
.hint-editable:hover { color: var(--accent-strong); border-bottom-color: var(--accent-strong); }
.hint-editable:focus-visible { outline: 2px solid var(--accent-strong); outline-offset: 2px; color: var(--accent-strong); }
.hint-input {
    margin-left: auto;
    width: 64px;
    padding: 1px 6px;
    border: 1px solid var(--accent);
    border-radius: 5px;
    background-color: var(--surface);
    color: var(--text);
    font-size: 11px;
    text-align: right;
    outline: none;
    -moz-appearance: textfield;
}
.hint-input::-webkit-inner-spin-button,
.hint-input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }

/* vCard download button (Export panel, template mode) */
.vcard-btn { width: 100%; justify-content: center; }

/* Per-field remove (eye) toggle — sits at the right of the field label */
.label-row { display: flex; align-items: center; gap: 6px; margin-bottom: 5px; }
.label-row > label { flex: 1 1 auto; margin-bottom: 0; }
.el-toggle {
    flex: none;
    width: 24px; height: 22px;
    display: inline-flex; align-items: center; justify-content: center;
    border: none; border-radius: 5px;
    background: none; color: var(--muted);
    font-size: 11.5px; line-height: 1; cursor: pointer;
    transition: background-color 0.12s, color 0.12s;
}
.el-toggle:hover { background-color: var(--bg); color: var(--text); }
.field.is-hidden > *:not(.label-row) { opacity: 0.4; }
.field.is-hidden > .label-row > label { opacity: 0.55; text-decoration: line-through; }
.field.is-hidden .el-toggle { color: var(--danger); }
/* Element removal only applies to template mode (imported HTML owns its content) */
body.custom-mode .el-toggle { display: none; }

.template-option.active .t-line,
.template-option.active .t-photo,
.template-option.active .t-side,
.template-option.active .t-topbar,
.template-option.active .t-wide,
.template-option.active .t-bar { background-color: var(--accent); opacity: 0.65; }

.t-label { font-size: 11px; font-weight: 560; color: var(--muted); }
.template-option.active .t-label { color: var(--accent-strong); }

/* Custom-mode notice + dimming */
.custom-notice {
    display: flex;
    gap: 11px;
    margin: 14px 0 2px;
    padding: 12px 13px;
    background-color: var(--accent-soft);
    border: 1px solid rgba(37, 99, 235, 0.22);
    border-radius: var(--r-md);
    font-size: 12px;
    line-height: 1.5;
    color: var(--text);
}
.custom-notice i { color: var(--accent-strong); margin-top: 2px; }
.custom-notice strong { display: block; margin-bottom: 2px; }

/* Imported (free-edit) mode only exposes the controls that actually apply to a
 * pasted-in signature — Profiles, Template, Picture, Imported images, Separator
 * and Export. The template-driven sections (Identity, Contact, Style, Footer,
 * Button, Extra images…) don't drive an imported layout, so rather than dim and
 * clutter the panel we hide them outright. Text, colours and links are edited in
 * place from the floating toolbar instead. */
body.custom-mode .needs-template { display: none; }

/* Custom-only controls (e.g. imported-text spacing) — shown ONLY while an
 * imported / free-edit signature is loaded, the mirror image of needs-template. */
body:not(.custom-mode) .custom-only { display: none; }

/* The plaintext template's body text — the one field no other layout renders,
 * so it appears only while that layout is selected. Same shape as custom-only
 * above; the class is toggled in updateModeUI(). */
body:not(.plaintext-template) .plaintext-only { display: none; }

/* Colours panel (custom mode): one swatch per colour used in the import */
.color-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 4px 2px;
    cursor: pointer;
    border-radius: var(--r-sm);
}
.color-row:hover { background-color: var(--bg); }
.color-row input[type="color"] {
    flex: none;
    width: 36px;
    height: 26px;
    padding: 1px;
    border: 1px solid var(--line);
    border-radius: 6px;
    background-color: var(--surface);
    cursor: pointer;
}
.color-hex {
    font-family: ui-monospace, 'SF Mono', monospace;
    font-size: 11.5px;
    color: var(--text);
    min-width: 62px;
}
.color-uses { font-size: 11px; color: var(--muted); }

/* Imports made with this tool (sig-socials marker) keep their icons editable, so
 * the Social Media section stays visible for them. */
body.custom-mode.social-editable .social-section { display: block; }

/* Separator sliders are inert while "Show separator" is unchecked */
#sep-controls.controls-off {
    opacity: 0.42;
    pointer-events: none;
    user-select: none;
}

/* ── MODALS ───────────────────────────────────────────────── */
.modal-overlay {
    position: fixed;
    inset: 0;
    background-color: var(--scrim);
    backdrop-filter: blur(3px);
    display: flex;
    justify-content: center;
    align-items: center;
    z-index: 1000;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.18s ease;
}
.modal-overlay.active { opacity: 1; pointer-events: auto; }
/* opacity + pointer-events hide a modal from the MOUSE only — they do nothing
   for the tab order or the accessibility tree. Nine role="dialog"
   aria-modal="true" nodes were therefore always exposed and all their inputs
   always tabbable — including ones holding sensitive values, which stayed
   focusable after "Done" and across a logout.
   display:none is what actually removes them. Tradeoff: the fade-OUT no
   longer runs (display flips immediately); the fade-in is unaffected. */
.modal-overlay:not(.active) { display: none; }

.modal-card {
    background-color: var(--surface);
    width: 680px;
    max-width: calc(100vw - 40px);
    max-height: calc(100vh - 60px);
    border-radius: var(--r-lg);
    padding: 22px 24px;
    box-shadow: var(--shadow-lg);
    transform: translateY(14px) scale(0.99);
    transition: transform 0.18s ease;
    display: flex;
    flex-direction: column;
    gap: 13px;
    overflow-y: auto;
}
.modal-overlay.active .modal-card { transform: translateY(0) scale(1); }
.modal-card-sm { width: 380px; }

.modal-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
}
.modal-head h2 { margin: 0; font-size: 16.5px; font-weight: 650; letter-spacing: -0.01em; }

.close-btn {
    background: none;
    border: none;
    font-size: 16px;
    color: var(--muted);
    cursor: pointer;
    padding: 6px 9px;
    border-radius: var(--r-sm);
    line-height: 1;
    transition: color 0.13s, background-color 0.13s;
}
.close-btn:hover { color: var(--danger); background-color: var(--danger-soft); }

.modal-tabs { display: flex; gap: 6px; }
.modal-tab {
    padding: 6px 13px;
    font-size: 12px;
    font-weight: 550;
    font-family: inherit;
    border: 1px solid var(--line);
    border-radius: 999px;
    background: none;
    color: var(--muted);
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 6px;
    transition: all 0.13s;
}
.modal-tab:hover { border-color: var(--accent); color: var(--accent); }
.modal-tab.active {
    background-color: var(--accent);
    border-color: var(--accent);
    color: #fff;
}

.modal-hint { margin: 0; font-size: 12px; color: var(--muted); line-height: 1.55; }
.modal-hint code {
    font-family: ui-monospace, 'SF Mono', monospace;
    background-color: var(--line-soft);
    padding: 1px 5px;
    border-radius: 5px;
    font-size: 11px;
    color: var(--accent-strong);
}

#code-output, #import-code {
    width: 100%;
    background-color: #161a22;
    color: #ccd3e0;
    font-family: ui-monospace, 'SF Mono', 'Fira Mono', 'Consolas', monospace;
    font-size: 12px;
    line-height: 1.6;
    padding: 13px;
    border-radius: var(--r-md);
    border: none;
    resize: vertical;
    box-sizing: border-box;
    outline: none;
}
#code-output  { height: 280px; }
#import-code  { height: 130px; }
#import-code::placeholder { color: #5d6678; }

/* Install / deployment guide (export modal third tab) */
#install-guide {
    max-height: 340px;
    overflow-y: auto;
    padding: 2px 2px 2px 0;
}
.install-step { margin-bottom: 16px; }
.install-step:last-of-type { margin-bottom: 10px; }
.install-step h4 {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0 0 7px;
    font-size: 13px;
    font-weight: 650;
    color: var(--text);
}
.install-step h4 i { color: var(--accent-strong); width: 16px; text-align: center; }
.install-step ol {
    margin: 0;
    padding-left: 20px;
    color: var(--muted);
    font-size: 12.5px;
    line-height: 1.65;
}
.install-step ol b { color: var(--text); font-weight: 600; }
.install-note {
    display: flex;
    gap: 9px;
    margin: 4px 0 0;
    padding: 11px 13px;
    background-color: var(--accent-soft);
    border-radius: var(--r-md);
    font-size: 12px;
    line-height: 1.5;
    color: var(--text);
}
.install-note i { color: var(--accent-strong); margin-top: 2px; }

.modal-foot {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 10px;
}
.foot-actions { display: flex; gap: 8px; }

.char-count {
    font-size: 11px;
    color: var(--hint);
    font-family: ui-monospace, monospace;
}

/* Dropzone */
.dropzone {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    border: 2px dashed var(--line);
    border-radius: var(--r-md);
    padding: 22px 16px;
    cursor: pointer;
    text-align: center;
    color: var(--muted);
    transition: border-color 0.15s, background-color 0.15s, color 0.15s;
}
.dropzone:hover, .dropzone.dragover {
    border-color: var(--accent);
    background-color: var(--accent-soft);
    color: var(--accent-strong);
}
.dropzone i { font-size: 24px; margin-bottom: 3px; }
.dz-main { font-size: 13px; font-weight: 600; }
.dz-sub  { font-size: 11px; }

/* ── TOASTS ───────────────────────────────────────────────── */
#toast-container {
    position: fixed;
    bottom: 22px;
    right: 22px;
    z-index: 9999;
    display: flex;
    flex-direction: column;
    gap: 9px;
    pointer-events: none;
}
.toast {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 12px 15px;
    border-radius: var(--r-md);
    font-size: 13px;
    font-weight: 500;
    background-color: #1f2430;
    color: #f0f2f7;
    max-width: 380px;
    box-shadow: var(--shadow-lg);
    opacity: 0;
    transform: translateY(8px);
    transition: opacity 0.22s ease, transform 0.22s ease;
    line-height: 1.45;
    pointer-events: auto;
}
.toast.show { opacity: 1; transform: translateY(0); }
.toast i { margin-top: 1.5px; flex-shrink: 0; }
.toast-success i { color: #34d399; }
.toast-error   i { color: #f87171; }
.toast-info    i { color: #60a5fa; }

/* ── SUB-NAV (workspace switcher) ─────────────────────────── */
/* [hidden] must beat display:flex rules on view containers */
[hidden] { display: none !important; }

.subnav {
    display: flex;
    align-items: center;
    gap: 2px;
    padding: 0 20px;
    background-color: var(--surface);
    border-bottom: 1px solid var(--line);
    flex-shrink: 0;
    z-index: 15;
}
.subnav-tab {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 11px 14px 9px;
    font-family: inherit;
    font-size: 13px;
    font-weight: 600;
    color: var(--muted);
    background: none;
    border: none;
    border-bottom: 2px solid transparent;
    cursor: pointer;
    transition: color 0.13s, border-color 0.13s;
}
.subnav-tab:hover { color: var(--text); }
.subnav-tab.active { color: var(--accent-strong); border-bottom-color: var(--accent); }
.subnav-tab .tab-chip {
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: 0.06em;
    background-color: var(--line-soft);
    color: var(--muted);
    border-radius: 5px;
    padding: 2px 5px;
}
.subnav-tab.active .tab-chip { background-color: var(--accent-soft); color: var(--accent-strong); }
.subnav-hint { margin-left: auto; font-size: 11.5px; color: var(--muted); }

/* Topbar actions are editor-specific — hide them in the bulk workspace */

/* ── BULK GENERATOR ───────────────────────────────────────── */
.bulk-view { flex: 1; min-height: 0; overflow-y: auto; }
.bulk-layout {
    display: flex;
    flex-direction: column;
    gap: var(--stack);
    padding: 24px 30px 48px;
    max-width: 1340px;
    margin: 0 auto;
}
/* Setup row: Template (compact) beside People/CSV (wide) so the dense mapping
   grid + preview table get room to breathe; Generate + Preview run full-width
   below, filling what used to be an empty right column. */
.bulk-top { display: flex; gap: var(--stack); align-items: flex-start; }
.bulk-card-tpl { flex: 0 1 380px; min-width: 0; }
.bulk-card-csv { flex: 1 1 480px; min-width: 0; }

.bulk-card {
    background-color: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-sm);
    padding: 18px 20px 20px;
}

.bulk-step { display: flex; align-items: center; gap: 10px; margin-bottom: 6px; flex-wrap: wrap; }
.bulk-step h3 { margin: 0; font-size: 14px; font-weight: 650; letter-spacing: -0.01em; }
.bulk-step.slim h3 { font-size: 13px; }
.step-no {
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background-color: var(--accent-soft);
    color: var(--accent-strong);
    font-size: 12px;
    font-weight: 700;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}
.bulk-actions { margin-left: auto; display: flex; gap: 8px; flex-wrap: wrap; }
.bulk-example-btn { margin-left: auto; }
.bulk-ready { font-size: 11.5px; color: var(--muted); }

.bulk-sub { margin: 0 0 13px; font-size: 12px; color: var(--muted); line-height: 1.55; }
.bulk-sub code, .bulk-note code {
    font-family: ui-monospace, 'SF Mono', monospace;
    background-color: var(--line-soft);
    padding: 1px 5px;
    border-radius: 5px;
    font-size: 11px;
    color: var(--accent-strong);
}

.bulk-tpl-meta { display: flex; gap: 6px; margin: 2px 0 10px; flex-wrap: wrap; }
.meta-chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 10.5px;
    font-weight: 650;
    padding: 3px 9px;
    border-radius: 999px;
    background-color: var(--line-soft);
    color: var(--muted);
}
.meta-chip.accent { background-color: var(--accent-soft); color: var(--accent-strong); }

/* Scaled-down live preview (template + generated signature) */
.bulk-frame-wrap {
    position: relative;
    overflow: hidden;
    border: 1px solid var(--line-soft);
    border-radius: var(--r-md);
    background-color: #fff;
}
/* 1:1 preview — render at true size (no scale) so it matches the paste exactly;
   scroll horizontally if the signature is wider than the pane. */
.bulk-frame-wrap.one-to-one { overflow: auto; }
.bulk-frame-wrap.one-to-one iframe {
    position: static;
    transform: none !important;
    max-width: none;
}
.bulk-frame-wrap iframe {
    position: absolute;
    top: 0;
    left: 0;
    border: none;
    display: block;
    transform-origin: top left;
}

#bulk-basevals { margin-top: 14px; }
.bulk-base-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0 14px; }
.bulk-note {
    display: flex;
    gap: 9px;
    padding: 10px 12px;
    margin-bottom: 12px;
    background-color: var(--accent-soft);
    border: 1px solid rgba(37, 99, 235, 0.22);
    border-radius: var(--r-md);
    font-size: 11.5px;
    line-height: 1.5;
    color: var(--text);
}
.bulk-note i { color: var(--accent-strong); margin-top: 2px; }

.bulk-tip { display: flex; gap: 8px; margin: 13px 0 0; font-size: 12px; line-height: 1.55; color: var(--muted); }
.bulk-tip i { margin-top: 1px; }
.bulk-tip b { color: var(--text); }

.dropzone-slim { padding: 13px 14px; flex-direction: row; gap: 10px; justify-content: center; margin-bottom: 10px; }
.dropzone-slim i { font-size: 17px; margin: 0; }

#bulk-csv {
    width: 100%;
    height: 108px;
    background-color: #161a22;
    color: #ccd3e0;
    font-family: ui-monospace, 'SF Mono', 'Fira Mono', 'Consolas', monospace;
    font-size: 12px;
    line-height: 1.6;
    padding: 11px 13px;
    border-radius: var(--r-md);
    border: none;
    resize: vertical;
    outline: none;
    box-sizing: border-box;
}
#bulk-csv::placeholder { color: #5d6678; }

.bulk-parse-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    margin-top: 10px;
    font-size: 11.5px;
    color: var(--muted);
    flex-wrap: wrap;
}
.bulk-parse-row .check { font-size: 11.5px; color: var(--muted); }
#bulk-mapping { margin-top: 11px; }
.bulk-map-note { margin: 2px 0 10px; font-size: 11.5px; color: var(--muted); line-height: 1.5; }
.bulk-map-note b { color: var(--text); font-weight: 600; }
/* 2 columns by default; on a wide People card the 8 fields fall into a tidy
   4-wide grid where row 1 = name columns, row 2 = contact columns. */
.bulk-map-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0 14px; }
@media (min-width: 1080px) {
    .bulk-map-grid { grid-template-columns: repeat(4, 1fr); }
}
#bulk-people-preview { margin-top: 11px; }
#bulk-people-preview .bulk-table-wrap { max-height: 240px; }

/* Data tables (people preview + results) */
.bulk-table-wrap {
    overflow: auto;
    max-height: 430px;
    border: 1px solid var(--line-soft);
    border-radius: var(--r-md);
}
table.bulk-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.bulk-table th {
    position: sticky;
    top: 0;
    z-index: 1;
    background-color: var(--bg);
    text-align: left;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--hint);
    padding: 8px 12px;
    border-bottom: 1px solid var(--line-soft);
    white-space: nowrap;
}
.bulk-table td { padding: 8px 12px; border-bottom: 1px solid var(--line-soft); vertical-align: middle; }
.bulk-table tbody tr:last-child td { border-bottom: none; }
.bulk-table.clickable tbody tr { cursor: pointer; transition: background-color 0.1s; }
.bulk-table.clickable tbody tr:hover { background-color: var(--bg); }
.bulk-table.clickable tbody tr.selected { background-color: var(--accent-soft); }
.bulk-table .cell-sub { display: block; font-size: 11.5px; color: var(--muted); }
.bulk-table .cell-bad { color: var(--danger); }
.people-more { padding: 7px 12px; font-size: 11px; color: var(--hint); }

.chip {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    font-size: 10.5px;
    font-weight: 650;
    border-radius: 999px;
    padding: 3px 9px;
    white-space: nowrap;
}
.chip-ok,   .vchip.ok   { background-color: var(--ok-soft);   color: var(--ok); }
.chip-warn, .vchip.warn { background-color: var(--warn-soft); color: var(--warn); }
.chip-err,  .vchip.err  { background-color: var(--danger-soft); color: var(--danger); }

.row-actions { display: flex; gap: 3px; justify-content: flex-end; }
.row-actions button {
    width: 27px;
    height: 27px;
    border: none;
    border-radius: 6px;
    background: none;
    color: var(--muted);
    cursor: pointer;
    font-size: 12px;
    transition: background-color 0.12s, color 0.12s;
}
.row-actions button:hover { background-color: var(--line-soft); color: var(--accent-strong); }

.bulk-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    padding: 46px 20px 50px;
    color: var(--hint);
    text-align: center;
}
.bulk-empty i { font-size: 30px; opacity: 0.5; }
.bulk-empty p { margin: 0; font-size: 12.5px; line-height: 1.6; }
.bulk-empty b { color: var(--muted); }

.bulk-stale {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 4px 0 12px;
    padding: 9px 12px;
    background-color: var(--warn-soft);
    border: 1px solid var(--warn);
    border-radius: var(--r-md);
    color: var(--warn);
    font-size: 12px;
}

/* ── RESPONSIVE ───────────────────────────────────────────── */
@media (max-width: 860px) {
    .bulk-top { flex-direction: column; }
    .bulk-card-tpl, .bulk-card-csv { flex: 1 1 auto; width: 100%; }
}
@media (max-width: 940px) {
    .subnav-hint { display: none; }
}
@media (max-width: 980px) {
    .layout { flex-direction: column; overflow-y: auto; }
    .panel { width: 100%; border-left: none; border-top: 1px solid var(--line); }
    .stage { overflow: visible; padding: 18px; }
    .app { overflow-y: auto; }
    /* Stacked: brand, then the editor tools, then the signed-in user. No
       overflow-y here — these containers wrap, and a wrapping flex row reflows
       rather than scrolling, so the property could never fire. The base rule's
       min-height lets the bar take the height it needs. */
    .topbar { flex-direction: column; flex-wrap: wrap; padding: var(--s-2) var(--s-2); }
    .topbar-actions { flex-direction: row; }
}

/* ── Signature health check (under the preview) ──────────────── */
.health-bar {
    margin: 10px 2px 0;
    padding: 8px 11px;
    border-radius: var(--r-sm);
    font-size: 12px;
    line-height: 1.5;
    display: flex;
    flex-wrap: wrap;
    gap: 5px 14px;
    align-items: center;
}
.health-bar:empty { display: none; }
.health-bar.ok   { background-color: var(--ok-soft);   color: var(--ok); }
.health-bar.info { background-color: var(--accent-soft); color: var(--accent-strong); }
.health-bar.warn { background-color: var(--warn-soft); color: var(--warn); }
.health-item { display: inline-flex; align-items: center; gap: 6px; }
.health-item.warn { color: var(--warn); }
.health-item.info { color: var(--muted); }

#preview-bg-toggle.active { background-color: #1f1f1f; color: #fff; }

/* ── Profile folders + backup tools ──────────────────────────── */
.mini-label { display: block; font-size: 11px; font-weight: 600; color: var(--muted); margin: 10px 0 4px; }
.panel-section > .mini-label:first-of-type { margin-top: 0; }
.profile-row + .mini-label { margin-top: 10px; }
.profile-tools { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.profile-tools .btn-sm { flex: 1 1 auto; justify-content: center; }
#bulk-profile optgroup { font-style: normal; font-weight: 700; color: var(--muted); }

/* ── Bulk validation summary chips ───────────────────────────── */
.bulk-validation { display: flex; flex-wrap: wrap; gap: 6px; margin: 11px 0 5px; }
.vchip {
    display: inline-flex; align-items: center; gap: 5px;
    padding: 3px 9px; border-radius: 999px;
    font-size: 11.5px; font-weight: 600;
}
/* .vchip.ok / .vchip.warn / .vchip.err colors are shared with .chip-* above */
.vchip.info { background-color: var(--accent-soft); color: var(--accent-strong); }
.bulk-table .cell-warn { color: var(--warn); }

/* ── Bulk results search/filter bar ──────────────────────────── */
.bulk-results-bar { display: flex; align-items: center; gap: 12px; margin: 0 0 11px; }
.bulk-search { position: relative; flex: 1; max-width: 340px; }
.bulk-search i { position: absolute; left: 11px; top: 50%; transform: translateY(-50%); color: var(--muted); font-size: 12px; pointer-events: none; }
.bulk-search input {
    width: 100%; padding: 8px 11px 8px 30px;
    border: 1px solid var(--line); border-radius: var(--r-sm);
    background: var(--bg); color: var(--text); font-size: 13px;
}
.bulk-search input:hover { border-color: var(--line-hover); }
.bulk-search input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring); }
.bulk-filter-count { font-size: 12px; color: var(--muted); white-space: nowrap; }
.bulk-nomatch { text-align: center; color: var(--muted); padding: 18px; font-size: 13px; }

/* ═════════════════════════════════════════════════════════════
   COMPANY PORTAL (view: company — server-backed)
   ═════════════════════════════════════════════════════════════ */

.company-view {
    padding: 22px clamp(16px, 3vw, 36px) 40px;
    max-width: 1500px;
    margin: 0 auto;
    width: 100%;
    /* the .app shell is 100vh/overflow-hidden — like .bulk-view, this
       workspace scrolls itself (Users/Templates panes grow tall) */
    flex: 1;
    min-height: 0;
    overflow-y: auto;
}

/* ── Login ──
   "Mit SSO anmelden" is a full page navigation to Keycloak's own login form,
   so anything that does not line up across it reads as the page jumping.
   Both sides are therefore built to the same skeleton:

       [ 64px ][ brand box 340x111.9 ][ 10px ][ card 380x437 ]
       centred in a column (viewport − 84px) tall, then an 84px black bar

   which puts the two cards within a tenth of a pixel of each other at every
   viewport height, with no magic offsets on either side. Every number was
   measured off the real Keycloak page and is documented at its rule below;
   the other half lives in the Keycloak login theme's own stylesheet
   (its §2/§3/§4/§17).
   Changing one of these without the other is what breaks the handoff. */

/* The gate (v59: the whole app sits behind a login) is a page column,
   mirroring what the Keycloak theme does to <body>: the stage takes
   everything above the black bar, the bar is the last block in flow. */
/* Montserrat, for the footer only — it is what the other workingFlow apps'
   footers use, and matching them is the point.
   Scoped to .co-login-footer rather than applied to the app: quillDrop's chrome
   is on the system stack and re-typesetting it is not what was asked.
   Self-hosted because the CSP is `font-src 'self'` — a Google Fonts CDN link is
   blocked outright, and so is a data: URI. `vendor/` is already an allowed
   static dir and already serves FontAwesome's woff2, so this needs no server
   change. `swap` so the footer text is legible while the 16KB file arrives. */
@font-face {
    font-family: 'Montserrat';
    src: url('../vendor/montserrat/montserrat-400-latin.woff2') format('woff2');
    font-weight: 400;
    font-style: normal;
    font-display: swap;
    /* The latin subset — verified to cover ©, and the German umlauts and ß this
       footer's sibling pages need. */
    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;
}

#auth-gate {
    position: fixed;
    inset: 0;
    z-index: 4000;
    /* Not var(--bg). The gate is pinned to the Keycloak login it hands off to,
       and that page is #f3f4f6 — --bg is #f5f6f8, so the two backgrounds were
       two shades apart across the redirect. */
    background: #f3f4f6;
    display: flex;
    flex-direction: column;
    overflow: auto;
}
/* `flex: 1 0 auto` (not `1 1 auto`): on a window too short for the card, the
   stage keeps its natural height and the gate scrolls, instead of squashing
   the card and pulling it out of line with Keycloak's. */
.co-login-stage {
    flex: 1 0 auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 40px 16px;
}
/* The 84px black band — the shared workingFlow chrome, drawn here by the same
   numbers as the Keycloak theme's footer (its §17), which carries
   the same three links so the bar does not change as the login hands off.
   It was #auth-gate::after while it was empty decoration; carrying links it has
   to be real markup, because a pseudo-element cannot contain anchors and these
   have to be focusable and reachable by a screen reader.
   Three columns rather than space-between: the links stay optically centred on
   the viewport no matter how wide the version string on the left grows. */
/* Every number below is measured off the reference footer the other workingFlow
   apps render, so they all share one footer:
     84px tall, #000, padding 32px 24px, a 3-column grid with a 16px gap and its
     single 20px row vertically centred; version left, links centre, copyright
     right; 14px/20px Montserrat 400 throughout.
   Link order is the reference's — AGB, Impressum, Datenschutz. */
.co-login-footer {
    flex: none;
    height: 84px;
    background: #000000;
    font-family: 'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    align-items: center;
    gap: 16px;
    padding: 32px 24px;
}
.co-login-version {
    grid-column: 1;
    justify-self: start;
    font-size: 14px;
    line-height: 20px;
    color: #6a7282;
    font-variant-numeric: tabular-nums;
}
.co-login-legal {
    grid-column: 2;
    justify-self: center;
    display: flex;
    align-items: center;
    gap: 12px;
}
.co-login-legal a {
    font-size: 14px;
    line-height: 20px;
    color: #d1d5dc;
    text-decoration: none;
    transition: color 0.14s;
}
.co-login-legal a:hover { color: #ffffff; }
/* The ring has to be light here: --accent on black is close to invisible, and
   these are the only focusable things on the page sitting on a dark surface. */
.co-login-legal a:focus-visible {
    outline: 2px solid #ffffff;
    outline-offset: 3px;
    border-radius: 3px;
}
.co-login-copyright {
    grid-column: 3;
    justify-self: end;
    font-size: 14px;
    line-height: 20px;
    color: #ffffff;
    white-space: nowrap;
}
/* The reference stacks its three cells on small screens (its grid is
   `grid-cols-1 sm:grid-cols-3`). Below 640px do the same, and drop the 32px
   vertical padding — three stacked 20px rows do not fit inside 84px otherwise,
   and this bar's height is pinned to Keycloak's. */
@media (max-width: 639px) {
    .co-login-footer {
        grid-template-columns: 1fr;
        align-content: center;
        gap: 2px;
        padding: 8px 16px;
    }
    .co-login-version,
    .co-login-legal,
    .co-login-copyright {
        grid-column: 1;
        justify-self: center;
    }
    /* order: links first, then the two small grey/white lines under them */
    .co-login-legal { order: 1; }
    .co-login-copyright { order: 2; font-size: 12px; line-height: 16px; }
    .co-login-version { order: 3; font-size: 12px; line-height: 16px; }
}
.co-login-card {
    width: 380px;
    max-width: 100%;
    /* Pinned to the height Keycloak's card actually renders, so "Mit SSO
       anmelden" hands off without the frame moving. The fraction is not noise:
       rounded up to 359 the browser snaps this card's top border one row higher
       than Keycloak's and the frame visibly ticks as the page navigates.

       MEASURED 2026-08-10, in headless Chrome at 1440x900, against the realm
       quillDrop actually redirects to — dev-sso-wof, which has registration and
       forgot-password OFF. Confirmed identical in German and English (the
       heading stays one line at 380px either way), so the locale switch does not
       move it.

       ** THIS NUMBER IS A PROPERTY OF THE REALM, NOT OF THE STYLESHEET. **
       It was 436.9px until today, measured against a local realm that had
       registration and forgot-password ON — 78.8px taller, because those two add
       a link and a footer band. Toggling either on dev-sso-wof, or adding
       remember-me, moves it again. Re-measure against the real realm, never a
       convenient local one:

         .pf-v5-c-login__main -> getBoundingClientRect().height

       min-height, not height, so revealing the password form (411.1px) or an
       error grows the card rather than clipping it. In SSO mode the natural
       height is 259.1px and the flex centring below absorbs the difference. */
    min-height: 358.1px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-md);
    padding: 30px 28px;
}
/* Matches #kc-page-title on the Keycloak side (19px/600, line-height 1.35),
   including the 20px below it, so the first line of the card holds still. */
.co-login-title {
    margin: 0 0 20px;
    font-size: 19px;
    font-weight: 600;
    line-height: 1.35;
    letter-spacing: -0.01em;
}
/* The brand sits ABOVE the card, in the slot Keycloak gives the workingFlow
   wordmark. The box is that wordmark's box exactly — 340px wide at the
   asset's 3123:1028 — and the margins are PatternFly's own around #kc-header
   (64px above, and the 10px the theme sets below). That is the whole trick:
   both pages centre [brand + margins + card] as one block, so with the block
   the same height on both, the cards cannot land anywhere but the same y.
   The lockup itself is centred inside the box rather than filling it, so the
   box keeps its height whatever the mark's own proportions are. */
.co-login-brand {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 16px;
    width: 100%;
    max-width: 340px;
    aspect-ratio: 3123 / 1028;
    margin: 64px 0 10px;
}
.co-login-splash { height: 54px; width: auto; }
body.theme-dark .co-login-splash { filter: invert(1); }
.co-login-wordmark { font-size: 40px; font-weight: 600; letter-spacing: -0.02em; }
.co-login-sub { margin: 0 0 40px; font-size: 12.5px; color: var(--muted); line-height: 1.5; }
.co-login-card .field { margin-bottom: 13px; }
/* Disclosing the form under the SSO button needs a gap, or the first field
   label reads as a caption for the button. 26px is what .co-login-alt was
   using, so the first label appears exactly where "Lokal anmelden" was.
   Scoped to that case: with SSO off the form follows .co-login-sub instead,
   which already brings its own 40px. */
#co-login-sso:not([hidden]) ~ #co-login-form { margin-top: 26px; }
.co-login-btn { width: 100%; justify-content: center; margin-top: 4px; }
.co-login-error {
    background: var(--danger-soft);
    color: var(--danger);
    font-size: 12.5px;
    padding: 8px 11px;
    border-radius: var(--r-sm);
    margin-bottom: 12px;
}
/* An ?sso_error= lands the box under the SSO button, where it had nothing
   above it and sat flush against "Lokal anmelden". Only in that case: with
   SSO off the box follows .co-login-sub, which already brings its own 40px. */
#co-login-sso:not([hidden]) ~ .co-login-error { margin-top: 18px; }
/* SSO button is an <a>, so it needs the anchor reset the .btn class assumes */
a.co-login-btn { text-decoration: none; }
/* The login card's primary action is pinned to Keycloak's own navy rather
   than the app-wide --accent blue, so the button carries over visually
   unchanged across the "Mit SSO anmelden" handoff. Scoped to the login card
   only — every other .btn-primary in the app (editor, admin, ...) keeps the
   ordinary brand blue. */
.co-login-card .btn-primary {
    background-color: #284978;
    box-shadow: 0 1px 2px rgba(40, 71, 120, .35);
}
.co-login-card .btn-primary:hover:not(:disabled) { background-color: #1e3860; }
.co-login-card .btn-primary:focus-visible { outline-color: #284978; }
.co-login-alt {
    display: block;
    width: 100%;
    margin: 26px 0 0;
    padding: 0;
    background: none;
    border: none;
    font: inherit;
    font-size: 12.5px;
    text-align: center;
    color: #99a1af;
    text-decoration: underline;
    cursor: pointer;
}
/* --fg was never defined anywhere, so this hover resolved to nothing and the
   login card's "use a password instead" link had no hover feedback in either
   theme. The token it meant is --text. */
.co-login-alt:hover { color: var(--text); }
.co-login-hint {
    margin: 16px 0 0;
    font-size: 12px;
    color: var(--muted);
    line-height: 1.55;
    border-top: 1px solid var(--line-soft);
    padding-top: 13px;
}
.co-login-hint code { background: var(--bg); padding: 1px 5px; border-radius: 4px; }

/* Both breakpoints below mirror the Keycloak theme's §14 one for one — same
   queries, same numbers. They are the only two places the login page is
   allowed to change shape, and it has to change shape on both sides at the
   same width or the handoff stops lining up. */
@media (max-width: 480px) {
    /* top-aligned, not centred: on a phone the keyboard eats the lower half */
    .co-login-stage { justify-content: flex-start; padding: 24px 12px; }
    .co-login-brand { margin: 0 0 8px; }
    /* the same Keycloak card measured at this breakpoint, where its own
       padding and title both step down — same realm, same day, same caveat as
       the desktop number above (was 427.2px against a realm with registration
       and forgot-password on) */
    .co-login-card { min-height: 348.4px; padding: 22px 18px; border-radius: 14px; }
    .co-login-title { font-size: 17px; }
}
/* Short and wide (a laptop in a video call, a phone in landscape): centring
   would put the top of the card off-screen with no way to scroll up to it. */
@media (max-height: 620px) {
    .co-login-stage { justify-content: flex-start; padding-top: 24px; }
}

/* ── Portal bar (tabs + user chip) ── */
.co-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    flex-wrap: wrap;
    margin-bottom: 18px;
}
.co-tabs { display: flex; gap: 6px; }
.co-firma-pick { display: inline-flex; align-items: center; gap: 8px; font-size: var(--fs-2); color: var(--muted); }
/* Admin > Companies: one customer's setup, row by row */
.co-setup-list { list-style: none; margin: 0; padding: 0; border: 1px solid var(--line-soft); border-radius: var(--r-md); }
.co-setup-row {
    display: grid; grid-template-columns: 18px 140px minmax(0, 1fr) auto;
    align-items: center; gap: 10px; padding: 8px 12px; font-size: var(--fs-2);
}
.co-setup-row + .co-setup-row { border-top: 1px solid var(--line-soft); }
.co-setup-row > .fa { color: var(--accent); text-align: center; }
.co-setup-label { font-weight: 600; }
.co-setup-status { color: var(--muted); min-width: 0; overflow-wrap: anywhere; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.co-setup-actions { display: inline-flex; gap: 6px; }
.co-setup-chip {
    display: inline-flex; align-items: center; gap: 2px; padding: 1px 4px 1px 9px;
    border: 1px solid var(--line-soft); border-radius: 999px; background: var(--bg); color: var(--text);
}
.co-setup-chip-act {
    border: 0; background: transparent; color: var(--hint); cursor: pointer; width: 20px; height: 20px;
    border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; font-size: 10px;
}
.co-setup-chip-act:hover { color: var(--accent); background: var(--accent-soft); }
.co-firma-pick .select { min-width: 220px; }
.co-tab {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 8px 15px;
    font-size: 13px;
    font-weight: 560;
    color: var(--muted);
    background: transparent;
    border: 1px solid transparent;
    border-radius: 999px;
    cursor: pointer;
    font-family: inherit;
}
.co-tab:hover { color: var(--text); background: var(--accent-soft); }
.co-tab.active {
    color: var(--accent-strong);
    background: var(--accent-soft);
    border-color: var(--accent-ring);
}
.co-role-badge {
    display: inline-block;
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--accent-strong);
    background: var(--accent-soft);
    border-radius: 999px;
    padding: 2px 8px;
    vertical-align: 1px;
}
.co-badge-off { color: var(--muted); background: var(--bg); }

/* ── Layout ── */
/* ── Vertical rhythm ──
   One value for "space between stacked sections", everywhere. --stack was
   introduced because this concept had grown four different answers: .co-panel
   had NO rule at all (so two cards in Admin > SSO, Companies, Git and six
   other panels sat flush against each other with a doubled border between
   them), the detail wrappers used 14px, the column containers 16px, and the
   grids 18px. The Bulk Generator was the one view that felt right, and 18px is
   what it uses — so that is the value the rest moved to, not the average. */
.co-panel { display: flex; flex-direction: column; gap: var(--stack); }
/* A sub-head is a divider between two stacked blocks; the gap now provides
   its separation, so its own vertical margins would double it. */
.co-panel > .co-card-sub-head { margin: 0; }

.co-cols { display: grid; grid-template-columns: minmax(0, 1fr) 360px; gap: var(--stack); align-items: start; }
.co-cols-split { grid-template-columns: 300px minmax(0, 1fr); }
.co-main, .co-side, .co-detail { display: flex; flex-direction: column; gap: var(--stack); min-width: 0; }
@media (max-width: 1024px) {
    .co-cols, .co-cols-split { grid-template-columns: 1fr; }
    /* The .co-list-pane unpin that used to live here MOVED — see the "List
       panes" section below. It sat before `.co-list-pane`'s own rule, and a
       media query adds no specificity, so the later base rule won and the fix
       never applied at any width. */
}

.co-card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-sm);
    padding: 18px;
}
.co-card-head {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
    margin-bottom: 12px;
}
.co-card-head h3 { margin: 0; font-size: 14.5px; letter-spacing: -0.01em; display: flex; align-items: center; gap: 8px; }
.co-card-head h3 i { color: var(--accent); font-size: 13px; }
.co-card-actions { margin-left: auto; display: flex; gap: 7px; flex-wrap: wrap; }
.co-sub { margin: -4px 0 12px; font-size: 12px; color: var(--muted); line-height: 1.5; }

.co-sig-status-ok   { color: var(--ok); }
.co-sig-status-err  { color: var(--danger); }
.co-sig-status-none { color: var(--muted); }

/* Fixed-width slot on the signature tree row, outside .co-list-main's
   flex-shrink — so a long name/e-mail can truncate without ever hiding
   whether that signature failed to push. */
.co-sig-status-badge { flex-shrink: 0; font-size: 12px; width: 16px; text-align: center; }

.co-card-sub-head { margin: 16px 0 11px; padding-top: 14px; border-top: 1px solid var(--line); }
.co-card-sub-head h4 { margin: 0 0 4px; font-size: 12.5px; font-weight: 600; color: var(--text); }
.co-card-sub-head .co-sub { margin: 0; }
.field-row { display: flex; gap: 12px; }
.field-row .field { flex: 1 1 0; min-width: 0; }

.co-target-card { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; padding: 13px 18px; }
.co-target-label { font-size: 12.5px; font-weight: 560; color: var(--muted); }

/* ── Forms ── */
/* A standalone action button sitting directly in a card follows content
   rather than heading it — "Update password" under the two password fields
   is the case. Buttons in a .co-card-head / .co-card-actions row are nested
   deeper and keep their own layout, so this cannot reach them. Without it the
   button sits flush against the field above. 12px matches the card head's own
   offset from its content, which is the in-card rhythm. */
.co-card > .btn { margin-top: 12px; }

.co-form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 11px 12px; }
.co-form-grid .field { margin: 0; }
.co-form-grid .co-wide { grid-column: 1 / -1; }
/* A row of toggles inside a form grid. A bare checkbox is a fraction of a
   labelled control's height, so interleaving one between selects leaves the
   grid ragged — every control after it stops lining up with its neighbour.
   Collecting the toggles into one full-width row means selects only ever pair
   with selects. (Not a rule on checkboxes generally: two ADJACENT .co-field-
   check fields, as the user form has, already pair with each other fine.) */
.co-form-grid .co-checks { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 4px 24px; }
.co-form-grid .co-checks > label { margin-bottom: 0; }
.co-upload-row { margin-top: 6px; }
.co-field-check { display: flex; align-items: center; gap: 9px; }
.co-field-check input[type="checkbox"] { width: 16px; height: 16px; accent-color: var(--accent); }
.co-code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; line-height: 1.5; resize: vertical; }

.co-kv-list { display: flex; flex-direction: column; gap: 7px; margin-bottom: 9px; }
.co-kv { display: grid; grid-template-columns: 1fr 1.4fr auto; gap: 7px; }
.co-custom-wrap { margin-top: 13px; }
.co-custom-wrap .hint { margin-left: 9px; font-size: 11.5px; color: var(--hint); }
.co-custom-wrap .hint code { background: var(--bg); padding: 1px 4px; border-radius: 4px; }

/* ── Signature cards ── */
.co-sig-card { margin-bottom: 0; }
#co-sig-list, .co-user-sig-list { display: flex; flex-direction: column; gap: 16px; }
.co-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 11px;
    font-weight: 620;
    border-radius: 999px;
    padding: 3px 10px;
    white-space: nowrap;
}
.co-chip-gen { background: var(--ok-soft);   color: var(--ok); }
.co-chip-override { background: var(--warn-soft); color: var(--warn); }

.co-frame-wrap {
    overflow: auto;
    border: 1px solid var(--line-soft);
    border-radius: var(--r-md);
    background-color: #fff;
}
.co-frame-wrap iframe { border: none; display: block; }
body.theme-dark .co-frame-wrap { background-color: #ffffff; }

.co-empty-card {
    background: var(--surface);
    border: 1px dashed var(--line);
    border-radius: var(--r-lg);
    padding: 34px 20px;
    text-align: center;
    color: var(--muted);
    font-size: 13px;
}
.co-empty-card i { font-size: 26px; color: var(--hint); display: block; margin-bottom: 10px; }
.co-empty-card p { margin: 0; line-height: 1.55; }
.co-sigs-heading { margin: 4px 0 0; font-size: 14.5px; display: flex; align-items: center; gap: 8px; }
.co-sigs-heading i { color: var(--accent); font-size: 13px; }

/* ── List panes (users / templates) ── */
.co-list-pane {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-lg);
    box-shadow: var(--shadow-sm);
    padding: 13px;
    display: flex;
    flex-direction: column;
    gap: 11px;
    position: sticky;
    top: 14px;
    max-height: calc(100vh - 130px);
}
/* THE worst responsive defect: the list kept `position: sticky` +
   `max-height: calc(100vh - 130px)` AFTER the grid collapsed at 1024px, so in
   the stacked layout it became a height-capped, internally-scrolling box
   pinned to the viewport while its own detail pane scrolled past underneath.
   Eight panes: Templates, Signatures, Config, Companies, Users, Roles and both
   Git lists.

   This MUST stay after the rule above. It was originally written up beside the
   `.co-cols` collapse and had no effect at any width — same specificity, and a
   media query adds none, so the later base rule simply won. `tests/check_css_
   shadowing.py` now fails the suite if a media-query declaration is shadowed
   like that again. */
@media (max-width: 1024px) {
    .co-list-pane { position: static; max-height: none; top: auto; }
}
/* wrap rather than overflow once a pane carries extra controls (the CSV
   reconciliation opt-in) — no effect on heads that still fit on one line */
.co-list-head { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.co-list-head .input { flex: 1; min-width: 0; }
/* the reconciliation opt-in: compact, never stretches, own line when tight */
.co-imp-recon { align-items: center; white-space: nowrap; flex: 0 0 auto; font-size: 12px; }
/* The users list head stacks its rows: search + add on one, import on the
   next. Every control in one wrapping row left the search box whatever the
   fixed buttons did not take — 37px at a 1440px window. */
.co-user-head { flex-direction: column; align-items: stretch; flex-wrap: nowrap; }
/* nowrap is load-bearing: in a WRAPPING column each line is as wide as its
   widest item, so the one unbreakable label below stretched every row past
   the pane. The label wraps instead. */
.co-user-head .co-imp-recon { white-space: normal; }
.co-head-row { display: flex; gap: 8px; align-items: center; min-width: 0; }
.co-head-row .input { flex: 1; min-width: 0; }
.co-user-import-row #co-imp-company { max-width: none; }
.co-list-title { font-size: 13.5px; font-weight: 650; flex: 1; }
.co-list { overflow-y: auto; display: flex; flex-direction: column; gap: 4px; }
.co-list-row {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    text-align: left;
    background: transparent;
    border: 1px solid transparent;
    border-radius: var(--r-md);
    padding: 8px 10px;
    cursor: pointer;
    font-family: inherit;
    color: var(--text);
}
.co-list-row:hover { background: var(--bg); }
.co-list-row.selected { background: var(--accent-soft); border-color: var(--accent-ring); }
.co-list-row.co-inactive .co-list-main { opacity: 0.5; }
.co-bulk-check { flex-shrink: 0; accent-color: var(--accent); cursor: pointer; }
.co-bulk-check-spacer { flex-shrink: 0; width: 13px; }
.co-bulkbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    padding: 8px 10px;
    margin-bottom: 6px;
    background: var(--accent-soft);
    border: 1px solid var(--accent-ring);
    border-radius: var(--r-md);
    font-size: 12.5px;
}
.co-bulkbar #co-bulk-count, .co-bulkbar #co-sig-bulk-count { font-weight: 650; color: var(--accent-strong); margin-right: 2px; white-space: nowrap; }
.co-sig-tools { display: flex; align-items: center; gap: 8px; margin: 8px 0 4px; }
/* CSV generator: people from quillDrop instead of a file */
.bulk-people-pick { margin: 10px 0; padding: 10px 12px; border: 1px solid var(--line-soft); border-radius: var(--r-md); background: var(--bg); }
.bulk-people-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.bulk-people-row .select { flex: 1; min-width: 160px; }
.bulk-people-pick .hint { margin: 8px 0 0; }
.co-sig-tools .select { flex: 1; min-width: 0; }
.co-bulkbar select { max-width: 150px; }
.co-list-main { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; }
.co-list-name { font-size: 13px; font-weight: 580; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.co-list-sub { font-size: 11.5px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.co-ov-badge {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 10.5px;
    font-weight: 650;
    color: var(--warn);
    background: var(--warn-soft);
    border-radius: 999px;
    padding: 2px 7px;
}
.co-warn { color: var(--warn); }
/* ── Empty states ──
   An empty state IS the whole content of an otherwise blank pane, so 14px of
   padding and left alignment read as a stray sentence stuck in the top-left
   corner rather than as a deliberate message. Centre it and give it room. */
.co-empty {
    color: var(--muted);
    font-size: 13px;
    line-height: 1.6;
    padding: 26px 20px;
    text-align: center;
}
/* Several of these are two sentences; without a measure they stretch the full
   width of a wide detail pane, which is what made them hard to read. */
p.co-empty { max-width: 54ch; margin-left: auto; margin-right: auto; }
/* "Select a company on the left." is alone in a tall pane — sitting it flush
   against the top edge is what reads as stuck. */
.co-detail > p.co-empty { padding-top: 52px; padding-bottom: 52px; }
/* .co-imp-table td is MORE specific than .co-empty (0,1,1 vs 0,1,0), so none
   of the above ever reached the Activity table's "Nothing logged yet." row —
   it kept the 7px padding of an ordinary data cell. */
.co-imp-table td.co-empty { padding: 34px 12px; }

.co-empty code { background: var(--bg); padding: 1px 5px; border-radius: 4px; font-size: 12px; }

/* ── Admin overview (dashboard) ── */
.co-stat-row { display: flex; flex-wrap: wrap; gap: 22px; margin-bottom: 18px; }
.co-stat { display: flex; flex-direction: column; gap: 2px; min-width: 84px; }
.co-stat-num { font-size: 26px; font-weight: 600; letter-spacing: -0.02em; }
.co-stat-label { font-size: 12px; color: var(--muted); }
.co-overview-fail-list { margin: 0 0 10px; padding-left: 20px; font-size: 13px; }
.co-overview-fail-list li { margin-bottom: 2px; }

/* ── Admin › SSO ── */
/* Read-only label/value grid (Admin > SSO status, the user identity row).
   Renamed from .co-kv: that name was ALREADY taken at :1865 by the editable
   custom-field rows, at the same specificity — so this block won, and the
   key/value editor silently never got its `1fr 1.4fr auto` grid. Its field,
   value and delete controls stacked onto separate tracks instead. */
.co-infogrid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 10px 22px;
    margin-bottom: 4px;
}
.co-infogrid-item { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.co-infogrid-label { font-size: 12px; color: var(--muted); }
.co-infogrid-value { font-size: 13px; overflow-wrap: anywhere; }
.co-infogrid-muted { color: var(--hint); }
.co-banner-error, .co-banner-info {
    margin: 0 0 10px;
    padding: 9px 12px;
    border-radius: 7px;
    border-left: 3px solid;
    font-size: 12.5px;
    line-height: 1.5;
}
.co-banner-error { background: var(--danger-soft); border-color: var(--danger); color: var(--danger); }
.co-banner-info { background: var(--bg); border-color: var(--hint); color: var(--muted); }

/* ── Template editor extras ── */
/* The palette used to be a flat column of wrapping chip rows with a fixed
   field list. A company can declare its own extra placeholders now, so it has
   to stay readable whether it holds 12 fields or 40: a filter box, and groups
   that fold. Fold state lives in CO.phClosed, not in the DOM — the pane is
   re-rendered on every filter keystroke. */
.co-ph-chips { display: flex; flex-direction: column; gap: var(--s-1); margin-top: 11px; }

.co-ph-filter { position: relative; display: flex; align-items: center; margin-bottom: var(--s-1); }
.co-ph-filter i {
    position: absolute;
    left: 9px;
    font-size: 10.5px;
    color: var(--hint);
    pointer-events: none;      /* the icon sits ON the input; never eat its click */
}
.co-ph-filter-input {
    width: 100%;
    padding: 5px 9px 5px 26px;
    font-family: inherit;
    font-size: var(--fs-2);
    color: var(--text);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    outline: none;
}
.co-ph-filter-input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring); }
.co-ph-filter-input::-webkit-search-cancel-button { cursor: pointer; }

.co-ph-group { display: flex; flex-direction: column; gap: var(--s-1); }
/* A real <button>, so the fold is keyboard-operable without the
   role="button" + tabindex + activateOnKey dance the tree rows need. */
.co-ph-head {
    display: flex;
    align-items: center;
    gap: 6px;
    width: 100%;
    padding: 2px 0;
    font-family: inherit;
    text-align: left;
    background: none;
    border: none;
    cursor: pointer;
}
.co-ph-chev { font-size: 9px; color: var(--hint); width: 8px; flex-shrink: 0; }
.co-ph-count {
    margin-left: auto;
    font-size: 10px;
    font-variant-numeric: tabular-nums;
    color: var(--hint);
    background: var(--line-soft);
    border-radius: 999px;
    padding: 0 6px;
}
.co-ph-chiprow { display: flex; flex-wrap: wrap; gap: 5px; padding-left: 14px; }
/* The conditional-insert switch. Sits under the filter so the two controls that
   change what the palette DOES are together. */
.co-ph-cond {
    display: flex;
    align-items: center;
    gap: 6px;
    margin: 0 0 var(--s-1);
    font-size: 11.5px;
    color: var(--muted);
    cursor: pointer;
}
.co-ph-cond input { accent-color: var(--accent); cursor: pointer; }
/* Dashed, because a guarded token behaves differently from a plain one and the
   chip face alone ({{#fax}}…{{/fax}}) is easy to skim past. */
.co-ph-chip.is-cond { border-style: dashed; border-color: var(--accent-ring); }
.co-ph-group.is-closed .co-ph-chiprow { display: none; }
.co-ph-empty { font-size: var(--fs-2); color: var(--muted); margin: var(--s-2) 0 0; }
/* Which company's fields these are. The palette follows the Save-to company
   picker, and an admin has none of their own — without saying so, an empty
   Zusatzfelder group looks like the feature is missing rather than scoped. */
.co-ph-scope {
    font-size: 10px;
    color: var(--accent-strong);
    background: var(--accent-soft);
    border-radius: 999px;
    padding: 0 6px;
    max-width: 14ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.co-ph-grouphint { font-size: 11px; color: var(--muted); margin: 0 0 2px; padding-left: 14px; line-height: 1.5; }
/* key · label · prefix · link · bin. Its own rule rather than .co-kv-row's two
   columns, because five controls at equal width leaves none of them readable —
   the key and label carry the most text, the prefix is short by nature. */
.co-xf-row {
    display: grid;
    grid-template-columns: minmax(0, 1.3fr) minmax(0, 1.2fr) minmax(0, 0.8fr) minmax(0, 0.9fr) auto;
    gap: var(--s-2);
    align-items: center;
}
@media (max-width: 1024px) {
    /* Five columns stop being readable well before the pane does. */
    .co-xf-row { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto; }
}
.co-ph-jump {
    font-family: inherit;
    font-size: 11px;
    font-weight: 550;
    color: var(--accent-strong);
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    text-decoration: underline;
}
.co-ph-label { font-size: 11.5px; color: var(--muted); }
.co-ph-chip {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 11px;
    color: var(--accent-strong);
    background: var(--accent-soft);
    border: 1px solid transparent;
    border-radius: 6px;
    padding: 2px 7px;
    cursor: pointer;
}
.co-ph-chip:hover { border-color: var(--accent-ring); }
/* A plain chip is selected in the visual editor — clicking one of these now
   replaces it instead of inserting at the cursor; the dashed outline is the
   only cue that mode is active. */
.co-lint { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 10px; }
.co-lint:empty { display: none; }
.co-preview-user { font-size: 12px; padding: 6px 9px; max-width: 280px; }

#co-ov-modal .co-code { width: 100%; margin-top: 12px; }
#co-ov-modal .modal-foot { align-items: center; gap: 10px; flex-wrap: wrap; }

/* The auth gate (v59: the whole app sits behind a login) is defined up in the
   "Login" section — it and the card are one component, and keeping every
   number that has to agree with Keycloak in one place is the point. */

/* ── Role editor: permission checklist ── */
.co-perm-list { display: flex; flex-direction: column; gap: 7px; margin: 14px 0 10px; }
.co-perm {
    display: flex;
    gap: 11px;
    align-items: flex-start;
    padding: 9px 12px;
    border: 1px solid var(--line-soft);
    border-radius: var(--r-md);
    cursor: pointer;
}
.co-perm:hover { border-color: var(--accent-ring); }
.co-perm.locked { opacity: 0.6; cursor: default; }
.co-perm.locked:hover { border-color: var(--line-soft); }
.co-perm input { margin-top: 3px; accent-color: var(--accent); }
.co-perm-main { display: flex; flex-direction: column; gap: 1px; }
.co-perm-key {
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 12px;
    font-weight: 650;
    color: var(--accent-strong);
}
.co-perm-label { font-size: 12px; color: var(--muted); line-height: 1.45; }

/* ── Config tab structured editors (signatures.json / profile.json) ── */
.co-cfg-sig-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 10px;
    padding: 9px 12px;
    border: 1px solid var(--line-soft);
    border-radius: var(--r-md);
    margin-bottom: 7px;
}
.co-cfg-msg-card { margin-bottom: 10px; }

/* ── Signature picker (token input + typeahead) ─────────────────
   The one absolutely-positioned dropdown in the app besides the editor's
   floating toolbar, and unlike that one it anchors to a real element rather than
   to a selection range inside an iframe — so `absolute` inside a relative wrapper
   is enough and it needs no scroll/resize repositioning.

   The clipping ancestor is `main.company-view` (overflow-y: auto), NOT the cards
   between: an `overflow-x: visible` beside an `overflow-y: auto` computes to
   auto, so it scrolls and clips on both axes. That is survivable here precisely
   because nothing OUTSIDE it scrolls, so the dropdown cannot detach from its
   field the way a `fixed` one would. The cost is that an open list near the
   bottom extends that scroll area instead of flipping upward — acceptable while
   the list is capped, and the reason for the cap.

   Deliberately does NOT borrow .format-toolbar's skin: that hard-codes a dark
   palette and ignores the theme tokens. */
.co-picker { position: relative; }
/* Looks like .input, but it is a container holding chips plus the real input. */
.co-picker-control {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    /* Row gap slightly wider than the column gap. A full field wraps to four or
       five rows of chips, and at a uniform 6px the rows read as one block — the
       eye needs the horizontal channel between them more than it needs space
       between two chips sitting side by side. */
    gap: 7px 8px;
    min-height: 38px;
    padding: 7px 8px;
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    background-color: var(--bg);
    cursor: text;
}
.co-picker-control { transition: border-color var(--t-base), box-shadow var(--t-base); }
.co-picker-control:hover { border-color: var(--line-hover); }
/* Focus lives on the inner input, so the ring is drawn on the container via
   :focus-within — otherwise the visible box and the focused element disagree. */
.co-picker-control:focus-within {
    border-color: var(--accent);
    background-color: var(--surface);
    box-shadow: 0 0 0 3px var(--accent-ring);
}
.co-picker-input {
    flex: 1;
    min-width: 120px;
    border: none;
    outline: none;
    background: none;
    color: var(--text);
    font-size: 13px;
    font-family: inherit;
    padding: 2px 0;
}
.co-picker-list {
    position: absolute;
    left: 0;
    right: 0;
    top: calc(100% + 4px);
    z-index: 40;
    max-height: 240px;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 3px;
    padding: 5px;
    background-color: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    box-shadow: var(--shadow-md);
}
.co-picker-list .co-list-row { cursor: pointer; }
.co-picker-empty { padding: 8px 10px; font-size: 12.5px; color: var(--muted); }

/* ── Devices accordion (Config > profile) ───────────────────────
   One row per device, one open at a time. Reuses the tree row so it reads like
   the manifest's signatures fold rather than inventing a second disclosure. */
.co-cfg-device { border: 1px solid var(--line-soft); border-radius: var(--r-md); margin-bottom: 7px; }
.co-cfg-device-head { padding: 8px 10px; }
.co-cfg-device.is-open { border-color: var(--line); background-color: var(--bg); }
.co-cfg-device-body { padding: 0 10px 10px; }
/* The chosen entries. Short by construction — it lists what was picked, never
   the roster — so a plain row per entry stays readable. */
.co-cfg-dev-entry {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 6px 0;
    border-top: 1px solid var(--line-soft);
    font-size: 12.5px;
}
.co-cfg-dev-entry .co-list-main { min-width: 0; }
.co-cfg-dev-file { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; color: var(--muted); }
.co-cfg-dev-rev { font-size: 11px; color: var(--hint); white-space: nowrap; }
.co-cfg-dev-flag { display: flex; align-items: center; gap: 5px; white-space: nowrap; color: var(--muted); }
/* An entry whose signature no longer exists. Dashed, like the picker's orphan
   chip — it must stay visible and removable rather than vanish from a document
   that still names it. */
.co-cfg-dev-entry.is-orphan .co-cfg-dev-file { border-bottom: 1px dashed var(--line-hover); }

/* ── Breathing room in the Config detail pane ───────────────────
   Scoped to this pane on purpose. The shared `.field` rhythm (5px label→control,
   11px between fields) is tuned for short labels with two-word hints, and it
   holds up everywhere it is used. This pane is the exception: its hints are
   explanatory SENTENCES, several of them longer than the label they sit beside,
   and `.field label` is a non-wrapping flex row — so a long hint shrank until it
   wrapped into two lines against the label's baseline and the whole block closed
   up on the control below it. Measured before: three labels rendered 28px tall
   for one line of text.

   Not changed globally: the same rule governs the editor's dense side panel,
   where the hints really are two-word asides and the tight rhythm is correct. */
#co-cfg-detail .field > label {
    flex-wrap: wrap;
    row-gap: 2px;
    margin-bottom: 7px;
}
/* `:not(.co-checks)` is load-bearing. A checkbox row's label holds THREE children
   — the box, its text, and the hint — and space-between spreads all three across
   the full width, leaving the box marooned at the far left from the words it
   belongs to. Only the two-child labels get the swap. */
#co-cfg-detail .field:not(.co-checks) > label {
    /* Replaces the hint's `margin-left: auto`. Same result while both fit on one
       line — label left, hint right — but when the hint wraps it lands at the
       start of its own line instead of being shoved to the right edge. */
    justify-content: space-between;
}
#co-cfg-detail .field:not(.co-checks) > label > .hint {
    margin-left: 0;
    /* The wrap threshold. A flex item shrinks before it wraps, so without a floor
       the hint squeezes into a column of two-word lines rather than moving down. */
    min-width: 22ch;
}
#co-cfg-detail .field { margin-bottom: var(--s-4); }
/* Column gap too, not only row. A hint is right-aligned to its cell edge, so in
   a two-column row it ends 12px from the NEXT column's label — Domain's hint ran
   straight into "Hilfe-URL" with nothing between them. Widening the gutter is
   what separates two columns of label+hint pairs; the rows needed the same. */
#co-cfg-detail .co-form-grid { row-gap: var(--s-4); column-gap: var(--s-5); }

/* .co-sub carries `margin-top: -4px`, which is right for a subtitle tucked under
   a card heading and wrong for both uses below — each sits BELOW the thing it
   describes, so the negative pull put it flush against a control.

   The warning belongs to the field above it, close but not touching. */
#co-cfg-prof-warn.co-sub { margin-top: var(--s-2); }

/* An Options group caption separates the previous group's controls from the next
   group's, so it needs room ABOVE, not less. The first one keeps the tight fit
   because the thing above IT is the "Optionen" label, which is what -4px was for. */
.co-cfg-optgroup { margin: var(--stack) 0 var(--s-2); }
.co-cfg-optgroup:first-child { margin-top: 0; }

/* Chip for a chosen signature. Its own class rather than reskinning the
   overrides-row chip that used to live here: that one made the WHOLE chip the
   remove button, and here only the × is, so sharing the rule meant a chip body
   advertising a click it does not handle. */
.co-picker-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 9px;
    border: 1px solid var(--line);
    border-radius: 999px;
    /* --surface, not --bg. The tray is already --bg, so a chip painted the same
       colour was separated from the field only by its own 1px border — and a full
       field of forty of them read as one grey mass however wide the gap between
       them was. Lifting the chip onto the surface colour is what makes the gap
       visible at all. */
    background-color: var(--surface);
    color: var(--text);
    font-size: 12px;
}
.co-picker-chip-x {
    border: none;
    background: none;
    padding: 0;
    cursor: pointer;
    color: var(--muted);
    font-size: 14px;
    line-height: 1;
}
.co-picker-chip-x:hover { color: var(--danger); }
/* An entry naming a file with no signature row — it exists only in the git
   repository. Dashed, so it reads as "not one of ours" without being an error. */
.co-picker-chip.is-orphan { border-style: dashed; color: var(--muted); }

/* The JSON escape hatch, folded. Mirrors details.adv's chevron treatment rather
   than inventing a second disclosure look. */
.co-cfg-json-fold > summary {
    cursor: pointer;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--muted);
    padding: 4px 0;
    list-style: none;
}
.co-cfg-json-fold > summary::-webkit-details-marker { display: none; }
.co-cfg-json-fold > summary::after {
    content: '\f078';
    font-family: 'Font Awesome 6 Free';
    font-weight: 900;
    font-size: 9px;
    margin-left: 7px;
    display: inline-block;
    transition: transform 0.15s;
}
.co-cfg-json-fold[open] > summary::after { transform: rotate(180deg); }
.co-cfg-json-fold > summary:hover { color: var(--text); }

/* ── Template editor UX pass (v61) ── */
/* Breathing room between the editor card and the preview card — these
   detail wrappers sit INSIDE .co-detail, so its gap doesn't reach them. */
#co-tpl-detail, #co-comp-detail, #co-role-detail {
    display: flex;
    flex-direction: column;
    gap: var(--stack);
}
.co-starter-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin: 2px 0 12px; }
.co-starter-label { font-size: 12px; color: var(--muted); font-weight: 570; }
.co-starter-btn {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    padding: 7px 12px;
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: var(--r-md);
    cursor: pointer;
    font-family: inherit;
    text-align: left;
}
.co-starter-btn:hover { border-color: var(--accent-ring); background: var(--accent-soft); }
.co-starter-btn i { font-size: 15px; color: var(--accent); }
.co-starter-main { display: flex; flex-direction: column; line-height: 1.25; }
.co-starter-main b { font-size: 12px; font-weight: 620; }
.co-starter-main small { font-size: 10.5px; color: var(--hint); }

/* ── Templates tree: companies ▸ departments ▸ templates (v62) ── */
.co-tree-group, .co-tree-tpl { display: flex; align-items: center; gap: 8px; cursor: pointer; }
.co-tree-chev { font-size: 10px; color: var(--hint); width: 10px; flex-shrink: 0; }
.co-tree-icon { font-size: 12px; color: var(--accent); width: 15px; text-align: center; flex-shrink: 0; }
.co-tree-tpl { padding-left: calc(14px + var(--depth, 1) * 20px); }
.co-list-subhead {
    padding: 6px 0 4px calc(14px + var(--depth, 1) * 20px);
    font-size: 10.5px;
    font-weight: 650;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--hint);
}
.co-tree-count {
    font-size: 10.5px;
    color: var(--hint);
    background: var(--bg);
    border: 1px solid var(--line-soft);
    border-radius: 999px;
    padding: 1px 7px;
    flex-shrink: 0;
}
.co-tree-act {
    /* opacity, not visibility: a hidden-via-visibility button can never
       receive keyboard focus in the first place, so Tab would skip straight
       past it — opacity keeps it in the tab order while still invisible
       until hovered/focused.
       …and width 0 while hidden, because an invisible 24px button still takes
       24px: three of them squeezed a company name down to "Must…" in a 300px
       pane. A zero-width button is still focusable, and focusing it expands
       the row's actions (:focus-within below). The negative margin cancels
       the row's own 8px gap, which applies between items of any width. */
    opacity: 0;
    width: 0;
    padding: 0;
    margin-left: -8px;
    overflow: hidden;
    height: 22px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    color: var(--muted);
    background: transparent;
    border: 0 solid transparent;
    border-radius: 6px;
    cursor: pointer;
    flex-shrink: 0;
}
.co-tree-group:hover .co-tree-act, .co-tree-group:focus-within .co-tree-act {
    opacity: 1; width: 24px; margin-left: 0; border-width: 1px;
}
/* No hover on a touch screen: the actions would never appear, and at width 0
   there would be nothing to tap. Always shown there. */
@media (hover: none) {
    .co-tree-act { opacity: 1; width: 24px; margin-left: 0; border-width: 1px; }
}
.co-tree-act:focus-visible { opacity: 1; outline: 2px solid var(--accent-strong); outline-offset: 1px; }
.co-tree-act:hover { color: var(--accent); background: var(--accent-soft); }
/* Still clickable (surfaces a "not configured yet" toast) but visually
   quieter than a fully-active tree action. */
.co-tree-group:hover .co-tree-act-disabled, .co-tree-group:focus-within .co-tree-act-disabled { opacity: 0.4; }
.co-tree-act-disabled:hover { color: var(--muted); background: transparent; }

/* A company or folder row while an .html file hovers over it — the drop target
   for a dragged signature file (wireSigTreeDrop in js/company.js). Same accent
   language as the editor's own dropzone; the extra 1px ring is what makes a
   single-pixel border read as a border at 13px row text.

   Declared AFTER .co-list-row.selected on purpose. Identical specificity, so
   order decides — and the selected row is the one most likely to be aimed at,
   which is exactly the row that must not be the one giving no feedback. */
.co-list-row.co-drop-on {
    border-color: var(--accent);
    background: var(--accent-soft);
    box-shadow: 0 0 0 1px var(--accent);
}

/* The permanent one-line affordance under the Signatures filter. A drag target
   nobody can see is a feature nobody finds. */
.co-drop-hint {
    margin: 2px 0 0;
    font-size: 10.5px;
    line-height: 1.35;
    color: var(--hint);
}
.co-drop-hint i { margin-right: 4px; }

/* ── Login animation (the visual lives in css/login-animation.css +
   js/login-animation.js) — only the theme fix for the ink lives here ── */
body.theme-dark .ink-login-ink { filter: invert(1); }

/* ── v75: account preference row ── */
.co-pref-row { margin-top: 14px; border-top: 1px solid var(--line-soft); padding-top: 12px; }
.co-pref { display: flex; gap: 9px; align-items: flex-start; font-size: 12.5px; cursor: pointer; }
.co-pref input { margin-top: 2px; accent-color: var(--accent); }

/* ── v75: CSV import + audit tables ── */
#co-imp-company { max-width: 160px; }
.co-imp-table-wrap { overflow: auto; max-height: 420px; border: 1px solid var(--line-soft); border-radius: var(--r-md); }
.co-imp-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.co-imp-table th, .co-imp-table td { text-align: left; padding: 7px 11px; border-bottom: 1px solid var(--line-soft); }
.co-imp-table th { position: sticky; top: 0; background: var(--bg); font-size: 11.5px; color: var(--muted); }
.co-imp-table tr.co-imp-err td { color: var(--danger); }
.co-imp-table tr.co-imp-warn-row td { color: var(--warn); }
.co-imp-warn { color: var(--warn); font-weight: 600; }
.co-code-cell { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.co-audit-ts { white-space: nowrap; color: var(--muted); }
.co-audit-detail { color: var(--muted); }
#co-audit-q { max-width: 260px; }

/* ── v75: template version history ── */
#co-tpl-history { margin: 4px 0 14px; }
.co-history-row { display: flex; align-items: center; gap: 8px; }
.co-history-row .co-list-main { flex: 1; }

/* ── Reduced motion ──────────────────────────────────────────────────────
   21 animation/transition declarations and no honouring of the OS setting.
   This is the standard blanket override: near-zero rather than 0s, because
   some code waits on transitionend/animationend and a true 0s duration can
   skip the event entirely. The login animation already respects the setting
   separately (js/company.js checks it before playing). */
@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}

/* ── Locked profile fields ────────────────────────────────────────────────
   A company can freeze branding-bearing profile fields against self-service
   (companies.locked_profile_fields). The server silently drops those keys on
   PUT /api/me, so without a visible affordance the employee typed a value,
   saw "Profile saved", and nothing changed. */
.co-field-locked .input { background-color: var(--line-soft); color: var(--muted); cursor: not-allowed; }
.co-locked-note { display: flex; align-items: center; gap: var(--s-1); margin-top: 3px; }
.co-locked-note i { color: var(--muted); }
.co-empty-card.co-loading i { color: var(--muted); }

/* ── askConfirm() — shares #co-prompt-modal with askText() ────────────────
   The scrollable list exists because native confirm() could not show one: the
   leaver-reconciliation dialog truncated the people about to be deactivated
   to 15 entries in a box that could be neither scrolled nor copied. */
.co-prompt-list {
    max-height: 220px;
    overflow-y: auto;
    border: 1px solid var(--line);
    border-radius: var(--r-sm);
    padding: var(--s-2);
    background: var(--bg);
    font-size: var(--fs-2);
    display: flex;
    flex-direction: column;
    gap: 3px;
}
.btn-danger { background-color: var(--danger); color: #fff; }
.btn-danger:hover:not(:disabled) { filter: brightness(0.92); }
.co-selectall { display: flex; align-items: center; gap: var(--s-1); font-size: var(--fs-2); color: var(--muted); white-space: nowrap; cursor: pointer; }
.co-filter-hint { flex-basis: 100%; margin: 2px 0 0; }

/* ═════════════════════════════════════════════════════════════════════════
   FOCUS — one ring, everywhere
   ═════════════════════════════════════════════════════════════════════════
   There were FOUR :focus-visible rules in the whole sheet, and seven elements
   set `outline: none`. Four of those replaced it with nothing at all, so
   tabbing into the export code box, the import box or the CSV box produced no
   visual change whatsoever — the control was simply invisible to a keyboard
   user. Twenty more interactive elements had no focus style either.

   One rule, one ring. Previously the colour varied (--accent vs
   --accent-strong) and the offset was variously 2px, -2px and 1px. */
.btn:focus-visible,
.close-btn:focus-visible,
.modal-tab:focus-visible,
.subnav-tab:focus-visible,
.co-tab:focus-visible,
.seg button:focus-visible,
.template-option:focus-visible,
.social-btn:focus-visible,
.sg-btn:focus-visible,
.el-toggle:focus-visible,
.row-actions button:focus-visible,
.format-toolbar button:focus-visible,
.co-ph-chip:focus-visible,
.co-ph-head:focus-visible,
.co-starter-btn:focus-visible,
.co-login-alt:focus-visible,
.co-tree-act:focus-visible,
.hint-editable:focus-visible,
.dropzone:focus-visible,
.color-row:focus-visible,
.imported-img-row:focus-visible,
.co-selectall:focus-within {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* Same ring, turned INWARD. Two reasons land here.
   The controls that suppressed the outline and put nothing in its place sit
   flush against their own borders, so an outset ring would straddle them.
   The two ROW selectors are here for a stronger reason: .co-list and
   .bulk-table-wrap both clip their overflow, so an outset ring on the first or
   last row is cut off by the container edge. .co-list-row had precisely this
   rule until the group above re-declared it outset — same selector, same
   property, later in the file, so the inset original silently lost and every
   list in Firma and Admin got a clipped ring.
   check_css_shadowing.py cannot catch that: it compares a media query against
   a later base rule, not two base rules against each other. */
.co-list-row:focus-visible,
.bulk-table.clickable tbody tr:focus-visible,
.ft-size input:focus-visible,
.hint-input:focus-visible,
#code-output:focus-visible,
#import-code:focus-visible,
#bulk-csv:focus-visible,
.ft-select:focus-visible,
.ft-link-edit input:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

/* ═════════════════════════════════════════════════════════════════════════
   MOTION
   ═════════════════════════════════════════════════════════════════════════
   Sixteen elements transitioned hover at EIGHT different durations, and ten
   more had hover styling with no transition at all — every one of those ten
   in the company portal, because the whole .co-* layer was written without
   them while the editor layer has them. */
.co-tab,
.co-list-row,
.co-perm,
.co-ph-chip,
.co-starter-btn,
.co-tree-act,
.hint-editable {
    transition: background-color var(--t-fast) ease,
                border-color var(--t-fast) ease,
                color var(--t-fast) ease;
}

/* ═════════════════════════════════════════════════════════════════════════
   RESPONSIVE — down to 768px
   ═════════════════════════════════════════════════════════════════════════ */
@media (max-width: 1024px) {
    /* Two inputs side by side had no wrap and stayed side by side to 320px. */
    .field-row, .co-form-grid, .bulk-base-grid, .bulk-map-grid { flex-wrap: wrap; }
    .co-form-grid, .bulk-base-grid, .bulk-map-grid { grid-template-columns: 1fr; }
    /* The three-tab export modal overflowed .modal-card's max-width. */
    .modal-tabs { flex-wrap: wrap; }
}

@media (max-width: 768px) {
    /* The workspace nav clipped the Admin tab with no scroll affordance. The
       portal bars wrap here so a long tab strip takes a second line instead of
       overflowing. (Sign out used to be what got orphaned; it lives in the top
       bar now.) */
    .subnav { overflow-x: auto; flex-wrap: nowrap; -webkit-overflow-scrolling: touch; }
    .subnav::-webkit-scrollbar { height: 0; }
    .topbar, .co-bar { flex-wrap: wrap; row-gap: var(--s-2); }
    /* #toast-container right:22px + .toast max-width:380px exceeded a 375px
       viewport, so toasts were clipped on the phone that most needs them. */
    #toast-container { right: var(--s-2); left: var(--s-2); }
    .toast { max-width: none; }
    .bulk-layout, .company-view { padding: var(--s-4) var(--s-3) var(--s-6); }
    .modal-card { padding: var(--s-4) var(--s-3); }
}

/* ── Customer onboarding readiness (Admin > Overview) ─────────────────────
   The eight-step journey spans four tabs with nothing tracking it. */
.co-onboard { border-top: 1px solid var(--line-soft); padding-top: var(--s-2); margin-top: var(--s-2); }
.co-onboard-head { display: flex; align-items: baseline; gap: var(--s-2); justify-content: space-between; }
.co-onboard-steps { list-style: none; margin: var(--s-2) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--s-1); }
.co-onboard-steps li { display: flex; align-items: center; gap: var(--s-2); font-size: var(--fs-3); }
.co-onboard-steps li > span { flex: 1; }
.co-onboard-steps li i { color: var(--hint); width: 14px; text-align: center; }
.co-onboard-steps li.is-done { color: var(--muted); }
.co-onboard-steps li.is-done i { color: var(--ok); }
.co-onboard-steps li.is-failed i { color: var(--danger); }

/* Save-time config warnings, one per line. They stay on screen — a 7-second
   toast is not where "this edit will not reach any Mac" belongs. */
#co-cfg-warn.co-sub { margin-top: var(--s-2); display: flex; flex-direction: column; gap: 4px; }
#co-cfg-warn .co-warn-line { display: flex; gap: 6px; align-items: baseline; }
