/* Porting Info Tool -- a dense working surface, not a marketing page.
   The achievement table is where people live, so it gets the space. */

:root {
    /* Tells the browser which palette native controls (select popups, the
       date-input calendar, checkboxes, scrollbars) should render in --
       without it they ignore this file's own dark colors entirely and fall
       back to the OS's light theme, which is why an unstyled <select> or
       <input type="date"> could look like a stray white box on a dark page.
       Kept in sync with the same data-theme-mode/-family combinations as
       the color variables below. */
    color-scheme: light;

    --paper: #eef1f3;
    --surface: #fafbfc;
    --surface-recessed: #e4e8eb;
    --ink: #131a20;
    --ink-body: #4a565f;
    --ink-muted: #78868f;
    --border: #cbd3d8;
    --border-soft: #dce2e6;
    --accent: #0b6c74;
    /* Text/link-safe accent -- distinct from --accent because GC Red isn't
       readable as text on a dark background (see the gc-dark block below).
       The simple theme's teal has no such problem, so it just forwards. */
    --accent-text: var(--accent);
    --accent-soft: #d6e7e8;
    --warn: #97500b;
    --warn-soft: #f2e3d0;
    --danger: #9b2a2c;
    --danger-soft: #f2d9d9;
    --ok: #2a6440;
    --ok-soft: #d8e7dd;

    --ui: "Segoe UI", system-ui, -apple-system, sans-serif;
    --mono: "Cascadia Mono", "Consolas", ui-monospace, monospace;
    --font-display: var(--ui);
    --font-body: var(--ui);

    /* Solid-fill controls (button.primary and friends): the accent color
       itself doesn't always give enough contrast against readable text
       (GC Red against white/paper is under 4:1), so filled controls get
       their own bg/text pair. Simple themes just forward to accent/paper;
       GC overrides both to a deeper red + white below. */
    --fill-strong: var(--accent);
    --fill-ink: var(--paper);
}

/* Two independent axes, both stamped on <html> as attributes:
   data-theme-family: "simple" (default) | "gc"
   data-theme-mode:   "light" (default, or OS preference) | "dark"

   :root above is the simple/light base. Each block below overrides just
   the combination it owns; when an axis has no explicit choice yet it
   falls back to the OS color-scheme preference. */

@media (prefers-color-scheme: dark) {
    :root:not([data-theme-mode="light"]):not([data-theme-family="gc"]) {
        color-scheme: dark;
        --paper: #10161a;
        --surface: #1c262c;
        --surface-recessed: #0b1013;
        --ink: #e2e8eb;
        --ink-body: #a5b2b9;
        --ink-muted: #78868f;
        --border: #3a4850;
        --border-soft: #212c32;
        --accent: #4fc3c9;
        --accent-soft: #12333a;
        --warn: #e2a15c;
        --warn-soft: #332514;
        --danger: #e28184;
        --danger-soft: #351b1d;
        --ok: #79c293;
        --ok-soft: #16301f;
    }
}

:root[data-theme-family="gc"]:not([data-theme-mode]) ,
:root[data-theme-family="gc"][data-theme-mode="light"] {
    /* GC Light -- canonical token values from ../ToolsTheme/GC-THEME-SPEC.md.
       GC Studios Business Identity manual palette on its Almost White /
       White surfaces, GC Red accent, Russo One display / Inter body. */
    color-scheme: light;
    --paper: #f4f4f4;
    --surface: #ffffff;
    --surface-recessed: #e8e8e8;
    --ink: #000000;
    --ink-body: #333333;
    --ink-muted: #707070;
    --border: #d6d6d6;
    --border-soft: #e2e2e2;
    --accent: #ff003e;
    --accent-text: var(--accent);
    --accent-soft: #ffdce3;
    --warn: #97500b;
    --warn-soft: #f2e3d0;
    /* Own hue, not derived from GC Red -- see the spec's Contrast rules. */
    --danger: #c0392b;
    --danger-soft: #f6dad6;
    --ok: #2a6440;
    --ok-soft: #d8e7dd;

    --font-display: "Russo One", var(--ui);
    --font-body: "Inter", var(--ui);
    --fill-strong: #c4002f;
    --fill-ink: #ffffff;
}

:root[data-theme-mode="dark"]:not([data-theme-family="gc"]) {
    color-scheme: dark;
    --paper: #10161a;
    --surface: #1c262c;
    --surface-recessed: #0b1013;
    --ink: #e2e8eb;
    --ink-body: #a5b2b9;
    --ink-muted: #78868f;
    --border: #3a4850;
    --border-soft: #212c32;
    --accent: #4fc3c9;
    --accent-soft: #12333a;
    --warn: #e2a15c;
    --warn-soft: #332514;
    --danger: #e28184;
    --danger-soft: #351b1d;
    --ok: #79c293;
    --ok-soft: #16301f;
}

:root[data-theme-family="gc"][data-theme-mode="dark"] {
    /* GC Dark -- same manual palette, GC Red accent on the studio's dark
       gray surface instead of its light one. */
    color-scheme: dark;
    --paper: #1e1e1c;
    --surface: #383836;
    --surface-recessed: #141412;
    --ink: #ffffff;
    --ink-body: #e0e0e0;
    --ink-muted: #a5a5a5;
    --border: #484846;
    --border-soft: #3a3a38;
    --accent: #ff003e;
    /* GC Red text/links on this dark ground fall under 4.5:1 -- lightened
       per the spec's Contrast rules. Raw --accent stays for borders/icons. */
    --accent-text: #ff4d73;
    --accent-soft: #451420;
    --warn: #e2a15c;
    --warn-soft: #3a2a16;
    /* Own hue, not derived from GC Red -- see the spec's Contrast rules. */
    --danger: #e2717a;
    --danger-soft: #3d2020;
    --ok: #79c293;
    --ok-soft: #1c3324;

    --font-display: "Russo One", var(--ui);
    --font-body: "Inter", var(--ui);
    --fill-strong: #c4002f;
    --fill-ink: #ffffff;
}

@media (prefers-color-scheme: dark) {
    :root[data-theme-family="gc"]:not([data-theme-mode="light"]) {
        color-scheme: dark;
        --paper: #1e1e1c;
        --surface: #383836;
        --surface-recessed: #141412;
        --ink: #ffffff;
        --ink-body: #e0e0e0;
        --ink-muted: #a5a5a5;
        --border: #484846;
        --border-soft: #3a3a38;
        --accent: #ff003e;
        --accent-text: #ff4d73;
        --accent-soft: #451420;
        --warn: #e2a15c;
        --warn-soft: #3a2a16;
        --danger: #e2717a;
        --danger-soft: #3d2020;
        --ok: #79c293;
        --ok-soft: #1c3324;

        --font-display: "Russo One", var(--ui);
        --font-body: "Inter", var(--ui);
        --fill-strong: #c4002f;
        --fill-ink: #ffffff;
    }
}

* { box-sizing: border-box; }

body {
    margin: 0;
    background: var(--paper);
    color: var(--ink);
    font-family: var(--font-body);
    font-size: 15px;
    line-height: 1.5;
}

main { padding: 1.5rem 2rem 4rem; max-width: 1500px; margin: 0 auto; }

/* A <dialog>'s own ::backdrop blocks clicks on the page behind it, but not
   the mouse wheel/trackpad -- without this, scrolling over the backdrop
   scrolls the page underneath the modal instead of doing nothing. */
body:has(dialog[open]) { overflow: hidden; }

/* ------------------------------------------------------------- topbar */

