/* ============================================================================
 * directory-ui.css — the Directory modal (directory-ui.js).
 *
 * Builds on the pbk-* primitives in playbooks-ui.css (buttons, switches, menus,
 * the markdown/code viewer) and adds only what a browsing surface needs: the
 * modal shell, the left rail, the card grid, and the two-pane pack detail with
 * its file tree.
 *
 * Load AFTER playbooks-ui.css.
 * ========================================================================== */

@keyframes dirIn {
    from { opacity: 0; transform: translateY(10px) scale(0.99); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}
@keyframes dirFade {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* ══════════════════════════════════════════════════════════════════════════
   SHELL
   ══════════════════════════════════════════════════════════════════════════ */

.dir-overlay {
    position: fixed;
    inset: 0;
    z-index: 100300;   /* above the settings modal, which is what opens it */
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: rgba(0, 0, 0, 0.58);
    backdrop-filter: blur(4px);
    animation: dirFade 0.18s var(--ui-ease, ease);
}

.dir-modal {
    display: flex;
    flex-direction: column;
    width: min(1040px, 100%);
    height: min(760px, 92vh);
    border: 1px solid var(--border-color, rgba(255, 255, 255, 0.1));
    border-radius: 18px;
    background: var(--primary-color, #17171a);
    box-shadow: 0 30px 80px rgba(0, 0, 0, 0.5);
    overflow: hidden;
    animation: dirIn 0.22s var(--ui-ease, ease);
}

.dir-head {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 16px 16px 14px 22px;
    flex: 0 0 auto;
}
.dir-head-title { flex: 1; font-size: 17px; font-weight: 600; color: var(--text-color); }

.dir-body { display: flex; flex: 1; min-height: 0; }

/* ── left rail ──────────────────────────────────────────────────────────── */
.dir-rail {
    flex: 0 0 178px;
    display: flex;
    flex-direction: column;
    gap: 3px;
    padding: 4px 12px 16px;
    border-right: 1px solid var(--border-color, rgba(255, 255, 255, 0.07));
}
.dir-rail-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 11px;
    border: none;
    border-radius: 9px;
    background: transparent;
    color: var(--subheading-color, #8a8a8a);
    font: inherit;
    font-size: 13.5px;
    font-weight: 600;
    text-align: left;
    cursor: pointer;
    transition: background 0.15s var(--ui-ease, ease), color 0.15s var(--ui-ease, ease);
}
.dir-rail-item:hover { background: var(--hover-bg, rgba(255, 255, 255, 0.05)); color: var(--text-color); }
.dir-rail-item.is-on {
    background: var(--hover-bg, rgba(255, 255, 255, 0.09));
    color: var(--text-color);
}
.dir-rail-item .material-symbols-rounded { font-size: 19px; }

/* ── main pane ──────────────────────────────────────────────────────────── */
.dir-main {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    /* The head above already pays 14px of bottom padding; adding only 4px here
       left the toolbar sitting almost against the title. 10px reads as one gap
       rather than "the toolbar slipped". */
    padding: 10px 22px 0;
}

.dir-toolbar {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 0 0 12px;
    flex: 0 0 auto;
}
.dir-search {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 1;
    min-width: 0;
    height: 38px;
    /* Asymmetric: the clear button carries its own 28px of hit area on the right,
       so matching the left padding there would push the glyph off-centre. */
    padding: 0 5px 0 12px;
    border: 1px solid var(--border-color, rgba(255, 255, 255, 0.1));
    border-radius: 10px;
    background: var(--secondary-color, rgba(255, 255, 255, 0.03));
    transition: border-color 0.16s ease, box-shadow 0.16s ease;
}
.dir-search:focus-within {
    border-color: var(--accent, #b7795c);
    box-shadow: 0 0 0 3px var(--accent-soft, rgba(183, 121, 92, 0.16));
}
.dir-search .material-symbols-rounded { font-size: 19px; color: var(--subheading-color, #8a8a8a); }
.dir-search input {
    flex: 1;
    min-width: 0;
    border: none;
    background: transparent;
    outline: none;
    color: var(--text-color);
    font: inherit;
    font-size: 13.5px;
    /* type="search" gets us the right mobile keyboard (an enter key that says
       "search") and Escape-to-clear semantics, but the UA decoration that comes
       with it is a second, differently-styled clear button sitting next to ours. */
    -webkit-appearance: none;
    appearance: none;
}
.dir-search input::-webkit-search-cancel-button,
.dir-search input::-webkit-search-decoration { -webkit-appearance: none; appearance: none; display: none; }

.dir-search-clear { flex: 0 0 auto; }
.dir-search-clear[hidden] { display: none; }

/* ── the scroller ─────────────────────────────────────────────────────────
 * NO TOP MASK. This used to carry
 *   mask-image: linear-gradient(to bottom, transparent 0, #000 8px)
 * to soften the scroll edge, and it was a straight mistake: the first thing in
 * the scroller is a section label ("CONNECTED 7") or the first row of cards, and
 * the gradient faded its top pixels to nothing on a resting, unscrolled panel.
 * It read as a shadow cutting the heading in half. A fade only ever makes sense
 * where content is actually passing under something; nothing overlaps here, so
 * the honest answer is a plain scroller with real breathing room at the top. */
.dir-scroll {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    /* Room for the focus ring / hover lift of the first row of cards, which
       otherwise clipped against the scroller's top edge. */
    padding: 2px 2px 24px;
    margin: 0 -2px;
    scrollbar-gutter: stable;
    overscroll-behavior: contain;
}

.dir-loading {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 60px 0;
    font-size: 13px;
    color: var(--subheading-color, #8a8a8a);
}
.dir-spinner {
    width: 15px; height: 15px;
    border: 2px solid var(--border-color, rgba(255, 255, 255, 0.2));
    border-top-color: var(--accent, #b7795c);
    border-radius: 50%;
    animation: pbkSpin 0.7s linear infinite;
    display: inline-block;
}
.dir-spinner.small { width: 13px; height: 13px; border-width: 2px; }

/* ══════════════════════════════════════════════════════════════════════════
   SHIMMER SKELETONS

   A catalogue is a network round trip behind a proxy, and on a cold cache it can
   take a second or two. A centred spinner throws the whole pane away and then
   slams a grid into place; a skeleton keeps the layout still and tells the truth
   about what is arriving. The shapes deliberately match the real cards' metrics,
   because a skeleton that resizes on load is worse than none.

   The sweep is one background-position animation on a gradient — no extra DOM,
   and it is dropped entirely under prefers-reduced-motion (below), where the
   plates simply sit there as static placeholders.
   ══════════════════════════════════════════════════════════════════════════ */
@keyframes dirShimmer {
    from { background-position: -180% 0; }
    to   { background-position: 180% 0; }
}

.dir-skel-line,
.dir-skel-tile {
    background-color: var(--secondary-color, rgba(255, 255, 255, 0.05));
    background-image: linear-gradient(
        100deg,
        transparent 20%,
        rgba(255, 255, 255, 0.07) 42%,
        rgba(255, 255, 255, 0.07) 58%,
        transparent 80%);
    background-size: 220% 100%;
    background-repeat: no-repeat;
    animation: dirShimmer 1.45s var(--ui-ease, ease-in-out) infinite;
}
body.light_mode .dir-skel-line,
body.light_mode .dir-skel-tile {
    background-color: rgba(16, 24, 40, 0.055);
    background-image: linear-gradient(
        100deg,
        transparent 20%,
        rgba(16, 24, 40, 0.06) 42%,
        rgba(16, 24, 40, 0.06) 58%,
        transparent 80%);
}

.dir-skel-line { height: 10px; border-radius: 999px; }
.dir-skel-line.title  { height: 13px; width: 46%; }
.dir-skel-line.meta   { height: 9px;  width: 30%; }
.dir-skel-line.body   { width: 100%; }
.dir-skel-line.body-2 { width: 78%; }
.dir-skel-line.chip   { height: 15px; width: 62px; border-radius: 999px; }
.dir-skel-tile { width: 38px; height: 38px; border-radius: 10px; flex: 0 0 auto; }

/* Same border, radius and padding as .dir-card / .dir-conn so nothing shifts by
   a pixel when the real cards replace these. */
.dir-skel-card {
    display: flex;
    flex-direction: column;
    gap: 9px;
    padding: 14px 15px 12px;
    border: 1px solid var(--border-color, rgba(255, 255, 255, 0.07));
    border-radius: 13px;
    background: var(--secondary-color, rgba(255, 255, 255, 0.014));
}
.dir-skel-head { display: flex; align-items: center; gap: 11px; }
.dir-skel-headtext { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 7px; }
.dir-skel-card[aria-hidden="true"] { pointer-events: none; }

/* ── load-more sentinel ───────────────────────────────────────────────────
 * The row an IntersectionObserver watches. It is a real, visible element rather
 * than a zero-height tripwire on purpose: when more pages exist the user should
 * see that the list continues, and a keyboard user needs something focusable to
 * reach — hence the fallback button inside it. */
.dir-more {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 9px;
    min-height: 46px;
    margin-top: 14px;
    font-size: 12.5px;
    color: var(--subheading-color, #8a8a8a);
}
.dir-more[hidden] { display: none; }
.dir-more-end {
    padding: 18px 0 4px;
    text-align: center;
    font-size: 11.5px;
    letter-spacing: 0.02em;
    color: var(--subheading-color, #8a8a8a);
    opacity: 0.7;
}
.dir-count {
    margin: 0 0 10px;
    font-size: 11.5px;
    color: var(--subheading-color, #8a8a8a);
}

.dir-empty {
    padding: 64px 24px;
    text-align: center;
}
.dir-empty > .material-symbols-rounded {
    font-size: 40px;
    color: var(--accent, #b7795c);
    opacity: 0.7;
}
.dir-empty h4 { margin: 12px 0 6px; font-size: 16px; font-weight: 600; color: var(--text-color); }
.dir-empty p {
    margin: 0 auto 16px;
    max-width: 420px;
    font-size: 13px;
    line-height: 1.6;
    color: var(--subheading-color, #8a8a8a);
}
.dir-empty.small { padding: 44px 20px; }
.dir-empty.small p { margin-bottom: 0; }

/* ══════════════════════════════════════════════════════════════════════════
   PLAYBOOK CARDS
   ══════════════════════════════════════════════════════════════════════════ */

.dir-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(272px, 1fr));
    gap: 12px;
}

.dir-card {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 14px 15px 12px;
    border: 1px solid var(--border-color, rgba(255, 255, 255, 0.09));
    border-radius: 13px;
    background: var(--secondary-color, rgba(255, 255, 255, 0.022));
    cursor: pointer;
    transition: border-color 0.16s var(--ui-ease, ease), transform 0.16s var(--ui-ease, ease),
                background 0.16s var(--ui-ease, ease);
}
.dir-card:hover {
    border-color: var(--accent-ring, rgba(183, 121, 92, 0.38));
    background: var(--hover-bg, rgba(255, 255, 255, 0.04));
    transform: translateY(-1px);
}
.dir-card:focus-visible { outline: 2px solid var(--accent, #b7795c); outline-offset: 2px; }
.dir-card.is-installed { border-color: var(--accent-ring, rgba(183, 121, 92, 0.3)); }

/* GRID, NOT FLEX, and the reason matters.
 * `flex: 1; min-width: 0` on the name is the textbook recipe for "grow, but let
 * me ellipsise", and it works right up until any stylesheet loaded later asserts
 * a `flex` value on this element — at which point the name collapses to a couple
 * of characters and the card looks broken. A grid track of `minmax(0, 1fr)` is
 * declared on the PARENT, so the column's size is not something a child-level
 * override can take away. Same treatment on .dir-conn-head, for the same reason. */
.dir-card-top {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: start;
    column-gap: 10px;
}
.dir-card-name {
    min-width: 0;
    font-size: 14px;
    font-weight: 600;
    line-height: 1.4;
    color: var(--text-color);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.dir-card-action {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 27px; height: 27px;
    flex: 0 0 auto;
    border: 1px solid var(--border-color, rgba(255, 255, 255, 0.14));
    border-radius: 8px;
    background: transparent;
    color: var(--subheading-color, #8a8a8a);
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.dir-card-action:hover {
    background: var(--accent, #b7795c);
    border-color: transparent;
    color: var(--accent-contrast, #fff);
}
.dir-card-action .material-symbols-rounded { font-size: 17px; }
.dir-card-action.is-installed { color: var(--accent, #b7795c); border-color: var(--accent-ring, rgba(183, 121, 92, 0.4)); }
.dir-card-action:disabled { opacity: 0.6; cursor: default; }
.dir-card-action.busy { pointer-events: none; }

.dir-card-by {
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: 11.5px;
    color: var(--subheading-color, #8a8a8a);
}
.dir-verified { font-size: 14px !important; color: var(--accent, #b7795c); }
.dir-card-installs { display: inline-flex; align-items: center; gap: 2px; margin-left: 2px; }
.dir-card-installs .material-symbols-rounded { font-size: 13px !important; }

.dir-card-desc {
    margin: 2px 0 0;
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--subheading-color, #8a8a8a);
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    min-height: 2.9em;   /* keeps card heights even when a description is one line */
}

.dir-card-foot { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; min-height: 18px; }
.dir-chip {
    display: inline-flex;
    align-items: center;
    padding: 1px 8px;
    border-radius: 999px;
    background: var(--accent-soft, rgba(183, 121, 92, 0.16));
    color: var(--accent, #b7795c);
    font-size: 10.5px;
    font-weight: 600;
}
.dir-chip.subtle { background: rgba(128, 128, 128, 0.14); color: var(--subheading-color, #8a8a8a); }
.dir-chip.warn { background: rgba(217, 119, 6, 0.16); color: #d97706; }

/* ══════════════════════════════════════════════════════════════════════════
   PACK DETAIL
   ══════════════════════════════════════════════════════════════════════════ */

.dir-detail {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: 0;
    padding-bottom: 20px;
    overflow-y: auto;
    animation: dirFade 0.18s var(--ui-ease, ease);
}

/* The title block was an unclassed <div> with no flex growth, so it shrank to
   its own content and the Install button sat glued to the end of the byline
   instead of at the right edge of the pane. `flex: 1` on the titles plus an auto
   left margin on the actions pins the button to the right whatever the title's
   length — and keeps it there when the byline is short. */
.dir-detail-head {
    display: flex;
    align-items: flex-start;
    gap: 14px;
    padding-bottom: 2px;
}
.dir-detail-titles { flex: 1 1 auto; min-width: 0; }
.dir-detail-title { font-size: 19px; font-weight: 600; line-height: 1.25; color: var(--text-color); }
.dir-detail-by {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-top: 3px;
    font-size: 12.5px;
    color: var(--subheading-color, #8a8a8a);
    flex-wrap: wrap;
}
.dir-detail-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 0 0 auto;
    margin-left: auto;
    padding-top: 2px;
}

.dir-detail-desc {
    margin: 14px 0 0;
    font-size: 13px;
    line-height: 1.62;
    color: var(--text-color);
}
.dir-detail-when,
.dir-detail-readme {
    margin: 8px 0 0;
    font-size: 12.5px;
    line-height: 1.6;
    color: var(--subheading-color, #8a8a8a);
}

/* ── two-pane file browser ──────────────────────────────────────────────── */
.dir-files {
    display: flex;
    gap: 14px;
    margin-top: 18px;
    min-height: 0;
}

.dir-filetree {
    flex: 0 0 216px;
    display: flex;
    flex-direction: column;
    gap: 2px;
    max-height: 400px;
    padding: 6px;
    border: 1px solid var(--border-color, rgba(255, 255, 255, 0.09));
    border-radius: 12px;
    background: var(--secondary-color, rgba(255, 255, 255, 0.02));
    overflow-y: auto;
}
.dir-file {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 9px;
    border: none;
    border-radius: 8px;
    background: transparent;
    color: var(--subheading-color, #8a8a8a);
    font: inherit;
    font-size: 12.5px;
    text-align: left;
    cursor: pointer;
    transition: background 0.13s ease, color 0.13s ease;
}
.dir-file:hover { background: var(--hover-bg, rgba(255, 255, 255, 0.06)); color: var(--text-color); }
.dir-file.is-on {
    background: var(--accent-soft, rgba(183, 121, 92, 0.16));
    color: var(--text-color);
    font-weight: 600;
}
.dir-file .material-symbols-rounded { font-size: 16px; flex: 0 0 auto; opacity: 0.8; }
.dir-file-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dir-file-size { flex: 0 0 auto; font-size: 10.5px; opacity: 0.75; }

.dir-filepane {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    border: 1px solid var(--border-color, rgba(255, 255, 255, 0.09));
    border-radius: 12px;
    background: var(--highlight-background, rgba(0, 0, 0, 0.18));
    overflow: hidden;
}
.dir-filepane-bar {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border-bottom: 1px solid var(--border-color, rgba(255, 255, 255, 0.08));
}
.dir-filepane-path {
    flex: 1;
    min-width: 0;
    font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
    font-size: 11.5px;
    color: var(--subheading-color, #8a8a8a);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.dir-filepane-body {
    flex: 1;
    max-height: 352px;
    overflow-y: auto;
    padding: 0 16px 16px;
}
/* Inside the pane the code block already has the pane's border and background. */
.dir-filepane-body .pbk-raw,
.dir-filepane-body .pbk-code {
    border: none;
    background: transparent;
    padding: 14px 0 0;
}

/* ══════════════════════════════════════════════════════════════════════════
   CONNECTORS
   ══════════════════════════════════════════════════════════════════════════ */

.dir-conn-section { margin-bottom: 26px; }
/* The last section used to leave 26px of dead space above the scroller's own
   24px of bottom padding — a 50px hole that read as "something failed to load". */
.dir-conn-section:last-child { margin-bottom: 0; }
.dir-section-label {
    display: flex;
    align-items: center;
    gap: 7px;
    margin: 0 0 11px;
    font-size: 11.5px;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--subheading-color, #8a8a8a);
}
.dir-section-count {
    padding: 0 6px;
    border-radius: 999px;
    background: rgba(128, 128, 128, 0.16);
    font-size: 10.5px;
    letter-spacing: 0;
}

/* ── size guard ───────────────────────────────────────────────────────────
 * Same reason as the equivalent block in playbooks-ui.css: the embedded
 * Connectors panel renders inside the Settings modal, whose stylesheet has a
 * legacy `.data-window .content button:not(…)` rule that forces `width: 100px`
 * and a navy fill. That rule now exempts the `dir-*` namespace; asserting the
 * one property whose loss breaks LAYOUT rather than colour means this component
 * no longer depends on a `:not()` list in another file staying correct. */
/* Deliberately NOT `.dir-conn button` any more.
 *
 * A descendant selector like that scores higher than the plain `.dir-conn-name`
 * class rule that owns the connector title, so its `width: auto` outranked the
 * title's own sizing — a guard against another file's layout accident became a
 * layout accident of its own. Every control is named explicitly here, and
 * `width` is left to whichever component rule owns it. */
.dir-card-action,
.dir-conn-name,
.dir-pop,
.dir-cdetail-actions button,
.dir-cdetail-docs {
    min-width: 0;
    box-shadow: none;
}

.dir-pop,
.dir-cdetail-actions button,
.dir-cdetail-docs {
    width: auto;
}

.dir-card-action {
    width: 26px;
}

/* ── icon tiles ───────────────────────────────────────────────────────────
 * Two kinds, deliberately different.
 *
 * `.is-brand` holds a real logo, so it gets a near-white plate: Gmail's
 * red-on-white envelope and GitHub's near-black Octocat are drawn for light
 * backgrounds and vanish on a dark surface. Recolouring a logo to suit a theme is
 * not an option — a wrong-coloured brand mark looks broken and is a trademark
 * problem — so the tile supplies the background the mark expects instead.
 *
 * `.is-glyph` holds a category pictogram (SMTP, IMAP, REST, webhooks) or the
 * Material fallback, and keeps the accent-tinted tile. The visual difference is
 * the honest signal: a plate means "this is a product", a tint means "this is a
 * kind of thing". */
.dir-ico {
    --tile: var(--accent, #b7795c);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    flex: 0 0 auto;
    border-radius: 10px;
    overflow: hidden;
}
.dir-ico.is-glyph {
    /* colour-mix keeps a brand accent readable as a tint on either theme without
       shipping a second colour per connector. */
    background: color-mix(in srgb, var(--tile) 18%, transparent);
    color: var(--tile);
}
.dir-ico.is-brand {
    background: #fff;
    /* A hairline keeps a white plate from bleeding into a light background. */
    box-shadow: inset 0 0 0 1px rgba(16, 24, 40, 0.1);
}
.dir-ico .material-symbols-rounded { font-size: 20px; }
.dir-ico-svg { width: 62%; height: 62%; display: block; }
/* Logos already carry their own padding; category glyphs do not. */
.dir-ico.is-glyph .dir-ico-svg { width: 58%; height: 58%; }

.dir-ico.sm { width: 26px; height: 26px; border-radius: 7px; }
.dir-ico.sm .material-symbols-rounded { font-size: 15px; }
.dir-ico.lg {
    width: 54px;
    height: 54px;
    border-radius: 14px;
}
.dir-ico.lg .material-symbols-rounded { font-size: 28px; }

/* ── tier badge ───────────────────────────────────────────────────────────
 * "Official" and "Community" are not decoration: they say who wrote the code a
 * credential is about to be handed to. A bare tick could not carry that. */
.dir-badge {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    padding: 1px 7px 1px 5px;
    border-radius: 999px;
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: 0.01em;
    white-space: nowrap;
}
.dir-badge.official {
    background: var(--accent-soft, rgba(183, 121, 92, 0.16));
    color: var(--accent, #b7795c);
}
.dir-badge.community {
    padding: 1px 8px;
    background: rgba(128, 128, 128, 0.16);
    color: var(--subheading-color, #8a8a8a);
}
.dir-badge .material-symbols-rounded { font-size: 13px !important; }

/* ── popular strip ──────────────────────────────────────────────────────── */
.dir-pop-row { display: flex; flex-wrap: wrap; gap: 8px; }
.dir-pop {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    padding: 7px 8px 7px 9px;
    border: 1px solid var(--border-color, rgba(255, 255, 255, 0.1));
    border-radius: 11px;
    background: var(--secondary-color, rgba(255, 255, 255, 0.022));
    color: var(--text-color);
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    transition: border-color 0.16s var(--ui-ease, ease), background 0.16s var(--ui-ease, ease);
}
.dir-pop:hover {
    border-color: var(--accent-ring, rgba(183, 121, 92, 0.38));
    background: var(--hover-bg, rgba(255, 255, 255, 0.045));
}
.dir-pop:focus-visible { outline: 2px solid var(--accent, #b7795c); outline-offset: 2px; }
.dir-pop-name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.dir-pop-add {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    border-radius: 7px;
    color: var(--subheading-color, #8a8a8a);
    transition: background 0.15s ease, color 0.15s ease;
}
.dir-pop:hover .dir-pop-add { background: var(--accent, #b7795c); color: var(--accent-contrast, #fff); }
.dir-pop-add .material-symbols-rounded { font-size: 17px; }

/* ── connector cards ──────────────────────────────────────────────────────
 * Vertical rather than the old horizontal strip. The card has four things to say
 * — what it is, who made it, what it does, and whether it is working — and a
 * single row could only ever fit two of them before the description got
 * ellipsised into uselessness. */
.dir-conn-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: 10px;
}

.dir-conn {
    display: flex;
    flex-direction: column;
    gap: 9px;
    padding: 13px 14px 11px;
    border: 1px solid var(--border-color, rgba(255, 255, 255, 0.09));
    border-radius: 13px;
    background: var(--secondary-color, rgba(255, 255, 255, 0.022));
    cursor: pointer;
    transition: border-color 0.16s var(--ui-ease, ease), background 0.16s var(--ui-ease, ease),
                transform 0.16s var(--ui-ease, ease);
}
.dir-conn:hover {
    border-color: var(--accent-ring, rgba(183, 121, 92, 0.34));
    background: var(--hover-bg, rgba(255, 255, 255, 0.04));
    transform: translateY(-1px);
}
.dir-conn:focus-visible { outline: 2px solid var(--accent, #b7795c); outline-offset: 2px; }
.dir-conn.is-on { border-color: var(--accent-ring, rgba(183, 121, 92, 0.28)); }
.dir-conn.is-soon { opacity: 0.6; cursor: pointer; }
.dir-conn.is-soon:hover { border-color: var(--border-color, rgba(255, 255, 255, 0.09)); transform: none; }

/* THREE FIXED-INTENT COLUMNS: logo | title (all remaining width) | controls.
 *
 * This was `display: flex` with `flex: 1; min-width: 0` on the title, and the
 * connector names rendered as "G…", "Gi…", "W…" — the title column had collapsed
 * to about 25px while the toggle stayed pinned right, so every card in the grid
 * showed one letter and an ellipsis. Declaring the track as `minmax(0, 1fr)` on
 * the PARENT fixes the width where nothing on a child can override it, and the
 * `minmax(0, …)` lower bound is what lets it shrink far enough to ellipsise
 * instead of pushing the toggle off the card.
 *
 * align-items: center rather than flex-start: the 38px logo tile against a
 * two-line title block (name + badge row) is what "icons not lined up" was —
 * top-aligning a square tile to a text baseline leaves it visibly high. */
.dir-conn-head {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    column-gap: 11px;
}
.dir-conn-title { min-width: 0; }
/* A real <button> so keyboard and screen-reader users get one unambiguous "open
   this connector" control, but styled as the heading it reads as. The card's own
   hover/press states carry the affordance, so this stays visually quiet until it
   is focused. */
/* The truncation happens on the INNER span, not on the button.
 * A <button> sizes itself shrink-to-fit even at `display: block`, so
 * `overflow: hidden` on the button itself is at the mercy of whatever width the
 * button decided to be. An explicit `width: 100%` against a definite grid track,
 * with the ellipsis on a plain span inside, is deterministic. */
.dir-conn-name {
    display: flex;
    align-items: center;
    width: 100%;
    max-width: 100%;
    padding: 0;
    border: none;
    border-radius: 4px;
    background: none;
    font: inherit;
    font-size: 13.5px;
    font-weight: 600;
    line-height: 1.35;
    text-align: left;
    color: var(--text-color);
    cursor: pointer;
    overflow: hidden;
}
.dir-conn-name-text {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.dir-conn-name:focus-visible { outline: 2px solid var(--accent, #b7795c); outline-offset: 3px; }
.dir-conn-meta {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-top: 4px;
    min-width: 0;
    /* No wrap: a badge dropping to a second line changed the card's height and
       broke the row's alignment. Anything that does not fit is clipped, which is
       the correct trade for a badge that repeats the tier already shown. */
    overflow: hidden;
}
.dir-conn-meta > * { flex: 0 0 auto; }
.dir-conn-cat {
    font-size: 11px;
    color: var(--subheading-color, #8a8a8a);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.dir-conn-actions { display: flex; align-items: center; justify-content: flex-end; gap: 4px; }

.dir-conn-desc {
    margin: 0;
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--subheading-color, #8a8a8a);
    overflow: hidden;
    text-overflow: ellipsis;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    /* Fixed height keeps every card in a row the same height when one description
       is shorter than the others. */
    min-height: 2.9em;
}

/* The status line is the answer to "is this actually working right now", which is
   different from "is it installed" — a connector can be connected and switched
   off, or connected and missing a permission. */
.dir-conn-status {
    display: flex;
    align-items: center;
    gap: 6px;
    min-height: 16px;
    font-size: 11.5px;
    color: var(--subheading-color, #8a8a8a);
}
.dir-conn-status.is-empty { min-height: 0; }
.dir-conn-status-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dir-conn-status.on { color: #3fb950; }
.dir-conn-status.warn { color: #d97706; }
.dir-conn-status.off, .dir-conn-status.soon { color: var(--subheading-color, #8a8a8a); }
.dir-dot {
    width: 6px; height: 6px;
    flex: 0 0 auto;
    border-radius: 50%;
    background: currentColor;
}

/* ══════════════════════════════════════════════════════════════════════════
   CONNECTOR DETAIL

   A connector hands a third party's data to a model, or hands us a credential.
   The questions that deserve an answer before the Connect button — what can it
   read, what can it do, who wrote it, what must I supply — do not fit on a card,
   which is the entire reason this view exists.
   ══════════════════════════════════════════════════════════════════════════ */

.dir-cdetail { padding-right: 4px; }

.dir-cdetail-head {
    display: flex;
    align-items: flex-start;
    gap: 14px;
    padding-bottom: 4px;
}
.dir-cdetail-headmain { flex: 1; min-width: 0; }
.dir-cdetail-titlerow { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.dir-cdetail-title {
    margin: 0;
    font-size: 19px;
    font-weight: 600;
    line-height: 1.25;
    color: var(--text-color);
}
.dir-cdetail-tagline {
    margin: 6px 0 0;
    font-size: 13px;
    line-height: 1.55;
    color: var(--subheading-color, #8a8a8a);
}
.dir-cdetail-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 0 0 auto;
    padding-top: 2px;
}

/* ── inline notice ────────────────────────────────────────────────────────
 * One line, above the body copy: the single thing standing between this
 * connector and working. Anything more than one and it stops being read. */
.dir-note {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    margin: 16px 0 0;
    padding: 11px 13px;
    border: 1px solid var(--border-color, rgba(255, 255, 255, 0.12));
    border-radius: 11px;
    background: var(--secondary-color, rgba(255, 255, 255, 0.03));
    font-size: 12.5px;
    line-height: 1.55;
    color: var(--text-color);
}
.dir-note .material-symbols-rounded { font-size: 19px; flex: 0 0 auto; margin-top: -1px; }
.dir-note.warn {
    border-color: rgba(217, 119, 6, 0.32);
    background: rgba(217, 119, 6, 0.09);
}
.dir-note.warn .material-symbols-rounded { color: #d97706; }
.dir-note.info .material-symbols-rounded { color: var(--accent, #b7795c); }

.dir-cdetail-body {
    margin: 16px 0 0;
    font-size: 13.5px;
    line-height: 1.68;
    color: var(--text-color);
}

.dir-cdetail-by {
    margin-top: 16px;
    font-size: 12.5px;
    color: var(--subheading-color, #8a8a8a);
}
.dir-cdetail-by b { font-weight: 600; color: var(--text-color); }
.dir-cdetail-link {
    display: inline-flex;
    align-items: center;
    gap: 3px;
    margin-left: 8px;
    color: var(--accent, #b7795c);
    text-decoration: none;
}
.dir-cdetail-link:hover { text-decoration: underline; }
.dir-cdetail-link .material-symbols-rounded { font-size: 14px; }
.dir-cdetail-caution {
    margin: 8px 0 0;
    font-size: 12px;
    line-height: 1.55;
    color: var(--subheading-color, #8a8a8a);
}

/* ── blocks ─────────────────────────────────────────────────────────────── */
.dir-cdetail-block {
    margin-top: 24px;
    padding-top: 18px;
    border-top: 1px solid var(--border-color, rgba(255, 255, 255, 0.07));
}
.dir-block-head {
    display: flex;
    align-items: center;
    gap: 7px;
    margin-bottom: 11px;
    font-size: 13px;
    font-weight: 600;
    color: var(--text-color);
}
.dir-block-note {
    margin: 0;
    font-size: 12.5px;
    line-height: 1.6;
    color: var(--subheading-color, #8a8a8a);
}

/* Tool names are identifiers the model calls, so they are set in mono — it stops
   `sendEmail` reading as prose and makes the list scannable. */
.dir-tool-list { display: flex; flex-direction: column; gap: 6px; }
.dir-tool {
    display: flex;
    align-items: baseline;
    gap: 10px;
    flex-wrap: wrap;
    padding: 8px 11px;
    border: 1px solid var(--border-color, rgba(255, 255, 255, 0.08));
    border-radius: 9px;
    background: var(--secondary-color, rgba(255, 255, 255, 0.022));
}
.dir-tool-name {
    font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
    font-size: 12px;
    font-weight: 600;
    color: var(--accent, #b7795c);
    white-space: nowrap;
}
.dir-tool-desc {
    flex: 1;
    min-width: 160px;
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--subheading-color, #8a8a8a);
}

.dir-perm-list { margin: 0; padding: 0; list-style: none; }
.dir-perm-list li {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 4px 0;
    font-size: 12.5px;
    line-height: 1.55;
    color: var(--text-color);
}
.dir-perm-list .material-symbols-rounded {
    font-size: 18px;
    flex: 0 0 auto;
    color: var(--accent, #b7795c);
    margin-top: -1px;
}

.dir-step-list {
    margin: 10px 0 0;
    padding-left: 20px;
    font-size: 12.5px;
    line-height: 1.65;
    color: var(--subheading-color, #8a8a8a);
}
.dir-step-list li { margin: 5px 0; }
.dir-step-list li::marker { color: var(--accent, #b7795c); font-weight: 600; }

.dir-chip-row { display: flex; flex-wrap: wrap; gap: 6px; }

.dir-cdetail-docs {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    margin-top: 22px;
    padding: 8px 13px;
    border: 1px solid var(--border-color, rgba(255, 255, 255, 0.12));
    border-radius: 9px;
    color: var(--text-color);
    font-size: 12.5px;
    font-weight: 600;
    text-decoration: none;
    transition: border-color 0.16s ease, background 0.16s ease;
}
.dir-cdetail-docs:hover {
    border-color: var(--accent-ring, rgba(183, 121, 92, 0.4));
    background: var(--hover-bg, rgba(255, 255, 255, 0.045));
}
.dir-cdetail-docs .material-symbols-rounded { font-size: 17px; }
.dir-cdetail-docs .material-symbols-rounded:last-child { font-size: 14px; opacity: 0.65; }

.dir-cdetail-foot {
    margin-top: 22px;
    font-size: 12px;
    color: var(--subheading-color, #8a8a8a);
}
.dir-cdetail-foot.on { color: #3fb950; }
.dir-cdetail-foot.warn { color: #d97706; }

/* A larger switch in the detail header: it is the primary control on that row,
   sitting beside a button, and the 40px card size looks incidental there. */
.pbk-switch.lg { width: 46px; height: 26px; }
.pbk-switch.lg span::after { width: 20px; height: 20px; }
.pbk-switch.lg input:checked + span::after { transform: translateX(20px); }

/* ══════════════════════════════════════════════════════════════════════════
   EMBEDDED PANEL (Settings → Connectors)

   The same renderer, inline. The modal's fixed height and scroll container do
   not apply here — the settings panel already scrolls — so those constraints
   are released rather than nested.
   ══════════════════════════════════════════════════════════════════════════ */

.dir-embedded { display: flex; flex-direction: column; animation: dirFade 0.18s var(--ui-ease, ease); }
.dir-embedded .dir-toolbar { padding-bottom: 14px; }
.dir-embedded .dir-scroll {
    overflow: visible;
    max-height: none;
    padding: 0;
    margin: 0;
}
/* One column, so the same card has roughly half the width it gets in the modal.
   Everything inside it still has to fit at that width — which is exactly why the
   header is a grid with a minmax(0, 1fr) title track rather than flex. */
.dir-embedded .dir-conn-grid { grid-template-columns: 1fr; }
.dir-embedded .dir-empty { padding: 40px 18px; }
/* The settings panel is the scroll container here, so the sentinel has to watch
   that scroller instead of .dir-scroll. directory-ui.js passes `root: null`
   (the viewport) when embedded, which covers both. */
.dir-embedded .dir-more { margin-top: 10px; }

/* ══════════════════════════════════════════════════════════════════════════
   RESPONSIVE
   ══════════════════════════════════════════════════════════════════════════ */

@media (max-width: 860px) {
    .dir-overlay { padding: 0; }
    .dir-modal { width: 100%; height: 100%; border-radius: 0; border: none; }
    /* The rail becomes a horizontal tab strip: two items do not earn a sidebar on
       a phone, and the grid needs the width more. */
    .dir-body { flex-direction: column; }
    .dir-rail {
        flex: 0 0 auto;
        flex-direction: row;
        gap: 6px;
        padding: 0 16px 12px;
        border-right: none;
        border-bottom: 1px solid var(--border-color, rgba(255, 255, 255, 0.07));
        overflow-x: auto;
    }
    .dir-rail-item { flex: 0 0 auto; }
    .dir-main { padding: 12px 16px 0; }
    .dir-grid, .dir-conn-grid { grid-template-columns: 1fr; }

    /* The detail header stacks: a 54px logo, a title and two controls do not fit
       on one phone-width row without the actions being squeezed to nothing. */
    .dir-cdetail-head { flex-wrap: wrap; }
    .dir-cdetail-actions { width: 100%; padding-top: 4px; }
    .dir-cdetail-actions .pbk-btn { flex: 1; justify-content: center; }
    .dir-pop { flex: 1 1 140px; }
    .dir-tool { flex-direction: column; gap: 3px; align-items: flex-start; }
    .dir-tool-desc { min-width: 0; }

    /* A 216px tree beside a viewer is unusable at this width, so the tree becomes
       a full-width scroller above the file. */
    .dir-files { flex-direction: column; }
    .dir-filetree { flex: 0 0 auto; max-height: 172px; }
    .dir-filepane-body { max-height: 300px; }
    .dir-detail-head { flex-wrap: wrap; }
    .dir-detail-actions { width: 100%; }
    .dir-detail-actions .pbk-btn { flex: 1; justify-content: center; }
}

@media (prefers-reduced-motion: reduce) {
    .dir-overlay, .dir-modal, .dir-detail { animation: none; }
    .dir-card, .dir-conn, .dir-rail-item, .dir-file, .dir-pop { transition: none; }
    .dir-conn:hover { transform: none; }
    .dir-spinner { animation-duration: 1.6s; }
    /* The plates still communicate "content is coming" without the sweep. */
    .dir-skel-line, .dir-skel-tile { animation: none; background-image: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   LIGHT THEME

   Only the pieces that assume a dark surface need saying twice. The brand plate
   is the interesting one: it stays white in both themes because a logo's colours
   are fixed, so on a light background it needs a real border rather than the
   near-invisible hairline that is enough on dark.
   ══════════════════════════════════════════════════════════════════════════ */
body.light_mode .dir-ico.is-brand {
    background: #fff;
    box-shadow: inset 0 0 0 1px rgba(16, 24, 40, 0.14);
}
body.light_mode .dir-conn-status.on,
body.light_mode .dir-cdetail-foot.on { color: #1a7f37; }
body.light_mode .dir-overlay { background: rgba(16, 24, 40, 0.42); }
