/* ============================================================================
 * Mycelium One — Public directory (DIR-5 redesign).
 * Direction: minimal / grid-aligned / modern — Airbnb × Zillow × Apple Maps.
 * Signature: the biogeographic-realm colour language — a site carries its
 * bioregion's realm hue on the card rail (--mln-realm), the region dot, the map
 * pin, and the legend swatch. Brand primary #00594c, accent #bc4e2c.
 * FONTS: CSP blocks external font CDNs (font-src 'self' data:), so we use the
 * platform system stack (system-ui → SF Pro on macOS, matching Apple Maps) — no
 * fonts.googleapis.com, no new binary. Display weight + tracking carry the
 * headline character.
 * ==========================================================================*/
:root {
    --mln-primary: #00594c;
    --mln-primary-700: #04463b;
    --mln-accent: #bc4e2c;
    --mln-ink: #132420;
    --mln-muted: #5c6b65;
    --mln-faint: #93a099;
    --mln-bg: #f4f7f5;
    --mln-surface: #ffffff;
    --mln-surface-2: #eef3f0;
    --mln-line: #e3ebe7;
    --mln-line-2: #d6e0db;
    --mln-shadow-sm: 0 1px 2px rgba(19,36,32,.06), 0 1px 3px rgba(19,36,32,.08);
    --mln-shadow-md: 0 4px 14px rgba(19,36,32,.10);
    --mln-shadow-lg: 0 10px 34px rgba(19,36,32,.16);
    --mln-r: 14px;
    --mln-r-sm: 10px;
    --mln-r-pill: 999px;
    /* Mycelium view: side-panel width. The zoom cluster offsets by this so it
       lands at the bottom-right of the GRAPH rather than under the panel. */
    /* 260, was 300: the Mycelium graph is the content here and the side panel is
       reference material beside it. 40px back is a visible gain on a laptop. */
    --mln-net-side-w: 260px;
    --mln-disp: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
    --mln-ui: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

.mln-directory {
    display: flex;
    min-height: 100vh;
    flex-direction: column;
    margin: 0;
    font-family: var(--mln-ui);
    color: var(--mln-ink);
    background: var(--mln-bg);
    -webkit-font-smoothing: antialiased;
    line-height: 1.5;
    /* V1.1 — sticky footer: on short pages (e.g. the wishlist) the footer was
       riding up under the content. Flex-column body + growing main pins it to
       the viewport bottom while still flowing below tall content. */
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}
/* grows to fill remaining height on short pages; never shrinks below the
   full-height split/map/clusters views (which set their own 100vh-based height). */
.mln-dir-main { flex: 1 0 auto; }

/* ---------- Top nav ---------- */
.mln-dir-topnav {
    display: flex; align-items: center; justify-content: space-between; gap: 20px;
    padding: 0 1.4rem; height: 64px; background: var(--mln-surface);
    border-bottom: 1px solid var(--mln-line); position: sticky; top: 0; z-index: 1200;
}
.mln-dir-brand-link { display: flex; align-items: center; gap: 0.6rem; text-decoration: none; color: var(--mln-primary); }
.mln-dir-brand-link img { height: 30px; }
.mln-dir-brand-name { font-family: var(--mln-disp); font-weight: 700; font-size: 1.15rem; letter-spacing: -0.01em; }
.mln-dir-nav-actions { display: flex; align-items: center; gap: 0.35rem; }

/* Pre-existing mobile defect, surfaced by the events-list viewport spec: the topnav is
   a no-wrap flex row with a fixed 64px height, so at 420px the brand plus ~273px of
   actions rendered 439px wide and the whole public directory scrolled sideways.
   Scoped to narrow viewports, so the desktop bar is untouched. */
@media (max-width: 640px) {
    .mln-dir-topnav {
        flex-wrap: wrap; height: auto; min-height: 64px;
        padding: 0.5rem 1rem; row-gap: 0.4rem;
    }
    .mln-dir-nav-actions { flex-wrap: wrap; justify-content: flex-end; }
}
.mln-dir-portal-link {
    display: inline-flex; align-items: center; gap: 0.4rem;
    color: var(--mln-muted); text-decoration: none; font-weight: 600; font-size: 0.9rem;
    padding: 0.5rem 0.7rem; border-radius: var(--mln-r-pill);
}
.mln-dir-portal-link:hover { color: var(--mln-primary); background: var(--mln-surface-2); }
.mln-dir-portal-link--cta {
    border: 1px solid var(--mln-line-2); color: var(--mln-ink);
}
.mln-dir-portal-link--cta:hover { border-color: var(--mln-primary); color: var(--mln-primary); background: transparent; }

/*
 * Quiet chrome buttons (Log out, locale switcher).
 *
 * These are PrimeFaces buttons sitting among plain <a> nav links, and the Diamond
 * theme paints .ui-state-default solid brand green — so `p-button-text` alone left
 * Log out looking like the page's primary call to action, which is the last thing
 * it should be. Matched to .mln-dir-portal-link instead: transparent, muted, with
 * the same pill hover. !important is needed because the theme's .ui-state-default
 * selector is more specific than a single class.
 */
.mln-dir-quiet-btn.ui-button,
.mln-dir-quiet-btn .ui-button {
    background: transparent !important;
    background-image: none !important;
    border: 0 !important;
    box-shadow: none !important;
    color: var(--mln-muted) !important;
    font-weight: 600;
    font-size: 0.9rem;
    padding: 0.5rem 0.7rem;
    border-radius: var(--mln-r-pill);
    transition: color 0.15s ease, background-color 0.15s ease;
}
/*
 * PrimeFaces positions the icon absolutely and reserves 2.1em of left padding on
 * the label to clear it. At this size the icon ends around 22px while the label
 * starts at 32px, which reads as a gap rather than an icon-with-label. Pull the
 * label in to sit just clear of the icon, and anchor the icon to the same inset
 * as the button's own padding so the two move together.
 *
 * The label's vertical padding is zeroed as well: it stacks on top of the
 * button's, which is what made these ~49px tall and pushed the footer past the
 * fold.
 */
.mln-dir-quiet-btn .ui-button-icon-left { left: 0.6rem !important; }
.mln-dir-quiet-btn .ui-button-text { padding: 0 0 0 1.4rem !important; }
.mln-dir-quiet-btn.ui-button:hover,
.mln-dir-quiet-btn .ui-button:hover {
    background: var(--mln-surface-2) !important;
    color: var(--mln-primary) !important;
}
.mln-dir-quiet-btn.ui-button:focus-visible,
.mln-dir-quiet-btn .ui-button:focus-visible {
    outline: 2px solid var(--mln-primary);
    outline-offset: 2px;
}
/* The icon inherits the label colour rather than the theme's white. */
.mln-dir-quiet-btn .ui-button-icon-left,
.mln-dir-quiet-btn .ui-icon { color: inherit !important; }

/* ---------- Split layout + the three views (DIR-10) ---------- */
.mln-dir-split {
    display: grid; grid-template-columns: minmax(400px, 42%) 1fr;
    height: calc(100vh - 108px); min-height: 460px;
}
.mln-view-map, .mln-view-clusters { grid-template-columns: 1fr; }
.mln-view-map .mln-dir-listing, .mln-view-clusters .mln-dir-listing { display: none; }
/* V1.5 — "Map" signpost between the results and the map on mobile.
   Desktop puts the two side by side, so there is no seam to label: display:none
   also takes it out of the grid entirely, which matters because .mln-dir-split is
   a two-column grid up there and a third child would otherwise claim a cell. */
.mln-dir-map-divider { display: none; }
@media (max-width: 900px) {
    .mln-dir-map-divider {
        display: flex;
        align-items: center;
        gap: 0.6rem;
        /* Full width and its own button reset: it is a <button> now, so the UA's
           background, border, font and centring all have to be undone or it stops
           looking like a seam. */
        width: 100%;
        padding: 0.35rem 1.4rem 0.6rem;
        border: 0;
        background: none;
        font: inherit;
        color: var(--mln-muted);
        font-size: 0.7rem;
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: 0.08em;
        text-align: center;
        cursor: pointer;
        user-select: none;
        -webkit-appearance: none;
        appearance: none;
    }
    /* Deliberately undecorated: no underline, no colour jump, no background — the
       control should stay "hidden" in the seam. The only affordances are the
       cursor and a barely-there darkening, which is enough to feel live without
       announcing itself. */
    .mln-dir-map-divider:hover { color: var(--mln-ink); }
    .mln-dir-map-divider:hover::before,
    .mln-dir-map-divider:hover::after { background: var(--mln-line-2, #d6e0db); }
    .mln-dir-map-divider:focus { outline: none; }
    /* Keyboard users still need to see where they are, even on a quiet control. */
    .mln-dir-map-divider:focus-visible {
        outline: 2px solid var(--mln-primary);
        outline-offset: -2px;
        border-radius: 4px;
    }
    .mln-dir-map-divider i { transition: transform .16s ease; }
    .mln-dir-map-divider:hover i { transform: translateY(2px); }
    .mln-dir-map-divider::before,
    .mln-dir-map-divider::after {
        content: "";
        flex: 1 1 auto;
        height: 1px;
        background: var(--mln-line);
    }
    .mln-dir-map-divider i { font-size: 0.8rem; }
}

/* ---------- Listing column ---------- */
/* V1.6 — the two sheet-layout elements are OFF by default and switched on only by
   the sheet breakpoint. They must be declared here, not just inside the media
   query: .mln-dir-split is a two-column grid on desktop, so leaving the floating
   search visible gave it a grid cell of its own and pushed the map onto a second
   row — the side-by-side split silently became a three-row stack. Styling new
   markup for the narrow case only is exactly how that happens. */
.mln-dir-sheet-search { display: none; }
.mln-dir-sheet-grip   { display: none; }

.mln-dir-listing {
    overflow-y: auto; background: var(--mln-bg);
    border-right: 1px solid var(--mln-line); padding: 1.25rem 1.4rem 0.5rem;
}
.mln-dir-listing-head { margin-bottom: 1rem; }
.mln-dir-listing-title { display: flex; flex-direction: column; }
.mln-dir-h1 { font-family: var(--mln-disp); font-weight: 700; font-size: 1.6rem; letter-spacing: -0.02em; color: var(--mln-ink); margin: 0; }
.mln-dir-sub { color: var(--mln-muted); font-size: 0.92rem; margin: 0.3rem 0 0; }
.mln-dir-count { display: inline-block; margin-top: 0.6rem; font-size: 0.85rem; color: var(--mln-primary); font-weight: 700; }

/* V1.5 — result count and sort control share one row, pushed apart. Wraps on
   narrow screens so the select never squeezes the count into ellipsis. */
.mln-dir-listing-tools {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.75rem;
    flex-wrap: wrap;
}
.mln-dir-listing-tools .mln-dir-count { margin-top: 0; }
.mln-dir-sort { display: inline-flex; align-items: center; gap: 0.4rem; }
.mln-dir-sort-label {
    font-size: 0.78rem; font-weight: 600; color: var(--mln-muted);
    white-space: nowrap;
}
.mln-dir-sort-select {
    font: inherit; font-size: 0.82rem;
    padding: 0.32rem 1.7rem 0.32rem 0.55rem;
    color: var(--mln-ink); background-color: var(--mln-surface);
    border: 1px solid var(--mln-line); border-radius: var(--mln-r, 8px);
    cursor: pointer; max-width: 12rem;
    /* Native select arrows differ wildly across platforms and one of them is a
       fat grey block that breaks the row's rhythm; an inline SVG chevron keeps
       the control looking like the rest of the directory chrome. The background
       shorthand is avoided so background-color above still applies. */
    -webkit-appearance: none; -moz-appearance: none; appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%23698' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 0.55rem center;
}
.mln-dir-sort-select:hover { border-color: var(--mln-primary); }
.mln-dir-sort-select:focus-visible { outline: 2px solid var(--mln-primary); outline-offset: 1px; }
/* Pending state while a proximity origin is being resolved (geolocation prompt
   or the coarse lookup). Keeps the visitor from clicking again mid-resolve. */
.mln-dir-sort-select[disabled] { opacity: 0.6; cursor: progress; }
@media (max-width: 420px) {
    .mln-dir-sort { width: 100%; }
    .mln-dir-sort-select { flex: 1 1 auto; max-width: none; }
}

/* ---------- Search (DIR-8) ---------- */
.mln-dir-search {
    display: flex; align-items: center; gap: 0.5rem; margin-top: 0.85rem; position: relative;
    background: var(--mln-surface); border: 1px solid var(--mln-line-2);
    border-radius: var(--mln-r-pill); padding: 0.25rem 0.3rem 0.25rem 2.1rem;
    box-shadow: var(--mln-shadow-sm); transition: border-color .16s, box-shadow .16s;
}
.mln-dir-search:focus-within { border-color: var(--mln-primary); box-shadow: 0 0 0 4px rgba(0,89,76,.10); }
.mln-dir-search-icon { position: absolute; left: 0.85rem; color: var(--mln-muted); pointer-events: none; }
.mln-dir-search input[type="text"] {
    flex: 1 1 auto; padding: 0.5rem 0.4rem; border: 0; outline: 0; background: transparent;
    font: 500 0.95rem var(--mln-ui); color: var(--mln-ink); min-width: 0;
}
.mln-dir-search input[type="text"]::placeholder { color: var(--mln-faint); }
.mln-dir-search button {
    flex: 0 0 auto; padding: 0.5rem 1.05rem; border: none; border-radius: var(--mln-r-pill);
    background: var(--mln-primary); color: #fff; font: 600 0.85rem var(--mln-ui); cursor: pointer;
}
.mln-dir-search button:hover { background: var(--mln-primary-700); }

/* ---------- Cards (Airbnb-rich, Zillow-linked) ---------- */
.mln-dir-cards { display: flex; flex-direction: column; gap: 1rem; }
.mln-dir-card {
    position: relative; display: grid; grid-template-columns: 132px 1fr;
    background: var(--mln-surface); border: 1px solid var(--mln-line);
    border-radius: var(--mln-r); overflow: hidden; cursor: pointer;
    box-shadow: var(--mln-shadow-sm);
    transition: transform .15s, box-shadow .15s, border-color .15s;
}
.mln-dir-card:hover, .mln-dir-card--active {
    transform: translateY(-2px); box-shadow: var(--mln-shadow-md); border-color: var(--mln-line-2);
}
.mln-dir-card::before {
    content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px;
    background: var(--mln-realm, var(--mln-primary)); z-index: 2;
}
.mln-dir-card-thumb {
    position: relative; background: var(--mln-surface-2); background-size: cover; background-position: center;
    min-height: 118px;
}
.mln-dir-card-thumb--ph {
    background:
        radial-gradient(circle at 30% 25%, rgba(255,255,255,.32), transparent 58%),
        linear-gradient(135deg, rgba(255,255,255,.10), rgba(12,31,26,.55)),
        var(--mln-realm, #6f9c8b);
}
.mln-dir-card-topo {
    position: absolute; inset: 0; opacity: .45; pointer-events: none;
    background-image: repeating-radial-gradient(circle at 32% 68%, rgba(255,255,255,.30) 0 1px, transparent 1px 13px);
}
.mln-dir-card-thumb--img .mln-dir-card-topo { opacity: 0; }
.mln-dir-card-body { padding: 0.8rem 0.95rem 0.85rem; min-width: 0; }
.mln-dir-card-body .mln-dir-card-region {
    display: inline-flex; align-items: center; gap: 0.4rem; margin-bottom: 0.35rem;
    font: 600 0.66rem var(--mln-ui); text-transform: uppercase; letter-spacing: 0.05em; color: var(--mln-muted);
}
.mln-dir-card-realm-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--mln-realm, var(--mln-primary)); flex: 0 0 auto; }

/* V1.5 — orchestrator profile pictures on the public community page. The
   directory template loads mln-directory.css and NOT mln-avatar.css, so the
   avatar rules it needs are declared here rather than shared; a rule living only
   in a stylesheet this host never loads would silently not exist. Uploads are
   centre-cropped square, so border-radius alone is a correct circle; object-fit
   guards any non-square URL already sitting in person.avatar_url. */
.mln-avatar {
    border-radius: 50%;
    object-fit: cover;
    flex: 0 0 auto;
    background: var(--mln-line, #e3e8e4);
    vertical-align: middle;
}
.mln-avatar--xs { width: 22px; height: 22px; }
/* The chip is an inline-flex row of [dot?] name [role]; the picture becomes its
   first item and needs the same gap the chip already uses. */
.mln-region-person .mln-avatar--xs { margin-right: 0.35rem; }
.mln-dir-card-region-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 220px; }
.mln-dir-card-region-code { color: var(--mln-faint); font-weight: 700; }
.mln-dir-card-title { margin: 0 0 0.25rem; }
.mln-dir-card-title a {
    font-family: var(--mln-disp); font-weight: 600; font-size: 1.02rem; letter-spacing: -0.01em;
    color: var(--mln-ink); text-decoration: none; line-height: 1.25;
    display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden;
}
.mln-dir-card:hover .mln-dir-card-title a, .mln-dir-card-title a:hover { color: var(--mln-primary); }
.mln-dir-card-summary {
    color: var(--mln-muted); font-size: 0.85rem; line-height: 1.45; margin: 0;
    display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.mln-dir-card-meta { display: flex; gap: 0.4rem; margin-top: 0.6rem; flex-wrap: wrap; }
.mln-dir-pill {
    font: 600 0.68rem var(--mln-ui); padding: 0.15rem 0.55rem; border-radius: var(--mln-r-pill);
    background: var(--mln-surface-2); color: var(--mln-muted);
    max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mln-dir-pill--trust { background: rgba(0,89,76,.09); color: var(--mln-primary); }
.mln-dir-pill--practice { background: rgba(188,78,44,.10); color: var(--mln-accent); }

.mln-dir-empty-inline { padding: 2rem 1rem; text-align: center; color: var(--mln-muted); }

/* A.8 — save-to-wishlist heart, sits on the thumbnail */
.mln-dir-save {
    position: absolute; top: 0.55rem; left: 0.55rem; z-index: 3;
    width: 2rem; height: 2rem; padding: 0; border: none; border-radius: 50%;
    background: rgba(255,255,255,0.92); color: var(--mln-accent); cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center;
    box-shadow: var(--mln-shadow-sm); transition: transform .12s ease, background .12s ease;
}
.mln-dir-save:hover { transform: scale(1.1); background: #fff; }
.mln-dir-save.saved { background: var(--mln-accent); color: #fff; }
.mln-dir-save[disabled] { opacity: 0.6; cursor: default; }

/* ---------- Map + floating chrome (Apple Maps) ---------- */
.mln-dir-mapwrap { position: relative; min-height: 420px; }
.mln-dir-map { position: absolute; inset: 0; }
.leaflet-container { background: #dfe8e3; font-family: var(--mln-ui); }
@media (max-width: 900px) { .mln-dir-mapwrap { height: 62vh; } }
@media (min-width: 900px) { .mln-dir-listing-tools { margin-top: 1rem; } }

.mln-dir-attrib {
    position: absolute; left: 12px; bottom: 10px; z-index: 1000; font-size: 11px;
    color: var(--mln-muted); background: rgba(255,255,255,.72); padding: 2px 7px; border-radius: 6px;
}

/* V1.2 — Forest Regeneration map key. Bottom-left, stacked ABOVE .mln-dir-attrib
   (which owns bottom:10px). Only visible while the forest layer is on. */
.mln-dir-forestkey {
    position: absolute; left: 12px; bottom: 38px; z-index: 1000;
    background: rgba(255,255,255,.94); border-radius: var(--mln-r-md, 10px);
    box-shadow: var(--mln-shadow-sm); padding: 0.6rem 0.75rem;
    font: 500 0.76rem var(--mln-ui); color: var(--mln-ink);
    max-width: 15rem; pointer-events: none;
}
.mln-dir-forestkey[hidden] { display: none; }
.mln-dir-forestkey h4 {
    margin: 0 0 0.4rem; font: 600 0.72rem var(--mln-ui); letter-spacing: .02em;
    text-transform: uppercase; color: var(--mln-muted);
}
.mln-fk-row { display: flex; align-items: center; gap: 0.45rem; line-height: 1.7; }
.mln-fk-sw {
    width: 12px; height: 12px; border-radius: 4px; flex: 0 0 12px;
    box-shadow: inset 0 0 0 1px rgba(0,0,0,.14);
}
.mln-fk-note {
    display: block; margin-top: 0.45rem; padding-top: 0.4rem;
    border-top: 1px solid rgba(0,0,0,.08);
    font-size: 0.68rem; line-height: 1.35; color: var(--mln-muted);
}
@media (max-width: 900px) {
    /* Narrow screens: keep the key from eating the map — drop the coverage note. */
    .mln-dir-forestkey { max-width: 11rem; font-size: 0.72rem; }
    .mln-fk-note { display: none; }
}

/* DIR-10 view switcher — floating top-left segmented pill */
.mln-dir-views {
    position: absolute; top: 16px; left: 16px; z-index: 1000;
    display: inline-flex; background: var(--mln-surface); border-radius: var(--mln-r-pill);
    padding: 3px; box-shadow: var(--mln-shadow-lg);
}
.mln-dir-view-btn {
    display: inline-flex; align-items: center; gap: 0.35rem; border: 0; background: transparent;
    font: 600 0.78rem var(--mln-ui); color: var(--mln-muted);
    padding: 0.42rem 0.75rem; border-radius: var(--mln-r-pill); cursor: pointer;
}
.mln-dir-view-btn i { font-size: 0.85rem; }
.mln-dir-view-btn.is-on { background: var(--mln-surface-2); color: var(--mln-primary); box-shadow: inset 0 0 0 1px rgba(0,89,76,.10); }
.mln-dir-view-btn:not(.is-on):hover { color: var(--mln-ink); }

/* Apple-Maps zoom cluster — top-right */
.mln-dir-zoom {
    position: absolute; top: 16px; right: 16px; z-index: 1000;
    display: flex; flex-direction: column; background: var(--mln-surface);
    border-radius: 12px; box-shadow: var(--mln-shadow-lg); overflow: hidden;
}
.mln-dir-zoom-btn { width: 40px; height: 40px; border: 0; background: transparent; font-size: 20px; color: var(--mln-ink); cursor: pointer; }
.mln-dir-zoom-btn:hover { background: var(--mln-surface-2); }
.mln-dir-zoom-btn + .mln-dir-zoom-btn { border-top: 1px solid var(--mln-line); }

/* Zillow "search this area" affordance — top-center */
.mln-dir-search-area {
    position: absolute; top: 16px; left: 50%; transform: translate(-50%, -6px); z-index: 1000;
    display: inline-flex; align-items: center; gap: 0.5rem; height: 38px; padding: 0 16px;
    background: var(--mln-surface); border: 0; border-radius: var(--mln-r-pill);
    box-shadow: var(--mln-shadow-lg); font: 600 0.82rem var(--mln-ui); color: var(--mln-primary);
    cursor: pointer; opacity: 0; pointer-events: none; transition: opacity .2s, transform .2s;
}
.mln-dir-search-area.is-show { opacity: 1; pointer-events: auto; transform: translate(-50%, 0); }

/* DIR-12 clusters chrome — Back + level breadcrumb (top-left, below the switcher) */
.mln-dir-clusters-bar {
    position: absolute; top: 64px; left: 16px; z-index: 1000;
    display: flex; align-items: center; gap: 0.6rem; max-width: calc(100% - 120px);
    background: var(--mln-surface); border-radius: var(--mln-r-pill);
    box-shadow: var(--mln-shadow-lg); padding: 0.35rem 0.85rem 0.35rem 0.4rem;
}
.mln-dir-clusters-bar[hidden] { display: none; }
.mln-dir-clusters-back {
    display: inline-flex; align-items: center; gap: 0.35rem; border: 0;
    background: var(--mln-surface-2); color: var(--mln-primary); font: 600 0.8rem var(--mln-ui);
    padding: 0.4rem 0.8rem; border-radius: var(--mln-r-pill); cursor: pointer;
}
.mln-dir-clusters-back:hover:not([disabled]) { background: rgba(0,89,76,.12); }
.mln-dir-clusters-back[disabled] { opacity: 0.45; cursor: default; }
.mln-dir-clusters-hint { font-size: 0.78rem; color: var(--mln-muted); }
.mln-dir-clusters-level { font: 700 0.78rem var(--mln-ui); color: var(--mln-ink); text-transform: uppercase; letter-spacing: 0.04em; }
@media (max-width: 640px) { .mln-dir-clusters-hint { display: none; } }

/* DIR-10 full-map / clusters hover preview card */
.mln-dir-preview {
    position: absolute; z-index: 1100; width: 264px; background: var(--mln-surface);
    border: 1px solid var(--mln-line); border-radius: var(--mln-r); overflow: hidden;
    box-shadow: var(--mln-shadow-lg); cursor: pointer;
}
.mln-dir-preview[hidden] { display: none; }
.mln-dir-preview::before {
    content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px; z-index: 2;
    background: var(--mln-realm, var(--mln-primary));
}
.mln-dir-preview-thumb { height: 116px; background-size: cover; background-position: center; }
.mln-dir-preview-thumb.is-ph {
    background:
        radial-gradient(circle at 30% 25%, rgba(255,255,255,.32), transparent 58%),
        linear-gradient(135deg, rgba(255,255,255,.10), rgba(12,31,26,.55)),
        var(--mln-realm, #6f9c8b);
}
.mln-dir-preview-body { padding: 0.7rem 0.85rem 0.85rem; }
.mln-dir-preview-region { display: inline-flex; align-items: center; gap: 0.4rem; font: 600 0.64rem var(--mln-ui); text-transform: uppercase; letter-spacing: 0.05em; color: var(--mln-muted); margin-bottom: 0.25rem; }
.mln-dir-preview-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--mln-realm, var(--mln-primary)); }
.mln-dir-preview-title { font-family: var(--mln-disp); font-weight: 600; font-size: 1rem; color: var(--mln-ink); letter-spacing: -0.01em; margin-bottom: 0.25rem; }
.mln-dir-preview-summary { color: var(--mln-muted); font-size: 0.82rem; line-height: 1.4; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }

/* DIR-3 — collapsible "Filters" control, bottom-right (Google Maps/Earth style) */
.mln-dir-layers {
    position: absolute; bottom: 1rem; right: 1rem; z-index: 1000;
    display: flex; flex-direction: column-reverse; align-items: flex-end; gap: 0.6rem;
}
.mln-dir-layers-toggle {
    display: inline-flex; align-items: center; gap: 0.45rem; background: var(--mln-surface);
    color: var(--mln-primary); border: 0; border-radius: var(--mln-r-pill);
    padding: 0.6rem 1.05rem; font: 600 0.85rem var(--mln-ui);
    box-shadow: var(--mln-shadow-lg); cursor: pointer;
}
.mln-dir-layers-toggle:hover { color: var(--mln-accent); }
.mln-dir-layers-toggle i { font-size: 1rem; }
.mln-dir-layers-panel {
    background: var(--mln-surface); border-radius: 16px; padding: 0.85rem 1rem;
    box-shadow: var(--mln-shadow-lg); min-width: 230px;
}
.mln-dir-layers--collapsed .mln-dir-layers-panel { display: none; }
.mln-dir-layers h3 { margin: 0 0 0.55rem; font: 600 0.68rem var(--mln-ui); text-transform: uppercase; letter-spacing: 0.06em; color: var(--mln-faint); }
.mln-layer-row { display: flex; align-items: center; gap: 0.55rem; padding: 0.32rem 0; font-size: 0.88rem; cursor: pointer; }
.mln-layer-row input { accent-color: var(--mln-primary); }

/* DIR-11 — legend swatches mirror each layer's map color */
.mln-layer-swatch { display: inline-block; width: 14px; height: 14px; border-radius: 4px; flex: 0 0 auto; border: 1px solid rgba(0,0,0,.12); }
.mln-swatch-nodes { background: var(--mln-primary); border-radius: 50%; }
.mln-swatch-realms { background: linear-gradient(90deg,#4e79a7 0 25%,#59a14f 25% 50%,#af7aa1 50% 75%,#f28e2b 75% 100%); }
.mln-swatch-subrealms { background: #6b3d8a; }
.mln-swatch-bioregions { background: #2d5a8e; }
.mln-swatch-ecoregions { background: linear-gradient(90deg,#38A700 0 33%,#B3FB35 33% 66%,#69E7F5 66% 100%); }
/* zoom-in hint under the Ecoregions row (shown only while the layer is on but the map is too far out).
   :not([hidden]) so the base display rule doesn't defeat the `hidden` attribute we toggle from JS. */
.mln-layer-hint:not([hidden]) { display: block; margin: -0.1rem 0 0.25rem 1.95rem; font-size: 0.72rem; color: var(--mln-muted); font-style: italic; }
.mln-swatch-community { background: var(--mln-accent); }
.mln-swatch-forest { background: #4a8b3a; }
@media (max-width: 900px) { .mln-dir-layers { bottom: 0.75rem; right: 0.75rem; } }

/* Copyright and the locale switcher read as one centred line, so the inline form
   has to sit on the text baseline rather than below it. Padding stays slim: this
   footer is below a full-height map, so any height it gains is height the page
   has to scroll to reveal. */
.mln-dir-footer {
    display: flex; align-items: center; justify-content: center; gap: 0.15rem; flex-wrap: wrap;
    text-align: center; padding: 0.35rem 0.9rem; color: var(--mln-muted); font-size: 0.8rem;
    background: var(--mln-surface); border-top: 1px solid var(--mln-line);
}
/* The copyright was inheriting --mln-muted (#5c6b65) on --mln-surface (#ffffff) at
   0.8rem, which read as almost invisible. --mln-ink (#132420) is the body ink, so it
   is legible without becoming louder than the content above it. Scoped to this span
   so the locale switcher beside it keeps its quiet treatment. */
.mln-dir-footer .mln-dir-copy { color: var(--mln-ink); }
/*
 * NOTE the descendant selector. p:menuButton puts styleClass on a wrapping SPAN,
 * not on the button itself, so a `.mln-dir-quiet-btn.ui-button` rule matches the
 * plain commandButton (Log out) and silently misses the switcher — which is why
 * the switcher stayed full height here. Both forms are covered.
 */
.mln-dir-footer .mln-dir-quiet-btn.ui-button,
.mln-dir-footer .mln-dir-quiet-btn .ui-button {
    font-size: 0.8rem;
    padding: 0.5rem 0.7rem;
}
.mln-dir-footer .mln-dir-quiet-btn .ui-button-icon-left { left: 0.45rem !important; }
/* 1.35rem, not 1rem: at 1rem the glyph butts straight up against the icon. */
.mln-dir-footer .mln-dir-quiet-btn .ui-button-text { padding: 0 0 0 1.35rem !important; }

/* ---------- Node markers ----------
   MOVED to css/mln-map.css. mln-map.js emits this markup on the admin land node detail
   page too, and that page does not load mln-directory.css — the pin rendered as an
   unstyled empty span, i.e. an invisible marker. The rules now live beside the JS that
   creates them, in a stylesheet all three templates load. */

/* Leaflet tooltip skin */
.leaflet-tooltip { border: 0 !important; box-shadow: var(--mln-shadow-md); border-radius: 8px; font: 600 12px var(--mln-ui); color: var(--mln-ink); padding: 5px 9px; }

/* Loading overlay (fades when mln-loaded set on the host) */
.mln-loading-host .mln-dir-loading { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; background: rgba(244,247,245,.6); z-index: 900; transition: opacity .3s; }
.mln-loading-host.mln-loaded .mln-dir-loading { opacity: 0; pointer-events: none; }
.mln-spinner { width: 28px; height: 28px; border: 3px solid var(--mln-line); border-top-color: var(--mln-primary); border-radius: 50%; animation: mln-spin 0.8s linear infinite; }
@keyframes mln-spin { to { transform: rotate(360deg); } }

/* ---------- Land-node detail (A.4) + connection surface (A.7) ---------- */
.mln-dir-detail { max-width: 1100px; margin: 0 auto; padding: 1.5rem; }
.mln-dir-notfound { text-align: center; padding: 3rem; }
/* D1 — back-to-directory link above the breadcrumb on node detail pages. */
.mln-dir-back {
    display: inline-flex; align-items: center; gap: 0.4rem; margin-bottom: 0.6rem;
    font: 600 0.85rem var(--mln-ui); color: var(--mln-primary); text-decoration: none;
}
.mln-dir-back:hover { color: var(--mln-primary-700); text-decoration: none; }
.mln-dir-breadcrumb { font-size: 0.85rem; color: var(--mln-muted); margin-bottom: 0.75rem; }
.mln-dir-breadcrumb a { color: var(--mln-primary); text-decoration: none; }
/* O6 — the final crumb is the current node's own name. */
.mln-dir-breadcrumb-current { color: var(--mln-ink); font-weight: 600; }
.mln-dir-detail-grid { display: grid; grid-template-columns: 1fr minmax(280px, 360px); gap: 1.5rem; }
@media (max-width: 800px) { .mln-dir-detail-grid { grid-template-columns: 1fr; } }
.mln-dir-detail-main h1 { font-family: var(--mln-disp); color: var(--mln-ink); font-size: 1.7rem; letter-spacing: -0.02em; margin: 0 0 0.5rem; }
/* Detail page bioregion tag (p:tag styleClass="mln-dir-card-region") */
.mln-dir-detail-main .mln-dir-card-region { background: var(--mln-primary); color: #fff; }
.mln-dir-detail-summary { font-size: 1.05rem; color: var(--mln-ink); margin: 0.5rem 0 1rem; }
.mln-dir-detail-body { color: #333; line-height: 1.6; }
.mln-dir-node-map { position: relative; height: 300px; border-radius: var(--mln-r); overflow: hidden; border: 1px solid var(--mln-line); }
.mln-dir-tags { margin-top: 1rem; display: flex; flex-wrap: wrap; gap: 0.4rem; }
.mln-dir-tag { background: #eef3f1; color: var(--mln-primary); border-radius: 999px; padding: 0.2rem 0.7rem; font-size: 0.8rem; }
.mln-dir-tag--regen { background: #f7ece7; color: var(--mln-accent); }

.mln-dir-related { margin-top: 2rem; border-top: 1px solid var(--mln-line); padding-top: 1.25rem; }
/* phase22 task 4 — when the Events section rendered rows, its last row's
   border-bottom already IS the separator, and this section's border-top made a
   double rule. Drop only the border; the margin/padding spacing stays. */
.mln-dir-related--no-rule { border-top: none; }
.mln-dir-related h2 { font-family: var(--mln-disp); color: var(--mln-ink); font-size: 1.25rem; }
.mln-dir-related h3 { font-size: 0.85rem; text-transform: uppercase; letter-spacing: .5px; color: var(--mln-muted); margin: 1rem 0 0.5rem; }
.mln-dir-related-row { display: flex; flex-wrap: wrap; gap: 0.5rem; }
/* ---- the directory's outline-pill affordance -------------------------------
   ONE source of truth, shared by the Connections chips, the event sign-up
   trigger and the RSVP dialog's login/register actions. They must read as the
   same control; when the look lived in three copies they drifted immediately.
   `.ui-button` is on the last selector because the sign-up trigger is a
   p:commandButton and PrimeFaces' own .ui-button rules would otherwise win. */
.mln-dir-related-chip,
.mln-dir-outline-btn,
.mln-region-event-rsvp.ui-button {
    background: #fff;
    border: 1px solid var(--mln-primary);
    color: var(--mln-primary);
    border-radius: 8px;
    padding: 0.4rem 0.75rem;
    font-size: 0.85rem;
    text-decoration: none;
    box-shadow: none;
    transition: background-color .15s ease, color .15s ease;
    /* Measured against a live chip, not assumed: the rendered chip is system-ui
       13.6px / line-height 20.4px / padding 6.4px 12px = 35.2px tall. PrimeFaces
       forces Nunito 16px / line-height:normal on .ui-button, which would render the
       sign-up pill in a different typeface ~7px shorter than the chips beside it.
       font: inherit + line-height 1.5 reproduces 35.2px exactly (13.6 * 1.5 = 20.4,
       + 12.8 padding + 2 border). These two are no-ops for the chip itself, which
       already inherits them — stated explicitly so the group stays one look. */
    font-family: inherit;
    line-height: 1.5;
}
.mln-dir-related-chip:hover,
.mln-dir-outline-btn:hover,
.mln-region-event-rsvp.ui-button:hover {
    background: var(--mln-primary);
    color: #fff;
    border-color: var(--mln-primary);
}
/* Hover inverts the fill, so a focused control needs its own ring or keyboard
   users lose the focus position entirely. */
.mln-dir-related-chip:focus-visible,
.mln-dir-outline-btn:focus-visible,
.mln-region-event-rsvp.ui-button:focus-visible {
    outline: 2px solid var(--mln-primary);
    outline-offset: 2px;
}

/* Chip-only layout: long node titles must ellipsis rather than wrap the row. */
.mln-dir-related-chip { display: inline-block; max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mln-dir-muted { color: var(--mln-muted); }

/* ---------- A.5 directory chatbot ---------- */
.mln-dir-chat { margin: 0.85rem 0 0.25rem; border: 1px solid var(--mln-line-2); border-radius: var(--mln-r-sm); overflow: hidden; background: var(--mln-surface); }
.mln-dir-chat-log { max-height: 240px; overflow-y: auto; padding: 0.5rem; display: flex; flex-direction: column; gap: 0.4rem; }
.mln-dir-chat-log:empty { display: none; }
.mln-dir-chat-msg { padding: 0.4rem 0.6rem; border-radius: 8px; font-size: 0.88rem; line-height: 1.35; white-space: pre-wrap; max-width: 92%; }
.mln-dir-chat-q { align-self: flex-end; background: var(--mln-primary); color: #fff; }
.mln-dir-chat-a { align-self: flex-start; background: var(--mln-surface-2); color: var(--mln-ink); }
/* land-node references the assistant cites are rendered as new-tab links */
.mln-dir-chat-link { color: var(--mln-primary); font-weight: 600; text-decoration: none; white-space: nowrap; }
.mln-dir-chat-link:hover { text-decoration: underline; }
.mln-dir-chat-form { display: flex; gap: 0.5rem; padding: 0.5rem; border-top: 1px solid var(--mln-line); background: var(--mln-bg); }
.mln-dir-chat-form input { flex: 1; padding: 0.5rem 0.7rem; border: 1px solid var(--mln-line-2); border-radius: 6px; }
.mln-dir-chat-form input:focus { outline: none; border-color: var(--mln-primary); }
.mln-dir-chat-form button { padding: 0.5rem 1rem; border: none; border-radius: 6px; background: var(--mln-primary); color: #fff; font-weight: 600; cursor: pointer; }

/* ---------- A.6 grayed "coming soon" teasers ---------- */
.mln-dir-teasers { margin-top: 1.5rem; padding-top: 1.25rem; border-top: 1px dashed var(--mln-line-2); }
.mln-dir-teasers-head { font-size: 0.72rem; font-weight: 700; color: var(--mln-muted); margin: 0 0 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; }
.mln-dir-teaser-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 0.75rem; }
.mln-dir-teaser {
    display: flex; flex-direction: column; align-items: flex-start; gap: 0.4rem;
    padding: 0.85rem; border: 1px solid var(--mln-line); border-radius: var(--mln-r-sm);
    background: var(--mln-surface); color: var(--mln-faint); filter: grayscale(1); opacity: 0.85; cursor: default;
}
.mln-dir-teaser i { font-size: 1.35rem; color: var(--mln-faint); }
.mln-dir-teaser-title { font-size: 0.85rem; font-weight: 600; line-height: 1.3; }
.mln-dir-teaser-badge { font-size: 0.62rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; padding: 0.1rem 0.45rem; border-radius: 3px; background: var(--mln-surface-2); color: var(--mln-muted); }

/* ---------- A.8 "my wishlist" surface ---------- */
.mln-dir-wishlist { padding: 1.5rem clamp(1rem, 4vw, 3rem); max-width: 1280px; margin: 0 auto; }
/* Wider page → the same DIR-5 cards, laid out as a responsive grid.
   D4 — the DIR-5 card is a fixed 132px thumb + narrow body with overflow:hidden;
   in this grid the columns were too tight and clipped the title/summary/pills.
   Widen the container + columns and slim the thumbnail (wishlist-scoped) so the body
   breathes; the region/pill keep their graceful ellipsis caps (now with more room). */
.mln-dir-wishlist .mln-dir-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(360px, 1fr)); gap: 1rem; }
.mln-dir-wishlist .mln-dir-card { grid-template-columns: 108px 1fr; }
.mln-dir-empty { text-align: center; padding: 3rem 1rem; color: var(--mln-muted); }
.mln-dir-empty i { font-size: 2.5rem; color: var(--mln-accent); display: block; margin-bottom: 0.75rem; }
.mln-dir-empty-cta { display: inline-block; margin-top: 1rem; padding: 0.5rem 1.1rem; background: var(--mln-primary); color: #fff; border-radius: 8px; text-decoration: none; font-weight: 600; }
.mln-dir-empty-cta:hover { background: var(--mln-primary-700); }

/* Back out of the wishlist. Quiet by default — it is an escape hatch, not the
   thing the page is for — and it moves on hover so it reads as interactive. */
.mln-dir-back {
    display: inline-flex; align-items: center; gap: 0.45rem;
    margin-bottom: 1rem; padding: 0.35rem 0.15rem;
    color: var(--mln-text-2, #6b7280); text-decoration: none;
    font-size: 0.9rem; font-weight: 600;
    transition: color 0.15s ease, transform 0.15s ease;
}
.mln-dir-back:hover { color: var(--mln-primary); transform: translateX(-2px); }
.mln-dir-back:focus-visible { outline: 2px solid var(--mln-primary); outline-offset: 3px; border-radius: 4px; }

/* ============================================================================
 * P4b — Mycelium network view (4th directory view). Full-width Cytoscape stage
 * over the map area; realm-clustered graph + edge-type legend + node side panel.
 * ============================================================================ */
.mln-view-mycelium { grid-template-columns: 1fr; }
.mln-view-mycelium .mln-dir-listing { display: none; }
/* the Leaflet map + its map-only chrome are hidden while the graph is shown; the
   floating view switcher (z-index 1000) stays above the stage so you can switch back. */
/* Task #1 fix — .mln-dir-zoom is NO LONGER hidden here. The zoom cluster is shared
   chrome and now drives Cytoscape in this view (see wireZoom in mln-directory.js);
   hiding it was why the Mycelium view appeared to have no zoom buttons at all. It sits
   at z-index 1000, above the stage's 500, so it stays clickable. */
.mln-view-mycelium .mln-dir-map,
.mln-view-mycelium .mln-dir-layers,
.mln-view-mycelium .mln-dir-search-area { display: none; }

/* The zoom cluster is shared chrome sitting top-right for the Leaflet map. In the
   Mycelium view it drives Cytoscape instead, and belongs at the bottom-right of the
   GRAPH — so it is offset left by the side panel's width. The legend already owns
   bottom-left, so the two do not collide. */
.mln-view-mycelium .mln-dir-zoom {
    top: auto;
    bottom: 14px;
    right: calc(var(--mln-net-side-w) + 14px);
}

.mln-dir-mycelium { position: absolute; inset: 0; z-index: 500; display: none; flex-direction: column; background: var(--mln-bg); }
.mln-view-mycelium .mln-dir-mycelium { display: flex; }

.mln-net-statbar { display: flex; gap: 20px; flex-wrap: wrap; justify-content: flex-end; padding: 12px 16px; font-size: 0.82rem; color: var(--mln-muted); border-bottom: 1px solid var(--mln-line); background: var(--mln-surface); }
.mln-net-statbar b { color: var(--mln-ink); font-variant-numeric: tabular-nums; margin-right: 3px; }
.mln-net-body { flex: 1; display: flex; min-height: 0; }
.mln-net-graphwrap { position: relative; flex: 1; min-width: 0; }
.mln-net-graph { position: absolute; inset: 0; }
.mln-net-error { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: var(--mln-muted); }

/* bottom:38px, not 14px — the map attribution (.mln-dir-attrib) owns bottom:10px
   and was overlapping the last row of this box. Same stacking value the forest
   key already uses for the same reason. */
.mln-net-legend { position: absolute; left: 14px; bottom: 38px; background: rgba(255,255,255,0.9); border: 1px solid var(--mln-line); border-radius: 12px; padding: 10px 12px; font-size: 0.78rem; box-shadow: var(--mln-shadow-sm); }
.mln-net-legend h4 { margin: 0 0 7px; font-size: 0.62rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--mln-muted); }
/* Real <button role="switch"> elements, so reset the UA button chrome and keep
   the row layout these had as divs. */
.mln-net-legend-row {
    display: flex; align-items: center; gap: 8px; width: 100%;
    cursor: pointer; padding: 3px 4px; border-radius: 6px; user-select: none;
    background: none; border: 0; margin: 0; text-align: left;
    font: inherit; color: inherit;
}
.mln-net-legend-row:hover:not(:disabled) { background: var(--mln-surface-2); }
.mln-net-legend-row:focus-visible { outline: 2px solid var(--mln-primary); outline-offset: 1px; }
.mln-net-legend-row.off { opacity: 0.35; }
/* Struck through as well as dimmed: opacity alone is not a state anyone can name,
   and it is invisible to someone who cannot compare it with the rows above. */
.mln-net-legend-row.off .mln-net-legend-label { text-decoration: line-through; }
/* A type with no edges in the current graph — nothing to toggle. */
.mln-net-legend-row.empty { opacity: 0.3; cursor: default; }
.mln-net-legend-label { flex: 1 1 auto; }
.mln-net-legend-count {
    flex: 0 0 auto; min-width: 1.4rem; padding: 0 0.35rem;
    border-radius: 999px; background: var(--mln-surface-2);
    color: var(--mln-muted); font-size: 0.68rem; font-weight: 700; text-align: center;
}
.mln-net-swatch { width: 22px; height: 0; border-top-width: 3px; border-top-style: solid; border-radius: 2px; flex: 0 0 auto; }
.mln-net-swatch--doc { border-color: #1a6847; }
.mln-net-swatch--bio { border-color: #2d5a8e; border-top-style: dashed; }
.mln-net-swatch--tag { border-color: #bc4e2c; border-top-style: dotted; }
.mln-net-swatch--community { border-color: #c9a24b; }

.mln-net-side { flex: 0 0 var(--mln-net-side-w); background: var(--mln-surface); border-left: 1px solid var(--mln-line); padding: 16px; overflow-y: auto; }
.mln-net-side-empty { color: var(--mln-muted); font-size: 0.9rem; line-height: 1.5; }
.mln-net-eyebrow { font-size: 0.62rem; letter-spacing: 0.1em; text-transform: uppercase; font-weight: 800; }
.mln-net-side h3 { font-family: var(--mln-disp); margin: 0.35rem 0 0.2rem; font-size: 1.15rem; color: var(--mln-ink); }
.mln-net-meta { color: var(--mln-muted); font-size: 0.82rem; margin: 0 0 12px; }
.mln-net-pill { display: inline-block; font-size: 0.68rem; font-weight: 600; padding: 3px 9px; border-radius: 999px; background: var(--mln-surface-2); color: var(--mln-muted); margin: 0 5px 6px 0; }
.mln-net-pill--trust { background: rgba(0,89,76,0.1); color: var(--mln-primary); }
.mln-net-conns { margin-top: 12px; border-top: 1px dashed var(--mln-line); padding-top: 10px; }
.mln-net-conns h5 { margin: 0 0 6px; font-size: 0.68rem; letter-spacing: 0.08em; text-transform: uppercase; color: var(--mln-muted); }
.mln-net-conn { display: flex; align-items: center; gap: 8px; font-size: 0.82rem; padding: 4px 0; cursor: pointer; }
.mln-net-conn:hover { color: var(--mln-primary); }
.mln-net-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; }
.mln-net-cta { display: inline-block; margin-top: 14px; background: var(--mln-primary); color: #fff; text-decoration: none; font-weight: 600; font-size: 0.82rem; padding: 8px 14px; border-radius: 8px; }
.mln-net-cta:hover { background: var(--mln-primary-700); }

@media (max-width: 900px) {
    .mln-view-mycelium { grid-template-columns: 1fr; height: auto; }
    .mln-net-statbar { padding-left: 16px; }
    .mln-net-body { flex-direction: column; }
    /* Measured at 390x844: the graph got 340px and the side panel 395px — the
       reference panel was taking more of the stage than the thing it describes.
       The graph now takes what it needs and the panel is capped and scrolls. */
    .mln-net-side {
        flex: 0 1 auto;
        max-height: 38%;
        border-left: 0;
        border-top: 1px solid var(--mln-line);
    }
    /* panel is stacked BELOW the graph here, so the zoom needs no horizontal offset */
    .mln-view-mycelium .mln-dir-zoom { right: 14px; }
    .mln-net-graphwrap { flex: 1 1 auto; min-height: 55%; }
}

/* ===========================================================================
   V1.2 — region filter chip (directory listing head) + the public bioregion /
   community pages. The pages reuse .mln-dir-card etc. so they read as the same
   product as the directory listing; only the page chrome is new here.
   =========================================================================== */

.mln-dir-region-chip {
    display: inline-flex; align-items: center; gap: 0.4rem;
    margin-left: 0.5rem; padding: 0.2rem 0.35rem 0.2rem 0.7rem;
    border-radius: var(--mln-r-pill); background: rgba(45,90,142,.10);
    color: #2d5a8e; font: 600 0.75rem var(--mln-ui);
}
.mln-dir-region-chip[hidden] { display: none; }
.mln-dir-region-chip-label { max-width: 16rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mln-dir-region-chip-x {
    border: 0; background: transparent; color: inherit; cursor: pointer;
    font-size: 1rem; line-height: 1; padding: 0 0.25rem; border-radius: 50%;
}
.mln-dir-region-chip-x:hover { background: rgba(45,90,142,.18); }

/* ---- public bioregion / community page ---- */
.mln-region-page { max-width: 1100px; margin: 0 auto; }

.mln-region-head { margin: 0.5rem 0 1rem; }
.mln-region-head h1 {
    margin: 0; font-family: var(--mln-disp); font-size: clamp(1.6rem, 3vw, 2.3rem);
    letter-spacing: -0.02em; color: var(--mln-ink);
}
.mln-region-sub { display: flex; flex-wrap: wrap; gap: 0.75rem; margin: 0.35rem 0 0; align-items: baseline; }
.mln-region-code {
    font: 600 0.72rem var(--mln-ui); letter-spacing: .06em; text-transform: uppercase;
    color: var(--mln-primary); background: rgba(0,89,76,.08);
    padding: 0.15rem 0.5rem; border-radius: var(--mln-r-pill);
}
.mln-region-canonical { font-size: 0.82rem; color: var(--mln-muted); }

.mln-region-map {
    position: relative; height: 46vh; min-height: 320px;
    border-radius: var(--mln-r-md, 12px); overflow: hidden; margin-bottom: 1.25rem;
    background: #dfe8e3;
}

.mln-region-counts {
    display: grid; grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
    gap: 0.75rem; margin-bottom: 1.5rem;
}
.mln-region-count {
    background: var(--mln-surface-2); border-radius: var(--mln-r-md, 12px);
    padding: 0.9rem 1rem; text-align: center;
}
.mln-region-count-n {
    display: block; font-family: var(--mln-disp); font-size: 1.7rem;
    line-height: 1.1; color: var(--mln-primary);
}
.mln-region-count-l { display: block; margin-top: 0.15rem; font-size: 0.76rem; color: var(--mln-muted); }

.mln-region-section { margin-bottom: 2rem; }
.mln-region-section > h2 {
    margin: 0 0 0.75rem; font-family: var(--mln-disp); font-size: 1.15rem;
    letter-spacing: -0.01em; color: var(--mln-ink);
}
.mln-region-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)); gap: 1rem; }

.mln-region-events { list-style: none; margin: 0; padding: 0; }

/* One shared grid template for the header row and every event row.
   The tracks are FIXED, not content-sized: each row is its own grid container, so an
   `auto` track resolves per row and the movement-type column landed at a different x
   in every line — which is what made that column hard to read. */
.mln-region-events-head,
.mln-region-events li {
    display: grid;
    /* 8.5rem for the action track: the widest trigger label is "Invite only", which
       with its lock icon and pill padding needs ~114px — 7.5rem (120px) left no margin
       for a longer translation. */
    grid-template-columns: 7rem minmax(0, 1fr) 9rem 8.5rem;
    align-items: baseline;
    gap: 0.6rem;
    padding: 0.55rem 0;
}
.mln-region-events li { border-bottom: 1px solid rgba(0,0,0,.07); }

/* Column labels: deliberately NOT bold — uppercase micro-label at normal weight,
   the same language as .mln-dir-related h3, so they read as structure not content. */
.mln-region-events-head {
    padding: 0 0 0.4rem;
    border-bottom: 1px solid var(--mln-line);
}
.mln-region-events-head span {
    font-family: var(--mln-ui);
    font-size: 0.7rem;
    font-weight: 400;
    text-transform: uppercase;
    letter-spacing: .5px;
    color: var(--mln-muted);
}

.mln-region-event-date { font: 600 0.78rem var(--mln-ui); color: var(--mln-primary); }
.mln-region-event-name { color: var(--mln-ink); }
.mln-region-event-type { font-size: 0.75rem; color: var(--mln-muted); }

/* Narrow screens: a 4-column grid cannot hold, so stack and drop the header —
   each row's own content still reads date / name / type top to bottom. */
@media (max-width: 640px) {
    .mln-region-events-head { display: none; }
    .mln-region-events li { grid-template-columns: minmax(0, 1fr); gap: 0.15rem; }
    .mln-region-event-rsvp.ui-button { justify-self: start; margin-top: 0.35rem; }
}

.mln-region-person { display: inline-flex; align-items: baseline; gap: 0.4rem; }
.mln-region-person-role { font-size: 0.7rem; color: var(--mln-muted); }

/* phase22 task 2 — the public sign-up trigger sits at the end of the event row and
   must not stretch it: the row is a baseline-aligned flex line, so the button needs
   its own alignment or it drags the whole line's baseline down.
   Its colours/border come from the shared .mln-dir-outline-btn group above, so it
   matches the Connections chips. */
.mln-region-event-rsvp.ui-button {
    /* justify-self, not margin-left:auto — the row is a grid now, and the button
       occupies the fixed action track so buttons line up down the column even when
       the labels differ ("Sign up" vs "Invite only"). */
    justify-self: end; align-self: center;
    /* inline-flex + gap, same as .mln-dir-outline-btn, so icon spacing is handled
       once for both instead of per-element margins. */
    display: inline-flex; align-items: center; gap: 0.35rem;
}
/* Measured: PrimeFaces puts the padding on .ui-button-text (8px 16px 8px 32px — the
   32px left is an indent reserving room for an ABSOLUTELY positioned icon) and the
   button itself gets padding:0. Left as-is that indent squeezed the label into two
   lines, making the pill 62.8px against the chip's 35.2px.
   The .mln-region-events prefix is load-bearing, not decoration: the theme's own
   rule has the SAME specificity as `.mln-region-event-rsvp.ui-button .ui-button-text`
   and is loaded after this file, so it won on cascade order. The extra ancestor
   class out-specifies it without resorting to !important. */
/* line-height too: the theme pins this span to `normal`, which does not inherit the
   pill's 1.5 and made its box 16px instead of 20.4px — a pill 4.4px SHORTER than the
   chip beside it. Both properties have to be reclaimed for the heights to match. */
.mln-region-events .mln-region-event-rsvp.ui-button .ui-button-text {
    padding: 0;
    line-height: 1.5;
}
.mln-region-events .mln-region-event-rsvp.ui-button .ui-icon,
.mln-region-events .mln-region-event-rsvp.ui-button .ui-button-icon-left {
    color: inherit; position: static; margin: 0;
}

.mln-rsvp-event-name { margin: 0 0 0.15rem; }
.mln-rsvp-event-when { margin: 0 0 1rem; font-size: 0.82rem; }

#rsvpDialogForm\:rsvpConsent .ui-icon-check {
    top: 30%;
}

#rsvpDialogForm\:rsvpMessages .ui-messages-error-icon {
    margin: -5px 0.5rem 0 0;
}

/* phase22 task 2 — the dialog's login / register pair. They need real outline
   borders (a bare `p-button` class does nothing here: PrimeFaces styles
   `.ui-button`, and these are plain anchors) and breathing room between them. */
.mln-rsvp-actions { display: flex; flex-wrap: wrap; gap: 0.75rem; margin-top: 1rem; }
/* phase23 — the "new accounts are invitation only" line, shown in place of the
   register pill. Muted so it reads as a status note, not an error. */
.mln-rsvp-invite-note {
    display: flex; align-items: baseline; gap: 0.4rem;
    margin: 0.75rem 0 0; padding: 0.5rem 0.7rem;
    background: var(--mln-surface-2, rgba(0,0,0,.03));
    border-radius: var(--mln-r-md, 8px);
    font-size: 0.82rem; color: var(--mln-muted);
}
.mln-dir-outline-btn { display: inline-flex; align-items: center; gap: 0.4rem; padding: 0.5rem 0.9rem; }

/* The events list lives inside an h:form now (the trigger needs one). A form is a
   block box and would otherwise add its own margin between the heading and rows. */
.mln-region-section form { margin: 0; }

/* ---- profile read-out ----
   Task #13 — MOVED to css/profile-grid.css. The same markup renders on the ADMIN
   bioregion detail page (template.xhtml), which never loads this file, so the rules
   had to live somewhere both templates can include. See profile-grid.css. */

/* The listing card is a 132px-thumb + body ROW, tuned for the narrow split column.
   In the region pages' 3-up grid that body is too narrow and the title clamps to
   "Montever…", so here the card stacks: image on top, text beneath. */
.mln-region-cards .mln-dir-card { grid-template-columns: 1fr; }
.mln-region-cards .mln-dir-card-thumb { min-height: 132px; }
.mln-region-cards .mln-dir-card-title a { -webkit-line-clamp: 2; }

/* The One Earth code sits inside the h1, one step down in weight/size so the name
   still reads as the title: "Great Plains Steppe (NA23)". */
.mln-region-head h1 .mln-region-h1-code {
    margin-left: 0.4rem;
    font-size: 0.62em;
    font-weight: 500;
    color: var(--mln-muted);
    letter-spacing: 0;
    white-space: nowrap;
}


/* ---------- Task #6/#7 — beta / preview affordances ---------- */

/* The "See how nodes connect" card is now a real button into the Mycelium view, so it
   must not inherit the greyed-out look of the remaining "coming soon" teasers. */
.mln-dir-teaser--beta {
    cursor: pointer;
    opacity: 1;
    font: inherit;
    text-align: left;
    background: var(--mln-surface);
    border: 1px solid var(--mln-line-2);
    transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}
.mln-dir-teaser--beta:hover {
    border-color: var(--mln-primary);
    box-shadow: var(--mln-shadow-md);
    transform: translateY(-1px);
}
.mln-dir-teaser--beta:focus-visible {
    outline: 2px solid var(--mln-primary);
    outline-offset: 2px;
}
.mln-dir-teaser-badge--beta {
    background: var(--mln-primary);
    color: #fff;
    letter-spacing: .04em;
    text-transform: uppercase;
}

/* Preview notice across the top of the Mycelium stage. */
/* Beta notice — pinned to the BOTTOM of the side panel. margin-top:auto does the
   pinning (the panel is a flex column), so it sits under the node detail rather than
   across the top of the stage where it fought the floating view switcher. */
.mln-net-side { display: flex; flex-direction: column; }
.mln-net-beta {
    margin-top: auto;
    /* details, not flex: a flex display on the element itself fights the
       open/closed box model and hides the summary marker handling. */
    display: block;
    padding: .7rem .8rem;
    background: rgba(0, 89, 76, .06);
    border: 1px solid var(--mln-line);
    border-radius: var(--mln-r-sm);
    font-size: .76rem; color: var(--mln-ink); line-height: 1.5;
}
.mln-net-beta-badge {
    flex: none;
    background: var(--mln-primary); color: #fff;
    border-radius: var(--mln-r-pill);
    padding: .12rem .55rem;
    font-size: .68rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
}
/* Summary is the always-visible row: badge + affordance, one line tall. */
.mln-net-beta-sum {
    display: flex;
    align-items: center;
    gap: .5rem;
    cursor: pointer;
    list-style: none;
}
/* Both are needed: WebKit uses the pseudo-element, everything else list-style. */
.mln-net-beta-sum::-webkit-details-marker { display: none; }
.mln-net-beta-sum::marker { content: ''; }
.mln-net-beta-more {
    font-size: .72rem;
    font-weight: 600;
    color: var(--mln-primary);
}
.mln-net-beta-more::after {
    content: '\25be';           /* caret, flipped when open */
    display: inline-block;
    margin-left: .3rem;
    transition: transform .15s ease;
}
.mln-net-beta[open] .mln-net-beta-more::after { transform: rotate(180deg); }
.mln-net-beta-sum:focus-visible { outline: 2px solid var(--mln-primary); outline-offset: 2px; border-radius: 4px; }
.mln-net-beta-text { margin: .5rem 0 0; }

@media (max-width: 640px) {
    /* Switcher wraps on narrow screens, so it needs more headroom, not less. */
    .mln-net-beta { font-size: .74rem; padding: .6rem .7rem; }
}

/* ===========================================================================
   V1.6 — SHEET LAYOUT (<=900px): map on top, results in a draggable bottom
   sheet, Apple/Google Maps style. Desktop keeps the two-column split entirely.

   PLACED LAST IN THIS FILE ON PURPOSE. Media queries add no specificity, so
   source order is what decides between equal-specificity rules. When this block
   sat earlier, the later base rules quietly beat it — `.mln-dir-mapwrap
   { position: relative }` and `.mln-dir-listing-title { display: flex }` both won,
   so the map never became the top half and the page title never hid, while the
   rules with no later competitor (the sheet itself) did apply. That produced a
   half-working layout, which is worse to debug than one that plainly fails.

   Geometry lives in custom properties so the drag script and the stylesheet
   cannot disagree about where the snap points are:
     --sheet-h     total sheet height  (its expanded extent)
     --sheet-peek  how much stays on screen at the peek stop
   The script reads these off the element, so changing a number here moves the
   snap points too.
   =========================================================================== */
@media (max-width: 900px) {
    .mln-dir-split {
        /* An app surface, not a stack: a fixed-height positioning context that the
           map and the sheet are both placed inside. dvh (not vh) because mobile
           browser chrome shrinks the viewport as you scroll, and vh would leave the
           sheet's peek stop hanging under the address bar. */
        display: block;
        position: relative;
        height: calc(100dvh - 64px);
        min-height: 30rem;
        overflow: hidden;

        --sheet-h: 88%;
        /* Only a FLOOR for the instant before layout settles. The real peek height is
           measured from the grab handle plus the count/sort header in JS: 4.5rem
           clipped the sort control, 9rem left the chatbot field peeking below it,
           and neither number survives a locale whose sort label wraps. */
        --sheet-peek: 6rem;
    }
    /* Progressive fallback for engines without dvh. */
    @supports not (height: 100dvh) {
        .mln-dir-split { height: calc(100vh - 64px); }
    }

    /* ---- map: fills the WHOLE app area; the sheet floats over its lower half ----
       Not 50% height. With a half-height map, dragging the sheet down revealed bare
       page background instead of more map — a grey void where the whole point of the
       gesture is to see more map. Full height means the sheet is an overlay and the
       drag uncovers what was always there, which is what Apple and Google both do. */
    .mln-dir-mapwrap {
        position: absolute;
        inset: 0;
        height: 100%;
        min-height: 0;
    }

    /* Map chrome is pinned just above the sheet's default (half) line rather than to
       the container's bottom edge, which is now behind the sheet. It deliberately
       does NOT chase the sheet — controls that slide around under your thumb are
       harder to hit than controls that stay put. Consequence worth knowing: with the
       sheet expanded these sit behind it, so the OSM attribution is visible in the
       default and peek states but not while the sheet is pulled all the way up. */
    /* Map chrome rides just above the sheet, wherever the sheet currently is.
       --sheet-visible is published by the sheet script on every snap and on every
       drag frame, so these stay clear of it at peek, half and expanded alike. The
       fallback covers the moment before the script runs. */
    /* 1.6rem, not 0.75: the attribution occupies roughly the first 1.3rem above the
       sheet, and at 0.75rem the layers pill sat on top of it — "contributors" ran
       underneath the button. This stacks them instead of overlapping. */
    .mln-dir-layers {
        bottom: calc(var(--sheet-visible, 50%) + 1.6rem);
        right: 0.75rem;
    }
    .mln-dir-mapwrap .leaflet-bottom {
        bottom: calc(var(--sheet-visible, 50%) + 0.2rem);
    }
    .mln-dir-forestkey {
        bottom: calc(var(--sheet-visible, 50%) + 3rem);
    }

    /* The standing "© OpenStreetMap contributors" chip is REDUNDANT here: Leaflet
       renders its own control carrying the identical credit. On desktop the two sit
       at opposite corners so the duplication is invisible; at 390px they landed on
       the same line and overlapped each other — the Leaflet one rendered as "aflet"
       with its start covered. Hiding the duplicate keeps the required attribution
       (Leaflet's own, still visible) and removes the collision. */
    .mln-dir-attrib { display: none; }

    /* ---- the sheet ---- */
    .mln-dir-listing {
        position: absolute;
        left: 0; right: 0; bottom: 0;
        height: var(--sheet-h);
        display: flex;
        flex-direction: column;
        overflow: visible;
        padding: 0;
        background: var(--mln-surface);
        border-radius: 18px 18px 0 0;
        box-shadow: 0 -8px 28px -10px rgba(19, 36, 32, 0.28);
        z-index: 420;
        /* Snapping is animated; dragging is not (the script adds .is-dragging). */
        transition: transform .26s cubic-bezier(.32, .72, 0, 1);
        will-change: transform;
        touch-action: none;
    }
    .mln-dir-listing.is-dragging { transition: none; }
    @media (prefers-reduced-motion: reduce) {
        .mln-dir-listing { transition: none; }
    }

    /* ---- grab handle ---- */
    .mln-dir-sheet-grip {
        display: flex;
        align-items: center;
        justify-content: center;
        width: 100%;
        flex: 0 0 auto;
        padding: 0.7rem 0 0.45rem;
        border: 0;
        background: none;
        cursor: grab;
        touch-action: none;
        -webkit-appearance: none;
        appearance: none;
    }
    .mln-dir-sheet-grip:active { cursor: grabbing; }
    .mln-dir-sheet-grip-bar {
        display: block;
        width: 2.4rem;
        height: 4px;
        border-radius: 999px;
        background: var(--mln-line-2, #d6e0db);
        transition: background .16s;
    }
    .mln-dir-sheet-grip:hover .mln-dir-sheet-grip-bar,
    .mln-dir-sheet-grip:focus-visible .mln-dir-sheet-grip-bar { background: var(--mln-muted); }
    .mln-dir-sheet-grip:focus { outline: none; }
    .mln-dir-sheet-grip:focus-visible { outline: 2px solid var(--mln-primary); outline-offset: -4px; }

    /* ---- sheet header: count + sort stay put, results scroll ---- */
    .mln-dir-listing-head {
        flex: 0 0 auto;
        margin: 0;
        padding: 0 1.1rem 1rem;
        border-bottom: 1px solid var(--mln-line);
        /* Also a drag surface, so the whole header area feels grabbable. */
        touch-action: none;
    }
    /* The page title and strapline are the topnav's job in this layout — the sheet
       is results, and at the peek stop there is room for nothing else. */
    .mln-dir-listing-title { display: none; }
    /* The in-column search is replaced by the one floating over the map. */
    .mln-dir-listing .mln-dir-search { display: none; }
    .mln-dir-listing-tools { padding-top: 0.5rem; }

    /* ---- scrolling results ---- */
    .mln-dir-cards {
        flex: 1 1 auto;
        min-height: 0;
        max-height: none;
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
        padding: 0.8rem 1.1rem 1.4rem;
        /* The sheet is the drag surface; the list is a scroll surface. Letting the
           browser own vertical panning here is what stops a flick in the results
           from also dragging the sheet. */
        touch-action: pan-y;
    }
    /* Cards are flex children of a height-constrained column, and flex items shrink
       by default — without this all 154 collapse to a couple of pixels each and the
       list renders as blank grey bars. */
    .mln-dir-cards > .mln-dir-card { flex: 0 0 auto; }

    .mln-dir-chat { margin: 0 1.1rem; flex: 0 0 auto; margin-top: 5px; }

    /* Teasers are 296px of "coming soon" chrome; there is no room for them in a
       sheet whose job is results. */
    .mln-dir-split .mln-dir-teasers { display: none; }

    /* ---- search floating over the map ---- */
    .mln-dir-sheet-search {
        position: absolute;
        top: 0.75rem;
        left: 0.75rem;
        right: 0.75rem;
        z-index: 440;
        display: flex;
        align-items: center;
        gap: 0.5rem;
        padding: 0 0.85rem;
        height: 2.9rem;
        background: var(--mln-surface);
        border: 1px solid var(--mln-line);
        border-radius: 999px;
        box-shadow: 0 6px 18px -8px rgba(19, 36, 32, 0.35);
    }
    .mln-dir-sheet-search .mln-dir-search-icon { color: var(--mln-muted); font-size: 0.95rem; }
    .mln-dir-sheet-search input {
        flex: 1 1 auto;
        min-width: 0;
        border: 0;
        background: none;
        font: inherit;
        font-size: 0.92rem;
        color: var(--mln-ink);
        outline: none;
    }

    /* The clusters breadcrumb bar sits at top:64px in the base rules, which is
       exactly where the view switcher was moved to for the sheet layout (4.4rem =
       70px) — measured 34px of vertical overlap, and because the bar comes later in
       the DOM at the same z-index it won and hid the switcher entirely. Pushed
       below the switcher's 34px height instead. */
    .mln-dir-clusters-bar { top: 7.2rem; left: 0.75rem; max-width: calc(100% - 1.5rem); }

    /* The map-only / clusters / mycelium views still hide the results, and the view
       switcher (which floats over the map) is how you get them back. */
    .mln-view-map .mln-dir-listing,
    .mln-view-clusters .mln-dir-listing { display: none; }
    .mln-view-map .mln-dir-mapwrap,
    .mln-view-clusters .mln-dir-mapwrap { height: 100%; }

    /* The "MAP" seam signpost has nothing to point at now: the map is permanently
       on screen above the sheet. Kept in the markup because the desktop rule
       already hides it and the button is harmless; simply never shown. */
    .mln-dir-map-divider { display: none; }
    /* The floating search sits where the view switcher used to; push the switcher
       and the zoom cluster below it so nothing stacks on the search bar. */
    .mln-dir-views { top: 4.4rem; left: 0.75rem; }
    .mln-dir-zoom  { top: 4.4rem; right: 0.75rem; }

    /* .mln-dir-search-icon is absolutely positioned for the desktop search box,
       where it overlays the input's left padding. In the pill it is a flex item, so
       that positioning has to be handed back or the glyph lands on top of the text. */
    .mln-dir-sheet-search .mln-dir-search-icon {
        position: static;
        top: auto; left: auto;
        transform: none;
        flex: 0 0 auto;
    }
}