.topbar {
    display: flex;
    align-items: center;
    gap: 1.5rem;
    padding: 0.7rem 2rem;
    /* Stays put while a long achievement table or checklist scrolls
       underneath it -- sign-out, theme toggle and the Platforms/Achievements
       nav shouldn't require scrolling back to the top to reach. */
    position: sticky;
    top: 0;
    z-index: 10;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
}
/* Official isologo, header mark next to the product title. Shown under
   both families -- see THEME.md's "Logo shown under simple too" deviation
   from the spec's own default (hide under non-gc). Light/dark variant
   picked by mode so the wordmark's own coloring stays readable against the
   surface behind it. Never hand-redrawn; see the spec's Logo policy. */
.brand-logo { display: none; height: 28px; width: auto; }
.brand-logo.for-light { display: block; }
:root[data-theme-mode="dark"] .brand-logo.for-light { display: none; }
:root[data-theme-mode="dark"] .brand-logo.for-dark { display: block; }
@media (prefers-color-scheme: dark) {
    :root:not([data-theme-mode="light"]) .brand-logo.for-light { display: none; }
    :root:not([data-theme-mode="light"]) .brand-logo.for-dark { display: block; }
}
.brand {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    font-family: var(--font-display);
    font-weight: 700;
    letter-spacing: -0.02em;
    text-decoration: none;
    color: var(--ink);
}
.brand span { color: var(--accent-text); }
.topbar-project {
    font-family: var(--font-display);
    font-size: 1rem;
    letter-spacing: -0.01em;
    color: var(--ink);
    border-left: 1px solid var(--border);
    padding-left: 1.2rem;
}
/* Nav + the right-hand account cluster share one flex row (.topbar-menu) so
   they can be collapsed into a single dropdown together below the hamburger
   breakpoint, rather than each needing its own show/hide logic. */
.topbar-menu { display: flex; align-items: center; flex: 1; min-width: 0; }
.topbar nav { display: flex; gap: 1rem; align-items: center; }
.topbar nav a { color: var(--ink-body); text-decoration: none; font-size: 0.9rem; }
.topbar nav a:hover { color: var(--accent-text); }
.topbar nav a.nav-highlight {
    font-weight: 700;
    color: #fff;
    background: var(--accent);
    padding: 0.35rem 0.75rem;
    border-radius: 3px;
}
.topbar nav a.nav-highlight:hover { color: #fff; opacity: 0.9; }
.topbar nav .nav-sep { width: 1px; height: 1.1rem; background: var(--border); }
.topbar-right { margin-left: auto; display: flex; align-items: center; gap: 0.8rem; }
.who { font-size: 0.85rem; color: var(--ink-muted); text-decoration: none; }
.who:hover { color: var(--accent-text); }

/* Hidden until nav-toggle.js measures that the topbar no longer fits brand +
   project name + nav + account cluster on one line -- see the
   .topbar.nav-collapsed rules below. */
.nav-toggle { display: none; margin-left: auto; font-size: 1.1rem; line-height: 1; }

/* Applied by nav-toggle.js, not a CSS breakpoint -- how many nav links and
   account-cluster items fit on one line depends on the project name's
   length and how many links a given user's role shows, not just viewport
   width, so a fixed max-width guessed at nav's current item count would
   go stale (and undershoot) the next time a link is added. "Sign out" was
   the first thing to wrap, onto two lines inside its own button, with
   everything else silently squeezed against it, or cropped off past the
   edge of the window entirely. Collapsing both into one dropdown behind a
   hamburger button, instead of trying to keep shrinking them, is what
   actually fixes that rather than buying a bit more room. */
.topbar.nav-collapsed .topbar-project {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.topbar.nav-collapsed .nav-toggle { display: inline-flex; }
.topbar.nav-collapsed .topbar-menu {
    display: none;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0.9rem;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    padding: 0.8rem 1.2rem 1.1rem;
    box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
}
.topbar.nav-collapsed .topbar-menu.open { display: flex; }
.topbar.nav-collapsed nav { flex-direction: column; align-items: flex-start; gap: 0.7rem; }
.topbar.nav-collapsed nav a.nav-highlight { padding: 0.4rem 0.75rem; }
.topbar.nav-collapsed nav .nav-sep { width: 100%; height: 1px; }
.topbar.nav-collapsed .topbar-right { margin-left: 0; flex-wrap: wrap; }
.stack-form { display: grid; gap: 0.9rem; max-width: 420px; }
.stack-form label { display: block; font-size: 0.85rem; color: var(--ink-body); }
.stack-form input { margin-top: 0.25rem; }
.inline { display: inline; }

/* -------------------------------------------------------------- basics */

h1 { font-family: var(--font-display); font-size: 1.6rem; margin: 0; letter-spacing: -0.02em; }
h2 { font-size: 1.1rem; margin: 0; letter-spacing: -0.01em; }
.eyebrow {
    margin: 0 0 0.15rem;
    font-family: var(--mono);
    font-size: 0.68rem;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--accent-text);
}
.lede { color: var(--ink-body); max-width: 60ch; }
.hint { color: var(--ink-muted); font-size: 0.85rem; max-width: 75ch; }
.mono { font-family: var(--mono); }
.num { font-variant-numeric: tabular-nums; text-align: right; }

.page-head {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 1rem;
    padding-bottom: 1.2rem;
    margin-bottom: 1.4rem;
    border-bottom: 1px solid var(--border);
}

.page-head-actions {
    display: flex;
    align-items: center;
    gap: 0.6rem;
}

.dropdown { position: relative; }
.dropdown > summary { list-style: none; }
.dropdown > summary::-webkit-details-marker { display: none; }
.dropdown > summary::after { content: ' \25BE'; }
.dropdown[open] > summary { color: var(--ink); background: var(--surface-recessed); }
.dropdown-menu {
    position: absolute;
    top: calc(100% + 0.25rem);
    left: 0;
    z-index: 20;
    display: flex;
    flex-direction: column;
    min-width: max-content;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 3px;
    box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
    padding: 0.3rem;
}
.dropdown-menu a {
    padding: 0.35rem 0.6rem;
    border-radius: 3px;
    color: var(--ink-body);
    text-decoration: none;
    font-size: 0.88rem;
    white-space: nowrap;
}
.dropdown-menu a:hover { color: var(--ink); background: var(--surface-recessed); }

button, .button {
    font: inherit;
    font-size: 0.88rem;
    padding: 0.4rem 0.85rem;
    border-radius: 3px;
    border: 1px solid var(--border);
    background: var(--surface);
    color: var(--ink);
    cursor: pointer;
    text-decoration: none;
    display: inline-block;
}
button.primary, .button.primary {
    background: var(--fill-strong);
    border-color: var(--fill-strong);
    color: var(--fill-ink);
    font-weight: 600;
}
.ghost { background: transparent; border-color: transparent; color: var(--ink-body); }
.ghost:hover { color: var(--ink); background: var(--surface-recessed); }
.icon-toggle { padding: 0.4rem 0.55rem; line-height: 1; font-size: 1rem; }
.danger { color: var(--danger); }

input[type="text"], input[type="email"], input[type="password"], input[type="number"], input[type="search"], input[type="date"], select, textarea {
    font: inherit;
    font-size: 0.88rem;
    padding: 0.35rem 0.5rem;
    border: 1px solid var(--border);
    border-radius: 3px;
    background: var(--surface);
    color: var(--ink);
    width: 100%;
}
textarea { resize: vertical; }

/* A plain <input type="file"> otherwise renders as a raw, unstyled OS
   control (light-themed regardless of this app's own dark palette) --
   `::file-selector-button` is the one part of it every current browser
   lets an author actually restyle; the native file-picker dialog itself
   is untouchable and not worth chasing. */
input[type="file"] { color: var(--ink-body); font-size: 0.85rem; }
input[type="file"]::file-selector-button {
    font: inherit; font-size: 0.82rem; padding: 0.3rem 0.7rem; margin-right: 0.6rem;
    border: 1px solid var(--border); border-radius: 3px;
    background: var(--surface-recessed); color: var(--ink); cursor: pointer;
}
input[type="file"]::file-selector-button:hover { border-color: var(--accent); }
input:focus-visible, select:focus-visible, textarea:focus-visible, button:focus-visible, a:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}
input.invalid { border-color: var(--danger); background: var(--danger-soft); }
small { display: block; color: var(--ink-muted); font-size: 0.78rem; margin-top: 0.2rem; }

