:root{
  /* ── FERNSTONE STANDARD PALETTE (2026-09-02, v2 — cool blue) ──────────
     Five brand colors, dark to light: 070E18, 2E4972, 4E70A2, 6D90BF,
     A2A0AB. Unlike the first (warm-olive) attempt, the darkest tone here
     IS already a near-black navy, so --bg uses it directly with no
     adjustment needed. --text uses the lightest (a cool grey-lavender);
     --sub/--muted are derived blends of --text toward --bg, keeping
     secondary text legible without inventing a color outside this set.
     --accent uses the brighter of the two mid-blues (more contrast against
     near-black than the darker one), --accent2 the other; --accent-pressed
     (2E4972, darkest mid-tone) is the pressed/hover shade on the primary
     CTA (see .home-auth.login-link:hover). Status colors (--ok/--down/
     --starting/--sleeping) are deliberately NOT part of this palette —
     they're semantic (good/bad/on-demand-state), not brand, and need to
     stay recognizable regardless of brand changes.
     --text is A2A0AB lightened toward white — the raw palette color read
     as too flat/muted against near-black on its own ("needs to pop more");
     this keeps its grey-lavender cast while pushing it much closer to
     white for real contrast. --sub/--muted derive from that (not the raw
     A2A0AB), so the whole text hierarchy shifted brighter together. */
  --bg:#070E18;
  --surf:color-mix(in srgb, #6D90BF 7%, transparent);
  --surf-accent2:color-mix(in srgb, #4E70A2 10%, transparent);
  --border:color-mix(in srgb, #6D90BF 22%, transparent);
  --accent:#6D90BF;
  --accent2:#4E70A2;
  --accent-pressed:#2E4972;
  --text:color-mix(in srgb, #A2A0AB 45%, white);
  --sub:color-mix(in srgb, var(--text) 68%, var(--bg));
  --muted:color-mix(in srgb, var(--text) 42%, var(--bg));
  --ok:#21e6a1;
  --down:#ff477e;
  --unknown:var(--accent);
  /* On-demand lifecycle states. --starting is a warmer, more orange amber than
     --unknown and pulses, so the two amber dots never read as the same thing;
     --sleeping is a muted amethyst — clearly "at rest", never mistaken for a fault. */
  --starting:#ffa62b;
  --sleeping:#9a8fd8;
  --font:system-ui,-apple-system,'Segoe UI',sans-serif;
}
*,*::before,*::after{box-sizing:border-box}
html,body{margin:0;padding:0;background:var(--bg);color:var(--text);font-family:var(--font)}
a{color:inherit;text-decoration:none}

/* ── SITEWIDE TOP HEADER ──────────────────────────────────────────────
   Present on every page (mounted via bootstrap.js's mountTopHeader): a live
   clock, a live 3-day weather forecast, and an icon row — ported from the
   classic homepage's own proven widgets, restyled to this site's own
   palette. Sits inside .content-col, beside (not above) the rail — the
   rail runs the page's full height on the far left. */
.top-header{display:flex;align-items:center;gap:18px;padding:9px 20px;border-bottom:1px solid var(--border);background:var(--surf);flex-wrap:wrap;min-height:60px;flex-shrink:0}

#time-widget{display:flex;flex-direction:column;align-items:center;gap:1px;min-width:104px;opacity:1;transition:opacity .2s}
#time-widget.loading{opacity:.35}
#tw-time{font-size:1.05rem;font-weight:700;font-variant-numeric:tabular-nums;color:var(--text)}
.tw-colon{opacity:.5}
.tw-seconds{font-size:.68rem;color:var(--muted);margin-left:1px}
.tw-ampm{font-size:.56rem;font-weight:700;color:var(--accent);margin-left:3px}
#tw-date{font-size:.62rem;color:var(--sub)}
#tw-tz{font-size:.56rem;color:var(--muted)}

.top-header-icons{display:flex;gap:2px;flex-wrap:wrap;flex:1;justify-content:center}

#weather-widget{display:flex;align-items:center;gap:10px;min-width:150px;justify-content:flex-end;opacity:1;transition:opacity .2s}
#weather-widget.loading{opacity:.35}
.wx-location{font-size:.6rem;color:var(--sub);white-space:nowrap}
.wx-days{display:flex;gap:8px}
.wx-day{display:flex;flex-direction:column;align-items:center;gap:1px;font-size:.58rem}
.wx-day-name{color:var(--sub)}
.wx-hi{color:var(--text);font-weight:700}
.wx-lo{color:var(--muted);margin-left:3px}
.wx-unavailable{font-size:.6rem;color:var(--muted)}

@media(max-width:960px){
  #time-widget,#weather-widget{display:none}
}

.shell{display:flex;min-height:100vh}
/* Rail is a direct .shell child (full page height); the header+main pairing
   is the other .shell child, stacked vertically within its own column. */
.content-col{display:flex;flex-direction:column;flex:1;min-width:0}
.rail{width:96px;flex-shrink:0;display:flex;flex-direction:column;gap:6px;padding:18px 8px;border-right:1px solid var(--border)}
.rail-item{position:relative;overflow:hidden;display:flex;flex-direction:column;align-items:center;gap:6px;padding:10px 4px;border-radius:9px;color:var(--sub);border:1px solid transparent;text-align:center}
.rail-item.active{color:var(--accent);border-color:var(--accent);background:var(--surf)}
.rail-item:hover{background:var(--surf);color:var(--text)}
.rail-icon{display:flex;color:inherit;transition:transform .15s}
.rail-icon svg{width:24px;height:24px}
/* For the rare rail icon that's a real favicon image instead of hand-drawn
   SVG (Delta, 2026-09-03) — same footprint as the SVG icons, rounded to
   match the rest of this site's icon treatment. */
.rail-icon img{width:24px;height:24px;object-fit:contain;border-radius:4px}
.rail-item:hover .rail-icon{transform:scale(1.12)}
/* Same glossy diagonal shine as the outward-service icons and hero cards —
   a consistent hover language across the whole site, including the rail. */
.rail-item::after{
  content:'';position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  background:linear-gradient(135deg,rgba(255,255,255,.16) 0%,rgba(255,255,255,.05) 30%,rgba(255,255,255,0) 55%);
  opacity:0;transition:opacity .2s;
}
.rail-item:hover::after{opacity:1}
.rail-label{font-size:.76rem;font-weight:600;line-height:1.2;letter-spacing:.01em}

.main{flex:1;min-width:0;padding:24px 28px}

.badge-row{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:14px}
.badge{font-size:.68rem;padding:5px 10px;border-radius:999px;border:1px solid var(--border);background:var(--surf);display:inline-flex;align-items:center;gap:6px}
.badge.down{border-color:var(--down);color:var(--down)}

/* Message board (2026-09-06, replaced the old single-line dot-joined
   strip) — newest entry first, each its own row with a colored severity
   dot: green (info) / yellow (warning) / red (critical). */
.news-board{background:var(--surf);border:1px solid var(--border);border-radius:10px;margin-bottom:16px;overflow:hidden}
.news-item{display:flex;align-items:flex-start;gap:10px;padding:10px 14px;border-bottom:1px solid var(--border)}
.news-item:last-child{border-bottom:none}
.news-dot{flex-shrink:0;width:9px;height:9px;border-radius:50%;margin-top:5px}
.news-dot-info{background:var(--ok)}
.news-dot-warning{background:#e8b45c;box-shadow:0 0 5px rgba(232,180,92,.5)}
.news-dot-critical{background:var(--down);box-shadow:0 0 5px rgba(255,71,126,.5)}
.news-item-body{display:flex;flex-direction:column;gap:1px;min-width:0}
.news-date{font-size:.62rem;font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:var(--muted)}
.news-text{font-size:.78rem;color:var(--text);line-height:1.45}
.news-text b,.news-text strong{color:var(--accent)}

.panel-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:10px}

/* ── SUBSECTION ROWS (category pages using registry.js's `subsection`
   field — Media/Household/Games/Admin, 2026-09-03) ── */
.panel-section{margin-bottom:18px}
.panel-section-title{font-size:.76rem;font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:var(--sub);margin:0 0 8px;padding-bottom:6px;border-bottom:1px solid var(--border)}

/* ── HERO CARD ────────────────────────────────────────────────
   One component, three readings. A bare .panel is the original flat tile.
   .has-image turns it into an image-backed hero card: art bleeds to the card
   edges, a bottom gradient fades it into --bg, and the content stack sits over
   that fade. .has-avatar swaps the art for a colored initial disc (Family).
   The status dot and badge are always positioned over the top corners.
   Every height below is 20% shorter than the previous pass (2026-09-03),
   except the Game Servers cards (.has-image:has(.conn-info), further down)
   — deliberately excluded per explicit request, and untouched by this pass
   since :has(.conn-info)'s own min-height rule already wins over the base
   .has-image rule regardless of what the base rule says. */
.panel{
  position:relative;overflow:hidden;
  background:var(--surf);border:1px solid var(--border);border-radius:10px;padding:8px;
  min-height:42px;display:flex;flex-direction:column;
  transition:border-color .2s,transform .15s;
}
.panel:hover{border-color:var(--accent);transform:translateY(-2px) scale(1.025)}
.panel.accent2{background:var(--surf-accent2);border-color:color-mix(in srgb, var(--accent2) 55%, transparent)}
/* A service this portal doesn't host and can't check (renderPanelHTML's
   `external` flag) — a subtly different tint from the standard --surf
   background, using the palette's own pressed-accent tone rather than an
   unrelated color, so it reads as "not local" without shouting about it. */
/* The darker maroon-red from Delta's own "Widget" logo (the shadow-side
   triangle, not the brighter top face) — 2026-09-03, replacing a tint
   derived from this site's own blue accent. Currently the only category
   using external:true is Delta, so a Delta-specific literal color is fine
   here; if external:true is ever used outside Delta, this should become a
   per-category or per-service color instead of a hardcoded sitewide one. */
.panel.external{background:color-mix(in srgb, #7A1B2E 30%, var(--surf))}
/* Glossy diagonal shine on hover, same treatment as the outward-service
   icon tiles — a soft white highlight sweeping the top-left corner. Sits
   above the hero-card art (.has-image's own ::before fade) but below the
   text content (.panel-content has its own z-index:1), so it reads across
   the art without washing out the name/details. */
.panel::after{
  content:'';position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  background:linear-gradient(135deg,rgba(255,255,255,.22) 0%,rgba(255,255,255,.07) 30%,rgba(255,255,255,0) 55%);
  opacity:0;transition:opacity .2s;
}
.panel:hover::after{opacity:1}

.panel.has-image{
  min-height:112px;padding:8px;
  background-size:cover;background-position:center 30%;background-repeat:no-repeat;
}
/* The fade. Kept transparent through the top half so the art reads, then down to
   solid --bg so any amount of content below stays legible over it. */
.panel.has-image::before{
  content:'';position:absolute;inset:0;
  background:linear-gradient(180deg,transparent 30%,rgba(21,15,12,.62) 58%,var(--bg) 100%);
}
/* Cards carrying connection details need more room before the fade starts.
   25% larger than a normal hero card (2026-09-03, the Game Servers row was
   hard to read at the general "all boxes smaller" size from earlier the
   same session) — :has(.conn-info) scopes this to exactly the raw game-
   server cards without a class of its own, since they're the only cards
   that ever carry connection details; the wider grid column right below
   the same way, only on the grid that actually contains one. Text sizes
   inside these cards are scaled up to match, not just the box. */
.panel.has-image:has(.conn-info){min-height:238px}
/* 300px (up from 200px, 2026-09-03 — 50% wider per explicit request). */
.panel-grid:has(.conn-info){grid-template-columns:repeat(auto-fill,minmax(300px,1fr))}
.panel.has-image:has(.conn-info) .panel-name{font-size:.85rem}
.panel.has-image:has(.conn-info) .panel-sub{font-size:.7rem}
.panel.has-image:has(.conn-info) .conn-row{font-size:.75rem}
.panel.has-image:has(.conn-info) .conn-val{font-size:.74rem}
.panel.has-image:has(.conn-info) .conn-note{font-size:.7rem}
/* Service logos: shown at their own scale in the upper area, never stretched. */
.panel.has-image.img-icon{
  /* Fixed, not min — a min-height would let a 2-line subtitle grow the card
     taller than a 1-line one; overflow:hidden (on .panel, inherited) clips
     anything that doesn't fit instead. */
  height:64px;
  background-size:auto 27px;background-position:center 12px;
}
.panel.has-image.img-icon::before{background:linear-gradient(180deg,transparent 40%,var(--bg) 100%)}

.panel-content{position:relative;z-index:1;margin-top:auto;display:flex;flex-direction:column;gap:5px}

.panel-header{display:flex;flex-direction:column;gap:2px;font-size:.72rem;font-weight:600}
.panel-name{line-height:1.2}
.panel-sub{font-size:.6rem;font-weight:400;color:var(--sub)}
.panel-body{font-size:.68rem;color:var(--sub)}
/* .panel-link is the wrapper around a .panel — an <a> for services with a web UI,
   a plain <div> for the raw game servers, which have nothing to open. */
.panel-link{display:block}

/* Corner "open in new tab" escape hatch (2026-09-06) — a sibling anchor
   layered above .panel-link by z-index, not nested inside it (an <a> in an
   <a> is invalid HTML). Hidden until the tile is hovered, so it doesn't
   compete visually with the pane click that still covers the rest of the
   tile. */
.panel-link-wrap{position:relative}
.panel-popout{
  position:absolute;top:8px;right:8px;z-index:3;
  width:22px;height:22px;border-radius:6px;
  display:flex;align-items:center;justify-content:center;
  background:rgba(0,0,0,.45);color:#fff;
  opacity:0;transition:opacity .15s,background .15s;
}
.panel-link-wrap:hover .panel-popout{opacity:1}
.panel-popout:hover{background:rgba(0,0,0,.7)}

.card-badge{
  position:absolute;top:8px;right:8px;z-index:2;
  border-radius:999px;padding:3px 9px;
  font-size:.58rem;font-weight:700;letter-spacing:.04em;
  background:rgba(21,15,12,.72);border:1px solid var(--border);color:var(--accent);
  backdrop-filter:blur(4px);
}

/* Colored-initial avatar — used where no real image exists (Family). */
.panel.has-avatar{min-height:120px;align-items:center;justify-content:center}
.panel.has-avatar .panel-content{margin-top:8px;align-items:center;text-align:center}
.panel-avatar{
  position:relative;z-index:1;
  width:58px;height:58px;border-radius:50%;margin:8px auto 0;
  display:flex;align-items:center;justify-content:center;
  font-size:1.5rem;font-weight:700;color:var(--bg);
}

/* ── CONNECTION INFO ─────────────────────────────────────────── */
.conn-info{display:flex;flex-direction:column;gap:3px}
.conn-row{display:flex;align-items:baseline;gap:6px;font-size:.64rem;flex-wrap:wrap}
.conn-lbl{color:var(--sub);flex-shrink:0;min-width:50px}
.conn-val{
  font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.63rem;color:var(--text);
  background:rgba(255,183,3,.10);border-radius:4px;padding:1px 5px;word-break:break-all;
}
.conn-note{color:var(--muted);font-size:.6rem;font-style:italic;line-height:1.35;flex:1;min-width:0}

/* ── STATUS ──────────────────────────────────────────────────── */
.status-dot{
  position:absolute;top:9px;left:9px;z-index:2;
  width:9px;height:9px;border-radius:50%;display:inline-block;
  outline:2px solid rgba(21,15,12,.6);
}
/* 50% smaller than the base 9px dot, and re-centered within that same 9px
   slot (top/left nudged +2.25px) so it doesn't visibly shift corner — the
   ripple below needs the freed-up space to read as a real pulse instead of
   nearly overlapping the solid dot at every point in the animation. */
.status-dot.ok{width:4.5px;height:4.5px;top:11.25px;left:11.25px;background:var(--ok);box-shadow:0 0 6px var(--ok)}
.status-dot.unknown{background:var(--unknown);box-shadow:0 0 6px var(--unknown)}
.status-dot.down{background:var(--down);box-shadow:0 0 6px var(--down)}
.status-dot.starting{background:var(--starting);box-shadow:0 0 7px var(--starting);animation:dot-pulse 1.2s infinite}
.status-dot.sleeping{background:var(--sleeping);box-shadow:0 0 5px var(--sleeping)}
@keyframes dot-pulse{0%,100%{opacity:1}50%{opacity:.35}}

/* Radar-style ripple for a healthy ("ok") status — a solid dot with rings
   expanding outward and fading, like a sonar ping. Two rings staggered
   half a cycle apart so one is always mid-expansion; slower (2.8s) than
   the .starting opacity-pulse above, which is meant to read as "urgent/
   changing" rather than "calm and alive". Works on any element already
   position:absolute (status-dot, outward-dot) since that alone makes it a
   valid containing block for its own ::before/::after. The dot itself
   (.status-dot.ok/.outward-dot.ok, below) is 50% smaller than every other
   status dot specifically so this ripple has room to be clearly visible —
   an earlier version kept the full-size dot and scaled the ripple only
   1.6x, which was too subtle to notice at all. */
.status-dot.ok::before,.status-dot.ok::after,
.outward-dot.ok::before,.outward-dot.ok::after{
  content:'';position:absolute;inset:0;border-radius:50%;
  background:var(--ok);
  animation:dot-ripple 2.8s ease-out infinite;
}
.status-dot.ok::after,.outward-dot.ok::after{animation-delay:1.4s}
@keyframes dot-ripple{
  0%{transform:scale(1);opacity:.6}
  100%{transform:scale(4);opacity:0}
}

.game-state{font-size:.6rem;font-weight:700;letter-spacing:.05em;text-transform:uppercase}
.game-state.ok{color:var(--ok)}
.game-state.starting{color:var(--starting)}
.game-state.sleeping{color:var(--sleeping)}
.game-state.down{color:var(--down)}
.game-state.unknown{color:var(--muted)}

.tile{background:var(--surf);border:1px solid var(--border);border-radius:9px;padding:10px 8px;text-align:center;font-size:.68rem}

.page-note{color:var(--sub);font-size:.78rem;max-width:60ch;margin:-8px 0 14px}

/* ── EMBED FRAME PANE (family.html and classic.html) ─────────────────────
   A full-width frame host for a page that lives outside this portal's own
   navigation — family.html loads whichever member's page the rail's Family
   submenu asks for via ?open=<id>; classic.html always loads the one fixed
   classic-homepage URL. Shared styling since both are the same shape:
   selection (if any) happens elsewhere, this is just the frame. */
.embed-pane{height:calc(100vh - 190px);position:relative;border:1px solid var(--border);border-radius:10px;overflow:hidden;background:var(--surf)}
/* No h1/page-note above it (classic.html) — the only real overhead left is
   the top header (60px) and .main's own padding (24px top + 24px bottom).
   Was calc(100vh - 100px), leaving ~90px of dead space below the frame for
   no reason — tightened 2026-09-03 to actually match that overhead. */
.embed-pane.embed-pane-full{height:calc(100vh - 108px)}
.embed-placeholder{display:flex;align-items:center;justify-content:center;height:100%;color:var(--sub);font-size:.85rem;text-align:center;padding:20px}
.embed-frame{width:100%;height:100%;border:none;display:block}
/* A plain class selector's `display` is an author-stylesheet rule, which
   always beats the browser's own `[hidden]{display:none}` (a lower-priority
   user-agent-stylesheet rule) — so toggling the `hidden` attribute on either
   element above did nothing on its own. These two rules, with the extra
   attribute bumping their specificity above the bare class, restore it. */
.embed-placeholder[hidden]{display:none}
.embed-frame[hidden]{display:none}

/* ── PANE NAVIGATION (every page) ─────────────────────────────────────
   Clicking a rail link doesn't navigate — bootstrap.js's wirePaneNavigation
   hides #native-content and points #pane-frame at the target page's own
   URL (with ?embed=1) instead, so the rail and top header never reload.
   #native-content has no explicit `display` of its own, so the browser's
   default [hidden]{display:none} works for it unmodified; .pane-frame
   needs the same explicit-display fix as .embed-frame above.
   Height: #pane-frame is a sibling of #native-content, not nested inside
   it, and #native-content is fully hidden whenever the pane is showing —
   so whatever h1/text a given page's own native view has never actually
   sits above the pane. The only real overhead is the top header (60px)
   and .main's own padding (24px top + 24px bottom). Was
   calc(100vh - 190px) on every page, leaving ~80px of dead space below
   every pane for no reason — tightened 2026-09-03 (noticed on Family/
   Classic specifically, but the same overhead miscalculation affected
   every page's pane equally). */
.pane-frame{width:100%;height:calc(100vh - 108px);border:none;border-radius:10px;display:block}
.pane-frame[hidden]{display:none}

/* Full-bleed pane (data-pane-full, Classic only so far — see render.js and
   bootstrap.js's comments): the target has no idea it's being framed and
   assumes it owns the whole browser viewport, so it gets every pixel below
   the real top-header instead of also losing .main's 24px/28px padding on
   top of that — the standard .pane-frame calc above reserves BOTH, which
   left Classic's own content taller than the box it was given and forced
   an internal scrollbar for content that fits fine at its intended size.
   :has() scopes the padding removal to only when this specific pane is
   showing, without a second markup hook on .main itself. */
.main:has(.pane-frame-full){padding:0}
.pane-frame.pane-frame-full{height:calc(100vh - 60px);border-radius:0}

/* A page loaded with ?embed=1 (inside another page's pane) has nothing of
   its own to navigate with — the OUTER page's rail/header are what's
   visible. Set on <html> by mountCategoryPage/mountFrontDoor and by
   admin.html/classic.html's own scripts. */
.embed-mode .rail,.embed-mode .top-header{display:none}
.embed-mode .shell{min-height:0}
/* .main's height-calc rules above (.embed-pane-full, .pane-frame) assume
   the standard top-header (60px) + .main's own 24px/24px padding overhead
   that a top-level page has — but in embed mode that header is display:none
   above, and this page's own chrome contributes nothing. Left un-corrected,
   a nested page (classic.html or family.html loaded inside another page's
   own pane) rendered noticeably smaller than the space it actually had,
   boxed in by dead padding on every side — found 2026-09-03 testing
   Classic/Family specifically. Collapsing .main's padding and letting the
   embed frame claim the full nested viewport (100vh — of the IFRAME, not
   the outer page, so this is exactly the space actually available) fixes
   both dimensions at once, matching classic.html/family.html's own
   frameless full-bleed intent. */
.embed-mode .main{padding:0}
.embed-mode .embed-pane{height:100vh;border-radius:0;border:none}
.embed-mode .pane-frame{height:100vh;border-radius:0}

/* ── OUTWARD-SERVICE ICON TILES ───────────────────────────────────────
   Shared by the sitewide top header's icon row and Home's own outward-
   services row — a small icon-and-name tile with a corner status dot,
   distinct from the hero cards used on the category pages. */
.outward-row{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:18px}
.outward-item{display:flex;flex-direction:column;align-items:center;gap:6px;width:76px;padding:10px 6px;border-radius:10px;text-align:center;transition:background .15s}
.outward-item:hover{background:var(--surf)}
.outward-icon{position:relative;width:44px;height:44px;border-radius:9px;background-color:var(--surf);background-size:contain;background-position:center;background-repeat:no-repeat;transition:transform .15s}
.outward-item:hover .outward-icon{transform:scale(1.1)}
/* Glossy diagonal shine on hover — a soft white highlight across the icon's
   top-left, like light catching a glass surface. Sits above the icon image
   (z-index) but below the status dot's own layer, and doesn't intercept
   clicks (pointer-events:none) since it's purely decorative. */
.outward-icon::after{
  content:'';position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  background:linear-gradient(135deg,rgba(255,255,255,.38) 0%,rgba(255,255,255,.12) 30%,rgba(255,255,255,0) 55%);
  opacity:0;transition:opacity .2s;
}
.outward-item:hover .outward-icon::after{opacity:1}
.outward-dot{position:absolute;bottom:-2px;right:-2px;width:9px;height:9px;border-radius:50%;outline:2px solid var(--bg);z-index:1}
/* Same 50%-smaller, re-centered treatment as .status-dot.ok above — frees
   up room for the ripple to read as a real pulse. */
.outward-dot.ok{width:4.5px;height:4.5px;bottom:0.25px;right:0.25px;background:var(--ok);box-shadow:0 0 5px var(--ok)}
.outward-dot.down{background:var(--down)}
.outward-dot.unknown{background:var(--unknown)}
.outward-dot.starting{background:var(--starting);animation:dot-pulse 1.2s infinite}
.outward-dot.sleeping{background:var(--sleeping)}
.outward-name{font-size:.62rem;color:var(--sub);line-height:1.2}

/* Corner "open in new tab" escape hatch (2026-09-06) — same pattern as
   .panel-popout: a sibling anchor, not nested, layered above .outward-item
   by z-index. Positioned to sit over the icon's own top-right corner even
   though the icon is narrower than (and centered within) the wrap. */
.outward-item-wrap{position:relative;width:76px}
.outward-popout{
  position:absolute;top:2px;right:16px;z-index:2;
  width:16px;height:16px;border-radius:5px;
  display:flex;align-items:center;justify-content:center;
  background:rgba(0,0,0,.5);color:#fff;
  opacity:0;transition:opacity .15s,background .15s;
}
.outward-item-wrap:hover .outward-popout{opacity:1}
.outward-popout:hover{background:rgba(0,0,0,.8)}

/* Inside the top header, tiles run smaller and tighter — the row shares
   space with the clock and weather widgets. */
.top-header-icons .outward-item{width:60px;padding:4px}
.top-header-icons .outward-item-wrap{width:60px}
.top-header-icons .outward-popout{right:13px}
.top-header-icons .outward-icon{width:34px;height:34px}
.top-header-icons .outward-name{font-size:.56rem}

.home-auth{display:inline-block;font-size:.78rem;padding:8px 14px;border-radius:8px;margin-bottom:14px}
.home-auth.signed-in{color:var(--sub);background:var(--surf);border:1px solid var(--border)}
.home-auth.login-link{color:var(--bg);background:var(--accent);font-weight:700}
.home-auth.login-link:hover{background:var(--accent-pressed)}

/* ── RAIL ACCORDION ───────────────────────────────────────────────
   An expandable rail entry (Family) is a <button>, not a link — it has no
   destination of its own, clicking it drops down member links instead. */
.rail-group{display:flex;flex-direction:column}
.rail-toggle{width:100%;background:none;cursor:pointer;font-family:inherit;position:relative}
.rail-chevron{position:absolute;right:6px;top:50%;transform:translateY(-50%) rotate(0deg);width:14px;height:14px;transition:transform .15s}
.rail-toggle.expanded .rail-chevron{transform:translateY(-50%) rotate(90deg)}
.rail-submenu{display:flex;flex-direction:column;gap:2px;padding:4px 0 4px 10px;margin-left:14px;border-left:1px solid var(--border)}
/* Same fix as .pane-frame[hidden]/.embed-frame[hidden] elsewhere in this
   file: a plain class selector's display is an author-stylesheet rule,
   which always beats the browser's own low-priority [hidden]{display:none}
   — so wireRailToggles toggling the hidden attribute did nothing on its
   own, the submenu was always rendered regardless of expanded/collapsed
   state. Missed when those other elements were fixed; found 2026-09-03. */
.rail-submenu[hidden]{display:none}
.rail-subitem{font-size:.7rem;color:var(--sub);padding:5px 8px;border-radius:6px;text-decoration:none}
.rail-subitem:hover{background:var(--surf);color:var(--text)}
/* Set by applyWhoami() once /whoami resolves — marks which submenu entry is
   "you" when the signed-in Authentik account matches a family member. */
.rail-subitem.rail-subitem-you{color:var(--accent);font-weight:600}
.rail-subitem.rail-subitem-you::after{content:" · you";opacity:.7;font-weight:400}

/* Same source (applyWhoami) — a quiet line at the bottom of the rail, only
   ever present when someone's actually signed in. */
.rail-footer{margin-top:auto;padding:10px 6px 0;font-size:.62rem;color:var(--sub);text-align:center;border-top:1px solid var(--border)}
/* A real clickable login button (2026-09-03) — a signed-out visitor used to
   see nothing at all in this spot. Styled like the accent CTA elsewhere on
   the site (Home's own login link), not just plain rail-footer text. */
.rail-footer-login{display:block;margin:10px 6px 0;padding:8px 6px;font-size:.68rem;font-weight:700;color:var(--bg);background:var(--accent);border-radius:8px;text-align:center;border-top:none}
.rail-footer-login:hover{background:var(--accent-pressed)}
