      :root {
        --bg: #f7f8f6;
        --surface: #ffffff;
        --surface-2: #eef1ee;
        --line: #dfe3e0;
        --line-strong: #c7cdc7;
        --fg: #121815;
        --muted: #5b6960;
        --dim: #93a099;
        /* Hue-matched to the header logo badge's #00ff41 (135°) — same
           saturation/lightness as before (tuned for legibility on this
           light theme), just rotated from the old 147° so the workspace
           accent and the brand mark read as the same green. */
        --cta: #0f9d33;
        --cta-hover: #0c852a;
        --cta-soft: #e3f3e7;
        --cta-ink: #ffffff;
        --warn: #a66a12;
        --err: #c1392b;
        --shadow: 0 1px 2px rgba(18, 24, 21, 0.04), 0 8px 24px rgba(18, 24, 21, 0.07);
        --radius: 6px;
        --radius-sm: 4px;
        --font-body: "IBM Plex Sans", var(--font-cjk), -apple-system, "Segoe UI", sans-serif;
        --font-mono: "IBM Plex Mono", var(--font-cjk), Menlo, Monaco, "Courier New", monospace;

        /* Never empty. `var(--x)` with an empty value substitutes to
           nothing and leaves `"IBM Plex Sans", , system-ui` -- a double
           comma, which invalidates the whole font-family declaration and
           would have silently dropped the brand font in every Latin
           locale. So the default IS the next family in the stack, and the
           Latin result is byte-identical to what it was before. */
        --font-cjk: system-ui;
      }

      /* CJK font stacks, chosen by the page's own language
         (epic:localization #122).

         IBM Plex Sans and IBM Plex Mono carry no CJK glyphs at all, so
         before this every Chinese, Japanese and Korean character fell
         through to `system-ui` -- and system-ui resolves against the
         *system* locale, not the page's lang. Han characters are unified
         across the three scripts in Unicode but drawn differently in each,
         so a Japanese page on a Chinese-configured machine was rendering
         Japanese text with Chinese glyph shapes, and a Korean page
         whatever the machine happened to prefer. Naming the faces per
         :lang() puts glyph selection back under the page's control
         instead of the reader's OS settings.

         Plex stays first in every stack: Latin text inside a Japanese page
         should still be Plex, and the CJK face only supplies the glyphs
         Plex lacks. */
      :root:lang(ja) {
        --font-cjk: "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Yu Gothic",
          "Noto Sans JP", "Noto Sans CJK JP", Meiryo, system-ui;
      }

      :root:lang(zh) {
        --font-cjk: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
          "Noto Sans SC", "Noto Sans CJK SC", system-ui;
      }

      :root:lang(ko) {
        --font-cjk: "Apple SD Gothic Neo", "Malgun Gothic", "Noto Sans KR",
          "Noto Sans CJK KR", system-ui;
      }

      /* CJK glyphs fill the em box with none of the ascender/descender
         slack Latin type leaves, so a line-height tuned on English reads
         cramped. Measured at 1.35 on .hint before this. Applied only to
         the multi-line explanatory text: single-line controls gain
         nothing and would only grow taller. */
      :root:lang(ja) .hint,
      :root:lang(zh) .hint,
      :root:lang(ko) .hint {
        line-height: 1.7;
      }

      * { box-sizing: border-box; }

      body {
        margin: 0;
        background: var(--bg);
        color: var(--fg);
        font-family: var(--font-body);
        min-height: 100vh;
      }

      /* ── Layout ─────────────────────────────────────────── */
      .shell {
        display: grid;
        /* header / #accountNotice (only an in-flow box while shown — it's
           `.hidden{display:none}` the rest of the time) / workspace.
           Explicit grid-row below on all three, rather than relying on
           auto-placement order, so #accountNotice popping in and out of
           flow can never shift workspace out of its 1fr row (which would
           leave the trailing row empty and workspace only as tall as its
           own content instead of filling the viewport). */
        grid-template-rows: auto auto 1fr;
        min-height: 100vh;
      }
      .shell > header { grid-row: 1; }
      .shell > .account-notice { grid-row: 2; }
      .shell > .workspace { grid-row: 3; }

      .mcp-pill {
        border: 1px solid var(--line);
        color: var(--muted);
        font-size: 0.72rem;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        padding: 3px 7px;
        border-radius: var(--radius-sm);
        background: var(--surface);
        white-space: nowrap;
      }

      .mcp-url {
        color: var(--fg);
        border: 1px solid var(--line);
        border-radius: var(--radius-sm);
        padding: 5px 8px;
        background: var(--surface);
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        flex: 1;
        min-width: 120px;
        font-size: 0.82rem;
        font-family: var(--font-mono);
      }

      .mcp-url:focus {
        outline: none;
        border-color: var(--cta);
        background: var(--surface);
      }

      /* Same look as .mcp-url (Settings > API access, the revealed read-only
         key) but deliberately its own class, not a reuse of .mcp-url --
         that class carries a @media (max-width: 900px) rule (below) written
         specifically for the top mcp-strip's layout, which reordered this
         field's Copy button in front of it at narrow widths as an
         unintended side effect the one time it was shared. */
      .api-key-value {
        color: var(--fg);
        border: 1px solid var(--line);
        border-radius: var(--radius-sm);
        padding: 5px 8px;
        background: var(--surface);
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
        flex: 1;
        min-width: 120px;
        font-size: 0.82rem;
        font-family: var(--font-mono);
      }

      .api-key-value:focus {
        outline: none;
        border-color: var(--cta);
        background: var(--surface);
      }

      header {
        border-bottom: 1px solid var(--line);
        padding: 10px 16px;
        display: flex;
        justify-content: space-between;
        align-items: center;
        gap: 12px;
        background: var(--surface);
        /* Same reason as .header-right below: the brand/nav group and the
           controls group together exceed a phone's width on their own,
           with or without the language switcher. Wrapping is what stops
           the page scrolling sideways. */
        flex-wrap: wrap;
      }

      .logo { font-weight: 700; font-size: 1.1rem; letter-spacing: 0.1em; }

      /* The brand mark keeps its original black/green terminal identity even
         though the app around it moved to a light theme — a deliberate,
         self-contained exception (fixed colors, not theme tokens) rather
         than an oversight. Only applied in the app shell (index.html); the
         marketing page's plain .logo is untouched. */
      .logo-badge {
        display: inline-flex;
        align-items: center;
        font-family: var(--font-mono);
        background: #000000;
        color: #00ff41;
        padding: 5px 10px;
        border-radius: var(--radius-sm);
      }

      .header-right {
        display: flex;
        align-items: center;
        gap: 10px;
        /* Wraps rather than overflowing. This row already exceeded a phone's
           width before the language switcher joined it (measured at 390px:
           707px of content), pushing the account chip off-screen; adding a
           control to a row that doesn't fit means making the row cope.
           justify-content keeps it right-aligned when it does wrap. */
        flex-wrap: wrap;
        justify-content: flex-end;
      }

      /* Header nav (3A) — "My library" is the only real destination today;
         Explore/Activity render for visual completeness but are inert (no
         page exists yet), hence no :hover cursor styling suggesting they're
         clickable beyond the tooltip already on the element. */
      .app-nav {
        display: flex;
        gap: 4px;
        /* Wraps, because translated nav labels are longer than the English
           ones they replace: "My library" becomes "Meine Bibliothek",
           "Ma bibliothèque", "Mi biblioteca". Measured at 390px, those
           three pushed the header off-screen while English, Italian and
           the CJK locales fit. A fixed-width nav that only fits the
           language it was designed in is the classic localization break
           (epic:localization #115). */
        flex-wrap: wrap;
      }
      .nav-item {
        font-size: 0.9rem;
        color: var(--muted);
        padding: 6px 10px;
        border-radius: var(--radius-sm);
      }
      .nav-item.active {
        font-weight: 600;
        color: var(--fg);
        background: var(--surface-2);
      }

      /* Real connection state only — see auth.js's updateAuthUi comment.
         Deliberately styled via the #authStatus id (kept for setStatus's
         existing "status"/"ok"/"warn" className contract) rather than a
         className this element doesn't otherwise carry. */
      #authStatus.status {
        display: inline-flex;
        align-items: center;
        gap: 7px;
        font-size: 0.82rem;
        font-weight: 600;
        border-radius: 999px;
        padding: 5px 12px 5px 10px;
        border: 1px solid var(--line);
        background: var(--surface-2);
      }
      #authStatus.status::before {
        content: "";
        width: 8px;
        height: 8px;
        border-radius: 999px;
        background: var(--dim);
      }
      #authStatus.status.ok {
        color: #0b6f24;
        border-color: var(--cta-soft);
        background: var(--cta-soft);
      }
      #authStatus.status.ok::before { background: var(--cta); }
      #authStatus.status.warn {
        color: var(--warn);
        border-color: color-mix(in srgb, var(--warn) 30%, transparent);
        background: color-mix(in srgb, var(--warn) 12%, transparent);
      }
      #authStatus.status.warn::before { background: var(--warn); }

      /* Account chip + dropdown — replaces the old flat row of
         homeBtn/settingsBtn/logoutBtn buttons. Those buttons keep their
         ids and click handlers (main.js) unchanged; only their container
         changed, from always-visible to tucked behind this menu. */
      .account-chip { position: relative; }
      .account-chip-toggle {
        display: inline-flex;
        align-items: center;
        gap: 7px;
        border: 1px solid var(--line);
        border-radius: 999px;
        padding: 4px 10px 4px 5px;
        background: var(--surface);
        cursor: pointer;
        font: inherit;
      }
      .account-chip-toggle:hover { border-color: var(--line-strong); }
      .account-avatar {
        width: 22px;
        height: 22px;
        border-radius: 999px;
        background: var(--fg);
        color: var(--surface);
        font-size: 0.72rem;
        font-weight: 600;
        display: inline-flex;
        align-items: center;
        justify-content: center;
      }
      .account-caret { color: var(--dim); font-size: 0.7rem; }
      .account-menu {
        position: absolute;
        top: calc(100% + 8px);
        right: 0;
        z-index: 40;
        display: flex;
        flex-direction: column;
        gap: 6px;
        min-width: 220px;
        border: 1px solid var(--line);
        border-radius: var(--radius);
        background: var(--surface);
        box-shadow: var(--shadow);
        padding: 10px;
      }
      .account-menu .btn { width: 100%; text-align: left; }
      .account-menu-mcp {
        display: flex;
        align-items: center;
        gap: 6px;
        border-top: 1px solid var(--line);
        border-bottom: 1px solid var(--line);
        padding: 8px 0;
        margin: 2px 0;
      }
      .account-menu-mcp .mcp-url { min-width: 0; }

      /* Language switcher (epic:localization #114). Lives in the header
         rather than the account menu: one reachable only by opening a menu
         labelled in a language you can't read is one the person who needs
         it most can't find. */
      .locale-switcher {
        display: flex;
        align-items: center;
        gap: 4px;
      }
      .locale-globe { font-size: 0.9rem; line-height: 1; }
      .locale-select {
        font-family: var(--font-body);
        font-size: 0.8rem;
        padding: 3px 6px;
        border: 1px solid var(--line);
        border-radius: var(--radius-sm);
        background: var(--bg);
        color: var(--fg);
        cursor: pointer;
      }
      /* Labelled for screen readers, unlabelled on screen: the globe plus
         the language's own name in the option already say what it is, and
         a visible "Language" word would only be readable in one language. */
      .visually-hidden {
        position: absolute;
        width: 1px;
        height: 1px;
        margin: -1px;
        padding: 0;
        overflow: hidden;
        clip: rect(0 0 0 0);
        white-space: nowrap;
        border: 0;
      }

      .status {
        font-size: 0.85rem;
        color: var(--muted);
      }
      .status.ok { color: var(--cta); }
      .status.err { color: var(--err); }
      .status.warn { color: var(--warn); }

      .owner-link {
        color: var(--muted);
        text-decoration: none;
        border-bottom: 1px dotted var(--dim);
      }

      .owner-link:hover {
        color: var(--fg);
        border-bottom-color: var(--fg);
      }

      .workspace {
        display: grid;
        /* max-content, not a fixed length, so the rail's column collapses
           to 0 width when #folderRail is display:none (no publisher
           loaded) instead of leaving a permanent empty gutter. */
        grid-template-columns: max-content minmax(0, 1fr) 400px;
        grid-template-rows: 1fr;
        min-height: 0;
        overflow: hidden;
      }

      /* Explicit placement (not auto-placement order) so the three panels
         stay in column 2 and the detail panel in column 3 regardless of
         whether the rail is currently rendered — see the max-content note
         above. */
      .folder-rail { grid-column: 1; }
      .list-panel { grid-column: 2; }
      .detail-panel { grid-column: 3; }

      /* ── Sidebar rail (3A: Views + persistent folder tree) ────────── */
      .folder-rail {
        width: 226px;
        background: var(--surface-2);
        border-right: 1px solid var(--line);
        padding: 14px 0;
        overflow-y: auto;
        display: flex;
        flex-direction: column;
        gap: 4px;
      }
      .rail-section + .rail-section { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--line); }
      .rail-section-label {
        display: flex;
        font-size: 0.72rem;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        color: var(--dim);
        padding: 0 16px 6px;
      }
      .rail-view-row,
      .folder-tree-row {
        display: flex;
        align-items: center;
        gap: 8px;
        padding: 7px 16px;
        font-size: 0.9rem;
        color: var(--fg);
        cursor: pointer;
      }
      .rail-view-row:hover,
      .folder-tree-row:hover { background: var(--surface); }
      .rail-view-row.active,
      .folder-tree-row.current {
        font-weight: 600;
        background: var(--surface);
        border-left: 3px solid var(--cta);
        padding-left: 13px;
      }
      .rail-row-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
      .rail-row-count { font-size: 0.82rem; color: var(--dim); flex-shrink: 0; }
      .folder-tree-chevron {
        width: 10px;
        flex-shrink: 0;
        font-size: 0.72rem;
        color: var(--dim);
        text-align: center;
      }
      .folder-tree-name {
        flex: 1;
        min-width: 0;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
      }
      .folder-tree-count { font-size: 0.82rem; color: var(--dim); flex-shrink: 0; }

      /* ── Skill list panel ───────────────────────────────── */
      .list-panel {
        border-right: 1px solid var(--line);
        overflow-y: auto;
        display: flex;
        flex-direction: column;
        background: var(--surface);
      }

      .list-toolbar {
        padding: 10px;
        border-bottom: 1px solid var(--line);
        display: flex;
        gap: 8px;
      }

      .list-toolbar input {
        flex: 1;
        background: var(--bg);
        border: 1px solid var(--line);
        border-radius: var(--radius-sm);
        color: var(--fg);
        padding: 6px 8px;
        font: inherit;
        font-size: 0.9rem;
      }

      .list-toolbar button {
        border: 1px solid var(--line);
        border-radius: var(--radius-sm);
        background: var(--surface);
        color: var(--fg);
        padding: 6px 10px;
        cursor: pointer;
        font: inherit;
        font-size: 0.85rem;
      }
      .list-toolbar button:hover { background: var(--surface-2); }

      /* Location bar (3A) — breadcrumb + search/create actions share one
         row instead of stacking as separate strips; #catalogBreadcrumb
         keeps its own class/behavior (see .breadcrumb below), it's just
         nested in here now. */
      .location-bar {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 14px;
        padding: 8px 12px;
        background: var(--surface-2);
        border-bottom: 1px solid var(--line);
        flex-wrap: wrap;
      }
      .location-bar-actions {
        display: flex;
        align-items: center;
        gap: 8px;
        flex-shrink: 0;
      }
      .search-input {
        background: var(--surface);
        border: 1px solid var(--line);
        border-radius: var(--radius-sm);
        color: var(--fg);
        padding: 6px 8px;
        font: inherit;
        font-size: 0.86rem;
        width: 160px;
      }
      .search-input:focus { outline: none; border-color: var(--cta); }

      /* Column header row, aligned with the fixed-width row-agent/
         row-visibility/row-actions cells below (see .skill-item). */
      .list-columns {
        display: flex;
        align-items: center;
        gap: 12px;
        padding: 7px 16px 7px 26px;
        border-bottom: 1px solid var(--line);
        background: var(--surface-2);
        font-size: 0.72rem;
        letter-spacing: 0.06em;
        text-transform: uppercase;
        color: var(--dim);
      }
      .list-columns-name { flex: 1; min-width: 0; }
      .list-columns-agent { width: 112px; flex-shrink: 0; }
      .list-columns-visibility { width: 92px; flex-shrink: 0; }
      .list-columns-actions { width: 104px; flex-shrink: 0; text-align: right; }

      .skill-list { flex: 1; overflow-y: auto; }

      /* Unified row shape (M18.1) — a skill row and a folder row are the same
         kind of thing: icon + name/meta on the left (.skill-item-main),
         icon-button controls + optional chevron on the right, single line.
         Previously only folder rows (.collection-folder) used this flex
         layout; skill rows block-stacked name/meta/tags/controls across 3-4
         lines. Tags moved out of the compact row entirely (still shown in
         the detail panel) — that's what actually makes this fit one line. */
      .skill-item {
        display: flex;
        align-items: center;
        gap: 12px;
        padding: 10px 16px;
        border-bottom: 1px solid var(--line);
        cursor: pointer;
      }
      .skill-item:hover,
      .skill-item.active { background: var(--surface-2); }

      /* Leading 10px spacer column, aligned across folder and skill rows —
         folder rows put a disclosure glyph in it (see buildFolderRow),
         skill rows leave it empty so names still line up. */
      .row-disclosure {
        width: 10px;
        flex-shrink: 0;
        color: var(--muted);
        font-size: 0.78rem;
        text-align: center;
      }

      .skill-item-main { min-width: 0; flex: 1; }
      .skill-item-name {
        font-family: var(--font-mono);
        font-weight: 600;
        font-size: 0.92rem;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
      }
      .skill-item-meta { font-size: 0.78rem; color: var(--muted); margin-top: 3px; }

      /* Agent/Visibility columns (3A: "toggles became labeled states") —
         always visible regardless of hover/active, unlike row-actions
         below. Width matches .list-columns-agent/.list-columns-visibility
         so rows line up under the header. */
      .row-agent {
        width: 112px;
        flex-shrink: 0;
        font-size: 0.82rem;
        color: var(--muted);
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
      }
      .row-visibility { width: 92px; flex-shrink: 0; }
      .row-visibility-label {
        display: inline-block;
        font-size: 0.8rem;
        color: var(--muted);
        border: 1px solid var(--line);
        border-radius: var(--radius-sm);
        padding: 2px 8px;
        background: var(--surface);
        font: inherit;
      }
      button.row-visibility-label.toggleable {
        cursor: pointer;
      }
      button.row-visibility-label.toggleable::after { content: " ▾"; color: var(--dim); }
      button.row-visibility-label.toggleable:hover { border-color: var(--cta); color: var(--fg); }

      /* Actions column: real controls only for the active row, a quiet "⋯"
         otherwise — see buildRowActionsCell's comment in catalog.js. Both
         nodes always exist; only one is ever visible, toggled purely by
         :hover/.active so nothing needs to be rebuilt on selection. */
      .row-actions {
        width: 104px;
        flex-shrink: 0;
        display: flex;
        justify-content: flex-end;
      }
      .row-controls {
        display: none;
        align-items: center;
        gap: 6px;
      }
      .row-overflow {
        display: inline-flex;
        color: var(--dim);
        font-size: 1.05rem;
        line-height: 1;
      }
      .skill-item:hover .row-controls,
      .skill-item.active .row-controls { display: flex; }
      .skill-item:hover .row-overflow,
      .skill-item.active .row-overflow { display: none; }

      .collection-folder .skill-item-name { color: var(--fg); }
      .folder-chevron { color: var(--dim); font-size: 1.1rem; line-height: 1; flex-shrink: 0; }

      /* Private-visibility indicator (M18) — skills and folders alike. Amber
         rather than the error/danger color: not a problem, just a status. */
      .badge-private {
        display: inline-block;
        font-size: 0.68rem;
        font-weight: 600;
        letter-spacing: 0.03em;
        text-transform: uppercase;
        color: var(--warn);
        background: color-mix(in srgb, var(--warn) 14%, transparent);
        border-radius: var(--radius-sm);
        padding: 1px 6px;
        vertical-align: middle;
      }
      .skill-item-description {
        font-size: 0.8rem;
        color: var(--muted);
        margin-top: 4px;
      }

      /* "+ New subfolder" — a dashed-border add affordance rather than a
         plain text row, so it reads as an action, not another list item. */
      .new-subfolder-row {
        color: var(--muted);
        cursor: pointer;
        font-size: 0.82rem;
        border-bottom: 1px dashed var(--line-strong);
      }
      .new-subfolder-row:hover { color: var(--cta); background: var(--surface-2); }

      /* Drag-and-drop (folder tree, owners only): a lifted/dimmed source row
         while dragging, and a dashed highlight on whatever's under the
         pointer — folder rows and non-current breadcrumb crumbs alike. */
      .skill-item.dragging { opacity: 0.4; }
      .drop-target-active {
        background: var(--cta-soft) !important;
        outline: 2px dashed var(--cta);
        outline-offset: -2px;
      }
      a.breadcrumb-link.drop-target-active,
      span.breadcrumb-current.drop-target-active {
        border-radius: var(--radius-sm);
        padding: 1px 5px;
        margin: -1px -5px;
      }

      /* Greys out a skill row when it's excluded from the caller's MCP
         library (enabled: false) — a lightweight visual cue instead of a
         separate list showing the same skills twice. */
      .skill-item.library-disabled .skill-item-name,
      .skill-item.library-disabled .skill-item-meta { color: var(--dim); }
      .skill-item.library-disabled:hover { background: var(--surface-2); }

      /* Toggle switch (replaces the native checkbox for library visibility
         controls — per-skill and per-collection). */
      .switch {
        position: relative;
        display: inline-flex;
        flex-shrink: 0;
        width: 34px;
        height: 18px;
        cursor: pointer;
      }
      .switch input {
        position: absolute;
        opacity: 0;
        width: 0;
        height: 0;
      }
      .switch-track {
        position: absolute;
        inset: 0;
        background: var(--surface-2);
        border: 1px solid var(--line-strong);
        border-radius: 999px;
        transition: background 0.15s, border-color 0.15s;
      }
      .switch-track::before {
        content: "";
        position: absolute;
        top: 2px;
        left: 2px;
        width: 12px;
        height: 12px;
        border-radius: 50%;
        background: var(--dim);
        transition: transform 0.15s, background 0.15s, box-shadow 0.15s;
      }
      .switch input:checked + .switch-track {
        background: var(--cta-soft);
        border-color: var(--cta);
      }
      .switch input:checked + .switch-track::before {
        transform: translateX(16px);
        background: var(--cta);
        box-shadow: none;
      }
      .switch input:indeterminate + .switch-track::before {
        transform: translateX(8px);
        background: var(--warn);
      }
      .switch input:disabled + .switch-track {
        opacity: 0.5;
        cursor: not-allowed;
      }
      .switch input:focus-visible + .switch-track {
        border-color: var(--cta);
      }

      .detail-library-row { display: flex; align-items: center; gap: 10px; }

      /* Wayfinding (M18.1) — was a thin caption easy to miss; now a real
         location bar: heavier background/border, larger type, an explicit
         "up one level" control alongside the existing clickable crumb
         trail (breadcrumb-back only renders when there's somewhere to go
         back to — see renderBreadcrumb). */
      /* Nested inside .location-bar now (own background/border live there);
         this rule just handles the trail's own flex/typography. */
      .breadcrumb {
        display: flex;
        align-items: center;
        gap: 8px;
        font-size: 0.85rem;
        color: var(--muted);
        min-width: 0;
      }
      .breadcrumb-back {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 24px;
        height: 24px;
        flex-shrink: 0;
        border: 1px solid var(--line-strong);
        border-radius: var(--radius-sm);
        background: var(--surface);
        color: var(--fg);
        text-decoration: none;
        font-size: 0.9rem;
      }
      .breadcrumb-back:hover { border-color: var(--cta); color: var(--cta); }
      .breadcrumb-trail { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
      .breadcrumb-link { color: var(--fg); text-decoration: none; }
      .breadcrumb-link:hover { text-decoration: underline; }
      .breadcrumb-sep { margin: 0 6px; color: var(--dim); }
      .breadcrumb-current { color: var(--fg); font-weight: 600; }

      .folder-picker-list {
        max-height: min(360px, 60vh);
        overflow-y: auto;
        border: 1px solid var(--line);
        border-radius: var(--radius-sm);
      }
      .folder-picker-row {
        padding: 8px 12px;
        font-family: var(--font-mono);
        font-size: 0.88rem;
        cursor: pointer;
        border-bottom: 1px solid var(--line);
      }
      .folder-picker-row:last-child { border-bottom: none; }
      .folder-picker-row:hover { background: var(--surface-2); }
      .folder-picker-row-current { color: var(--muted); }
      .folder-picker-row-current::after { content: " (current)"; font-family: var(--font-body); }

      .skill-name-row {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 8px;
      }

      .compose-signal {
        position: relative;
        width: 18px;
        height: 12px;
        flex: 0 0 18px;
      }

      .compose-signal span {
        position: absolute;
        width: 6px;
        height: 6px;
        border: 1px solid var(--cta);
        border-radius: 999px;
        background: var(--cta-soft);
      }

      .compose-signal span:nth-child(1) { left: 0; top: 3px; }
      .compose-signal span:nth-child(2) { left: 6px; top: 0; }
      .compose-signal span:nth-child(3) { left: 12px; top: 6px; }

      /* Dependency graph */
      .dep-graph {
        position: relative;
        height: 260px;
        border: 1px solid var(--line);
        border-radius: var(--radius);
        background: var(--surface-2);
        overflow: hidden;
        margin-top: 8px;
      }

      .dep-graph svg {
        position: absolute;
        inset: 0;
        width: 100%;
        height: 100%;
      }

      .dep-edge {
        stroke: var(--line-strong);
        stroke-width: 2;
      }

      .dep-edge.optional {
        stroke: var(--warn);
        stroke-dasharray: 5 4;
      }

      .dep-node-card {
        position: absolute;
        transform: translate(-50%, -50%);
        border: 1px solid var(--line);
        border-radius: var(--radius-sm);
        background: var(--surface);
        color: var(--fg);
        font-family: var(--font-mono);
        padding: 6px 8px;
        min-width: 118px;
        max-width: 160px;
        font-size: 0.73rem;
        display: grid;
        gap: 3px;
        box-shadow: var(--shadow);
      }

      .dep-node-card.root {
        border-color: var(--cta);
        min-width: 140px;
      }

      .dep-node-card.dep {
        cursor: pointer;
      }

      .dep-node-card.dep:hover {
        border-color: var(--cta);
        background: var(--cta-soft);
      }

      .dep-node-card.optional {
        border-color: var(--warn);
      }

      .node-line {
        display: flex;
        align-items: center;
        gap: 6px;
      }

      .node-dot {
        width: 8px;
        height: 8px;
        border: 1px solid currentColor;
        border-radius: 999px;
      }

      .node-type {
        color: var(--dim);
        font-size: 0.68rem;
        letter-spacing: 0.05em;
        text-transform: uppercase;
      }

      .dep-node-card.optional .node-type {
        color: var(--warn);
      }

      .tag-badge {
        display: inline-block;
        font-size: 0.72rem;
        color: var(--muted);
        border: 1px solid var(--line);
        border-radius: var(--radius-sm);
        padding: 1px 5px;
        margin-right: 3px;
      }

      /* ── Detail / publish panel ─────────────────────────── */
      .detail-panel {
        overflow-y: auto;
        padding: 14px;
        background: var(--bg);
      }

      .section-label {
        color: var(--muted);
        font-size: 0.78rem;
        text-transform: uppercase;
        letter-spacing: 0.06em;
        margin-bottom: 6px;
      }

      .empty-state {
        color: var(--dim);
        font-size: 0.9rem;
        margin-top: 40px;
        text-align: center;
      }

      .compiled-preview-textarea {
        width: 100%;
        min-height: 420px;
        background: var(--surface);
        border: 1px solid var(--line);
        border-radius: var(--radius-sm);
        color: var(--fg);
        padding: 8px;
        font-family: var(--font-mono);
        font-size: 0.82rem;
        resize: vertical;
      }

      /* Content-first detail panel (3A/2a) — the rendered skill content
         leads; see renderDetail in catalog.js. */
      .detail-content-card {
        background: var(--surface);
        border: 1px solid var(--line);
        border-radius: var(--radius);
        padding: 15px 16px;
      }

      /* Typography for markdown.js's output — deliberately plain, matching
         the app's existing type scale rather than importing a generic
         "prose" look. */
      .markdown-body { font-size: 0.9rem; line-height: 1.55; color: var(--fg); }
      .markdown-body h1,
      .markdown-body h2,
      .markdown-body h3 { margin: 0 0 8px; font-weight: 600; line-height: 1.3; }
      .markdown-body h1 { font-size: 1.1rem; }
      .markdown-body h2 { font-size: 1.02rem; }
      .markdown-body h3 { font-size: 0.96rem; }
      .markdown-body p { margin: 0 0 10px; }
      .markdown-body p:last-child { margin-bottom: 0; }
      .markdown-body ul,
      .markdown-body ol { margin: 0 0 10px; padding-left: 20px; }
      .markdown-body li { margin: 2px 0; }
      .markdown-body code {
        font-family: var(--font-mono);
        font-size: 0.85em;
        background: var(--surface-2);
        border-radius: 3px;
        padding: 1px 4px;
      }
      .markdown-body pre {
        background: var(--surface-2);
        border-radius: var(--radius-sm);
        padding: 10px;
        overflow-x: auto;
        margin: 0 0 10px;
      }
      .markdown-body pre code { background: none; padding: 0; }
      .markdown-body a { color: var(--cta); }
      .markdown-body a:hover { color: var(--cta-hover); }

      /* Bottom disclosure for hashes/version/compat (3A/2a: collapse what
         used to lead the panel). Same collapsed-by-default pattern as
         .advanced-settings in the publish form. */
      .detail-disclosure summary {
        cursor: pointer;
        color: var(--muted);
        font-size: 0.78rem;
        text-transform: uppercase;
        letter-spacing: 0.04em;
      }
      .detail-disclosure summary:hover { color: var(--fg); }

      /* Publish form */
      .publish-form { display: grid; gap: 12px; }
      .field-row {
        display: grid;
        gap: 12px;
        grid-template-columns: repeat(2, minmax(0, 1fr));
      }
      .field label {
        display: block;
        color: var(--muted);
        font-size: 0.78rem;
        text-transform: uppercase;
        letter-spacing: 0.04em;
        margin-bottom: 4px;
      }
      .field input,
      .field textarea {
        width: 100%;
        background: var(--surface);
        border: 1px solid var(--line);
        border-radius: var(--radius-sm);
        color: var(--fg);
        padding: 7px;
        font: inherit;
        font-size: 0.9rem;
        resize: vertical;
      }
      .field input:focus,
      .field textarea:focus {
        outline: none;
        border-color: var(--cta);
      }
      .field textarea { min-height: 110px; }
      .hint {
        color: var(--dim);
        font-size: 0.75rem;
        line-height: 1.35;
      }
      .field .hint { margin-top: 4px; }
      .hint a { color: var(--cta); }
      .hint a:hover { color: var(--cta-hover); }

      .manifest-card {
        border: 1px solid var(--line);
        border-radius: var(--radius);
        background: var(--surface);
        padding: 10px;
      }

      .manifest-card .section-label { margin-bottom: 10px; }

      .usage-stat {
        flex: 1;
        border: 1px solid var(--line);
        border-radius: var(--radius-sm);
        padding: 10px;
        text-align: center;
      }
      .usage-stat-value {
        font-size: 1.4rem;
        font-weight: 700;
        color: var(--fg);
      }
      .usage-stat-label {
        font-size: 0.75rem;
        color: var(--muted);
        margin-top: 2px;
      }
      .usage-bar:hover rect,
      .usage-bar:hover path { fill: var(--cta-hover); }

      .advanced-settings {
        margin-top: 12px;
        border-top: 1px solid var(--line);
        padding-top: 10px;
      }
      .advanced-settings summary {
        cursor: pointer;
        color: var(--muted);
        font-size: 0.78rem;
        text-transform: uppercase;
        letter-spacing: 0.04em;
      }
      .advanced-settings summary:hover { color: var(--fg); }
      .advanced-settings[open] summary { margin-bottom: 10px; }
      .advanced-settings .field-row,
      .advanced-settings .field { margin-top: 12px; }
      .advanced-settings .field-row:first-of-type,
      .advanced-settings > .field:first-of-type { margin-top: 12px; }

      .files-list { display: grid; gap: 8px; }
      .file-card {
        border: 1px solid var(--line);
        border-radius: var(--radius-sm);
        background: var(--surface);
        padding: 8px;
      }
      .file-card-head {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 6px;
        font-size: 0.8rem;
        color: var(--muted);
      }

      .btn {
        border: 1px solid var(--line);
        border-radius: var(--radius-sm);
        background: var(--surface);
        color: var(--fg);
        padding: 7px 12px;
        cursor: pointer;
        font: inherit;
        font-size: 0.85rem;
      }
      .btn:hover { background: var(--surface-2); border-color: var(--line-strong); }
      .btn.primary {
        border-color: var(--cta);
        background: var(--cta);
        color: var(--cta-ink);
      }
      .btn.primary:hover { background: var(--cta-hover); border-color: var(--cta-hover); }
      .btn-google {
        display: inline-flex;
        align-items: center;
        gap: 10px;
      }
      .btn-google svg { flex-shrink: 0; }
      .btn-icon {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 30px;
        height: 30px;
        padding: 0;
        flex-shrink: 0;
      }
      .btn-icon svg {
        width: 15px;
        height: 15px;
        stroke: var(--fg);
        fill: none;
      }
      .btn-icon:hover { border-color: var(--cta); }
      .btn-icon:hover svg { stroke: var(--cta); }
      .btn-icon-danger:hover { border-color: var(--err); }
      .btn-icon-danger:hover svg { stroke: var(--err); }

      .auth-modal {
        position: fixed;
        inset: 0;
        z-index: 50;
        display: flex;
        align-items: center;
        justify-content: center;
        padding: 18px;
        background: rgba(18, 24, 21, 0.45);
        backdrop-filter: blur(4px);
      }

      .auth-card {
        width: min(520px, 100%);
        border: 1px solid var(--line);
        border-radius: var(--radius);
        background: var(--surface);
        box-shadow: var(--shadow);
        padding: 18px;
        display: grid;
        gap: 12px;
      }

      .auth-card h2 {
        margin: 0;
        font-size: 1.15rem;
      }

      .auth-actions {
        display: flex;
        gap: 8px;
        flex-wrap: wrap;
      }

      .auth-field {
        display: grid;
        gap: 6px;
      }

      .auth-field label {
        color: var(--muted);
        font-size: 0.78rem;
        text-transform: uppercase;
        letter-spacing: 0.04em;
      }

      .auth-field input {
        width: 100%;
        background: var(--surface);
        border: 1px solid var(--line);
        border-radius: var(--radius-sm);
        color: var(--fg);
        padding: 8px;
        font: inherit;
        font-size: 0.9rem;
      }
      .auth-field input:focus {
        outline: none;
        border-color: var(--cta);
      }

      .criteria {
        border: 1px solid var(--line);
        border-radius: var(--radius-sm);
        background: var(--surface-2);
        padding: 10px 12px;
        color: var(--muted);
        font-size: 0.82rem;
        line-height: 1.45;
      }

      .criteria ul {
        margin: 6px 0 0 18px;
        padding: 0;
      }

      .criteria li {
        margin: 2px 0;
      }

      pre.result {
        white-space: pre-wrap;
        word-break: break-word;
        font-family: var(--font-mono);
        font-size: 0.82rem;
        color: var(--muted);
        margin: 0;
      }

      .hidden { display: none; }

      @media (max-width: 900px) {
        .account-menu-mcp {
          flex-wrap: wrap;
        }
        .account-menu-mcp .mcp-url {
          order: 3;
          width: 100%;
          flex-basis: 100%;
        }

        .workspace {
          grid-template-columns: 1fr;
          grid-template-rows: auto auto 1fr;
          min-height: unset;
          overflow: visible;
        }
        /* Desktop's explicit grid-column:1/2/3 (see the rule above this
           media query) would otherwise force these into extra implicit
           columns instead of stacking under the single-column template. */
        .folder-rail,
        .list-panel,
        .detail-panel { grid-column: 1; }
        .folder-rail { width: auto; border-right: 0; border-bottom: 1px solid var(--line); }
        .list-panel { border-right: 0; border-bottom: 1px solid var(--line); min-height: 260px; }

        .dep-graph {
          height: 320px;
        }
      }

      /* Bulk upload review (epic:bulk-upload #97). Reuses the existing
         manifest-card/status/hint vocabulary; only the drop zone, the
         per-skill row and the dependency pills are new shapes. */
      /* `:not(.hidden)` is load-bearing: `.hidden { display: none }` is
         defined earlier in this file, so any later rule setting `display` on
         the same element wins the cascade on equal specificity and the zone
         stays on screen behind the review. */
      .bulk-drop-zone:not(.hidden) {
        display: grid;
        gap: 10px;
        justify-items: center;
      }
      .bulk-drop-zone {
        border: 1px dashed var(--line);
        background: var(--surface);
        padding: 32px 16px;
        text-align: center;
      }
      .bulk-drop-zone p { margin: 0; color: var(--muted); }
      .bulk-tree { list-style: none; margin: 8px 0 0; padding: 0; }
      .bulk-tree li { padding: 2px 0; color: var(--fg); }
      .bulk-issues { list-style: none; margin: 8px 0 0; padding: 0; display: grid; gap: 4px; }
      .bulk-issues li { font-size: 0.8rem; }
      .bulk-skill {
        border: 1px solid var(--line);
        background: var(--surface);
        padding: 10px 12px;
        display: grid;
        gap: 8px;
      }
      /* A collision is the only row that needs a decision, so it is the only
         one that gets a colour. */
      .bulk-skill.bulk-collision { border-color: var(--warn); }
      .bulk-skill-head { display: flex; gap: 10px; align-items: baseline; flex-wrap: wrap; }
      .bulk-deps { display: flex; gap: 6px; flex-wrap: wrap; }
      .bulk-dep {
        border: 1px solid var(--line);
        padding: 1px 6px;
        font-size: 0.75rem;
        color: var(--muted);
      }
      /* Where a dependency resolved to, at a glance: in this archive, already
         published, or nowhere yet. */
      .bulk-dep-batch { border-color: var(--cta); color: var(--cta); }
      .bulk-dep-unresolved { border-color: var(--err); color: var(--err); }
      .bulk-decision { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
      .bulk-decision label {
        font-size: 0.7rem;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        color: var(--dim);
      }
      .bulk-rename { min-width: 180px; }

      /* Account-level banner (a failed or publisher-less /publishers/me).
         No `display` declaration on purpose: a div is block already, and
         `.hidden { display: none }` sits earlier in this file, so declaring
         display here would win the cascade and the banner could never be
         hidden. */
      .account-notice {
        margin: 0 0 12px;
        padding: 10px 14px;
        border: 1px solid var(--warn);
        border-radius: var(--radius-sm);
        background: color-mix(in srgb, var(--warn) 12%, transparent);
        color: var(--warn);
        font-size: 0.85rem;
      }
      .account-notice.err {
        border-color: var(--err);
        background: color-mix(in srgb, var(--err) 12%, transparent);
        color: var(--err);
      }

      /* Signed-out prompt above the catalog list, where most visitors who
         reach the app actually are. `display: flex` only when not hidden,
         for the same cascade reason as .account-notice above. */
      .catalog-signup-cta:not(.hidden) {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 12px;
        flex-wrap: wrap;
        margin: 6px 12px;
        padding: 10px 14px;
        border: 1px solid var(--cta);
        border-radius: var(--radius-sm);
        background: var(--cta-soft);
        font-size: 0.85rem;
      }

      /* Publisher choice on the /connect consent screen (connect.js). Scoped
         under .auth-field to win over its label/input rules (uppercase
         labels, full-width inputs), which don't suit a radio list. */
      .auth-field .connect-publisher {
        display: flex;
        align-items: center;
        justify-content: flex-start;
        gap: 8px;
        padding: 4px 0;
        font-family: var(--font-mono);
        font-size: 0.85rem;
        text-transform: none;
        letter-spacing: normal;
        color: var(--fg);
        cursor: pointer;
      }
      .auth-field .connect-publisher input {
        width: auto;
        margin: 0;
      }