a { color: var(--accent-text); }

/* --------------------------------------------------------------- table */

/* overflow-y: auto (rather than the default visible) is what makes this
   element itself a scroll container -- position: sticky headers inside it
   (table.grid thead below) stick relative to whichever scroll container is
   nearest, so without this they'd have none and just scroll away with the
   page. Harmless on a short table: it never grows tall enough to need its
   own scrollbar unless something (achievements.js, for the achievements
   table) also caps max-height. */
.table-scroll { overflow-x: auto; overflow-y: auto; border: 1px solid var(--border); border-radius: 4px; }

table.grid {
    border-collapse: collapse;
    /* min-width, not width: 100% -- with table-layout: fixed, a hard 100%
       width forces every <col>'s explicit rem width to be squeezed
       proportionally to fit the container instead of the table growing
       wider and letting .table-scroll's overflow-x actually scroll. That
       squeeze is what was clipping the delete column's "x" once enough
       platform columns (Epic/Android/iOS added to the original six) no
       longer fit the viewport at their real widths. min-width still fills
       the container when there are few columns; it just stops being a cap. */
    width: auto;
    min-width: 100%;
    background: var(--surface);
    font-size: 0.87rem;
    /* Fixed layout: column widths come from the <colgroup> below, not from
       the widest word in a header ("Gamerscore" over a 3-digit value was
       what stretched that column). Description gets the room that frees up. */
    table-layout: fixed;
}
#achievements-table .c-handle { width: 1.6rem; }
#achievements-table .c-order { width: 2.6rem; }
#achievements-table .c-key { width: 9rem; }
#achievements-table .c-name { width: 11rem; }
#achievements-table .c-desc { width: 16rem; }
#achievements-table .c-value { width: 4.5rem; }
/* "Platinum" is the longest grade option (8 letters) -- 4.5rem was sized for
   a 3-4 digit Gamerscore number, not a word, and was clipping the select's
   own closed-state text ("Pla", "Sil", "Go...") even though the opened
   dropdown itself always showed the full option list. */
#achievements-table .c-value-grade { width: 6rem; }
#achievements-table .c-value-mirror { width: 8rem; }
#achievements-table .c-art { width: 5rem; }
/* Wide enough for the uppercase, letter-spaced "HIDDEN" header text to fit
   on one line -- narrower than that overflowed straight into the header
   next to it once this column moved out from being the table's last one,
   where the same overflow just spilled into empty space nobody noticed. */
#achievements-table .c-hidden { width: 3.5rem; }
#achievements-table .c-delete { width: 1.8rem; }
table.grid th, table.grid td {
    padding: 0.3rem 0.5rem;
    border-bottom: 1px solid var(--border-soft);
    text-align: left;
    vertical-align: middle;
}
table.grid thead {
    /* .table-scroll (overflow-y: auto, above) is this thead's nearest scroll
       container, so "top: 0" sticks it to the top of *that* box's own
       scrollport -- not the viewport -- as it's scrolled internally. */
    position: sticky;
    top: 0;
    z-index: 2;
}
table.grid thead th {
    background: var(--surface-recessed);
    font-size: 0.72rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-body);
    border-bottom: 1px solid var(--border);
    white-space: nowrap;
}
/* Rows whose cells vary a lot in height (e.g. the users table, where the
   "Projects" cell stacks a variable number of membership rows) leave the
   plain border-bottom looking disconnected from the vertically-centered
   text above it -- there's a visible line, but it sits far below what a
   reader's eye is tracking. Top-aligning those cells and alternating the
   row background gives a boundary that reads at a glance regardless of
   how tall any one row gets. */
table.grid.compact td { vertical-align: top; }
table.grid tbody tr:nth-child(even) { background: var(--surface-recessed); }
table.grid tbody tr { border-bottom: 1px solid var(--border); }

table.grid thead tr.subhead th {
    text-transform: none;
    letter-spacing: 0;
    font-size: 0.68rem;
    padding-top: 0;
    padding-bottom: 0.25rem;
    /* Narrow value columns (e.g. "Gamerscore" over a 3-digit number) wrap to
       two lines instead of forcing the column wide enough for one. */
    white-space: normal;
    line-height: 1.15;
    text-align: center;
}
.platform-col {
    text-align: center !important;
    border-left: 1px solid var(--border);
    white-space: normal !important;
    line-height: 1.2;
}
.region { color: var(--ink-muted); font-weight: 400; }
.col-handle { width: 1.5rem; }
.col-order { width: 2.5rem; color: var(--ink-muted); font-family: var(--mono); }
.col-hidden { width: 3.5rem; text-align: center; padding-left: 0.25rem; padding-right: 0.25rem; }
.handle { cursor: grab; color: var(--ink-muted); user-select: none; }
.pin { color: var(--accent-text); user-select: none; cursor: default; }
tr[data-pinned="platinum"] { background: var(--accent-soft); }
tr[data-pinned="platinum"] td { border-bottom-color: var(--accent); }

.art-cell { text-align: center; padding: 0.2rem !important; }
.art-upload {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: 60px;
    aspect-ratio: var(--art-ratio, 1);
    border-radius: 3px;
    border: 1px dashed var(--border);
    cursor: pointer;
    overflow: hidden;
    background: var(--surface-recessed);
}
.art-upload:hover { border-color: var(--accent); }
.art-upload.uploading { opacity: 0.5; pointer-events: none; }
.art-upload.mirrored { opacity: 0.6; cursor: default; border-style: solid; }
.art-thumb { width: 100%; height: 100%; object-fit: cover; display: block; }
.art-placeholder { color: var(--ink-muted); font-size: 1rem; line-height: 1; }

.mirror-cell { text-align: center; }
.mirror-toggle {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.2rem;
    font-size: 0.72rem;
    color: var(--ink-body);
    cursor: pointer;
    line-height: 1.15;
}
.mirror-toggle input[disabled] { cursor: not-allowed; }

.unlocked-desc-cell { display: flex; flex-direction: column; gap: 0.2rem; }
.reuse-toggle {
    display: flex;
    align-items: center;
    gap: 0.3rem;
    font-size: 0.72rem;
    color: var(--ink-muted);
    cursor: pointer;
}
.reuse-toggle input[disabled] { cursor: not-allowed; }

#achievements-table .c-translate { width: 2.4rem; }
.col-translate { text-align: center; }
.translate-toggle {
    position: relative;
    padding: 0.15rem 0.35rem;
    font-size: 0.85rem;
    line-height: 1;
}
.translate-count {
    position: absolute;
    top: -0.35rem;
    right: -0.35rem;
    background: var(--accent);
    color: var(--fill-ink);
    border-radius: 999px;
    font-size: 0.6rem;
    line-height: 1;
    padding: 0.12rem 0.32rem;
}
.translations-row td { padding: 0.6rem 1rem; background: var(--surface-recessed); }
.translations-panel { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
.translations-panel th {
    text-align: left;
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--ink-muted);
    padding: 0 0.5rem 0.3rem;
}
.translations-panel td { padding: 0.25rem 0.5rem; vertical-align: top; }
.translations-panel .locale-code { color: var(--ink-muted); }
.translations-panel input, .translations-panel textarea { width: 100%; }

.asset-slots-section { padding: 0 1rem 1rem; border-top: 1px solid var(--border-soft); margin-top: 0.4rem; }
.asset-slots-section .group-label { margin-top: 0.9rem; }
.asset-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 0.8rem;
    margin-top: 0.9rem;
}
.asset-slot {
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 0.6rem;
    display: grid;
    gap: 0.4rem;
    background: var(--surface);
}
.asset-slot .slot-label { font-size: 0.8rem; font-weight: 600; }
.asset-slot .slot-spec { font-size: 0.7rem; color: var(--ink-muted); font-family: var(--mono); }
.asset-preview {
    width: 100%;
    aspect-ratio: 1;
    border-radius: 3px;
    background: var(--surface-recessed);
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    cursor: pointer;
    border: 1px dashed var(--border);
}
.asset-preview:hover { border-color: var(--accent); }
.asset-preview img { width: 100%; height: 100%; object-fit: contain; }
.asset-preview .placeholder { color: var(--ink-muted); font-size: 1.4rem; }
.asset-slot.uploading .asset-preview { opacity: 0.5; }
.asset-slot .slot-status { font-size: 0.72rem; color: var(--ink-muted); display: grid; gap: 0.15rem; }
.asset-slot .slot-status.current { color: var(--ok); }
.slot-filename {
    color: var(--ink-body);
    font-family: var(--mono);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    display: block;
    max-width: 100%;
}
td .cell { border-color: transparent; background: transparent; }
td .cell:hover:not([readonly]) { border-color: var(--border); }
td .cell:focus { background: var(--surface); border-color: var(--accent); }
/* The description textarea's `rows="2"` sets its intrinsic height, but a
   table cell with vertical-align: middle can still let it shrink below that
   -- min-height pins it to two lines no matter what the row otherwise sizes to. */
textarea.cell { min-height: 2.7rem; max-height: 6.5rem; line-height: 1.3; overflow-y: auto; }
.narrow { max-width: 7rem; }
/* The Gamerscore column (.c-value, 4.5rem) is too narrow for a 3-4 digit
   number AND the browser's native up/down spinner -- the spinner was
   overlapping the last digit rather than the column being genuinely too
   small. Points are typed or pasted, never incremented one at a time, so
   the spinner earns nothing back for the width it costs. */
input[type="number"].cell::-webkit-inner-spin-button,
input[type="number"].cell::-webkit-outer-spin-button {
    -webkit-appearance: none;
    margin: 0;
}
input[type="number"].cell { -moz-appearance: textfield; }
.key { color: var(--ink-body); white-space: nowrap; }
.key { color: var(--ink-body); white-space: nowrap; }

tr.dragging { opacity: 0.4; }
tr.saved td { background: var(--ok-soft); transition: background 0.6s; }
tr.save-failed td { background: var(--danger-soft); }

tfoot .totals td {
    background: var(--surface-recessed);
    font-weight: 600;
    border-top: 1px solid var(--border);
    border-bottom: none;
}
.total-cell { text-align: center; font-family: var(--mono); font-variant-numeric: tabular-nums; }
.total-cell.bad { color: var(--danger); background: var(--danger-soft); }

tr.rule-captions td { background: var(--surface-recessed); border-bottom: none; padding-top: 0; }
.caption-cell {
    text-align: center;
    font-size: 0.7rem;
    color: var(--ink-muted);
    font-family: var(--mono);
}
.caption-cell span { display: block; }
.caption-cell span + span { margin-top: 0.1rem; }

table.compact td, table.compact th { padding: 0.25rem 0.5rem; }

/* ------------------------------------------------------------ findings */

.findings { display: grid; gap: 0.3rem; margin-bottom: 1rem; }

.art-sub {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.3rem;
    padding-bottom: 0.4rem !important;
}
.art-sub-label { line-height: 1; }
.bulk-art-trigger {
    display: inline-flex;
    align-items: center;
    gap: 0.2rem;
    padding: 0.15rem 0.4rem;
    border-radius: 3px;
    border: 1px solid var(--accent);
    background: var(--accent-soft);
    color: var(--accent-text);
    cursor: pointer;
    font-size: 0.68rem;
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: none;
    line-height: 1.3;
    transition: transform 0.1s ease, background 0.1s ease;
}
.bulk-art-icon { font-size: 0.85rem; line-height: 1; }
.bulk-art-trigger:hover {
    background: var(--fill-strong);
    color: var(--fill-ink);
    transform: translateY(-1px);
}
.bulk-art-trigger.uploading { opacity: 0.5; pointer-events: none; transform: none; }

.bulk-summary {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 0.8rem 1rem;
    margin-bottom: 1rem;
    font-size: 0.85rem;
    display: grid;
    gap: 0.25rem;
}
.bulk-summary-heading { margin: 0 0 0.3rem; font-weight: 600; }
.bulk-summary-row { margin: 0; font-family: var(--mono); font-size: 0.8rem; }
.bulk-summary-row.ok { color: var(--ok); }
.bulk-summary-row.error { color: var(--danger); }
.bulk-summary-row.warning { color: var(--warn); }
.bulk-summary button { margin-top: 0.4rem; }
.finding {
    margin: 0;
    padding: 0.45rem 0.7rem;
    border-radius: 3px;
    font-size: 0.87rem;
    border-left: 3px solid var(--border);
    background: var(--surface);
}
.finding.error { border-left-color: var(--danger); background: var(--danger-soft); color: var(--danger); }
.finding.warning { border-left-color: var(--warn); background: var(--warn-soft); color: var(--warn); }
.finding.info { border-left-color: var(--ink-muted); color: var(--ink-body); }
.finding.ok { border-left-color: var(--ok); color: var(--ok); background: var(--ok-soft); }

.error-banner {
    background: var(--danger-soft);
    color: var(--danger);
    border: 1px solid var(--danger);
    padding: 0.5rem 0.8rem;
    border-radius: 3px;
    margin-bottom: 1rem;
    font-size: 0.88rem;
}
.field-error { color: var(--danger); font-size: 0.8rem; margin: 0.25rem 0 0; }

/* ------------------------------------------------------------ platforms */

.platform-tabs, .admin-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: 0.3rem;
    margin-bottom: 1rem;
    border-bottom: 1px solid var(--border);
}
.platform-tab, .admin-tab {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    background: none;
    border: 1px solid transparent;
    border-bottom: none;
    border-radius: 4px 4px 0 0;
    padding: 0.5rem 0.9rem;
    margin-bottom: -1px;
    font: inherit;
    font-size: 0.85rem;
    color: var(--ink-muted);
    cursor: pointer;
}
.platform-tab:hover, .admin-tab:hover { color: var(--ink-body); }
.platform-tab.active, .admin-tab.active {
    color: var(--ink-body);
    background: var(--surface);
    border-color: var(--border);
    font-weight: 600;
}
.platform-tab .region { font-size: 0.7rem; color: var(--ink-muted); }
.admin-tab .count { font-size: 0.7rem; color: var(--ink-muted); }
.tab-dot { width: 0.4rem; height: 0.4rem; border-radius: 50%; display: inline-block; }
.tab-dot.ok { background: var(--ok); }
.tab-dot.warn { background: var(--warn); }

.platform {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 4px;
    margin-bottom: 1.2rem;
}
.platform > header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 1rem;
    padding: 0.8rem 1rem;
    border-bottom: 1px solid var(--border-soft);
}
.holder { margin: 0; font-size: 0.78rem; color: var(--ink-muted); }
.platform-status { display: flex; align-items: center; gap: 0.6rem; }

.pill {
    font-size: 0.7rem;
    font-family: var(--mono);
    padding: 0.15rem 0.45rem;
    border-radius: 2px;
}
.pill.ok { background: var(--ok-soft); color: var(--ok); }
.pill.warn { background: var(--warn-soft); color: var(--warn); }
.pill.note { background: var(--accent-soft); color: var(--accent-text); }
.pill.small { font-size: 0.62rem; padding: 0.05rem 0.35rem; margin-left: 0.4rem; vertical-align: middle; }

.fields { padding: 1rem; display: grid; gap: 0.9rem; max-width: 640px; }

dialog { background: var(--surface); color: var(--ink); border: 1px solid var(--border); border-radius: 4px; padding: 0; }
dialog::backdrop { background: rgba(0, 0, 0, 0.5); }
dialog .fields { padding: 1.1rem 1.3rem; max-width: 420px; }
#bulk-import-dialog .fields { max-width: 640px; width: 90vw; }
#bulk-import-text { width: 100%; font-family: var(--mono); font-size: 0.8rem; box-sizing: border-box; resize: vertical; }
.field-label { font-size: 0.8rem; font-weight: 600; color: var(--ink-body); display: block; margin-bottom: 0.2rem; }
.field-label em { font-style: normal; font-weight: 400; color: var(--ink-muted); font-size: 0.72rem; }
.pattern-help { font-family: var(--mono); font-size: 0.72rem; }

/* ------------------------------------------------------------- issue board */

.checkbox-row { display: flex; flex-wrap: wrap; gap: 0.6rem; }
.checkbox-inline { display: flex; align-items: center; gap: 0.3rem; font-size: 0.82rem; font-weight: 400; }

.issue-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 0.7rem; margin-bottom: 1rem; }
.issue-tabs, .issue-view-toggle { display: flex; gap: 0.3rem; }
.issue-tab, .issue-view-tab {
    background: none; border: 1px solid var(--border); border-radius: 3px;
    padding: 0.3rem 0.6rem; font-size: 0.8rem; color: var(--ink-muted); cursor: pointer;
}
.issue-tab.active, .issue-view-tab.active { border-color: var(--accent); color: var(--ink); }
.issue-board-picker { width: auto; max-width: 12rem; }
#issue-search { flex: 1; min-width: 160px; }
.issue-active-filters { display: flex; flex-wrap: wrap; gap: 0.3rem; width: 100%; }
.issue-active-filters button { background: none; border: none; color: inherit; cursor: pointer; font-size: 0.85rem; }

.issue-list { display: flex; flex-direction: column; gap: 0.5rem; }
.issue-list[hidden] { display: none; }
.issue-row {
    background: var(--surface); border: 1px solid var(--border); border-radius: 4px;
    padding: 0.6rem 0.7rem; cursor: pointer;
}
.issue-row-title { margin: 0 0 0.35rem; font-size: 0.85rem; }

.issue-board-list-add { display: flex; gap: 0.4rem; margin-bottom: 0.7rem; }
.issue-board-list-add[hidden] { display: none; }
.issue-board-list-add select { width: auto; max-width: 14rem; }

.issue-board { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem; align-items: start; }
.issue-board[hidden] { display: none; }
.issue-column { border-radius: 4px; padding: 0.3rem; transition: background 0.1s, box-shadow 0.1s; }
.issue-column-head { display: flex; align-items: center; justify-content: space-between; gap: 0.4rem; }
.issue-column h2 { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--ink-muted); margin: 0 0 0.6rem; }
.issue-column-remove {
    background: none; border: none; color: var(--ink-muted); cursor: pointer;
    font-size: 0.9rem; line-height: 1; padding: 0 0.2rem; margin-bottom: 0.6rem;
}
.issue-column-remove:hover { color: var(--danger); }
.issue-column-body { display: flex; flex-direction: column; gap: 0.6rem; min-height: 60px; }
/* Live feedback for which column a drag is currently over -- there's no
   continuous card-follows-the-mouse preview anymore (see the drag/drop
   comment in public/issues.js for why), so this is the only visual cue
   that a drop here is live and will land correctly. */
.issue-column.drag-over { background: var(--surface-recessed); box-shadow: inset 0 0 0 2px var(--accent); }

.issue-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 0.6rem 0.7rem;
    cursor: pointer;
}
.issue-card.dragging { opacity: 0.4; }
.issue-card-title { margin: 0 0 0.4rem; font-size: 0.85rem; }
.issue-card-pills { display: flex; flex-wrap: wrap; gap: 0.3rem; }
.issue-card-pills .pill { margin-left: 0; }

.pill.gitlab-label { font-family: inherit; }

.issue-detail-header { display: flex; align-items: center; justify-content: space-between; gap: 0.6rem; }
.issue-detail-header h2 { margin: 0; font-size: 1.05rem; }
.issue-byline { font-size: 0.72rem; color: var(--ink-muted); font-style: italic; margin: -0.4rem 0 0.6rem; }

.issue-detail-description { font-size: 0.85rem; margin-bottom: 0.4rem; }
.issue-detail-description p { margin: 0 0 0.6rem; }
.issue-detail-description code { font-family: var(--mono); font-size: 0.8rem; background: var(--surface-2, var(--border-soft)); padding: 0.05rem 0.3rem; border-radius: 2px; }
.issue-detail-description h1, .issue-detail-description h2, .issue-detail-description h3,
.issue-detail-description h4, .issue-detail-description h5, .issue-detail-description h6 {
    margin: 0.8rem 0 0.4rem;
    line-height: 1.3;
}
.issue-detail-description h1 { font-size: 1.3rem; }
.issue-detail-description h2 { font-size: 1.15rem; }
.issue-detail-description h3 { font-size: 1.05rem; }
.issue-detail-description h4, .issue-detail-description h5, .issue-detail-description h6 { font-size: 0.9rem; }
.issue-detail-description ul, .issue-detail-description ol { margin: 0 0 0.6rem; padding-left: 1.4rem; }
.issue-detail-description li { margin-bottom: 0.2rem; }
.issue-body-image { max-width: 100%; border-radius: 4px; display: block; margin: 0.4rem 0; }

/* New/edit issue dialogs -- two-column, GitLab-style: content on the left,
   labels/state sidebar on the right, action buttons spanning the bottom.
   Always the same large size regardless of content (a short issue just
   leaves empty room in its column instead of the dialog itself looking
   small against the backdrop), with each column scrolling on its own --
   like GitLab's sticky sidebar -- instead of the whole dialog, including
   the sidebar, scrolling together as one long page.

   This needs a real `height` here, not `max-height` -- only an actual
   definite height makes `minmax(0, 1fr)` below resolve to a real pixel
   value for the row track (which is also why grid-template-rows itself,
   not a scoped max-height on each column separately, is what's driving
   the columns' height this time: a `fr` track has no such row to give a
   real size to when the grid's own height is merely capped rather than
   fixed, which is the bug an earlier version of this hit).

   Also deliberately no align-items:start -- that disables the grid's
   default stretch, so .dialog-main/.dialog-sidebar would size to their own
   content height (ignoring the row track height) instead of being bounded
   to it, which is exactly what left their overflow-y:auto with no real box
   to clip against and let their content run into the actions row below
   (the bug an even earlier version of this hit). */
#new-issue-dialog .fields.two-col, #issue-detail-dialog .fields.two-col {
    width: min(1000px, 94vw); height: min(720px, 88vh); max-width: none;
    display: grid; grid-template-columns: 1fr 240px; grid-template-rows: minmax(0, 1fr) auto;
    gap: 0 1.2rem;
}
/* grid-auto-rows: min-content -- without it, a row holding an item taller
   than the container's own (definite, scrolling) height doesn't get sized
   to that item's actual content: Chromium fits its multiple auto rows
   into the container's available block size instead of growing past it,
   so the oversized item's box ends up clipped to whatever's left after
   the other rows, while its content still paints beyond that box (visible
   overflow) on top of the following rows instead of pushing them down.
   min-content here means "the intrinsic height this content needs",
   which is exactly the max-content size for a block element -- not to be
   confused with the min-content *inline* size (narrowest-without-overflow
   width) that name evokes for text. */
.dialog-main {
    grid-column: 1; grid-row: 1; display: grid; gap: 0.7rem; align-content: start; grid-auto-rows: min-content; min-width: 0;
    min-height: 0; overflow-y: auto; padding-right: 0.4rem;
}
.dialog-sidebar {
    grid-column: 2; grid-row: 1; display: grid; gap: 0.6rem; align-content: start; grid-auto-rows: min-content;
    border-left: 1px solid var(--border-soft); padding-left: 1rem; min-width: 0;
    min-height: 0; overflow-y: auto;
}
.dialog-actions { grid-column: 1 / -1; grid-row: 2; display: flex; justify-content: flex-end; gap: 0.5rem; padding-top: 0.7rem; margin-top: 0.2rem; border-top: 1px solid var(--border-soft); }
.sidebar-divider { border: none; border-top: 1px solid var(--border-soft); width: 100%; margin: 0.3rem 0; }
@media (max-width: 640px) {
    #new-issue-dialog .fields.two-col, #issue-detail-dialog .fields.two-col {
        grid-template-columns: 1fr; grid-template-rows: minmax(0, 1fr) minmax(0, 1fr) auto;
        height: min(85vh, 720px);
    }
    .dialog-main, .dialog-sidebar { grid-column: 1; }
    .dialog-sidebar { grid-row: 2; border-left: none; border-top: 1px solid var(--border-soft); padding-left: 0; padding-top: 0.8rem; }
    .dialog-actions { grid-row: 3; }
}

/* Compact, GitLab-style sidebar fields -- collapsed to a one-line summary
   by default, with the actual widget only shown while its "Edit" toggle
   is open (see public/issues.js's wireSidebarField). Rules that set
   `display` on an editor element are scoped with :not([hidden]) --
   otherwise they'd win over the browser's default `[hidden]{display:none}`
   and the editor would never actually collapse (the same class of bug the
   dialog's own display:flex hit earlier). */
.sidebar-field { display: grid; gap: 0.3rem; }
.sidebar-field-head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; }
.sidebar-field-head .field-label { margin: 0; }
.sidebar-field-summary { margin: 0; font-size: 0.85rem; }
.sidebar-edit-toggle { padding: 0.1rem 0.45rem; font-size: 0.72rem; }

.sidebar-select { width: 100%; border-radius: 3px; box-sizing: border-box; }
.sidebar-time-form:not([hidden]) { display: grid; grid-template-columns: 1fr; gap: 0.35rem; margin-top: 0.2rem; }
.sidebar-time-form select, .sidebar-time-form input { width: 100%; box-sizing: border-box; border-radius: 3px; }
.sidebar-time-reset { display: flex; gap: 0.4rem; margin-top: 0.3rem; flex-wrap: wrap; }

.label-groups { display: flex; flex-direction: column; gap: 0.6rem; }
.label-group { min-width: 0; }
.label-group select { border-radius: 3px; width: 100%; transition: background 0.15s, border-color 0.15s; }
.label-groups-actions { display: flex; justify-content: space-between; gap: 0.5rem; flex-wrap: wrap; }

/* One bordered "box" -- tabs / toolbar / textarea-or-preview / hint footer
   read as a single unit, matching GitLab's own description/comment editor
   chrome (see Phase 3b in issues-gitlab-parity-plan.md). The pieces inside
   are the same write-preview-tabs/format-toolbar/write-preview-pane
   elements used everywhere else in this file; this just frames them. */
/* align-self: start -- this is now the grid item .write-preview-pane's
   align-self: start rule (below) used to be on directly, before the pane
   moved inside this wrapper; see that rule's comment for why .dialog-main
   requires it on whichever element is its direct child here. */
.markdown-editor { border: 1px solid var(--border-soft); border-radius: 4px; overflow: hidden; align-self: start; }
.markdown-editor .write-preview-tabs {
    border-bottom: 1px solid var(--border-soft); padding: 0.15rem 0.5rem 0;
}
.markdown-editor textarea, .markdown-editor .write-preview-pane {
    border: none; border-radius: 0; margin: 0;
}
.markdown-editor textarea:focus { outline: none; box-shadow: none; }
.markdown-editor-footer {
    padding: 0.25rem 0.6rem; font-size: 0.7rem; color: var(--ink-muted);
    border-top: 1px solid var(--border-soft);
}

.format-toolbar { display: flex; align-items: center; gap: 0.25rem; flex-wrap: wrap; padding: 0.3rem 0.5rem; border-bottom: 1px solid var(--border-soft); }
.format-toolbar[hidden] { display: none; }
.format-toolbar button {
    border: 1px solid transparent; background: none; color: var(--ink-muted);
    border-radius: 3px; padding: 0.15rem 0.45rem; font-size: 0.78rem; cursor: pointer; line-height: 1.5;
}
.format-toolbar button:hover { color: var(--ink); border-color: var(--accent); }
.format-toolbar .format-style-select {
    width: auto; font-size: 0.72rem; padding: 0.15rem 0.3rem; border-color: var(--border-soft);
}
.format-toolbar-divider { width: 1px; align-self: stretch; background: var(--border-soft); margin: 0.1rem 0.2rem; }

.markdown-editor-help {
    margin-left: auto; align-self: center; color: var(--ink-muted); text-decoration: none;
    border: 1px solid var(--border-soft); border-radius: 50%; width: 1.1rem; height: 1.1rem;
    font-size: 0.7rem; display: inline-flex; align-items: center; justify-content: center;
}
.markdown-editor-help:hover { color: var(--ink); border-color: var(--accent); }

.write-preview-tabs { display: flex; align-items: center; gap: 0.2rem; }
.write-preview-tab {
    border: none; background: none; color: var(--ink-muted); font-size: 0.72rem;
    padding: 0.3rem 0.5rem; cursor: pointer; border-bottom: 2px solid transparent;
}
.write-preview-tab.active { color: var(--ink); border-bottom-color: var(--accent); }
/* Fits its content instead of capping it -- .dialog-main (its containing
   column) already scrolls as a whole, so a second, nested scrollbar here
   just hid the rest of a long description/comment behind a tiny box
   instead of letting the column itself scroll down to it.

   align-self: start is required, not cosmetic -- .dialog-main's grid
   items default to align-items: normal (stretch). A stretched grid item
   with an auto block size contributes only its MIN-CONTENT height to the
   row's auto-track sizing (a CSS Grid gotcha, since stretching is what
   then expands it to fill that track) -- for wrapped text that's
   whatever height the longest unbreakable word needs, typically one line,
   nowhere near this pane's actual rendered content. The rest of the
   content still painted (overflow: visible), just on top of Linked
   items/Activity below instead of pushing them down. Opting this item
   out of stretch makes its contribution (and so the row, and so
   .dialog-main's scrollable height) match what's actually rendered. */
.write-preview-pane { border: 1px solid var(--border-soft); border-radius: 3px; padding: 0.5rem 0.6rem; min-height: 3rem; align-self: start; }
.write-preview-pane:empty::before { content: "Nothing to preview."; color: var(--ink-muted); font-style: italic; font-size: 0.8rem; }

.issue-detail-title-input {
    font-size: 1.05rem; font-weight: 700; border: 1px solid transparent; background: none;
    color: inherit; padding: 0.1rem 0.3rem; margin: -0.1rem -0.3rem; border-radius: 3px; flex: 1;
}
.issue-detail-title-input:hover, .issue-detail-title-input:focus { border-color: var(--border); background: var(--surface-recessed); }

.label-chip-row { display: flex; flex-wrap: wrap; gap: 0.4rem; width: 100%; }
.label-chip {
    display: inline-flex; align-items: center; gap: 0.35rem;
    border: 1px solid var(--border); background: none; color: var(--ink-muted);
    border-radius: 12px; padding: 0.15rem 0.6rem; font-size: 0.72rem; cursor: pointer;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.label-chip::before {
    content: ""; width: 0.55rem; height: 0.55rem; border-radius: 50%; flex: none;
    background: var(--chip-color); border: 1px solid rgba(0,0,0,0.2);
}
.label-chip.active {
    background: var(--chip-color); color: var(--chip-text-color); border-color: transparent;
}
.label-chip.active::before { border-color: rgba(255,255,255,0.6); }

.issue-state-toggle { justify-self: start; }

.add-label-color-row { display: flex; align-items: center; gap: 0.6rem; }
#add-label-color { padding: 0; width: 2.4rem; height: 1.8rem; border: 1px solid var(--border); border-radius: 3px; background: none; }

.issue-detail-description, #issue-detail-notes { max-width: 100%; overflow-wrap: anywhere; }
.issue-note { border-top: 1px solid var(--border-soft); padding-top: 0.5rem; margin-top: 0.5rem; }
/* The byline + timestamp on one line -- a <p>, which otherwise carries the
   browser's default paragraph margin (~1em top+bottom) stacked on top of
   the comment card's own padding, is what made a comment's header look
   noticeably taller than GitLab's tight one-line version. */
.issue-note-header { margin: 0 0 0.3rem; font-size: 0.85rem; }
.issue-note-body { font-size: 0.85rem; }
.issue-note-body p { margin: 0 0 0.5rem; }
.issue-note-body blockquote { border-left: 3px solid var(--border); margin: 0 0 0.5rem; padding-left: 0.6rem; color: var(--ink-muted); }
.issue-note-actions { display: flex; gap: 0.4rem; margin-top: 0.3rem; }

/* A discussion thread: root note, then any replies indented under it with
   a connecting left border (GitLab's own visual cue for "these belong to
   the same thread"), then a reply box + resolve toggle for a real
   (resolvable, non-system) thread. */
/* No margin-top of its own -- each .issue-note inside already carries its
   own top spacing (see .issue-note/.issue-note.system below); adding more
   here just doubled up the gap between consecutive entries. */
.issue-discussion { position: relative; }
.issue-discussion-replies {
    margin: 0.5rem 0 0 1.2rem; padding-left: 0.8rem; border-left: 2px solid var(--border-soft);
}
.issue-discussion-replies .issue-note { margin-top: 0.6rem; }
.issue-discussion-replies .issue-note:first-child { margin-top: 0; }
.issue-reply-form { display: flex; gap: 0.4rem; margin: 0.6rem 0 0 1.2rem; align-items: flex-start; }
.issue-reply-form .markdown-editor { flex: 1; min-width: 0; }
.issue-reply-form textarea { min-height: 1.7rem; resize: none; box-sizing: border-box; }
.issue-reply-form .primary.small { margin-top: 0.3rem; }
.issue-discussion > .ghost.small { margin: 0.4rem 0 0 1.2rem; }

/* The continuous vertical "timeline" rail down the whole Activity feed,
   with a small dot marking each system-note line -- GitLab's own visual
   cue that these are all one ordered history, not just a plain flat list.
   No such marker on a real comment: without an avatar system (deferred --
   see the plan doc) there's nothing to anchor a dot to on those, so the
   rail simply runs behind/past their card instead. */
#issue-detail-notes { position: relative; padding-left: 1rem; }
#issue-detail-notes::before {
    content: ""; position: absolute; left: 0.3rem; top: 0.4rem; bottom: 0.4rem; width: 2px;
    background: var(--border-soft);
}
.issue-note.system { position: relative; }
.issue-note.system::before {
    content: ""; position: absolute; left: -0.95rem; top: 0.5rem; width: 6px; height: 6px;
    border-radius: 50%; background: var(--border);
}
.issue-note textarea { width: 100%; box-sizing: border-box; }

/* An actual comment reads as a distinct card, same as GitLab's own --
   visually set apart from the plain system-note lines around it instead of
   just another paragraph in the same flat list. */
.issue-note:not(.system) {
    background: var(--surface-recessed); border: 1px solid var(--border-soft);
    border-radius: 4px; padding: 0.6rem 0.7rem;
}

/* A GitLab system note (label/status/assignee/milestone change, close/reopen)
   -- same feed as comments (see public/issues.js's buildNoteEl), just a
   plain muted line with no byline/edit/reply actions, since there's nothing
   to edit and no author to attribute beyond GitLab's own note text. */
.issue-note.system { font-size: 0.78rem; color: var(--ink-muted); padding-top: 0.35rem; margin-top: 0.35rem; }
.issue-note.system .issue-note-body { font-size: inherit; color: inherit; }
.issue-note.system strong { color: var(--ink-body); }

/* GitLab's inline diff markup inside a system note's "changed title/
   description" body (`{+added+}` / `{-removed-}`), rendered as real
   highlighted spans by renderInlineMarkdown instead of the literal
   `{+...+}` braces. */
.system-note-add { background: var(--ok-soft); color: var(--ok); border-radius: 2px; padding: 0 0.2rem; }
.system-note-remove { background: var(--danger-soft); color: var(--danger); border-radius: 2px; padding: 0 0.2rem; text-decoration: line-through; }

.issue-linked-items { margin: 1.1rem 0; padding: 0 0.15rem; }
.issue-linked-item {
    display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
    padding: 0.35rem 0.1rem; border-top: 1px solid var(--border-soft); font-size: 0.85rem;
}
.issue-linked-item:first-child { border-top: none; }
.issue-linked-item-type { color: var(--ink-muted); font-size: 0.75rem; margin-right: 0.4rem; }
.issue-link-add { display: flex; gap: 0.4rem; margin-top: 0.5rem; flex-wrap: wrap; }
.issue-link-add input { width: 11rem; flex: none; }
#add-label-dialog .fields { max-width: 420px; width: 90vw; }

.derived-group {
    background: var(--surface-recessed);
    border-radius: 3px;
    padding: 0.7rem 0.9rem;
    display: grid;
    gap: 0.5rem;
}
.group-label {
    margin: 0;
    font-size: 0.72rem;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--ink-muted);
}
.field.derived output { display: block; font-size: 0.88rem; color: var(--ink); }

.reference-group summary {
    cursor: pointer;
    font-size: 0.82rem;
    color: var(--ink-muted);
    padding: 0.3rem 0;
}
.reference-group[open] summary { margin-bottom: 0.6rem; }
.reference-group .field { margin-bottom: 0.6rem; }

.row-actions { display: flex; align-items: center; gap: 0.6rem; }
.save-state { color: var(--ok); font-size: 0.82rem; }

.compliance-section { padding: 0 1rem 1rem; border-top: 1px solid var(--border-soft); margin-top: 0.4rem; }
.compliance-section .group-label { margin-top: 0.9rem; }
.compliance-group { margin-bottom: 0.8rem; }
.compliance-group summary {
    cursor: pointer;
    font-weight: 600;
    padding: 0.4rem 0;
}
.group-label.sub { margin: 0.6rem 0 0.3rem; }
.compliance-table { margin-bottom: 0.6rem; }
.compliance-table select, .compliance-table input[type="text"] { width: 100%; }

/* ------------------------------------------------------------------ builds */

.tab-panel[hidden] { display: none; }
.build-version {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 4px;
    margin-bottom: 0.6rem;
    padding: 0 0.9rem;
}
.build-version summary {
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 0.6rem 0;
    list-style: none;
}
.build-version summary::-webkit-details-marker { display: none; }
.build-version[open] summary { border-bottom: 1px solid var(--border-soft); margin-bottom: 0.5rem; }
.build-name { font-weight: 600; }
.build-platform-icons { display: flex; gap: 0.7rem; margin-left: auto; }
.platform-icon {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.4rem;
    height: 1.4rem;
    padding: 0 0.35rem;
    border-radius: 3px;
    font-size: 0.7rem;
    white-space: nowrap;
    background: var(--surface-recessed);
    color: var(--ink-muted);
}
.platform-icon.present { background: var(--ok-soft); color: var(--ok); }
/* A submitted platform keeps showing its abbreviation (present styling) --
   this just adds a small checkmark badge in the corner rather than replacing
   the label, so it's still clear which platform it is at a glance. */
.platform-icon.submitted::after {
    content: '\2713';
    position: absolute;
    top: -0.3rem;
    right: -0.3rem;
    width: 0.85rem;
    height: 0.85rem;
    line-height: 0.85rem;
    font-size: 0.55rem;
    text-align: center;
    border-radius: 50%;
    background: var(--ok);
    color: var(--surface);
    box-shadow: 0 0 0 1px var(--surface);
}
.build-platform-rows td { vertical-align: middle; }

.drive-config-form, .project-drive-config-form,
.project-rclone-config-form, .project-assets-drive-config-form { display: grid; gap: 0.9rem; max-width: 640px; }
.rclone-config, .assets-drive-config, .drive-builds-config { margin-top: 2rem; }
.rclone-config h2, .assets-drive-config h2, .drive-builds-config h2 { margin-bottom: 0.7rem; }
.assets-drive-config .row-actions + .row-actions { margin-top: 0.8rem; }
.drive-builds-config { margin-bottom: 1.4rem; }
.compliance-table tr.unanswered td { background: var(--warn-soft); }
.compliance-table select.unanswered { border-color: var(--warn); color: var(--warn); }
.compliance-table td { position: relative; }
.note-flag { position: absolute; top: 0.3rem; right: 0.4rem; color: var(--accent-text); font-size: 0.85rem; }

.inline-form { display: flex; gap: 0.5rem; align-items: center; margin-bottom: 1.2rem; max-width: 640px; }
.add-platform, .members { margin-top: 2rem; }
.add-platform h2, .members h2 { margin-bottom: 0.7rem; }

/* ---------------------------------------------------------------- misc */

.cards { list-style: none; padding: 0; margin: 0; display: grid; gap: 1rem;
         grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); }
.card { background: var(--surface); border: 1px solid var(--border); border-radius: 4px;
        padding: 1rem; position: relative; }
.card.pinned { border-color: var(--accent); }
.card a { text-decoration: none; color: inherit; display: flex; justify-content: space-between;
          align-items: baseline; gap: 0.5rem; padding-right: 1.4rem; }
.card h2 { font-size: 1rem; }
.role { font-family: var(--mono); font-size: 0.7rem; color: var(--ink-muted); text-transform: uppercase; }
.chips { display: flex; flex-wrap: wrap; gap: 0.3rem; margin-top: 0.7rem; }
.chip { font-size: 0.72rem; padding: 0.15rem 0.4rem; border: 1px solid var(--border);
        border-radius: 2px; color: var(--ink-body); }
.chip.muted { color: var(--ink-muted); border-style: dashed; }

.publisher-group { margin-bottom: 1.6rem; }
.publisher-group > summary {
    list-style: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin-bottom: 0.8rem;
    user-select: none;
}
.publisher-group > summary::-webkit-details-marker { display: none; }
.publisher-group > summary::before {
    content: '\25B8'; /* ▸ */
    display: inline-block;
    font-size: 1.1rem;
    color: var(--ink-muted);
    transition: transform 0.12s;
}
.publisher-group[open] > summary::before { transform: rotate(90deg); }
.publisher-group > summary h2 { margin: 0; }
.publisher-group > summary a { text-decoration: none; color: inherit; }
.publisher-group > summary a:hover { text-decoration: underline; }

/* Privacy mode -- blurred by default, revealed on hover/focus so a screen
   share never shows more than the one name being pointed at. An open
   <details> reveals its own publisher name too, since expanding it is
   already a deliberate reveal; everything else stays collapsed and blurred. */
body[data-privacy="on"] .privacy-name { filter: blur(6px); transition: filter 0.1s; }
body[data-privacy="on"] .card:hover .privacy-name,
body[data-privacy="on"] .card:focus-within .privacy-name,
body[data-privacy="on"] summary:hover .privacy-name,
body[data-privacy="on"] summary:focus-within .privacy-name,
body[data-privacy="on"] .publisher-group[open] > summary .privacy-name {
    filter: none;
}

.pin-form { position: absolute; top: 0.7rem; right: 0.7rem; }
.pin-toggle {
    border: none;
    background: none;
    padding: 0;
    font-size: 1.1rem;
    line-height: 1;
    cursor: pointer;
    color: var(--border);
}
.pin-toggle:hover { color: var(--accent-text); }
.pin-toggle.pinned { color: var(--accent-text); }
.archive-project-form { margin-top: 0.7rem; }

.empty {
    background: var(--surface);
    border: 1px dashed var(--border);
    border-radius: 4px;
    padding: 2rem;
    text-align: center;
    color: var(--ink-body);
}
.empty p { margin: 0.4rem 0; }

.auth { max-width: 380px; margin: 3rem auto; }
.auth form { display: grid; gap: 0.9rem; margin-top: 1.2rem; }
.auth label { display: block; }
.auth .field-label, .auth label { font-size: 0.85rem; color: var(--ink-body); }
.auth input { margin-top: 0.2rem; }
.alt { margin-top: 1.2rem; font-size: 0.85rem; }

.new-row {
    background: var(--surface);
    border: 1px solid var(--accent);
    border-radius: 4px;
    padding: 1rem;
    margin-bottom: 1rem;
    display: grid;
    gap: 0.8rem;
    max-width: 520px;
}

@media (prefers-reduced-motion: reduce) {
    * { transition: none !important; animation: none !important; }
}

/* ---------------------------------------------------------------- admin */

/* Matches the .ghost buttons either side of it (Theme, Sign out) -- it was
   previously a bordered box, which read as a different kind of control
   rather than the same row of topbar actions. */
.admin-link {
    font-size: 0.88rem;
    padding: 0.4rem 0.85rem;
    border-radius: 3px;
    border: 1px solid transparent;
    color: var(--ink-body);
    text-decoration: none;
}
.admin-link:hover { color: var(--ink); background: var(--surface-recessed); }

.muted { color: var(--ink-muted); }
button.small, .small { font-size: 0.76rem; padding: 0.25rem 0.5rem; }
.small-pill { font-size: 0.62rem; padding: 0.08rem 0.3rem; }

tr.row-deactivated { opacity: 0.55; }
tr.row-deactivated .who, tr.row-deactivated td { color: var(--ink-muted); }

.membership-list { display: grid; gap: 0.3rem; }
.membership-row {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.85rem;
}
.membership-row .role {
    font-family: var(--mono);
    font-size: 0.7rem;
    color: var(--ink-muted);
    text-transform: uppercase;
}

.user-search { max-width: 260px; margin-bottom: 0.6rem; }

.reveal-panel {
    background: var(--accent-soft);
    border: 1px solid var(--accent);
    border-radius: 4px;
    padding: 0.8rem 1rem;
    margin: 0.8rem 0;
    max-width: 480px;
}
.reveal-label { margin: 0 0 0.4rem; font-weight: 600; font-size: 0.85rem; }
.reveal-row { display: flex; align-items: center; gap: 0.5rem; }
.reveal-row input {
    font-family: var(--mono);
    font-weight: 600;
    letter-spacing: 0.02em;
}
.reveal-copied { font-size: 0.8rem; color: var(--ok); white-space: nowrap; }
.reveal-panel .hint { margin: 0.5rem 0 0; max-width: none; }
.action-cell { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; }

.catalog-platform {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 4px;
    padding: 1rem;
    margin-bottom: 1.5rem;
}
.catalog-platform header { margin-bottom: 0.8rem; }
.catalog-platform .group-label { margin: 1.2rem 0 0.6rem; }
.slot-actions { display: flex; align-items: center; gap: 0.4rem; white-space: nowrap; }
