/* ============================================================================
   circate.css — the device stylesheet.

   Extracted verbatim from the inline <style> block in home_screen.html. No
   rules were changed; only the leading indentation was removed. Lives here so
   the browser fetches it once and caches it, instead of re-sending and
   re-parsing 35KB on every navigation.
   ============================================================================ */

   :root { --fs-body: 1rem; }

   * { box-sizing: border-box; margin: 0; padding: 0; }
   
   html, body {
     height: 100%;
     overflow: hidden;             /* page itself never scrolls — sealed device */
     background: #0b0f14;          /* page behind the device (temporary) */
   }
   body {
     /* center the device, fill the viewport, no page scroll */
     display: flex;
     justify-content: center;
   }
   
   /* ===== The device: a sealed handheld, viewport-tall, phone-narrow ===== */
   .device {
     --pcb: #0d1b2a;
     width: 100%;
     max-width: 430px;
     height: 100dvh;               /* sealed: exactly viewport tall, never grows */
     display: flex;
     flex-direction: column;
     overflow: hidden;             /* nothing escapes the shell */
     position: relative;
   
     /* PCB plastic: the same board gradient as the old device */
     background: linear-gradient(160deg, #16293d 0%, var(--pcb) 45%, #081320 100%);
     padding: 10px;                 /* the side/edge thickness of the plastic */
   }
   
   /* ===== SPARC (animated pixel-art light-cycle) — alert toggle character =====
      Ported intact from the old device: rider, spinning wheels, waving scarf.
      Reset/zoom choreography is omitted here (lived with the timer). */
   .sparc-cycle {
     width: 44px;
     height: 40px;
     position: relative;
     transition: transform 0.6s cubic-bezier(0.4, 0, 0.2, 1);
   }
   
   .sparc-cycle.signaling {
     animation: sparc-signal 0.15s ease-in-out 6;
   }
   
   .sparc-cycle.signaling::after {
     content: '';
     position: absolute;
     top: 50%;
     left: 50%;
     width: 40px;
     height: 40px;
     border: 2px solid #e67e22;
     border-radius: 50%;
     transform: translate(-50%, -50%);
     animation: sparc-ring 0.9s ease-out forwards;
   }
   
   /* Zoom animation for reset */
   .sparc-cycle.zooming-right {
     animation: zoom-off-right 0.5s ease-in forwards;
   }
   
   .sparc-cycle.zooming-left {
     animation: zoom-in-left 0.5s ease-out forwards;
   }
   
   @keyframes zoom-off-right {
     0% { transform: translateX(0); }
     100% { transform: translateX(calc(100vw + 50px)); }
   }
   
   @keyframes zoom-in-left {
     0% { transform: translateX(calc(-100vw - 50px)); }
     100% { transform: translateX(0); }
   }
   
   @keyframes sparc-signal {
     0%, 100% { transform: scale(1); filter: drop-shadow(0 0 8px rgba(230, 126, 34, 0.5)); }
     50% { transform: scale(1.1); filter: drop-shadow(0 0 15px rgba(230, 126, 34, 1)); }
   }
   
   @keyframes sparc-ring {
     0% { width: 40px; height: 40px; opacity: 1; }
     100% { width: 120px; height: 120px; opacity: 0; }
   }
   
   /* SPARC pixel grids */
   .sparc-cycle .pixel-grid {
     display: grid;
     position: relative;
   }
   
   .sparc-cycle .pixel {
     background: transparent;
   }
   
   /* Wheel colors */
   .sparc-cycle .pixel.face { background: #1a1a2a; }
   .sparc-cycle .pixel.edge { background: #444455; }
   .sparc-cycle .pixel.center { background: #e67e22; box-shadow: 0 0 2px #e67e22; }
   .sparc-cycle .pixel.glow { background: #ff9933; box-shadow: 0 0 3px #ff9933; }
   
   /* Frame colors */
   .sparc-cycle .pixel.metal { background: #2a2a3a; }
   .sparc-cycle .pixel.metal-light { background: #3a3a4a; }
   .sparc-cycle .pixel.metal-dark { background: #1a1a2a; }
   .sparc-cycle .pixel.accent { background: #e67e22; box-shadow: 0 0 3px #e67e22; }
   
   /* Back wheel - 16px grid at 1x = 16px */
   .sparc-cycle .pixel-grid-16 {
     grid-template-columns: repeat(16, 1px);
     width: 16px;
     height: 16px;
   }
   .sparc-cycle .pixel-grid-16 .pixel { width: 1px; height: 1px; }
   
   /* Front wheel - 24px grid at 1x = 24px */
   .sparc-cycle .pixel-grid-24 {
     grid-template-columns: repeat(24, 1px);
     width: 24px;
     height: 24px;
   }
   .sparc-cycle .pixel-grid-24 .pixel { width: 1px; height: 1px; }
   
   /* Frame - 32×16 grid at 1x = 32×16px */
   .sparc-cycle .pixel-grid-frame {
     grid-template-columns: repeat(32, 1px);
     width: 32px;
     height: 16px;
   }
   .sparc-cycle .pixel-grid-frame .pixel { width: 1px; height: 1px; }
   
   /* Assembly positioning */
   .sparc-cycle .back-wheel-container {
     position: absolute;
     bottom: 0;
     left: 0;
     z-index: 1;
   }
   
   .sparc-cycle .front-wheel-container {
     position: absolute;
     bottom: 0;
     right: 0;
     z-index: 1;
   }
   
   .sparc-cycle .frame-container {
     position: absolute;
     bottom: 10px;
     left: 10px;
     z-index: 2;
   }
   
   .sparc-cycle .sparc-rider-container {
     position: absolute;
     bottom: 16px;
     left: 13px;
     z-index: 3;
   }
   
   /* SPARC rider pixels */
   .sparc-pixel {
     position: absolute;
     width: 2px;
     height: 2px;
     background: #ffd93d;
     box-shadow: 0 0 2px #ffd93d, 0 0 4px rgba(255, 217, 61, 0.5);
     /* sparkle only runs when SPARC is the active tab (see rule below) */
   }
   /* rider sparkles only while SPARC is active; otherwise it's calm */
   .mode-alerts[aria-current="page"] .sparc-pixel {
     animation: sparkle 0.12s ease-in-out infinite alternate;
   }
   
   .sparc-pixel.bright {
     background: #fff8dc;
     box-shadow: 0 0 3px #fff, 0 0 6px #ffd93d;
   }
   
   .sparc-pixel.core {
     background: #ffeb3b;
     box-shadow: 0 0 4px #ffeb3b, 0 0 8px rgba(255, 235, 59, 0.6);
   }
   
   @keyframes sparkle {
     0% { opacity: 0.85; }
     100% { opacity: 1; }
   }
   
   /* Scarf pixels (the trailing cape) — was missing, so the scarf was invisible */
   .scarf-pixel {
     position: absolute;
     width: 2px;
     height: 2px;
     background: #ff6b35;
     box-shadow: 0 0 2px #ff6b35, 0 0 4px rgba(255, 107, 53, 0.6);
     border-radius: 1px;
   }
   .scarf-pixel.bright {
     background: #ff9f1c;
     box-shadow: 0 0 3px #ff9f1c, 0 0 6px rgba(255, 159, 28, 0.8);
   }
   .scarf-pixel.spark {
     width: 1.5px;
     height: 1.5px;
     background: #ffdd00;
     box-shadow: 0 0 3px #ffdd00, 0 0 6px rgba(255, 221, 0, 0.9);
     border-radius: 50%;
   }
   
   /* ===== HEADER: fixed band of plastic at the top ===== */
   /* ===== HEADER: [wordmark | alerts] toggle on the left, timer on the right.
           The toggle is the screen's MODE switch: wordmark = game content,
           alerts = alerts/messages. One state active (lit), one dim. Wired to
           a dead seam until each mode's screen content exists. ===== */
   .device-header {
     flex: 0 0 48px;                /* fixed height */
     display: flex; align-items: center; justify-content: space-between;
     gap: 10px; padding: 0 4px;
   }
   
   /* left: the two-state mode toggle */
   .mode-toggle { display: flex; align-items: stretch; gap: 0; height: 44px; }
   .mode-btn {
     appearance: none; -webkit-appearance: none;
     background: none; border: none; cursor: pointer; font: inherit;
     text-decoration: none;         /* these are links now, not buttons */
     padding: 0; position: relative; -webkit-tap-highlight-color: transparent;
     transition: color .15s, opacity .15s;
     /* both buttons fill the row's height → their bottoms (and underlines) align;
        content is centered vertically within each */
     display: flex; align-items: center;
   }
   /* lit underline under the active state (like the old Persona/Feed tabs),
      sized to each item's own width */
   .mode-btn::after {
     content: ''; position: absolute; left: 0; right: 0; bottom: 0;
     height: 2px; border-radius: 2px;
     background: #46e0c0;
     box-shadow: 0 0 8px rgba(70,224,192,.8), 0 0 2px rgba(70,224,192,1);
     opacity: 0; transition: opacity .15s;
   }
   .mode-btn[aria-current="page"]::after,
   .cap-start[aria-current="page"]::after { opacity: 1; }
   /* the wordmark state */
   .mode-wordmark {
     font-family: 'Audiowide', cursive; font-size: 1.25rem; letter-spacing: .12em;
     color: #c6ccd4; text-shadow: 0 1px 0 rgba(0,0,0,.6), 0 -1px 0 rgba(160,168,180,.15);
   }
   .mode-alerts { position: relative; }
   .mode-alerts .sparc-cycle { transform: scale(0.82); transform-origin: center; }
   /* exclamation notification badge — shown only when there are notifications */
   .sparc-badge {
     position: absolute; top: -2px; right: -6px;
     min-width: 14px; height: 14px; padding: 0 3px;
     display: none;                       /* hidden by default; controller shows it */
     align-items: center; justify-content: center;
     font-family: 'IBM Plex Mono', monospace; font-size: 10px; font-weight: 700;
     line-height: 1; color: #1a0e06;
     background: #e74c3c; border-radius: 7px;
     box-shadow: 0 0 6px rgba(231,76,60,.7);
   }
   .sparc-badge.show { display: inline-flex; }
   /* the alerts state (stub label, no graphics) */
   .mode-alerts {
     font-family: 'IBM Plex Mono', monospace; font-size: 0.7rem;
     letter-spacing: 2px; text-transform: uppercase; color: #c6ccd4;
   }
   /* a thin divider between the two toggle states */
   .mode-sep {
     align-self: center; width: 1px; height: 18px; margin: 0 12px;
     background: rgba(198,204,212,.25);
   }
   /* Nothing dims. The three nav items are three different kinds of object —
      a text wordmark, a pixel character, a raised cap — and no single opacity
      treatment reads correctly across all of them. The lit underline is the
      one signal that applies to all three identically, so it carries the state
      on its own. (This is also why the old cape-punchthrough block is gone: it
      existed only to work around parent opacity flattening the subtree.) */
   .mode-btn:focus-visible { outline: 2px solid rgba(120,200,255,.6); outline-offset: 2px; border-radius: 3px; }
   
   /* right: reset timer */
   .modal-link-btn {
     appearance: none; -webkit-appearance: none;
     background: none; border: 0; padding: 0;
     font: inherit; cursor: pointer;
   }
   
   .modal-link {
     color: #1c1409;
     text-decoration: underline;
     text-underline-offset: 2px;
   }
   .modal-link:hover { color: #9a4a00; }
   .modal-link:focus-visible {
     outline: 2px solid #9a4a00; outline-offset: 2px;
     border-radius: 2px;
   }
   /* CTA links get a little breathing room from the explanatory copy */
   .modal-cta { margin-top: 2px; }
   .modal-cta:first-of-type { margin-top: 14px; }
   
   /* Flash toasts — float over the screen, don't shift layout */
   .device-toasts {
     position: absolute; top: 14px; left: 50%;
     transform: translateX(-50%);
     z-index: 60; width: calc(100% - 40px); max-width: 360px;
     display: flex; flex-direction: column; gap: 8px;
     pointer-events: none;
   }
   .device-toast {
     background: #0c1622;
     border: 1px solid rgba(70, 224, 192, 0.35);
     border-radius: 8px;
     padding: 12px 14px;
     font-size: var(--fs-body); line-height: 1.4;
     color: #e8ecf1;
     box-shadow: 0 8px 24px rgba(0,0,0,.5);
     animation: toast-in .2s ease, toast-out .3s ease 4s forwards;
   }
   @keyframes toast-in {
     from { opacity: 0; transform: translateY(-6px); }
     to   { opacity: 1; transform: none; }
   }
   @keyframes toast-out {
     to { opacity: 0; transform: translateY(-6px); }
   }
   @media (prefers-reduced-motion: reduce) {
     .device-toast {
       animation: toast-fade-out .01s linear 4s forwards;
     }
   }
   @keyframes toast-fade-out { to { opacity: 0; } }
   .timer-display {
     font-family: 'IBM Plex Mono', monospace; font-size: var(--fs-body); font-weight: 500;
     color: #e67e22; letter-spacing: 2px; text-shadow: 0 0 10px rgba(230,126,34,.5);
   }
   .timer-display.urgent {
     color: #e74c3c;
     animation: timer-pulse 0.5s ease-in-out infinite;
   }
   @keyframes timer-pulse {
     0%, 100% { opacity: 1; }
     50% { opacity: .5; }
   }
   
   /* ===== SCREEN: the window. Takes the leftover space, scrolls its OWN
           content, and can never push the header or console. ===== */
   /* ===== Info modal (archive rules) — sits inside the sealed device ===== */
   .modal-overlay {
     position: absolute; inset: 0; z-index: 50;
     display: flex; align-items: center; justify-content: center;
     padding: 20px;
     background: rgba(2, 5, 8, 0.72);
     opacity: 0; visibility: hidden;
     transition: opacity 0.18s ease, visibility 0.18s ease;
   }
   .modal-overlay.open { opacity: 1; visibility: visible; }
   .modal-overlay[hidden] { display: none; }   /* beat display:flex when sealed shut */
   .modal-card {
     background: #faf9f7;
     border: 1px solid #d0cec8;
     border-radius: 10px;
     max-width: 340px; width: 100%;
     max-height: 100%; overflow-y: auto;
     padding: 20px 20px 22px;
     box-shadow: 0 12px 40px rgba(0,0,0,.45);
     transform: translateY(8px) scale(0.98);
     transition: transform 0.18s ease;
   }
   .modal-overlay.open .modal-card { transform: none; }
   .modal-head {
     display: flex; align-items: flex-start; justify-content: space-between;
     gap: 12px; margin-bottom: 12px;
   }
   .modal-title {
     font-family: 'IBM Plex Mono', monospace;
     font-size: var(--fs-body); font-weight: 500; letter-spacing: 0.5px;
     color: #1c1409; line-height: 1.2;
   }
   .modal-close {
     flex: 0 0 auto;
     background: none; border: 0; cursor: pointer;
     width: 32px; height: 32px; border-radius: 6px;
     color: #4a4a4a; font-size: 1.4rem; line-height: 1;
     display: flex; align-items: center; justify-content: center;
   }
   .modal-close:hover { color: #1c1409; }
   .modal-close:focus-visible {
     outline: 2px solid #9a4a00; outline-offset: 2px; color: #1c1409;
   }
   .modal-body {
     font-size: var(--fs-body);
     line-height: 1.55; color: #1c1409;
   }
   .modal-body p { margin: 0 0 10px; }
   .modal-body p:last-child { margin-bottom: 0; }
   .modal-body ul {
     margin: 0 0 10px; padding-left: 1.2em;
     list-style: none;
   }
   .modal-body li { position: relative; margin-bottom: 4px; }
   .modal-body li::before {
     content: '›'; position: absolute; left: -1.1em;
     color: #9a4a00;
   }
   @media (prefers-reduced-motion: reduce) {
     .modal-overlay, .modal-card { transition: none; }
     .modal-overlay.open .modal-card { transform: none; }
   }
   
   /* The glass, and the box that doesn't scroll.
   
      The scroller and the screen used to be one element. They can't stay one:
      the transport has to sit against the bottom edge of the glass and stay
      there while rows move underneath it, and an absolutely-positioned child of
      a scrolling box is positioned against the SCROLLED padding box — so it
      would ride up and down with the content it's meant to sit over.
   
      So the outer box owns the size, the glass and the corners; the inner one
      owns the scrolling. Everything that was here has gone to one or the other
      and nothing has been added, which is why this step should be invisible.
   
      overflow: hidden is the rule doing work you can't see yet. The radius
      lives here now, and when the sheet opens to the full height of this box in
      a later step, its corners have to be clipped by it — without this they'd
      square off against the plastic. */
      .screen-shell {
        flex: 1 1 0;                   /* takes whatever is left */
        min-height: 0;                 /* critical: lets it shrink, so it can't grow the device */
        position: relative;            /* the frame the transport will be pinned to */
        margin: 10px 0;                /* gap between screen and the plastic bands */
        background: #05080a;           /* the dark glass */
        border-radius: 6px;
        overflow: hidden;              /* the corners clip what's inside them */
        --np-h: 45px;                  /* the collapsed transport's height */
      }
      
      /* Fills the shell exactly and scrolls inside it. inset:0 rather than
         width/height: no dependence on the parent having a resolved height, which
         it doesn't until flex has run. */
      .device-screen {
        position: absolute; inset: 0;
        overflow-y: auto;             /* the screen's OWN content scrolls here */
        overflow-x: hidden;           /* never scroll sideways; content stays in bounds */
        -webkit-overflow-scrolling: touch;
        padding: 18px 16px calc(18px + var(--np-h) + var(--kb, 0px));
      }
   
   /* ===== Circate search bar — bracketed [ input ] terminal/HUD twist ===== */
   .circate-search {
     display: flex; align-items: stretch; gap: 8px;
     height: 44px;
   }
   /* the bar lives outside the swapped region so its input node survives
      navigation; hiding it needs to beat the display:flex above */
   .circate-search[hidden] { display: none !important; }
   .cs-bracket {
     font-family: 'IBM Plex Mono', monospace;
     font-size: 2.2rem; line-height: 44px;       /* tall brackets spanning the field */
     font-weight: 300; color: #46e0c0;
     text-shadow: 0 0 8px rgba(70,224,192,.5);
     user-select: none; flex: 0 0 auto;
   }
   .cs-field { position: relative; flex: 1 1 auto; min-width: 0; display: flex; }
   .cs-input {
     flex: 1 1 auto; min-width: 0;
     appearance: none; -webkit-appearance: none;
     background: none; border: none; outline: none;
     font-family: 'IBM Plex Mono', monospace; font-size: 1rem;
     color: #eaf6ff; caret-color: #46e0c0; letter-spacing: .5px;
     padding: 0 4px;
     position: relative; z-index: 2;
   }
   .cs-input::placeholder { color: #5d748a; }
   .cs-input::-webkit-search-decoration,
   .cs-input::-webkit-search-cancel-button { -webkit-appearance: none; }
   
   /* animated typewriter "ghost" placeholder — shown only when the field is
      empty and unfocused; the real input sits transparently on top. */
   .cs-ghost {
     position: absolute; left: 0; top: 50%; transform: translateY(-50%);
     pointer-events: none;
     font-family: 'IBM Plex Mono', monospace; font-size: 1rem; letter-spacing: .5px;
     color: #5d748a; padding: 0 4px; white-space: pre;
   }
   /* the blinking cursor that trails the typed ghost text */
   .cs-ghost::after {
     content: ''; display: inline-block;
     width: 0.55ch; height: 1.05em; margin-left: 1px;
     background: #46e0c0; box-shadow: 0 0 6px rgba(70,224,192,.6);
     vertical-align: text-bottom;
     animation: cs-blink 1s steps(1) infinite;
   }
   @keyframes cs-blink { 0%,50% { opacity: 1; } 50.01%,100% { opacity: 0; } }
   /* hide the ghost once the user is in the field or has typed */
   .cs-ghost.hidden { display: none; }
   /* focus: brackets brighten/glow to show the field is live */
   .circate-search:focus-within .cs-bracket {
     color: #7df0d8;
     text-shadow: 0 0 12px rgba(70,224,192,.8);
   }
   
   /* the results / pre-search canvas under the bar. The gap only applies when
      the bar is actually present — Sparc and Start have no bar to sit under. */
   .circate-search:not([hidden]) + #screen-content { margin-top: 16px; }
   .circate-results { margin-top: 16px; }
   
   /* ===== Filter tabs (appear after a search) ===== */
   .filter-tabs {
     display: flex; flex-wrap: wrap; gap: 6px 4px;
     padding-bottom: 12px; margin-bottom: 12px;
     border-bottom: 1px solid rgba(70,224,192,.16);
   }
   .filter-tab {
     appearance: none; -webkit-appearance: none;
     background: none; border: 1px solid transparent; cursor: pointer;
     font-family: 'IBM Plex Mono', monospace; font-size: var(--fs-body, 1rem);
     letter-spacing: .5px; color: #5d748a;
     padding: 4px 10px; border-radius: 5px;
     -webkit-tap-highlight-color: transparent;
     transition: color .12s, background .12s, border-color .12s;
   }
   .filter-tab:hover { color: #9fb4c4; }
   .filter-tab[aria-selected="true"] {
     color: #eaf6ff; background: rgba(70,224,192,.1);
     border-color: rgba(70,224,192,.4);
   }
   .filter-tab:focus-visible { outline: 2px solid rgba(70,224,192,.7); outline-offset: 2px; }
   
   /* ===== Result items ===== */
   .result-list { display: flex; flex-direction: column; gap: 8px; }
   .result-item {
     display: flex; align-items: flex-start; gap: 10px;
     padding: 11px 10px; border-radius: 7px;
     background: rgba(255,255,255,.02);
     border: 1px solid rgba(120,160,200,.08);
     cursor: pointer; text-align: left; width: 100%;
     appearance: none; -webkit-appearance: none; font: inherit;
     -webkit-tap-highlight-color: transparent;
     transition: background .12s, border-color .12s;
   }
   .result-item:hover { background: rgba(70,224,192,.05); border-color: rgba(70,224,192,.25); }
   /* melody/minigame cards are links: strip anchor defaults */
   a.result-item { text-decoration: none; }
   /* game type marker */
   .type-chip[data-type="game"] { color: #46e0c0; text-shadow: 0 0 3px rgba(70,224,192,.35); }
   /* visited read-state cue: once you've opened a melody, it dims.
      Browsers restrict :visited to color only — which is all we need. */
   a.result-item:visited .result-title { color: #7d8b99; }
   a.result-item:visited .type-chip { color: #5d748a; text-shadow: none; }
   
   /* Expandable "Melody Puzzles" card (Mini-games filter) */
   .melody-group { display: flex; flex-direction: column; gap: 6px; }
   .melody-toggle { cursor: pointer; }
   .melody-chevron {
     align-self: center; color: #8a94a0; font-size: 0.8rem;
     transition: transform .15s ease;
   }
   .melody-toggle[aria-expanded="true"] .melody-chevron { transform: rotate(180deg); }
   .melody-days { display: flex; flex-direction: column; gap: 6px; padding-left: 8px; }
   .melody-days[hidden] { display: none; }   /* beat display:flex when collapsed */
   .melody-day {
     display: flex; align-items: flex-start; gap: 10px;
     padding: 9px 10px; border-radius: 7px;
     background: rgba(255,255,255,.02);
     border: 1px solid rgba(120,160,200,.08);
     text-decoration: none;
     transition: background .12s, border-color .12s;
   }
   .melody-day:hover { background: rgba(70,224,192,.05); border-color: rgba(70,224,192,.25); }
   .melody-day-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
   .melody-day:visited .result-title { color: #7d8b99; }
   .melody-day:visited .type-chip { color: #5d748a; text-shadow: none; }
   @media (prefers-reduced-motion: reduce) {
     .melody-chevron { transition: none; }
   }
   .result-item:focus-visible { outline: 2px solid rgba(70,224,192,.7); outline-offset: 1px; }
   
   /* type marker (J/W/L) — colored letter, no chip */
   .type-chip {
     flex: 0 0 auto;
     width: 22px; text-align: center;
     font-family: 'IBM Plex Mono', monospace; font-size: 1.05rem; font-weight: 400;
     line-height: 1.2;
   }
   .type-chip[data-type="journal"] { color: #e6c84a; text-shadow: 0 0 3px rgba(230,200,74,.35); }   /* J — WIM gold */
   .type-chip[data-type="window"]  { color: #38bdf8; text-shadow: 0 0 3px rgba(56,189,248,.35); }   /* W — blue */
   .type-chip[data-type="loop"]    { color: #ff6b9d; text-shadow: 0 0 3px rgba(255,107,157,.35); }   /* L — GLO pink */
   
   .result-body { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; overflow: hidden; }
   .result-title {
     font-family: 'IBM Plex Mono', monospace; font-size: 1rem; color: #eaf6ff;
     max-width: 100%;
     white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
   }
   .result-def {
     font-family: 'IBM Plex Mono', monospace; font-size: var(--fs-body, 1rem); color: #8fa6b8;
     max-width: 100%;
     white-space: nowrap; overflow: hidden; text-overflow: ellipsis;   /* one line + … */
     margin-top: 2px;
   }
   /* medium + tag lines share the definition's treatment */
   .result-line {
     font-family: 'IBM Plex Mono', monospace; font-size: var(--fs-body, 1rem); color: #8fa6b8;
     max-width: 100%;
     white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
     margin-top: 2px;
   }
   
   /* ===== Notification cards (alerts pane) ===== */
   /* title + unread dot share the top line */
   .notif-headline { display: flex; align-items: center; gap: 8px; min-width: 0; }
   .notif-headline .result-title { flex: 0 1 auto; }
   /* strong red unread dot — presence is the signal (not color alone) */
   .notif-dot {
     flex: 0 0 auto;
     width: 9px; height: 9px; border-radius: 50%;
     background: #ff3b30;
     box-shadow: 0 0 6px rgba(255,59,48,.7);
   }
   .result-by { font-size: var(--fs-body, 1rem); color: #5d748a; margin-top: 4px; }
   
   /* player item: glowing avatar silhouette (CSS, in the spirit of the old
      Me profile icon) + name + stats */
   .player-avatar {
     flex: 0 0 auto; position: relative;
     width: 22px; height: 22px;
     --pa-head: #d4d2ff; --pa-body: #a8a4ff; --pa-glow: rgba(168,164,255,.55);
   }
   .player-avatar::before {       /* head */
     content: ''; position: absolute; left: 50%; top: 2px; transform: translateX(-50%);
     width: 8px; height: 8px; border-radius: 50%;
     background: var(--pa-head); box-shadow: 0 0 6px var(--pa-glow);
   }
   .player-avatar::after {        /* shoulders/body */
     content: ''; position: absolute; left: 50%; bottom: 1px; transform: translateX(-50%);
     width: 16px; height: 10px; border-radius: 8px 8px 0 0;
     background: var(--pa-body); box-shadow: 0 0 6px var(--pa-glow);
   }
   .player-name {
     font-family: 'IBM Plex Mono', monospace; font-size: 1rem; color: #eaf6ff;
     white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
   }
   .player-stats {
     display: flex; gap: 14px; margin-top: 4px;
     font-family: 'IBM Plex Mono', monospace; font-size: var(--fs-body, 1rem); color: #8fa6b8;
   }
   .player-stats .stat-n { color: #eaf6ff; }
   
   /* profile stuff */
   
   .player-profile {
     padding: 16px 10px;
   }
   
   .player-profile-slug {
     font-family: 'IBM Plex Mono', monospace;
     font-size: .8rem;
     color: #a8c0d4;
     margin: 0 0 4px;
     letter-spacing: .5px;
   }
   
   .player-profile-name {
     font-family: 'IBM Plex Mono', monospace;
     font-size: 1.5rem;
     font-weight: 500;
     color: #eaf6ff;
     margin: 0;
     letter-spacing: .5px;
   }
   
   .player-profile-about,
   .challenge-rules {
     font-family: 'IBM Plex Mono', monospace;
     font-size: 1rem;
     line-height: 1.5;
     color: #a8c0d4;
     margin: 8px 0 0;
     max-width: 60ch;
   }
   
   .player-outlets {
     list-style: none;
     padding: 0;
     margin: 16px 0 0;
   }
   
   .player-outlet {
     font-family: 'IBM Plex Mono', monospace;
     font-size: 1rem;
     color: #eaf6ff;
     padding: 10px 12px;
     margin-bottom: 6px;
     background: rgba(255,255,255,.02);
     border: 1px solid rgba(255,255,255,.06);
     border-radius: 6px;
   }
   
   /* outlet cards are real links now (was a click-handled <li>) */
   a.outlet-card { display: block; text-decoration: none; }
   .outlet-card {
     font-family: 'IBM Plex Mono', monospace;
     font-size: 1rem;
     color: #eaf6ff;
     padding: 10px 12px;
     margin-bottom: 6px;
     background: rgba(255,255,255,.05);
     border: 1px solid rgba(255,255,255,.06);
     border-radius: 6px;
     cursor: pointer;
     list-style: none;
   }
   .outlet-card:hover {
     background: rgba(70,224,192,.05);
     border-color: rgba(70,224,192,.25);
   }
   .player-outlets, .outlet-children {
     list-style: none;
     padding: 0;
     margin: 16px 0 0;
   }
   .outlet-home-link {
     display: block;
     font-family: 'IBM Plex Mono', monospace;
     font-size: 1rem;
     color: #a8c0d4;
     text-decoration: none;
     margin-bottom: 8px;
   }
   .outlet-home-link:hover { color: #eaf6ff; text-decoration: underline; }
   .outlet-title {
     font-family: 'IBM Plex Mono', monospace;
     font-size: 1.5rem;
     font-weight: 500;
     color: #eaf6ff;
     margin: 0 0 8px;
   }
   
   .outlet-up-link {
     display: block;
     font-family: 'IBM Plex Mono', monospace;
     font-size: 1rem;
     color: #a8c0d4;
     text-decoration: none;
     margin: 0 0 8px 0;   
   }
   .outlet-up-link:hover { color: #eaf6ff; text-decoration: underline; }
   
   .artifact {
     margin: 0 0 28px;            /* generous space between artifacts */
     padding-top: 20px;
     border-top: 1px solid rgba(255,255,255,.07);  /* faint hairline separator */
   }
   .artifact:first-child {
     border-top: none;            /* no hairline above the first one */
     padding-top: 8px;
   }
   .artifact-meta,
   .challenge-prompt-count {
     font-family: 'IBM Plex Mono', monospace;
     font-size: 1rem;
     color: #a8c0d4;              /* muted, AAA on your background */
     margin: 0 0 8px;
     letter-spacing: .3px;
   }
   .artifact-meta-sep {
     color: #5d748a;              /* the divider bar even quieter */
     margin: 0 2px;
   }
   .artifact-label {
     font-family: 'IBM Plex Mono', monospace;
     font-size: 1rem;
     color: #eaf6ff;             /* label brighter than meta — it's content-level */
     font-weight: 500;
     margin: 0 0 6px;
   }
   .artifact-text,
   .challenge-cta-lead {
     font-family: 'IBM Plex Mono', monospace;
     font-size: 1rem;
     line-height: 1.6;
     color: #eaf6ff;
     margin: 0;
   }
   
   .artifact-audio {
     width: 100%;
     max-width: 420px;
     margin: 4px 0 0;
   }
   
   .artifact-embed {
     width: 100%;
     max-width: 560px;
     aspect-ratio: 16 / 9;
     margin: 8px 0 0;
   }
   
   /* launch: a link that opens a URL in a new tab, with a new-tab icon.
      Teal-on-dark ≈ 7:1 contrast (WCAG AA/AAA); 1rem text; the icon is
      decorative (aria-hidden) and the "opens in a new tab" cue is provided
      to screen readers via a visually-hidden span. */
   .artifact-launch {
     display: inline-flex;
     align-items: center;
     gap: 7px;
     font-family: 'IBM Plex Mono', monospace;
     font-size: 1rem;
     font-weight: 500;
     color: #6fb8aa;
     text-decoration: none;
     padding: 6px 2px;
     margin: 2px 0 0;
     -webkit-tap-highlight-color: transparent;
     transition: color .15s;
   }
   .artifact-launch:hover,
   .artifact-launch:focus-visible { color: #a9efe0; text-decoration: underline; }
   .artifact-launch-icon { flex: 0 0 auto; }
   
   /* Screen-reader-only text: present in the accessibility tree, off-screen
      visually. Used to announce "opens in a new tab". */
   .visually-hidden {
     position: absolute !important;
     width: 1px; height: 1px;
     padding: 0; margin: -1px;
     overflow: hidden; clip: rect(0 0 0 0);
     white-space: nowrap; border: 0;
   }
   
   /* SPARC activity log: a console-style stream on SPARC's surface, grouped
      into archive periods. Monospace + dividers evoke a system log. All text
      ≥ 1rem; teal/blue on #05080a checked ≥ 7:1. */
   .sparc-log { margin: 28px 0 4px; }
   
   /* title left, countdown right — same shape as .sparc-log-divider below.
      Wraps rather than crushing if the countdown is at its longest ("2d 4h 13m"). */
   .sparc-log-head {
     display: flex; align-items: baseline; justify-content: space-between;
     gap: 12px; flex-wrap: wrap;
     margin: 0 0 12px;
   }
   .sparc-log-head .sparc-log-heading { margin: 0; }
   
   .archive-timer {
     appearance: none; -webkit-appearance: none;
     display: flex; align-items: baseline; gap: 7px;
     background: none; border: 0; padding: 0; margin: 0;
     cursor: pointer;
     border-radius: 4px;
     -webkit-tap-highlight-color: transparent;
   }
   .archive-timer:focus-visible {
     outline: 2px solid rgba(70,224,192,.7);
     outline-offset: 3px;
   }
   
   /* was 0.55rem (~8.8px) in the header, which is below comfortable reading.
      Matched to the divider scale here. #8fa8bd is 8.1:1 on the screen glass. */
   .archive-timer-label {
     font-family: 'IBM Plex Mono', monospace;
     font-size: .7rem; letter-spacing: .14em; text-transform: uppercase;
     color: #8fa8bd;
   }
   
   /* keeps the orange the timer has always had (7.1:1 here), scaled to sit as a
      peer of the heading rather than dominating the section */
   .archive-timer .timer-display {
     font-size: .875rem;
     letter-spacing: 1px;
     text-shadow: 0 0 8px rgba(230,126,34,.35);
   }
   
   .sparc-log-heading {
     font-family: 'IBM Plex Mono', monospace;
     font-size: .75rem; font-weight: 600;
     letter-spacing: .16em; text-transform: uppercase;
     color: #6fb8aa; margin: 0 0 12px;
   }
   
   .sparc-log-list { list-style: none; margin: 0; padding: 0; }
   
   .sparc-log-divider {
     font-family: 'IBM Plex Mono', monospace;
     font-size: .7rem; letter-spacing: .14em; text-transform: uppercase;
     color: #8fa8bd;                 /* was #5d748a on the date: 4.14:1, under 4.5 */
     padding: 16px 0 8px;
     border-top: 1px solid #12232c;
     margin-top: 8px;
     display: flex; justify-content: space-between; gap: 10px;
   }
   .sparc-log-divider:first-child { border-top: 0; margin-top: 0; padding-top: 0; }
   .sparc-log-divider-date { font-weight: 400; }
   
   .sparc-log-entry { margin: 0; padding: 0; }
   
   .sparc-log-link {
     display: block;
     font-family: 'IBM Plex Mono', monospace;
     text-decoration: none;
     padding: 10px 8px;
     margin: 0 -8px;                 /* hover bleeds to the screen's padding edge */
     border-radius: 4px;
     border-bottom: 1px solid #0f1c24;
     -webkit-tap-highlight-color: transparent;
     transition: background .12s;
   }
   .sparc-log-link:hover,
   .sparc-log-link:focus-visible { background: #0c151b; }
   .sparc-log-entry:last-child .sparc-log-link { border-bottom: 0; }
   
   /* Each entry is the profile header at list scale: a quiet path, the work, the
      place it lives. No accent — size and brightness carry the hierarchy, exactly
      as .player-profile-slug / .player-profile-name do at the top of a profile.
      #a8c0d4 10.7:1 · #eaf6ff 18.3:1 · #8fa8bd 8.1:1 on the screen glass. */
   .sparc-log-who {
     display: block;
     font-size: .8rem; letter-spacing: .5px;
     color: #a8c0d4;
     margin: 0 0 3px;
   }
   .sparc-log-what {
     display: block;
     font-size: 1rem; line-height: 1.35;
     color: #eaf6ff;
   }
   .sparc-log-where {
     display: block;
     font-size: .8125rem;
     color: #8fa8bd;
     margin: 3px 0 0;
   }
   .artifact-embed iframe { width: 100%; height: 100%; }
   
   
   .alerts-tool-link,
   .challenge-cta-join {
     display: inline-block;
     font-family: 'IBM Plex Mono', monospace;
     font-size: 1rem;
     color: #eaf6ff;
     text-decoration: none;
     padding: 10px 12px;
     margin-bottom: 10px;
     background: rgba(255,255,255,.02);
     border: 1px solid rgba(255,255,255,.06);
     border-radius: 6px;
   }
   .alerts-tool-link:hover {
     background: rgba(70,224,192,.05);
     border-color: rgba(70,224,192,.25);
   }
   
   /* ===== Screen panes (Circate search / SPARC alerts) ===== */
   .alerts-list { display: flex; flex-direction: column; gap: 10px; }
   .alerts-empty {
     font-family: 'IBM Plex Mono', monospace; font-size: 1rem;
     color: #5d748a; text-align: center; padding: 32px 8px;
   }
   
   /* SPARC surface placeholder (UI-first; real content TBD).
      #cfe8e0 on #05080a ≈ 13:1 contrast — well past WCAG AA. */
   .sparc-placeholder {
     font-family: 'IBM Plex Mono', monospace;
     font-size: 1.5rem; letter-spacing: 0.15em;
     color: #cfe8e0; text-align: center; padding: 48px 8px;
   }
   
   /* right-side magnifying glass: search indicator + clickable submit */
   .cs-go {
     flex: 0 0 auto;
     appearance: none; -webkit-appearance: none;
     background: none; border: none; cursor: pointer;
     display: inline-flex; align-items: center; justify-content: center;
     padding: 0 4px; color: #6fb8aa;
     -webkit-tap-highlight-color: transparent;
     transition: color .15s, transform .1s;
   }
   .cs-go:hover { color: #a9efe0; }
   .cs-go:active { transform: scale(.9); }
   .cs-go:focus-visible { outline: 2px solid rgba(70,224,192,.7); outline-offset: 2px; border-radius: 4px; }
   /* glass brightens with the brackets when the field is focused */
   .circate-search:focus-within .cs-go { color: #7df0d8; }
   
   /* ===== CONSOLE: fixed band of plastic at the bottom ===== */
   /* ===== CONSOLE: [START] .. ^ .. [+] =====
      Each cap sits at the centre of its own half — not hugging the centre
      line, as it did when a now-removed LED lived there. The two caps are
      different widths, so centring them in equal columns puts their inner
      edges at different distances from the middle: START's is 50.5px out,
      +'s is 62.5px. That asymmetry belongs to the caps themselves rather
      than to a nudge, and it's what stops the viewer mark reading as a
      third button in an evenly spaced row.

      position: relative because the viewer mark is positioned against this
      box. It is NOT a grid item — see .cap-view — so it cannot move the
      caps no matter what happens to it. */
   .device-console {
     position: relative;
     flex: 0 0 78px;                /* fixed height — sized to hold the cap face */
     display: grid;
     grid-template-columns: 1fr 1fr;
     align-items: center;
     padding: 0 20px;
   }
   /* By class, not :first-child/:last-child — the viewer mark now sits
      between them in the markup, and position rules should name what they
      mean rather than depend on sibling order. */
   .device-console > .cap-start,
   .device-console > .cap-plus { justify-self: center; }

   .device-console {
    transition: flex-basis .18s ease, opacity .12s ease, visibility .18s;
  }

  .device.kb-open > .device-console {
    flex-basis: 0;
    opacity: 0;
    visibility: hidden;
    overflow: hidden;
    pointer-events: none;
  }
   
   /* --- Button caps (harvested look from the old STATS / Me caps) --- */
   .cap-btn {
     appearance: none; -webkit-appearance: none; cursor: pointer;
     position: relative; z-index: 2;
     display: inline-flex; align-items: center; justify-content: center;
     -webkit-tap-highlight-color: transparent;
     background:
       radial-gradient(ellipse at 40% 25%, rgba(70,76,88,.5), transparent 60%),
       linear-gradient(160deg,#26282e 0%,#16171b 55%,#0e0f12 100%);
     border: 2px solid rgba(255,255,255,0.77);
     box-shadow: 0 5px 8px rgba(0,0,0,.55), inset 0 1px 1px rgba(120,128,140,.22);
     color: #c6ccd4;
     text-decoration: none;
     transition: transform .08s ease, box-shadow .08s ease, border-color .08s ease;
   }
   .cap-btn:hover { border-color: #58606c; }
   .cap-btn:active {
     transform: translateY(4px);
     box-shadow: 0 1px 3px rgba(0,0,0,.5), inset 0 1px 2px rgba(0,0,0,.4);
   }
   .cap-btn:focus-visible { outline: 2px solid rgba(120,200,255,.6); outline-offset: 3px; }
   
   /* Start: a wider, shorter cap (like the bare STATS cap) */
   .cap-start {
     width: 84px; height: 44px; border-radius: 5px;
     font-family: 'IBM Plex Mono', monospace;
     font-size: 0.7rem; letter-spacing: 2px; font-weight: 500;
     text-shadow: 0 1px 0 rgba(0,0,0,.5);
     /* letter-spacing is applied after every character including the last T,
        so the word carries 2px of empty space on its right and sits 1px left
        of the cap's true centre. Without this the A never lands on centre. */
     text-indent: 2px;
     /* And the cap itself sits 3px right of its column centre — judged by eye
        against the console as a whole, not derived. `left` rather than a
        margin or a transform: .cap-btn is already position:relative, and
        :active owns transform. */
     left: 3px;
   }
   /* START is a place, not an action, so it carries the same lit underline as
      the header items. Sits just below the cap and presses down with it. */
   .cap-start::after {
     content: ''; position: absolute; left: 0; right: 0; bottom: -9px;
     height: 2px; border-radius: 2px;
     background: #46e0c0;
     box-shadow: 0 0 8px rgba(70,224,192,.8), 0 0 2px rgba(70,224,192,1);
     opacity: 0; transition: opacity .15s;
   }
   
   /* + : a squarer cap (like the Me cap), big plus glyph instead of the avatar */
   .cap-plus {
     width: 60px; height: 58px; border-radius: 5px;
     font-size: 1.8rem; line-height: 1; font-weight: 300;
     text-shadow: 0 1px 0 rgba(0,0,0,.5);
   }

   /* ===== THE VIEWER MARK ====================================================
      Opens the viewer, and it's reachable from every screen, so it lives in the
      console rather than inside any one surface.

      Deliberately NOT a cap. A third cap would make the console a keypad of
      three evenly spaced keys; this is a chevron cut into the case with the
      artifact mark beneath it — the act above, the subject below, both
      engraved. Two objects and one marking, not three objects.

      An engraving is a dark cut with a lit lower lip. The lip is
      rgba(255,255,255,.77) — the exact border colour of the two caps — so the
      groove is lit by the same light as their edges rather than being drawn in
      an arbitrary grey.

      Absolutely positioned, so it is not a grid item and cannot displace the
      caps. 56 × 64 rather than the 74px of clear plastic it sits in: that
      leaves 9px of dead case on each side, so a thumb aimed at the edge of +
      doesn't open the viewer instead. top:3px keeps its lower edge clear of
      the home indicator and centres the target on the ink rather than on the
      band. Both dimensions are well past the 44px floor. */
   .cap-view {
     position: absolute; z-index: 1;
     left: calc(50% - 28px); top: 3px;
     width: 56px; height: 64px;
     appearance: none; -webkit-appearance: none;
     background: none; border: 0; padding: 0; cursor: pointer;
     overflow: visible;
     -webkit-tap-highlight-color: transparent;
   }
   /* The drawing is bigger than the target and overflows it on purpose — the
      target is sized for a thumb, the artwork for the eye. pointer-events:none
      so the SVG never swallows the press. top:-3px puts the drawing back into
      the console's own coordinates after the button was pushed down 3px. */
   .cap-view svg {
     position: absolute; left: 50%; top: -3px; transform: translateX(-50%);
     display: block; overflow: visible; pointer-events: none;
   }
   .cap-view:focus-visible { outline: 2px solid rgba(120,200,255,.6); outline-offset: 2px; border-radius: 6px; }

   .cv-cut { fill: none; stroke: rgba(0,0,0,.8);          stroke-linecap: round; stroke-linejoin: round; }
   .cv-lip { fill: none; stroke: rgba(255,255,255,.77);   stroke-linecap: round; stroke-linejoin: round; }
   .cv-lit { fill: none; stroke: #46e0c0;                 stroke-linecap: round; stroke-linejoin: round;
             opacity: 0; transition: opacity .2s; }
   .cv-t-cut { fill: none; stroke: rgba(0,0,0,.8); stroke-linejoin: round; }
   .cv-t-lip { fill: rgba(255,255,255,.77); }
   .cv-t-lit { fill: #46e0c0; opacity: 0; transition: opacity .2s; }

   /* ── state ────────────────────────────────────────────────────────────────
      The transport sets data-np; the button's own aria-expanded carries open,
      so there's no invented attribute for a state ARIA already names.

      This is state, not place. The teal underline (.cap-start::after, the
      header items) means "you are here" and none of these take it — the
      marking itself lights instead. Opening the viewer is an act; having
      something loaded is a condition. Different vocabularies, on purpose. */
   .cap-view[data-np="loaded"]  .cv-lit,
   .cap-view[data-np="loaded"]  .cv-t-lit { opacity: .5; }
   .cap-view[data-np="playing"] .cv-lit,
   .cap-view[data-np="playing"] .cv-t-lit { opacity: 1; }
   .cap-view[data-np="playing"] .cv-lit { filter: drop-shadow(0 0 4px rgba(70,224,192,.75)); }
   /* Open: the cut floods. No rotation to a down-chevron — an engraving in a
      case cannot turn over, and the light is the honest way to say it. */
   .cap-view[aria-expanded="true"] .cv-lit,
   .cap-view[aria-expanded="true"] .cv-t-lit { opacity: 1; }
   .cap-view[aria-expanded="true"] .cv-lit {
     filter: drop-shadow(0 0 9px rgba(70,224,192,.95)) drop-shadow(0 0 2px rgba(70,224,192,1));
   }

   /* There is no hover on glass and an engraving can't be pressed down, so the
      light is the entire tap feedback. Without this the control feels dead on
      the first touch of every session. */
   .cap-view:active .cv-lit,
   .cap-view:active .cv-t-lit { opacity: 1; }

   @media (prefers-reduced-motion: reduce) {
     .cv-lit, .cv-t-lit { transition: none; }
   }
   
   /* ===== Challenge — canonical view =========================================
      The challenge page reuses the outlet frame. The four classes below are the
      challenge's own names for treatments that already exist, so they're added to
      those selectors rather than restated: same declarations, one source.
      Only .challenge-cta's spacing is genuinely new.
      ========================================================================= */
   .challenge-cta { margin: 20px 0 0; }
   
   /* The join trigger takes its box from .alerts-tool-link (font, size, padding,
      radius) but rests in the teal state that rule only reaches on hover — it's
      the primary action on the page, so it shouldn't be the quietest thing on it.
      Teal on the screen glass measures 12.1:1. */
   .challenge-cta-join,
   .profile-save {
     appearance: none; -webkit-appearance: none;
     cursor: pointer;
     color: #46e0c0;
     background: rgba(70,224,192,.07);
     border-color: rgba(70,224,192,.45);
     transition: background .15s, border-color .15s, color .15s;
     -webkit-tap-highlight-color: transparent;
   }
   .challenge-cta-join:hover,
   .profile-save:hover {
     background: rgba(70,224,192,.14);
     border-color: #46e0c0;
   }
   /* same focus ring as .cs-go, the screen's other teal control */
   .challenge-cta-join:focus-visible,
   .profile-save:focus-visible {
     outline: 2px solid rgba(70,224,192,.7);
     outline-offset: 3px;
   }
   
   
   /* ===== Profile identity — owner editing =================================
      The edit affordance is a quiet pencil beside the name, not a button under
      the content: landing on your own profile should show you your profile, not
      a control panel. Visible rather than hover-revealed — the device is
      touch-first and hover doesn't exist there.
   
      Save reuses .challenge-cta-join's teal (the screen's primary action).
      Cancel is deliberately NOT a matching box: two equal buttons make you read
      both to find the safe one.
      ======================================================================= */
   
   /* text and pencil side by side, so a long name wraps in its own column
      instead of running under an absolutely-positioned icon */
   .profile-identity-view {
     display: flex;
     align-items: flex-start;
     gap: 10px;
   }
   .profile-identity-text { flex: 1 1 auto; min-width: 0; }
   
   .profile-edit {
     flex: 0 0 auto;
     appearance: none; -webkit-appearance: none;
     cursor: pointer;
     display: inline-flex; align-items: center; justify-content: center;
     width: 34px; height: 34px;          /* real tap target around a 15px glyph */
     margin: 2px -6px 0 0;               /* optically aligned to the name's cap */
     color: #8fa8bd;                     /* 8.1:1 — quiet, still legible */
     background: none;
     border: 1px solid transparent;
     border-radius: 5px;
     transition: color .12s, background .12s, border-color .12s;
     -webkit-tap-highlight-color: transparent;
   }
   .profile-edit:hover {
     color: #46e0c0;
     background: rgba(70,224,192,.07);
     border-color: rgba(70,224,192,.3);
   }
   .profile-edit:focus-visible {
     outline: 2px solid rgba(70,224,192,.7);
     outline-offset: 2px;
   }
   
   .profile-identity-edit { margin: 4px 0 0; }
   
   .profile-field { margin: 0 0 14px; }
   
   .profile-label-row {
     display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
   }
   
   .profile-label {
     display: block;
     font-family: 'IBM Plex Mono', monospace;
     font-size: .75rem; letter-spacing: .12em; text-transform: uppercase;
     color: #8fa8bd;                     /* 8.1:1 */
     margin: 0 0 6px;
   }
   
   .profile-count {
     font-family: 'IBM Plex Mono', monospace;
     font-size: .75rem;
     color: #8fa8bd;
     margin: 0 0 6px;
     font-variant-numeric: tabular-nums; /* stops the field jittering as it counts */
   }
   .profile-count-near { color: #ffc46b; }   /* 12.8:1 */
   
   .profile-input {
     width: 100%;
     appearance: none; -webkit-appearance: none;
     font-family: 'IBM Plex Mono', monospace;
     font-size: 1rem;
     color: #eaf6ff;
     caret-color: #46e0c0;               /* same caret as the search field */
     background: rgba(255,255,255,.04);
     border: 1px solid rgba(255,255,255,.12);
     border-radius: 5px;
     padding: 10px 11px;
   }
   .profile-input:focus {
     outline: none;
     border-color: rgba(70,224,192,.55);
     background: rgba(70,224,192,.05);
   }
   .profile-textarea { resize: vertical; line-height: 1.5; }
   
   .profile-error {
     font-family: 'IBM Plex Mono', monospace;
     font-size: .8125rem;
     color: #ff9d8a;                     /* 10:1 — and every error is text too */
     margin: 6px 0 0;
   }
   
   .profile-actions {
     display: flex; align-items: center; gap: 4px;
     margin: 18px 0 0;
   }
   
   /* the primary action earns its width */
   .profile-save { flex: 1 1 auto; text-align: center; padding: 12px 16px; }
   
   /* quiet by contrast, so Save is unmistakably the one you want */
   .profile-cancel {
     flex: 0 0 auto;
     appearance: none; -webkit-appearance: none;
     cursor: pointer;
     font-family: 'IBM Plex Mono', monospace;
     font-size: 1rem;
     color: #8fa8bd;
     background: none;
     border: 1px solid transparent;
     border-radius: 6px;
     padding: 12px 14px;
   }
   .profile-cancel:hover { color: #eaf6ff; }
   .profile-cancel:focus-visible {
     outline: 2px solid rgba(70,224,192,.7);
     outline-offset: 2px;
   }
   
   
   /* ===== SYS button ========================================================
      Hardware labels its buttons in words rather than glyphs — and a word sits
      in the same monospace as the rest of the device instead of adding a third
      visual language beside the Audiowide wordmark and the pixel cycle.
   
      An action, not a place, so it never takes the lit underline.
      #c6ccd4 is the wordmark's own colour: 9.2:1 at the header's lightest point.
      ======================================================================= */
   .sys-btn {
     appearance: none; -webkit-appearance: none;
     cursor: pointer;
     position: relative;
     display: inline-flex; align-items: center; justify-content: center;
     height: 26px; padding: 0 10px; margin-right: 2px;
     font-family: 'IBM Plex Mono', monospace;
     font-size: .7rem; letter-spacing: .16em;
     color: #c6ccd4;
     background: none;
     border: 1px solid rgba(198,204,212,.22);
     border-radius: 4px;
     transition: color .15s, background .15s, border-color .15s;
     -webkit-tap-highlight-color: transparent;
   }
   
   /* the visible box stays small so it doesn't compete with the wordmark, but
      the tap target is extended to ~44px — comfortably past WCAG 2.2's 24x24 */
   .sys-btn::after {
     content: ''; position: absolute; inset: -9px -6px;
   }
   
   .sys-btn:hover {
     background: rgba(198,204,212,.08);
     border-color: rgba(198,204,212,.45);
   }
   .sys-btn:focus-visible {
     outline: 2px solid #46e0c0;
     outline-offset: 3px;
   }

   /* ===== Account screen ======================================================
   Appended to circate.css. Adds no new colours, no new font family, and no
   weight above 500 — the sheet only loads 400 and 500, so anything heavier
   would be synthesised rather than drawn.

   The form controls aren't restated here. Email reuses .profile-field,
   .profile-label, .profile-input, .profile-error, .profile-actions,
   .profile-save, .profile-cancel and .profile-edit wholesale, so the editor
   on this screen and the editor on the profile are the same object.

   Contrast, measured on the screen glass (#05080a):
     #eaf6ff  18.4:1   the email itself, and typed confirm text
     #a8c0d4  10.7:1   the address line, the consequence sentence
     #8fa8bd   8.1:1   labels, joined date, disarmed trigger, disabled button
     #ff9d8a  10.0:1   the danger colour, already in the sheet for .profile-error
   Nothing signals with colour alone: the trigger is a word, the consequence is
   a sentence, and confirmation is typed.
   ========================================================================= */

.account { padding: 16px 10px; }

/* A hardware label, not a page title. The device names its own parts in small
   uppercase words — SYS, START — and this is one of its parts. Nothing else on
   the device carries a heading, so a large one would be the first thing here
   that looks like a web page instead of a screen. */
.account-title {
  font-family: 'IBM Plex Mono', monospace;
  font-size: .75rem; font-weight: 500;
  letter-spacing: .16em; text-transform: uppercase;
  color: #8fa8bd; margin: 0 0 10px;
}

.account-address {
  font-family: 'IBM Plex Mono', monospace;
  font-size: .8rem; letter-spacing: .5px;
  color: #a8c0d4; margin: 0;
}
.account-joined {
  font-family: 'IBM Plex Mono', monospace;
  font-size: .8rem; letter-spacing: .5px;
  color: #8fa8bd; margin: 4px 0 0;
}

/* Rules, not cards. Two facts and a delete boxed up would read as a control
   panel; the screen should read as a short list of things about you. */
.account-section {
  margin: 24px 0 0; padding: 20px 0 0;
  border-top: 1px solid rgba(255,255,255,.08);
}

/* Same shape as .profile-identity-view: value and pencil in side-by-side
   columns, so a long address wraps in its own column instead of running under
   the icon. */
.account-row { display: flex; align-items: flex-start; gap: 10px; }
.account-row-text { flex: 1 1 auto; min-width: 0; }

.account-value {
  font-family: 'IBM Plex Mono', monospace;
  font-size: 1.125rem; color: #eaf6ff;
  margin: 0; letter-spacing: .5px;
  overflow-wrap: anywhere;      /* long addresses break rather than force a scroll */
}


/* ── Delete ────────────────────────────────────────────────────────────────
   Two states in one slot. The same move the identity block makes when you
   press its pencil, so the screen behaves consistently with itself. */

.account-danger {
  margin: 28px 0 0; padding: 20px 0 0;
  border-top: 1px solid rgba(255,255,255,.08);
}

/* Quiet at rest — this isn't an invitation, and it shouldn't be the loudest
   thing on a screen you mostly visit for other reasons. It only takes the
   danger colour once you reach for it. */
.account-delete-trigger {
  appearance: none; -webkit-appearance: none; cursor: pointer;
  font-family: 'IBM Plex Mono', monospace; font-size: 1rem;
  color: #8fa8bd;
  background: none; border: 1px solid transparent; border-radius: 6px;
  padding: 10px 12px;
  margin-left: -12px;           /* padding cancelled, so the word sits flush left */
  transition: color .12s, background .12s, border-color .12s;
  -webkit-tap-highlight-color: transparent;
}
.account-delete-trigger:hover {
  color: #ff9d8a;
  background: rgba(255,157,138,.06);
  border-color: rgba(255,157,138,.3);
}
.account-delete-trigger:focus-visible {
  outline: 2px solid rgba(255,157,138,.7); outline-offset: 2px;
}

/* Armed. The left rule is the one place on this screen that changes character
   — everything else stays exactly as quiet as it was. */
.account-confirm {
  border-left: 2px solid #ff9d8a;
  padding-left: 14px;
  animation: account-arm .18s ease-out;
}
@keyframes account-arm {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}

.account-confirm-heading {
  font-family: 'IBM Plex Mono', monospace;
  font-size: .75rem; font-weight: 500;
  letter-spacing: .12em; text-transform: uppercase;
  color: #ff9d8a; margin: 0 0 8px;
}
.account-confirm-consequence {
  font-family: 'IBM Plex Mono', monospace;
  font-size: .875rem; line-height: 1.55;
  color: #a8c0d4; margin: 0 0 16px; max-width: 52ch;
}

/* Everywhere else on the device the caret is teal. Here it isn't. The caret is
   the one element you're actually watching while you type, and this is the
   only field on the device where typing does something irreversible. */
.account-confirm-input { caret-color: #ff9d8a; }
.account-confirm-input:focus {
  border-color: rgba(255,157,138,.55);
  background: rgba(255,157,138,.05);
}

/* Takes .profile-save's box and geometry, in the danger colour instead of the
   teal — this is the one action on the device that isn't a primary action
   wearing the primary colour. */
.account-destroy {
  flex: 1 1 auto; text-align: center;
  appearance: none; -webkit-appearance: none; cursor: pointer;
  font-family: 'IBM Plex Mono', monospace; font-size: 1rem;
  padding: 12px 16px; border-radius: 6px;
  color: #ff9d8a;
  background: rgba(255,157,138,.07);
  border: 1px solid rgba(255,157,138,.45);
  transition: background .15s, border-color .15s, color .15s;
  -webkit-tap-highlight-color: transparent;
}
.account-destroy:hover:not(:disabled) {
  background: rgba(255,157,138,.15);
  border-color: #ff9d8a;
}
.account-destroy:focus-visible {
  outline: 2px solid rgba(255,157,138,.7); outline-offset: 3px;
}

/* Genuinely disabled, not merely dimmed — and still 8.1:1, because a control
   you can't read is a worse problem than one you can't press yet. Dropping the
   fill and the danger border is what says "not yet"; the colour change alone
   would be too quiet to notice. */
.account-destroy:disabled {
  cursor: not-allowed;
  color: #8fa8bd;
  background: none;
  border-color: rgba(255,255,255,.12);
}

@media (prefers-reduced-motion: reduce) {
  .account-confirm { animation: none; }
}

/* ============================================================================
   Challenge instance
   Contrast on the screen glass (#05080a):
     #eaf6ff 18.4:1   prompt text, your titles
     #a8c0d4 10.7:1   secondary copy, body text
     #8fa8bd  8.1:1   meta, locked numbers, locked word
     #46e0c0 12.1:1   current state, completed ticks
   Locked never relies on dimness alone — every locked row carries the word
   "locked" and a padlock, so state survives without colour perception.

   Three font sizes: 1rem, .875rem, .75rem. All three already exist in this
   sheet; nothing new was introduced. The 1.5rem title is .outlet-title,
   inherited unchanged.
   ============================================================================ */

   .ci-meta {
    font-family: 'IBM Plex Mono', monospace; font-size: .75rem;
    color: #8fa8bd; margin: 0 0 14px; letter-spacing: .3px;
  }
  .ci-meta-sep { opacity: .5; margin: 0 6px; }
  
  /* Five segments, one per prompt. Redundant with the rows by design — this is
     the glance, the rows are the detail. */
  .ci-progress-text {
    font-family: 'IBM Plex Mono', monospace; font-size: .75rem;
    letter-spacing: .12em; text-transform: uppercase; color: #8fa8bd;
    margin: 0 0 22px;
  }
  
  .ci-prompts { list-style: none; margin: 0; padding: 0; }
  .ci-prompt { border-top: 1px solid rgba(255,255,255,.08); }
  .ci-prompt:last-child { border-bottom: 1px solid rgba(255,255,255,.08); }
  
  /* The number column is constant across all three states, so the rows read as
     one series rather than three different components. */
  .ci-row { display: flex; gap: 12px; align-items: flex-start; padding: 16px 0; width: 100%; text-align: left; background: none; border: none; font: inherit; }
  .ci-num {
    flex: 0 0 auto; width: 26px;
    font-family: 'IBM Plex Mono', monospace; font-size: .75rem;
    color: #8fa8bd; padding-top: 2px; letter-spacing: .5px;
  }
  .ci-body { flex: 1 1 auto; min-width: 0; }
  
  /* ── locked ── */
  .ci-prompt.is-locked .ci-row { cursor: default; }
  .ci-locked-word {
    display: inline-flex; align-items: center; gap: 7px;
    font-family: 'IBM Plex Mono', monospace; font-size: .875rem;
    color: #8fa8bd; letter-spacing: .06em;
  }
  .ci-locked-hint { font-family: 'IBM Plex Mono', monospace; font-size: .75rem; color: #8fa8bd; opacity: .75; margin: 5px 0 0; }
  
  /* ── current ── */
  .ci-prompt.is-current { border-left: 2px solid #46e0c0; padding-left: 12px; margin-left: -14px; }
  .ci-prompt.is-current .ci-num { color: #46e0c0; }
  .ci-prompt-label {
    font-family: 'IBM Plex Mono', monospace; font-size: .75rem;
    letter-spacing: .12em; text-transform: uppercase; color: #46e0c0; margin: 0 0 8px;
  }
  .ci-prompt-text {
    font-family: 'IBM Plex Mono', monospace; font-size: 1rem; line-height: 1.65;
    color: #eaf6ff; margin: 0; max-width: 46ch;
  }
  .ci-add {
    cursor: pointer; margin: 16px 0 0;
    display: inline-flex; align-items: center; gap: 9px;
    font-family: 'IBM Plex Mono', monospace; font-size: 1rem;
    color: #46e0c0; background: rgba(70,224,192,.07);
    border: 1px solid rgba(70,224,192,.45); border-radius: 6px; padding: 11px 15px;
    transition: background .15s, border-color .15s;
  }
  .ci-add:hover { background: rgba(70,224,192,.14); border-color: #46e0c0; }
  
  /* ── completed ── */
  .ci-prompt.is-done .ci-row { cursor: pointer; }
  .ci-prompt.is-done .ci-num { color: #46e0c0; }
  .ci-done-line { display: flex; align-items: baseline; gap: 9px; }
  .ci-done-title { font-family: 'IBM Plex Mono', monospace; font-size: 1rem; color: #eaf6ff; overflow-wrap: anywhere; }
  .ci-done-date { font-family: 'IBM Plex Mono', monospace; font-size: .75rem; color: #8fa8bd; margin-left: auto; flex: 0 0 auto; }
  .ci-chev { flex: 0 0 auto; color: #8fa8bd; transition: transform .18s; margin-top: 4px; }
  .ci-prompt.is-open .ci-chev { transform: rotate(90deg); }
  .ci-detail { padding: 0 0 18px 38px; }
  .ci-detail-prompt {
    font-family: 'IBM Plex Mono', monospace; font-size: .875rem; line-height: 1.6;
    color: #8fa8bd; margin: 0 0 14px; padding-left: 11px;
    border-left: 1px solid rgba(255,255,255,.12);   /* it's the assignment, not the work */
    max-width: 44ch;
  }
  .ci-audio { width: 100%; height: 34px; margin: 0 0 12px; }
  .ci-body-text {
    font-family: 'IBM Plex Mono', monospace; font-size: .875rem; line-height: 1.7;
    color: #a8c0d4; margin: 0; white-space: pre-wrap; max-width: 46ch;
  }
  
  /* ── the dual form ── */
  .ci-form { padding: 4px 0 20px 38px; }
  .ci-file {
    display: flex; align-items: center; gap: 10px; cursor: pointer;
    font-family: 'IBM Plex Mono', monospace; font-size: 1rem; color: #a8c0d4;
    background: rgba(255,255,255,.04); border: 1px dashed rgba(255,255,255,.20);
    border-radius: 5px; padding: 14px 13px;
    transition: border-color .15s, color .15s, background .15s;
  }
  .ci-file:hover { border-color: rgba(70,224,192,.5); color: #eaf6ff; background: rgba(70,224,192,.04); }
  .ci-file:focus-within { outline: 2px solid rgba(70,224,192,.7); outline-offset: 2px; }
  .ci-file.has-file { border-style: solid; border-color: rgba(70,224,192,.45); color: #eaf6ff; }
  .ci-file-name { overflow-wrap: anywhere; }
  .ci-hint { font-family: 'IBM Plex Mono', monospace; font-size: .75rem; color: #8fa8bd; margin: 6px 0 0; }
  
  /* The unlock. The only animation on the screen, and it happens once per
     prompt — this is what the whole structure is for. */
  @keyframes ci-unlock {
    0%   { opacity: 0; transform: translateY(-6px); }
    100% { opacity: 1; transform: none; }
  }
  .ci-just-unlocked { animation: ci-unlock .42s cubic-bezier(.2,.7,.3,1); }
  @media (prefers-reduced-motion: reduce) { .ci-just-unlocked { animation: none; } }
  
  .ci-complete-note {
    font-family: 'IBM Plex Mono', monospace; font-size: .875rem; line-height: 1.6;
    color: #46e0c0; margin: 22px 0 0; padding: 14px 0 0;
    border-top: 1px solid rgba(70,224,192,.25);
  }
  
  /* ── about, collapsed ───────────────────────────────────────────────────────
     You read these on the canonical page seconds before joining, so they open
     closed. One tap, not a scroll past. */
  .ci-about { margin: 0 0 20px; }
  .ci-about-toggle {
    display: flex; align-items: center; gap: 8px; cursor: pointer;
    font-family: 'IBM Plex Mono', monospace; font-size: .75rem;
    letter-spacing: .12em; text-transform: uppercase; color: #8fa8bd;
    background: none; border: 1px solid transparent; border-radius: 5px;
    padding: 8px 10px; margin-left: -10px;
    transition: color .12s, background .12s, border-color .12s;
  }
  .ci-about-toggle:hover { color: #46e0c0; background: rgba(70,224,192,.06); border-color: rgba(70,224,192,.25); }
  .ci-about-toggle:focus-visible { outline: 2px solid rgba(70,224,192,.7); outline-offset: 2px; }
  .ci-about-chev { transition: transform .18s; }
  .ci-about-toggle[aria-expanded="true"] .ci-about-chev { transform: rotate(90deg); }
  .ci-about-panel {
    font-family: 'IBM Plex Mono', monospace; font-size: .875rem; line-height: 1.65;
    color: #a8c0d4; margin: 8px 0 0; padding: 0 0 2px 11px;
    border-left: 1px solid rgba(255,255,255,.12);
    max-width: 48ch; white-space: pre-wrap;
  }
  .ci-about-goal {
    font-family: 'IBM Plex Mono', monospace; font-size: .75rem;
    color: #8fa8bd; margin: 12px 0 0; letter-spacing: .3px;
  }
  
  /* ── progress, two renderings ───────────────────────────────────────────────
     Segments while they're still countable; a continuous fill once they aren't.
     The threshold is about legibility, not layout — twelve 14px blocks read as
     twelve things, thirty 5px slivers read as noise. The word count below is
     exact in both cases, so nothing is lost by the switch. */
  .ci-progress { display: flex; align-items: center; gap: 5px; margin: 0 0 4px; }
  .ci-seg { flex: 1 1 0; min-width: 4px; height: 4px; border-radius: 2px; background: rgba(255,255,255,.10); }
  .ci-seg.is-done    { background: #46e0c0; }
  .ci-seg.is-current { background: rgba(70,224,192,.35); box-shadow: 0 0 0 1px rgba(70,224,192,.55) inset; }
  
  .ci-bar { height: 4px; border-radius: 2px; background: rgba(255,255,255,.10); overflow: hidden; margin: 0 0 4px; }
  .ci-bar-fill { height: 100%; background: #46e0c0; border-radius: 2px; transition: width .4s cubic-bezier(.2,.7,.3,1); }


/* ── the add form ──────────────────────────────────────────────────────────
   Fields come from the challenge's response_type; the form itself is the same
   shape every time. Reuses .profile-field / .profile-label / .profile-input /
   .profile-actions / .profile-save / .profile-cancel, so posting work and
   editing your display name are visibly the same kind of act. */

.ci-form { padding: 4px 0 20px 38px; }   /* 38px clears the number column */

/* Only shown for 'dual', where there's a genuine choice to explain. On a
   text-only or audio-only challenge there's nothing to say, so nothing is. */
.ci-form-note {
  font-family: 'IBM Plex Mono', monospace; font-size: .875rem; line-height: 1.55;
  color: #a8c0d4; margin: 0 0 16px; max-width: 46ch;
}

/* 10:1 on the screen glass, and the same colour .profile-error already uses,
   so an error looks identical wherever it appears. */
.ci-form-error {
  font-family: 'IBM Plex Mono', monospace; font-size: .875rem; line-height: 1.55;
  color: #ff9d8a; margin: 0 0 16px; padding-left: 11px;
  border-left: 2px solid #ff9d8a; max-width: 46ch;
}

/* Dashed while empty, solid once a file is chosen — the border carries the
   state as well as the filename does, so it survives a glance. */
.ci-file {
  display: flex; align-items: center; gap: 10px; cursor: pointer;
  font-family: 'IBM Plex Mono', monospace; font-size: 1rem; color: #a8c0d4;
  background: rgba(255,255,255,.04);
  border: 1px dashed rgba(255,255,255,.20);
  border-radius: 5px; padding: 14px 13px;
  transition: border-color .15s, color .15s, background .15s;
  -webkit-tap-highlight-color: transparent;
}
.ci-file:hover { border-color: rgba(70,224,192,.5); color: #eaf6ff; background: rgba(70,224,192,.04); }
/* The real input is visually hidden, so the focus ring has to live on the
   label or keyboard users get nothing. */
.ci-file:focus-within { outline: 2px solid rgba(70,224,192,.7); outline-offset: 2px; }
.ci-file.has-file { border-style: solid; border-color: rgba(70,224,192,.45); color: #eaf6ff; }
.ci-file-name { overflow-wrap: anywhere; }

.ci-hint {
  font-family: 'IBM Plex Mono', monospace; font-size: .75rem;
  color: #8fa8bd; margin: 6px 0 0;
}

/* .profile-label is uppercase and letterspaced; "(optional)" shouldn't be. */
.ci-label-plain { text-transform: none; letter-spacing: 0; }

textarea.profile-input { resize: vertical; min-height: 110px; line-height: 1.6; }

.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}

/* ── editing and deleting posted work ─────────────────────────────────────
   Both live inside the expanded completed row, so nothing appears until you
   deliberately open your own work. */
 
   .ci-work-actions {
    display: flex; align-items: center; gap: 4px;
    margin: 16px 0 0;
  }
   
  .ci-work-edit, .ci-work-delete {
    appearance: none; -webkit-appearance: none; cursor: pointer;
    font-family: 'IBM Plex Mono', monospace; font-size: .875rem;
    background: none; border: 1px solid transparent; border-radius: 5px;
    padding: 9px 11px;
    transition: color .12s, background .12s, border-color .12s;
    -webkit-tap-highlight-color: transparent;
  }
  .ci-work-edit { color: #8fa8bd; }
  .ci-work-edit:hover { color: #46e0c0; background: rgba(70,224,192,.07); border-color: rgba(70,224,192,.3); }
  .ci-work-edit:focus-visible { outline: 2px solid rgba(70,224,192,.7); outline-offset: 2px; }
   
  /* Quiet until reached for — same treatment as the account screen's delete
     trigger, so destructive controls behave alike wherever they appear. */
  .ci-work-delete { color: #8fa8bd; }
  .ci-work-delete:hover { color: #ff9d8a; background: rgba(255,157,138,.06); border-color: rgba(255,157,138,.3); }
  .ci-work-delete:focus-visible { outline: 2px solid rgba(255,157,138,.7); outline-offset: 2px; }
   
  .ci-confirm {
    border-left: 2px solid #ff9d8a;
    padding-left: 14px; margin: 14px 0 0;
  }
  .ci-confirm-text {
    font-family: 'IBM Plex Mono', monospace; font-size: .875rem; line-height: 1.55;
    color: #a8c0d4; margin: 0; max-width: 46ch;
  }
   
  .ci-destroy {
    flex: 1 1 auto; text-align: center;
    appearance: none; -webkit-appearance: none; cursor: pointer;
    font-family: 'IBM Plex Mono', monospace; font-size: 1rem;
    padding: 12px 16px; border-radius: 6px;
    color: #ff9d8a; background: rgba(255,157,138,.07);
    border: 1px solid rgba(255,157,138,.45);
    transition: background .15s, border-color .15s;
    -webkit-tap-highlight-color: transparent;
  }
  .ci-destroy:hover { background: rgba(255,157,138,.15); border-color: #ff9d8a; }
  .ci-destroy:focus-visible { outline: 2px solid rgba(255,157,138,.7); outline-offset: 3px; }
   
  .ci-form-edit { padding-left: 0; }   /* already inside .ci-detail's indent */
   
  .ci-current-file {
    font-family: 'IBM Plex Mono', monospace; font-size: .75rem;
    color: #a8c0d4; margin: 0 0 8px; overflow-wrap: anywhere;
  }
   
  /* A real checkbox, not a styled span — it has to be reachable by keyboard and
     announced as a checkbox. Only the box's size and spacing are touched. */
  .ci-checkbox {
    display: flex; align-items: center; gap: 9px; cursor: pointer;
    font-family: 'IBM Plex Mono', monospace; font-size: .875rem;
    color: #a8c0d4; margin: 12px 0 0; padding: 6px 0;
  }
  .ci-checkbox input { width: 17px; height: 17px; accent-color: #ff9d8a; cursor: pointer; flex: 0 0 auto; }
  .ci-checkbox:hover { color: #eaf6ff; }
  
  /* ── upload feedback ───────────────────────────────────────────────────────
   Only appears when a file is actually being sent. Text-only saves are
   effectively instant, and a bar that flashes and vanishes reads as a glitch.
 
   Deliberately NOT .ci-bar / .ci-seg: that idiom already means "prompts
   complete" on this same screen, and borrowing it for bytes-in-flight would
   make two unrelated things look like one. Thicker, and it sits inside the
   form rather than in the header. */
 
.ci-uploading { margin: 18px 0 0; }
 
.ci-uploading-bar {
  height: 6px; border-radius: 3px;
  background: rgba(255,255,255,.10);
  overflow: hidden;
}
.ci-uploading-fill {
  height: 100%; width: 0;
  background: #46e0c0; border-radius: 3px;
  transition: width .2s linear;   /* linear: progress shouldn't ease, it should report */
}
 
.ci-uploading-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin: 10px 0 0;
}
 
/* 12.1:1 on the screen glass. The percentage is the feedback — there's no
   spinner, because a number tells you whether it's moving and a spinner
   doesn't. */
.ci-uploading-text {
  font-family: 'IBM Plex Mono', monospace; font-size: .875rem;
  color: #46e0c0; letter-spacing: .04em;
}
 
.ci-uploading-cancel {
  appearance: none; -webkit-appearance: none; cursor: pointer;
  font-family: 'IBM Plex Mono', monospace; font-size: .875rem;
  color: #8fa8bd; background: none;
  border: 1px solid transparent; border-radius: 5px;
  padding: 8px 10px;
  transition: color .12s, background .12s, border-color .12s;
  -webkit-tap-highlight-color: transparent;
}
.ci-uploading-cancel:hover {
  color: #ff9d8a; background: rgba(255,157,138,.06); border-color: rgba(255,157,138,.3);
}
.ci-uploading-cancel:focus-visible {
  outline: 2px solid rgba(255,157,138,.7); outline-offset: 2px;
}

/* ===== Create an outlet, and the outlet header =============================
   Appended to circate.css.

   Two colour worlds in this file, because the modal is a light paper card
   sitting inside a dark device:
     screen glass #05080a — #eaf6ff 18.4 · #a8c0d4 10.7 · #8fa8bd 8.1
     modal card   #faf9f7 — #1c1409 16.8 · #4a4a4a  8.9 · #9a4a00 7.0
   Three font sizes: 1rem, .875rem, .75rem.
   ========================================================================= */

/* ── ancestor row ──────────────────────────────────────────────────────────
   Destinations, not a breadcrumb: profile, project, and everything between.
   One line rather than stacked links — three stacked <a> blocks above a title
   is a lot of header for something you only glance at. */
   .outlet-ancestors {
    font-family: 'IBM Plex Mono', monospace; font-size: .75rem;
    margin: 0 0 12px; line-height: 1.7;
  }
  .outlet-ancestor {
    color: #8fa8bd; text-decoration: none;
    border-radius: 3px; padding: 2px 1px;
    transition: color .12s;
  }
  .outlet-ancestor:hover { color: #46e0c0; text-decoration: underline; text-underline-offset: 3px; }
  .outlet-ancestor:focus-visible { outline: 2px solid rgba(70,224,192,.7); outline-offset: 2px; }
  .outlet-ancestor-sep { color: #5d748a; margin: 0 6px; }
  
  .outlet-description {
    font-family: 'IBM Plex Mono', monospace; font-size: 1rem; line-height: 1.5;
    color: #a8c0d4; margin: 8px 0 0; max-width: 60ch;
  }
  
  /* ── empty outlet ──────────────────────────────────────────────────────── */
  .outlet-empty {
    margin: 28px 0 0; padding: 22px 16px;
    border: 1px dashed rgba(255,255,255,.16); border-radius: 8px;
    text-align: center;
  }
  .outlet-empty-line {
    font-family: 'IBM Plex Mono', monospace; font-size: .875rem; line-height: 1.7;
    color: #a8c0d4; margin: 0;
  }
  /* Draws the key so the glyph is unambiguous — "+" in a sentence could be
     anything; "+" in a keycap is the button on the console. */
  .outlet-empty-key {
    display: inline-flex; align-items: center; justify-content: center;
    width: 22px; height: 22px; margin: 0 2px; vertical-align: -5px;
    font-size: .875rem; color: #46e0c0;
    border: 1px solid rgba(70,224,192,.45); border-radius: 4px;
  }
  
  /* ── the + chooser ─────────────────────────────────────────────────────────
     Stacked choices rather than links: a link says "go and read", and these
     make something. They're the primary act of this modal. */
  .plus-choice {
    display: flex; align-items: baseline; gap: 10px; width: 100%;
    appearance: none; -webkit-appearance: none; cursor: pointer; text-align: left;
    font-family: 'IBM Plex Mono', monospace; font-size: 1rem;
    color: #1c1409; background: #fff;
    border: 1px solid #d0cec8; border-radius: 7px;
    padding: 14px; margin: 0 0 8px;
    transition: border-color .12s, background .12s;
  }
  .plus-choice:hover { border-color: #9a4a00; background: #fffdf8; }
  .plus-choice:focus-visible { outline: 2px solid #9a4a00; outline-offset: 2px; }
  .plus-choice-what { flex: 0 0 auto; }
  .plus-choice-note { font-size: .75rem; color: #4a4a4a; }
  /* Disabled rather than hidden while artifacts are unwired — the shape of the
     choice stays honest about what's coming. */
  .plus-choice:disabled { cursor: not-allowed; background: #f2f0ec; color: #4a4a4a; border-color: #ddd9d2; }
  .plus-choice:disabled:hover { border-color: #ddd9d2; background: #f2f0ec; }
  
  /* ── fields on a modal card ────────────────────────────────────────────────
     These cannot reuse .profile-input: that's styled for the dark screen —
     near-white text, teal caret — and would be invisible on paper. Same job,
     different surface, so a separate small vocabulary rather than an override.
  
     The rule this establishes: a modal can hold a FIELD, not a form. Naming an
     outlet is one input and doesn't earn a screen; an artifact needs a file
     picker and a textarea, so it gets one. */
  .modal-field { margin: 4px 0 0; }
  .modal-label {
    display: block; font-family: 'IBM Plex Mono', monospace;
    font-size: .75rem; letter-spacing: .12em; text-transform: uppercase;
    color: #4a4a4a; margin: 0 0 6px;
  }
  .modal-input {
    width: 100%; appearance: none;
    font-family: 'IBM Plex Mono', monospace; font-size: 1rem;
    color: #1c1409; caret-color: #9a4a00; background: #fff;
    border: 1px solid #c9c6bf; border-radius: 5px; padding: 11px 12px;
  }
  .modal-input:focus { outline: none; border-color: #9a4a00; box-shadow: 0 0 0 3px rgba(154,74,0,.14); }
  .modal-error { font-family: 'IBM Plex Mono', monospace; font-size: .875rem; color: #a3300c; margin: 6px 0 0; }
  
  .modal-actions { display: flex; align-items: center; gap: 4px; margin: 16px 0 0; }
  .modal-primary {
    flex: 1 1 auto; text-align: center; cursor: pointer;
    appearance: none; -webkit-appearance: none;
    font-family: 'IBM Plex Mono', monospace; font-size: 1rem;
    color: #faf9f7; background: #9a4a00;
    border: 1px solid #9a4a00; border-radius: 6px; padding: 12px 16px;
    transition: background .12s;
  }
  .modal-primary:hover:not(:disabled) { background: #7d3c00; }
  .modal-primary:focus-visible { outline: 2px solid #1c1409; outline-offset: 2px; }
  .modal-primary:disabled { cursor: not-allowed; background: #cfcac2; border-color: #cfcac2; color: #55504a; }
  /* Cancel deliberately isn't a matching box — two equal buttons make you read
     both to find the safe one. Same reasoning as the profile identity form. */
  .modal-secondary {
    flex: 0 0 auto; cursor: pointer; appearance: none; -webkit-appearance: none;
    font-family: 'IBM Plex Mono', monospace; font-size: 1rem;
    color: #4a4a4a; background: none; border: 1px solid transparent;
    border-radius: 6px; padding: 12px 14px;
  }
  .modal-secondary:hover { color: #1c1409; }
  .modal-secondary:focus-visible { outline: 2px solid #9a4a00; outline-offset: 2px; }
  
  .modal-back {
    appearance: none; -webkit-appearance: none; cursor: pointer;
    font-family: 'IBM Plex Mono', monospace; font-size: .75rem;
    letter-spacing: .08em; color: #4a4a4a; background: none;
    border: 1px solid transparent; border-radius: 5px;
    padding: 6px 8px; margin: 0 0 10px -8px;
  }
  .modal-back:hover { color: #1c1409; }
  .modal-back:focus-visible { outline: 2px solid #9a4a00; outline-offset: 2px; }

  /* ===== Create an outlet, and the outlet header =============================
   Appended to circate.css.

   Two colour worlds in this file, because the modal is a light paper card
   sitting inside a dark device:
     screen glass #05080a — #eaf6ff 18.4 · #a8c0d4 10.7 · #8fa8bd 8.1
     modal card   #faf9f7 — #1c1409 16.8 · #4a4a4a  8.9 · #9a4a00 7.0
   Three font sizes: 1rem, .875rem, .75rem.
   ========================================================================= */

/* ── ancestor links ───────────────────────────────────────────────────────
   No new classes: _ancestors.html reuses .outlet-home-link and
   .outlet-up-link, already in this sheet. The pattern simply repeats once per
   ancestor instead of appearing at most twice. */

.outlet-description {
  font-family: 'IBM Plex Mono', monospace; font-size: 1rem; line-height: 1.5;
  color: #a8c0d4; margin: 8px 0 0; max-width: 60ch;
}

/* ── empty outlet ──────────────────────────────────────────────────────── */
.outlet-empty {
  margin: 28px 0 0; padding: 22px 16px;
  border: 1px dashed rgba(255,255,255,.16); border-radius: 8px;
  text-align: center;
}
.outlet-empty-line {
  font-family: 'IBM Plex Mono', monospace; font-size: .875rem; line-height: 1.7;
  color: #a8c0d4; margin: 0;
}
/* Draws the key so the glyph is unambiguous — "+" in a sentence could be
   anything; "+" in a keycap is the button on the console. */
.outlet-empty-key {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; margin: 0 2px; vertical-align: -5px;
  font-size: .875rem; color: #46e0c0;
  border: 1px solid rgba(70,224,192,.45); border-radius: 4px;
}

/* ── the + chooser ─────────────────────────────────────────────────────────
   Stacked choices rather than links: a link says "go and read", and these
   make something. They're the primary act of this modal. */
.plus-choice {
  display: flex; align-items: baseline; gap: 10px; width: 100%;
  appearance: none; -webkit-appearance: none; cursor: pointer; text-align: left;
  font-family: 'IBM Plex Mono', monospace; font-size: 1rem;
  color: #1c1409; background: #fff;
  border: 1px solid #d0cec8; border-radius: 7px;
  padding: 14px; margin: 0 0 8px;
  transition: border-color .12s, background .12s;
}
.plus-choice:hover { border-color: #9a4a00; background: #fffdf8; }
.plus-choice:focus-visible { outline: 2px solid #9a4a00; outline-offset: 2px; }
.plus-choice-what { flex: 0 0 auto; }
.plus-choice-note { font-size: .75rem; color: #4a4a4a; }
/* Disabled rather than hidden while artifacts are unwired — the shape of the
   choice stays honest about what's coming. */
.plus-choice:disabled { cursor: not-allowed; background: #f2f0ec; color: #4a4a4a; border-color: #ddd9d2; }
.plus-choice:disabled:hover { border-color: #ddd9d2; background: #f2f0ec; }

/* ── fields on a modal card ────────────────────────────────────────────────
   These cannot reuse .profile-input: that's styled for the dark screen —
   near-white text, teal caret — and would be invisible on paper. Same job,
   different surface, so a separate small vocabulary rather than an override.

   The rule this establishes: a modal can hold a FIELD, not a form. Naming an
   outlet is one input and doesn't earn a screen; an artifact needs a file
   picker and a textarea, so it gets one. */
.modal-field { margin: 4px 0 0; }
.modal-label {
  display: block; font-family: 'IBM Plex Mono', monospace;
  font-size: .75rem; letter-spacing: .12em; text-transform: uppercase;
  color: #4a4a4a; margin: 0 0 6px;
}
.modal-input {
  width: 100%; appearance: none;
  font-family: 'IBM Plex Mono', monospace; font-size: 1rem;
  color: #1c1409; caret-color: #9a4a00; background: #fff;
  border: 1px solid #c9c6bf; border-radius: 5px; padding: 11px 12px;
}
.modal-input:focus { outline: none; border-color: #9a4a00; box-shadow: 0 0 0 3px rgba(154,74,0,.14); }
.modal-error { font-family: 'IBM Plex Mono', monospace; font-size: .875rem; color: #a3300c; margin: 6px 0 0; }

.modal-actions { display: flex; align-items: center; gap: 4px; margin: 16px 0 0; }
.modal-primary {
  flex: 1 1 auto; text-align: center; cursor: pointer;
  appearance: none; -webkit-appearance: none;
  font-family: 'IBM Plex Mono', monospace; font-size: 1rem;
  color: #faf9f7; background: #9a4a00;
  border: 1px solid #9a4a00; border-radius: 6px; padding: 12px 16px;
  transition: background .12s;
}
.modal-primary:hover:not(:disabled) { background: #7d3c00; }
.modal-primary:focus-visible { outline: 2px solid #1c1409; outline-offset: 2px; }
.modal-primary:disabled { cursor: not-allowed; background: #cfcac2; border-color: #cfcac2; color: #55504a; }
/* Cancel deliberately isn't a matching box — two equal buttons make you read
   both to find the safe one. Same reasoning as the profile identity form. */
.modal-secondary {
  flex: 0 0 auto; cursor: pointer; appearance: none; -webkit-appearance: none;
  font-family: 'IBM Plex Mono', monospace; font-size: 1rem;
  color: #4a4a4a; background: none; border: 1px solid transparent;
  border-radius: 6px; padding: 12px 14px;
}
.modal-secondary:hover { color: #1c1409; }
.modal-secondary:focus-visible { outline: 2px solid #9a4a00; outline-offset: 2px; }

.modal-back {
  appearance: none; -webkit-appearance: none; cursor: pointer;
  font-family: 'IBM Plex Mono', monospace; font-size: .75rem;
  letter-spacing: .08em; color: #4a4a4a; background: none;
  border: 1px solid transparent; border-radius: 5px;
  padding: 6px 8px; margin: 0 0 10px -8px;
}
.modal-back:hover { color: #1c1409; }
.modal-back:focus-visible { outline: 2px solid #9a4a00; outline-offset: 2px; }

/* ── toast ─────────────────────────────────────────────────────────────────
   Confirmation while the modal stays open. The list does update behind the
   card, but the card covers the middle of the screen — so the toast is what
   you actually see.

   Positioned against .device rather than the viewport: the toast belongs to
   the device, not the browser window, and .device is already the containing
   block for .modal-overlay. z-index sits above the modal (50) so it isn't
   dimmed by the overlay. */
#create-toast {
  position: absolute; left: 0; right: 0; bottom: 92px;
  z-index: 60; display: flex; justify-content: center;
  pointer-events: none;          /* never blocks the console underneath */
  padding: 0 16px;
}
.toast {
  font-family: 'IBM Plex Mono', monospace; font-size: .875rem;
  color: #05080a; background: #46e0c0;
  border-radius: 6px; padding: 11px 15px; margin: 0;
  max-width: 100%; overflow-wrap: anywhere; text-align: center;
  box-shadow: 0 6px 20px rgba(0,0,0,.5);
  animation: toast-in .16s ease-out, toast-out .3s ease-in 2.6s forwards;
}
/* Dark text on the teal, not light: #05080a on #46e0c0 is 12.1:1, whereas
   white on teal would be about 1.5:1 and effectively unreadable. */
@keyframes toast-in  { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@keyframes toast-out { to { opacity: 0; } }

@media (prefers-reduced-motion: reduce) {
  /* Still disappears — it just doesn't slide or fade on the way in. */
  .toast { animation: toast-out .01s linear 2.9s forwards; }
}

.modal-actions-stacked { flex-direction: column; align-items: stretch; gap: 8px; }

/* ===== The outlet ========================================================
   Everything inside the glass on an outlet screen. Ported from
   studio-first-login-2.html; that file's device chrome was an approximation
   and is not here — the real header, console and screen rules in this sheet
   are untouched.

   Reconciled against what this sheet already owns:
     .outlet-description  identical to the sheet's already — no rule here.
     .outlet-title        the sheet's, with two overrides scoped to the panel.
     .outlet-card         the sheet's, unchanged — child cards are unaffected.
     .outlet-children     the sheet's, unchanged.

   Contrast on the glass (#05080a):
     #eaf6ff 18.3:1 · #a8c0d4 10.7:1 · #8fa8bd 8.1:1 · #46e0c0 12.1:1
   ======================================================================== */

     /* Pinned as one. No negative top margin: sticky holds the MARGIN box at the
      offset, so a negative one puts the visible edge that far below the glass
      and opens a gap for rows to scroll through. */
   .page-top {
     position: sticky; top: 0; z-index: 3;
     background: #05080a;
     margin: 0 -16px 22px;
     padding: 8px 16px 3px;
     border-bottom: 1px solid rgba(255,255,255,.07)
   }
   /* The border stops margins collapsing through the wrapper, so the row's own
      22px would be drawn inside the pinned box. The gap belongs to the wrapper. */
   .page-top .nav-row { margin-bottom: 0; }
   .page-top { top: -18px; }



/* ── slug ──────────────────────────────────────────────────────────────────
   Type taken from .player-profile-slug above (.8rem, #a8c0d4, .5px) — same
   line, same place, so it reads as the address bar it always was.

   The -13px top holds the optical position against the screen's 18px padding.
   The 3px bottom buys 1px of clearance: the 44px tap box would otherwise end
   3px inside the globals row's expanded tap area. */
   .slug-line {
    display: inline-flex; align-items: center; min-height: 44px;
    font-family: 'IBM Plex Mono', monospace;
    font-size: .8rem; letter-spacing: .5px;
    color: #a8c0d4; text-decoration: none;
    margin: -13px 0 3px;
    transition: color .15s;
    -webkit-tap-highlight-color: transparent;
  }
  .slug-line > span { border-bottom: 1px solid transparent; transition: border-color .15s; }
  .slug-line:hover { color: #eaf6ff; }
  .slug-line:hover > span { border-bottom-color: rgba(255,255,255,.25); }
  .slug-line:active { color: #eaf6ff; }
  .slug-line:focus-visible { outline: 2px solid rgba(70,224,192,.7); outline-offset: 2px; border-radius: 3px; }
  
  /* Two states, one line, same position. The emphasis flips between them, and
     that flip IS the signal — nothing is added or removed to say which you're
     looking at:
         no URL yet  →  circate.com/~/ bright, yourname dim   (not real yet)
         URL claimed →  circate.com/~/ dim, yourname bright   (the part that's yours)
  
     .slug-dim is #6b7f90 — 4.84:1 on the glass, so it still clears AA for body
     text while reading as clearly secondary next to #a8c0d4's 10.7:1. Anything
     darker starts failing. */
  .slug-dim  { color: #6b7f90; transition: color .15s; }
  .slug-line:hover .slug-dim { color: #8fa8bd; }
  .slug-bright { color: #a8c0d4; transition: color .15s; }
  .slug-line:hover .slug-bright { color: #eaf6ff; }
  
  /* ── global tools ──────────────────────────────────────────────────────────
     Same verbs as the outlet's own chips, one scope wider. */
  .globals { display: flex; gap: 4px; margin: 0 0 22px; }
  .global-btn {
    appearance: none; -webkit-appearance: none; cursor: pointer;
    position: relative;
    display: inline-flex; align-items: center; justify-content: center;
    width: 40px; height: 40px;
    color: #8fa8bd; background: none;
    border: 1px solid transparent; border-radius: 6px;
    transition: color .15s, border-color .15s, background .15s;
    -webkit-tap-highlight-color: transparent;
  }
  /* pulls the first icon's optical edge back to the text margin above it */
  .global-btn:first-child { margin-left: -9px; }
  .global-btn:hover { color: #46e0c0; background: rgba(70,224,192,.07); border-color: rgba(70,224,192,.3); }
  .global-btn:active { background: rgba(70,224,192,.16); border-color: rgba(70,224,192,.5); }
  .global-btn:focus-visible { outline: 2px solid rgba(70,224,192,.7); outline-offset: 2px; }
  /* An abspos child resolves inset against the PADDING box, and border-box plus
     a 1px border makes that 38, not 40. 38 + 3 + 3 = 44. The pseudo has no
     paint — it exists only to reach the tap target. */
  .global-btn::after { content: ""; position: absolute; inset: -3px; }
  
  /* ── the outlet's own body ─────────────────────────────────────────────────
     .outlet-panel, not .outlet-card: this sheet already owns .outlet-card for
     the child LINK card, the thing you tap to descend. Two different objects
     can't share a name. */
  .outlet-panel {
    background: rgba(255,255,255,.028);
    border: 1px solid rgba(255,255,255,.1);
    border-radius: 8px;
    padding: 16px 15px 12px;
  }
  
  .outlet-identity-view { display: flex; align-items: flex-start; gap: 10px; }
  /* beat display:flex when the edit form takes the slot */
  .outlet-identity-view[hidden] { display: none; }
  .outlet-identity-text { flex: 1 1 auto; min-width: 0; }
  
  /* .outlet-title is already in this sheet and _outlet.html already emits it, so
     the name ports for free. Two overrides, scoped to the panel so the challenge
     screens that also use .outlet-title don't move:
  
     margin 0 — the description carries the 8px gap itself, so the title's own
     bottom margin would double it to 16px.
     letter-spacing — the sheet's rule has none. Applied here only; whether the
     other outlet screens want it is a separate decision. */
  .outlet-panel .outlet-title { margin: 0; letter-spacing: .5px; }
  
  /* Root ships with a blank title, so this is the common case rather than the
     exception. A placeholder that reads as a placeholder. */
  .outlet-title.is-empty {
    font-size: 1rem; font-weight: 400; letter-spacing: .12em;
    text-transform: lowercase; color: #8fa8bd;
  }
  
  /* .profile-edit, renamed. Margin differs from that rule on purpose: the
     profile's 2px optically aligns to a 1.5rem cap, and an empty title here is
     1rem. */
  .outlet-edit {
    flex: 0 0 auto;
    appearance: none; -webkit-appearance: none; cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center;
    position: relative;
    width: 34px; height: 34px; margin: -4px -6px 0 0;
    color: #8fa8bd; background: none;
    border: 1px solid transparent; border-radius: 5px;
    transition: color .12s, background .12s, border-color .12s;
    -webkit-tap-highlight-color: transparent;
  }
  .outlet-edit:hover { color: #46e0c0; background: rgba(70,224,192,.07); border-color: rgba(70,224,192,.3); }
  .outlet-edit:active { background: rgba(70,224,192,.16); border-color: rgba(70,224,192,.5); }
  .outlet-edit:focus-visible { outline: 2px solid rgba(70,224,192,.7); outline-offset: 2px; }
  .outlet-edit::after { content: ""; position: absolute; inset: -6px; }  /* 32 + 12 = 44 */
  
  /* --- tools: bottom of the same panel ------------------------------------ */
  .outlet-tools {
    display: flex; gap: 6px;
    margin: 16px 0 0; padding: 12px 0 0;
    border-top: 1px solid rgba(255,255,255,.08);
  }
  .tool-chip {
    appearance: none; -webkit-appearance: none; cursor: pointer;
    position: relative;
    display: inline-flex; align-items: center; justify-content: center;
    width: 44px; height: 34px;
    color: #a8c0d4;
    background: rgba(255,255,255,.05);
    border: 1px solid rgba(255,255,255,.15);
    border-radius: 3px;
    transition: color .15s, border-color .15s, background .15s;
    -webkit-tap-highlight-color: transparent;
  }
  .tool-chip:hover { color: #46e0c0; background: rgba(70,224,192,.09); border-color: rgba(70,224,192,.42); }
  .tool-chip:active { background: rgba(70,224,192,.18); border-color: rgba(70,224,192,.6); }
  .tool-chip:focus-visible { outline: 2px solid rgba(70,224,192,.7); outline-offset: 2px; }
  /* vertical only, so the 6px gaps between chips survive */
  .tool-chip::after { content: ""; position: absolute; inset: -6px 0; }
  
  /* the one chip that makes something rather than inspects it */
  .tool-chip.is-add {
    width: auto; gap: 6px; padding: 0 12px;
    margin-left: auto;
    font-family: 'IBM Plex Mono', monospace; font-size: .8125rem; letter-spacing: .03em;
    color: #46e0c0;
    background: rgba(70,224,192,.08);
    border-color: rgba(70,224,192,.42);
  }
  .tool-chip.is-add:hover { background: rgba(70,224,192,.16); border-color: #46e0c0; }
  .tool-chip.is-add:active { background: rgba(70,224,192,.24); }
  .add-plus { font-size: 1rem; line-height: 1; }
  
  /* --- edit state ------------------------------------------------------------
     The .profile-* editor block, renamed .outlet-*. Values are that block's,
     with two deliberate departures noted below. */
  .outlet-identity-edit { margin: 0; }
  .outlet-field { margin: 0 0 14px; }
  .outlet-label {
    display: block;
    font-family: 'IBM Plex Mono', monospace;
    font-size: .75rem; letter-spacing: .12em; text-transform: uppercase;
    color: #8fa8bd; margin: 0 0 6px;
  }
  /* No equivalent in the profile editor — its label row carries .profile-count,
     a character counter, which these two fields have no need for. */
  .outlet-hint {
    display: block;
    font-family: 'IBM Plex Mono', monospace;
    font-size: .75rem; line-height: 1.55;
    color: #8fa8bd; margin: 0 0 7px; max-width: 60ch;
  }
  /* .profile-input's background, border and padding. min-height stays: 10px
     padding + a 1rem line + 2px border lands at 43, one short of 44. */
  .outlet-input {
    display: block; width: 100%;
    appearance: none; -webkit-appearance: none;
    font-family: 'IBM Plex Mono', monospace; font-size: 1rem;
    color: #eaf6ff; caret-color: #46e0c0;
    background: rgba(255,255,255,.04);
    border: 1px solid rgba(255,255,255,.12); border-radius: 5px;
    padding: 10px 11px; min-height: 44px;
  }
  .outlet-input:focus { outline: none; border-color: rgba(70,224,192,.55); background: rgba(70,224,192,.05); }
  .outlet-input:focus-visible { outline: 2px solid rgba(70,224,192,.7); outline-offset: 2px; }
  .outlet-textarea { resize: vertical; min-height: 96px; line-height: 1.5; }
  
  /* Asymmetric on purpose — Save takes the width, Cancel goes quiet, so Save is
     unmistakably the one you want. */
  .outlet-actions { display: flex; align-items: center; gap: 4px; margin: 18px 0 0; }
  .outlet-save {
    flex: 1 1 auto; text-align: center;
    appearance: none; -webkit-appearance: none; cursor: pointer;
    font-family: 'IBM Plex Mono', monospace; font-size: 1rem;
    color: #46e0c0;
    background: rgba(70,224,192,.07);
    border: 1px solid rgba(70,224,192,.45); border-radius: 6px;
    padding: 12px 16px;
    transition: background .15s, border-color .15s, color .15s;
    -webkit-tap-highlight-color: transparent;
  }
  .outlet-save:hover { background: rgba(70,224,192,.14); border-color: #46e0c0; }
  .outlet-save:active { background: rgba(70,224,192,.22); }
  .outlet-cancel {
    flex: 0 0 auto;
    appearance: none; -webkit-appearance: none; cursor: pointer;
    font-family: 'IBM Plex Mono', monospace; font-size: 1rem;
    color: #8fa8bd; background: none;
    border: 1px solid transparent; border-radius: 6px;
    padding: 12px 14px;
    -webkit-tap-highlight-color: transparent;
  }
  .outlet-cancel:hover { color: #eaf6ff; }
  .outlet-save:focus-visible, .outlet-cancel:focus-visible {
    outline: 2px solid rgba(70,224,192,.7); outline-offset: 3px;
  }


/* ═══════════════════════════════════════════════════════════════════════════
   CIRCLE — the threshold
   Append to circate.css, inside the same media/scope block that holds
   .outlet-tools and .tool-chip (around line 2282), so it inherits the same
   context those do.
   ═══════════════════════════════════════════════════════════════════════════ */

/* The gate reuses .outlet-tools for its border and spacing — same row, same
   rhythm, because it IS that row with something else in it. This only adds
   what a form needs that a row of chips doesn't: wrapping, and somewhere for
   an error to sit underneath. */
   .circle-gate-row {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
  }
  
  .circle-gate {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
  }
  
  /* Matches .outlet-label, the field label used in the identity editor — this is
     a field on a form and should look like the other fields on forms. */
  .circle-gate-label {
    font-family: 'IBM Plex Mono', monospace;
    font-size: .75rem;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: #8fa8bd;
    white-space: nowrap;
  }
  
  /* Monospace and widely tracked, because this is a string of characters read
     off a screen and compared one at a time — the same reason a licence key is
     never set in a proportional face. flex: 1 1 9ch lets it hold eight
     characters comfortably and still wrap the button below on a narrow phone
     rather than crushing it. */
  .circle-gate-input {
    flex: 1 1 9ch;
    min-width: 9ch;
    font-family: 'IBM Plex Mono', monospace;
    font-size: .9375rem;
    letter-spacing: .18em;
    text-transform: uppercase;
    color: #dbe7f0;
    background: rgba(255,255,255,.05);
    border: 1px solid rgba(255,255,255,.15);
    border-radius: 3px;
    padding: 0 10px;
    height: 34px;                 /* the chip height, so the row sits level */
    appearance: none;
    -webkit-appearance: none;
  }
  .circle-gate-input:focus {
    outline: none;
    border-color: rgba(70,224,192,.42);
    background: rgba(70,224,192,.05);
  }
  .circle-gate-input:focus-visible {
    outline: 2px solid rgba(70,224,192,.7);
    outline-offset: 2px;
  }
  .circle-gate-input[aria-invalid="true"] {
    border-color: rgba(255,157,138,.5);
  }
  
  /* is-add carries margin-left:auto, which would push the button away from the
     field it belongs to. Everything else about the chip — the mint fill, the
     border, the type — is what this wants. */
  .circle-gate-enter {
    margin-left: 0;
  }
  
  /* Warm rather than red. A mistyped code is an ordinary event on a phone
     keyboard, not a failure state, and the palette already uses #ff9d8a for
     .tool-chip.is-danger. */
  .circle-gate-error {
    margin: 0;
    font-family: 'IBM Plex Mono', monospace;
    font-size: .8125rem;
    line-height: 1.5;
    color: #ff9d8a;
  }

  .outlet-error {
            margin: 0 0 14px;
            padding: 10px 12px;
            font-family: 'IBM Plex Mono', monospace;
            font-size: .8125rem;
            line-height: 1.5;
            color: #ff9d8a;
            background: rgba(255,157,138,.08);
            border-left: 2px solid rgba(255,157,138,.5);
            border-radius: 2px;
          }
  
  .circle-gate-closed {
    margin: 0;
    font-family: 'IBM Plex Mono', monospace;
    font-size: .8125rem;
    color: #8fa8bd;
  }
  
  /* Temporary — this is the placeholder standing where the role's tools will go
     once the room lands. Deliberately quiet: it's a confirmation, not a
     control. */
  .circle-standing {
    margin: 0;
    font-family: 'IBM Plex Mono', monospace;
    font-size: .8125rem;
    letter-spacing: .03em;
    color: #46e0c0;
  }
  
  
  /* ───────────────────────────────────────────────────────────────────────────
     4.  The byline, on the editor — append near .ae-title (line 3181)
     ─────────────────────────────────────────────────────────────────────────── */
  
  /* Sits under the title and reads as subordinate to it: the song has a name,
     and then it has a writer. Same borderless line, two steps down in size and
     one step back in weight, so the hierarchy is the type rather than a label
     saying which is which. */
  
  /* ───────────────────────────────────────────────────────────────────────────
     5.  The add field — append beside the gate rules
     ─────────────────────────────────────────────────────────────────────────── */
  
  /* Wider than the code field and not tracked out. A code is eight characters
     compared one at a time; a title is words being read. */
     .circle-add-input {
       flex: 1 1 12ch;
       letter-spacing: normal;
       text-transform: none;
     }
  
  /* The + chip is square rather than word-width — it sits beside a field that
     wants the room, and the label above the field already says what it does. */
     .circle-add .circle-gate-enter { padding-left: 12px; padding-right: 12px; }

/* Replaces the whole `── global tools ──` section in the outlet block you
   appended to circate.css — from `.globals { ... }` down to the
   `.global-btn::after` rule. Everything else in that block stays.

   .global-btn is unchanged and restated only so the section can be swapped
   whole rather than edited around. .globals becomes .nav-row: same three
   icons, plus the route. One object, not two stacked ones. */

/* ── navigation row ─────────────────────────────────────────────────────────
   Three icons acting across everything you've made, then the chips saying
   where you are, flowing on from them and wrapping onto a second line.

   Chips must be direct children of .nav-row. Grouping them in a wrapper makes
   the whole group one flex item, and they then wrap inside that item's own
   narrow column instead of across the row — which shifts the home chip as the
   contents change.

   At root there are no chips and this is the old globals row exactly. */
   .nav-row {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: 5px 4px;
    margin: 0 0 22px;
  }

  
  .global-btn {
    appearance: none; -webkit-appearance: none; cursor: pointer;
    position: relative;
    display: inline-flex; align-items: center; justify-content: center;
    width: 40px; height: 40px;
    color: #8fa8bd; background: none;
    border: 1px solid transparent; border-radius: 6px;
    transition: color .15s, border-color .15s, background .15s;
    -webkit-tap-highlight-color: transparent;
  }
  /* pulls the first icon's optical edge back to the text margin above it */
  .global-btn:first-child { margin-left: -9px; }
  .global-btn:hover { color: #46e0c0; background: rgba(70,224,192,.07); border-color: rgba(70,224,192,.3); }
  .global-btn:active { background: rgba(70,224,192,.16); border-color: rgba(70,224,192,.5); }
  .global-btn:focus-visible { outline: 2px solid rgba(70,224,192,.7); outline-offset: 2px; }
  /* An abspos child resolves inset against the PADDING box, and border-box plus
     a 1px border makes that 38, not 40. 38 + 3 + 3 = 44. The pseudo has no
     paint — it exists only to reach the tap target. */
  .global-btn::after { content: ""; position: absolute; inset: -3px; }
  
  /* .mode-sep from the device header, same rule. The icons act on everything and
     the chips describe one place — a hairline says so without a label. */
  .nav-sep {
    flex: 0 0 auto;
    width: 1px; height: 20px;
    background: rgba(255,255,255,.14);
    margin: 0 5px 0 7px;
  }
  
  /* .75rem against the slug line's .8rem: two mono lines the same size stacked
     at the top read as one confused object, so the route sits a step behind the
     address. 34px tall because every upward move is a tap on one of these. */
  .crumb {
    display: inline-flex; align-items: center;
    min-height: 34px;
    font-family: 'IBM Plex Mono', monospace;
    font-size: .75rem; letter-spacing: .04em;
    color: #8fa8bd; text-decoration: none; white-space: nowrap;
    padding: 0 9px; border-radius: 4px;
    background: rgba(255,255,255,.04);
    border: 1px solid rgba(255,255,255,.11);
    transition: color .15s, background .15s, border-color .15s;
    -webkit-tap-highlight-color: transparent;
  }
  .crumb:hover { color: #eaf6ff; background: rgba(70,224,192,.08); border-color: rgba(70,224,192,.42); }
  .crumb:active { background: rgba(70,224,192,.16); }
  .crumb:focus-visible { outline: 2px solid rgba(70,224,192,.7); outline-offset: 2px; }
  
  /* Where the route starts. A chip like any other — the class exists so the
     collapse can know never to drop it, not to make it look different.
  
     Root has no label, so the chip supplies its own word rather than borrowing
     one: `start` on the private door, matching both the console cap and the URL,
     and the owner's handle on the public one, where `start` would be meaningless
     to a visitor. */
  .crumb-home { letter-spacing: .06em; }
  
  /* What the collapse left out. A chip like the others — same fill, same
     hairline — because it sits in the run and shouldn't break it. Dimmer text
     is the only difference: it stands for chips rather than being one.
  
     Inert until the two-row cap lands. */
  .crumb-more { padding: 0 8px; letter-spacing: .1em; color: #6b7f90; }
  
  /* .crumb is inline-flex, which beats the browser's default [hidden] rule —
     without this the ellipsis renders at every depth regardless of the
     attribute. Same move as .modal-overlay[hidden] elsewhere in this sheet. */
  .crumb[hidden] { display: none; }

  /* Where you are, at the end of the path. Current, not a destination — so it
  takes the lit treatment rather than the link one, and doesn't move on
  hover. */
  .crumb-here {
    color: #46e0c0;
    background: rgba(70,224,192,.09);
    border-color: rgba(70,224,192,.42);
    cursor: default;
  }

  /* Append to the outlet block in circate.css. Nothing existing changes —
   .outlet-card and .outlet-children are untouched. */

/* ── contents ───────────────────────────────────────────────────────────────
   The child list holds every descendant at all times. Collapsed is this rule
   hiding everything past the first level, so expanding is a class flip rather
   than a request.

   Written as :not([data-depth="1"]) rather than a depth-2-and-up list because
   there's no upper bound — the tree goes as deep as it goes. */
.outlet-children:not(.is-expanded) > li[data-depth]:not([data-depth="1"]) {
  display: none;
}

/* The chip stays lit while the list is open — it's a state, not a command, and
   the list below may be scrolled past the point where that's obvious. */
.tool-contents[aria-expanded="true"] {
  color: #46e0c0;
  background: rgba(70,224,192,.09);
  border-color: rgba(70,224,192,.42);
}

/* ══════════════════════════════════════════════════════════════════════════
   THE CONTENTS LISTING
   Ported from rows-6_with_audio_transport.html. Append to circate.css.

   DELETE FIRST — the old collapse, at the very end of the sheet. Depth lives
   in the markup now, so a rule that hides by depth attribute would hide the
   nested lists permanently:

       .outlet-children:not(.is-expanded) > li[data-depth]:not([data-depth="1"]) {
         display: none;
       }

   KEEP .tool-contents[aria-expanded="true"] — the chip still lights, it just
   now means "everything is open" rather than "the list is showing".

   .outlet-card, .outlet-children and .player-outlets are left alone. The
   profile screen still uses them; the outlet screen no longer does.

   NOT ported from that file: the tuner, the switcher, the target overlays and
   the drawn variant of the text mark — all review-only. The now-playing bar is
   deferred; it needs .device-screen to become a flex column, which is a change
   to chrome and a separate conversation.

   The design's .vh utility is not here either. This sheet already has
   .visually-hidden doing exactly that job, and the markup uses it — one name
   for one thing.

   Contrast on the glass (#05080a):
     #eaf6ff 18.3:1 · #c6d5e2 14.1:1 · #a8c0d4 10.7:1 · #8fa8bd 8.1:1
   ══════════════════════════════════════════════════════════════════════════ */

/* ── the lists ─────────────────────────────────────────────────────────────
   One 44x44 control per row, on the left, holding the type mark — outlets and
   artifacts alike.

   The gutter width and the indent per level are INDEPENDENT. A child's slot
   sits under its parent's TITLE, on a different row, so they can't collide.
   The only horizontal conflict is between the slot and the title on one row.
   So the slot can be 44 wide with the indent at 20, and nothing overlaps.

   Rows stay 44 tall: a target can't grow past its row without overlapping the
   neighbouring row's, and an ambiguous hit is worse than a small one. */
   .kids { list-style: none; padding: 0; margin: 0; }
   .kids[hidden] { display: none; }
   
       /* An empty outlet says so rather than opening onto nothing. Indented to the
      44px gutter so it lines up with where a row's title would start — it
      belongs to the list, it just doesn't have anything in it. */
   .kids-empty {
    padding: 12px 8px 12px 44px;
    font-family: 'IBM Plex Mono', monospace; font-size: .8125rem;
    color: #55697a;
  }

   /* The outlet's own contents, seated into the bottom of its panel — a well cut
      into the object rather than a list sitting beside it. Negative margins reach
      back through the panel's 15px/12px padding to its edges; the radius picks up
      the panel's 8px less its 1px border. */
   .outlet-panel > .kids {
     margin: 12px -15px -12px; padding: 6px 15px 10px;
     background: rgba(0,0,0,.3);
     border-top: 1px solid rgba(0,0,0,.45);
     border-radius: 0 0 7px 7px;
     box-shadow: inset 0 7px 11px -9px rgba(0,0,0,.95);
   }
   
   /* 20px, not 44. The indent only has to make nesting legible; it isn't paying
      for the control. */
   li > .kids { padding-left: 20px; }
   
   .row { display: flex; align-items: stretch; min-height: 44px; border-radius: 4px; }
   .row:hover { background: rgba(255,255,255,.03); }
   
   /* ── the slot ──────────────────────────────────────────────────────────────
      The one control on the row. Opens this row's card and reveals its children. */
   .slot {
     flex: 0 0 var(--mark-box, 44px); align-self: stretch; min-height: 44px;
     display: inline-flex; align-items: center; justify-content: flex-start;
     appearance: none; -webkit-appearance: none;
     background: none; border: 0; cursor: pointer;
     padding: 0;
     color: #55697a; border-radius: 5px;
     transition: color .15s, background .15s;
     -webkit-tap-highlight-color: transparent;
   }
   .slot:hover { color: #46e0c0; }
   .slot:focus-visible { outline: 2px solid rgba(70,224,192,.7); outline-offset: -2px; }
   .slot[aria-expanded="true"] { color: #46e0c0; }
   
   /* ── the title ─────────────────────────────────────────────────────────────
      An <a> on an outlet, which descends; a <span> on an artifact, which has
      nowhere to go yet. The rule covers both. */
   .title {
     flex: 0 1 auto; min-width: 0;
     display: flex; align-items: center;
     font-family: 'IBM Plex Mono', monospace; color: #eaf6ff;
     text-decoration: none; font-size: 1rem;
   }
   .title span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
   .is-artifact .title { color: #c6d5e2; }
   
   .leader {
     flex: 1 1 auto; min-width: 18px; margin: 0 10px;
     align-self: center;
     border-bottom: 1px dotted rgba(255,255,255,.22);
   }
   
   /* ── the tail ──────────────────────────────────────────────────────────────
      A readout, not a control. No target, no focus ring, no hover, and it doesn't
      go teal when open — teal is what this device uses for things that respond to
      you, and the tail doesn't.
   
      It carries no count. A quantity sitting beside a control gets read as a
      description of what that control does. The chevron makes two claims instead,
      both true: there is something in here, and it is shut or open. An outlet
      with nothing in it has no tail — absence is the zero. */
   .tail {
     flex: 0 0 auto; align-self: center;
     display: grid; grid-template-columns: repeat(2, var(--tail-col, 17px));
     align-items: center; justify-items: stretch;
     color: #8fa8bd;
   }
   /* The profile mark. ~ is what this device already calls the profile — it is
      the slug at the top of the screen, circate.com/~/name — so this is that sign
      one scale down rather than a new one to learn.
   
      It sits on the right because the left edge indents 20px per level and so
      isn't a column at all; the right edge is flush at every depth. And it comes
      out of the tail's slack rather than the gutter's: a chevron is 10px in a
      44px region, while the gutter's 44 is already set by the widest mark. */
   .tail-pub, .tail-chev { display: flex; align-items: center; justify-content: center; }
   .tail-pub {
     font-family: 'IBM Plex Mono', monospace;
     font-size: .9375rem; line-height: 1;
     user-select: none; -webkit-user-select: none;
   }
   .tail-chev { line-height: 0; }
   .tail-chev svg { transition: transform .18s ease; }
   .is-open .tail-chev svg { transform: rotate(90deg); }
   
   
   /* ── the mark box ──────────────────────────────────────────────────────────
      One declared size. Every mark gets the same box and centres its glyphs in
      it, so alignment doesn't depend on what's inside — a bracket pair, a letter,
      a waveform, or a letter and a waveform together all occupy the same square
      and share the same centre line.
   
      This replaces per-glyph compensation. If a glyph looks off-centre in it, the
      glyph is asymmetric and gets redrawn.
   
      The gutter is derived from the box rather than tuned to fit it, and so is
      the card's indent. One number moves all three. */
   .mark {
     flex: 0 0 var(--mark-box, 44px);
     display: inline-flex; align-items: center; justify-content: center;
     gap: var(--mark-gap, 3px);
   }
   
   /* ── the outlet mark ───────────────────────────────────────────────────────
      Typed, not drawn. The device already says "container" with brackets — the
      search field is [ input ] in Plex Mono, and this is the same claim one scale
      down.
   
      The aperture is explicit, because nothing sits between the brackets to hold
      them apart. Left to close up on their own they read as [], which is a
      checkbox, not a container.
   
      Colour is inherited, so the mark brightens to teal with the slot exactly as
      the search brackets brighten on focus-within. No separate rule. */
   .bk {
     display: inline-flex; align-items: baseline;
     font-family: 'IBM Plex Mono', monospace;
     font-size: var(--bk-size, 1rem);
     font-weight: var(--bk-weight, 500);
     line-height: 1;
     user-select: none; -webkit-user-select: none;
   }
   .bk-ap { width: var(--bk-ap, 2px); }
   
   /* ── the text mark ─────────────────────────────────────────────────────────
      A bare letter as a type marker, which this sheet already has precedent for.
      Three horizontal lines were tried and collided with the panel's contents
      chip — dots-and-lines against lines, indistinguishable at 16px. The chip
      genuinely is a list of contents, so the artifact mark moved instead.
   
      Sized on its own variable because a cap T is about .7em tall; matched to the
      wave by font-size it would come out visibly shorter.
   
      Also carries the ? that stands for an artifact holding neither words nor
      audio — same box, same weight, no special case. */
   .mk-t {
     font-family: 'IBM Plex Mono', monospace;
     font-size: var(--t-size, 1.2rem);
     font-weight: var(--t-weight, 400);
     line-height: 1;
     user-select: none; -webkit-user-select: none;
   }
   
   /* ── the audio mark ────────────────────────────────────────────────────────
      Stroke is its own variable so weight and size tune apart. Drawn to fill its
      viewBox rather than sitting inside a margin of its own. */
   .mk-svg { overflow: visible; width: var(--mk-size, 16px); height: var(--mk-size, 16px); flex: 0 0 auto; }
   .mk-svg path {
     fill: none; stroke: currentColor;
     stroke-width: var(--mk-stroke, 2.4);
     stroke-linecap: round; stroke-linejoin: round;
   }
   
   @media (prefers-reduced-motion: reduce) {
     .tail-chev svg { transition: none; }
   }

   /* outlet panel spacing */

   .level-panels { display: flex; flex-direction: column; gap: 14px; }

   /* ══════════════════════════════════════════════════════════════════════════
   THE OPEN CONTROL
   Append to circate.css.
   ══════════════════════════════════════════════════════════════════════════ */

/* Icon and label on one line, description below on the margin. Centred rather
   than baseline-aligned because the label has two sizes — 1.5rem when named,
   1rem when it's the placeholder — and a baseline would move the icon between
   them. */
.outlet-title-line { display: flex; align-items: center; gap: 8px; }

/* .outlet-edit's rule, as a link rather than a button, and pulled left so the
   icon's optical edge lands on the panel's text margin instead of pushing the
   label in by its own width. Same move as .global-btn:first-child.

/* The title's own bottom margin is already zeroed inside a panel, but the line
   wrapper now sits between them — so the description's gap has to come from
   the wrapper rather than from the title. */
.outlet-title-line + .outlet-description { margin-top: 8px; }

/* ══════════════════════════════════════════════════════════════════════════
   THE HEADER
   Where you are. Append to circate.css.

   The same identity block a panel carries, with no box around it — so it reads
   as the room rather than as one of the things in it. Boxed things are the
   things inside.
   ══════════════════════════════════════════════════════════════════════════ */

   .outlet-header { margin: 0 0 20px; }

   /* The panel's own overrides are scoped to .outlet-panel, so the header's title
      would otherwise fall back to the sheet's generic .outlet-title — which
      carries a bottom margin the description already provides, and no tracking. */
   .outlet-header .outlet-title { margin: 0; letter-spacing: .5px; }
   
   
   /* .outlet-tools draws a hairline above itself to separate the tools from the
      identity inside a panel. That reads the same way here, so the rule is
      inherited as-is — but the panel's 15px side padding isn't, and without it
      the line would run the full width of the screen while the tools sit on the
      text margin. Pulling nothing in: the header IS at the text margin, so the
      line and the chips agree. */

      /* ══════════════════════════════════════════════════════════════════════════
   THE VIEWER
   Ported from transport-1.html. One element, two heights.

   The <audio> lives inside this subtree and never moves — open and collapsed
   are the same DOM in a different order, because two elements swapping would
   restart playback on every open.

   Contrast on the sheet (#06090d):
     #eaf6ff 18.0:1 · #c6d5e2 13.9:1 · #a8c0d4 10.5:1 · #8fa8bd 8.0:1
   ══════════════════════════════════════════════════════════════════════════ */

   .now-playing {
    position: absolute; left: 0; right: 0; bottom: 0; z-index: 5;
    height: var(--np-h);
    display: flex; flex-direction: column;
    background: rgba(9,13,18,.93);
    backdrop-filter: blur(9px); -webkit-backdrop-filter: blur(9px);
    border-top: 1px solid rgba(255,255,255,.07);
    transition: height .3s cubic-bezier(.4,0,.2,1), background .3s;
  }
  .now-playing[hidden] { display: none; }
  
  /* Open is a height change and nothing else. The transport row stays welded
     to the bottom of the glass in both states, so the scrubber doesn't jump
     under your thumb when the sheet arrives — content grows above it. */
  .now-playing.is-open {
    height: 100%;
    background: rgba(6,9,13,.99);
  }
  
  /* Nothing playing and nothing open: the bar is gone, so the clearance it
     needed goes with it. */
  .screen-shell.no-np .device-screen { padding-bottom: calc(18px + var(--kb, 0px)); }
  
  /* ── the sheet body: only exists when open ─────────────────────────────── */
  .np-sheet {
    order: 2;
    flex: 1 1 auto; min-height: 0; overflow-y: auto; overflow-x: hidden;
    -webkit-overflow-scrolling: touch;
    opacity: 0; visibility: hidden;
    transition: opacity .22s .06s, visibility .22s;
  }
  .is-open .np-sheet { opacity: 1; visibility: visible; }
  
  /* ── what's open, at the top of the sheet ──────────────────────────────── */
  /* Padding sits on the parts rather than on .np-now, so the sticky heading can
     span the full width with its own background instead of leaving a gutter
     the text scrolls up through. */
  .np-now { padding: 0 0 20px; }
  
  /* Sticky within .np-now, not within the sheet — the title holds while its own
     words scroll under it, then releases at the list. A heading that outlived
     its section would sit above a list of other artifacts still naming the one
     you left. */
  .np-now-head {
    position: sticky; top: 0; z-index: 2;
    display: flex; align-items: flex-start; gap: 8px;
    padding: 12px 16px 12px 8px;
    background: #06090d;
  }
  .np-now-head[hidden] { display: none; }
  .np-now-id { flex: 1 1 auto; min-width: 0; padding-top: 4px; }
  .np-now-id[hidden] { display: none; }
  
  .np-close {
    flex: 0 0 40px; height: 40px; margin-top: -2px;
    appearance: none; -webkit-appearance: none; cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center;
    background: none; border: 0; color: #8fa8bd; border-radius: 6px;
  }
  .np-close:hover { color: #46e0c0; background: rgba(70,224,192,.07); }
  
  /* The reveal, beside the words it governs. Not under the text: collapsing a
     long piece would mean scrolling to the bottom to find the button that
     scrolls you back, putting the control furthest away exactly when you most
     need it. */
  .np-more {
    flex: 0 0 auto; margin-top: 3px;
    appearance: none; -webkit-appearance: none; cursor: pointer;
    display: inline-flex; align-items: center; min-height: 34px;
    padding: 0 10px;
    font-family: 'IBM Plex Mono', monospace; font-size: .75rem; letter-spacing: .04em;
    color: #8fa8bd; background: rgba(255,255,255,.04);
    border: 1px solid rgba(255,255,255,.11); border-radius: 4px;
  }
  .np-more:hover { color: #46e0c0; background: rgba(70,224,192,.09); border-color: rgba(70,224,192,.42); }
  .np-more[hidden] { display: none; }
  
  .np-body { padding: 0 16px; }
  .np-now-title {
    font-family: 'IBM Plex Mono', monospace; font-size: 1.125rem; font-weight: 500;
    letter-spacing: .5px; color: #eaf6ff; margin: 0; line-height: 1.3;
  }
  /* 9px, not 6. The two lines are close in size and both monospace, so they
     read as one block unless the gap is clearly bigger than the leading inside
     the title itself. The line-height above is set for the same reason: at the
     browser default a wrapped two-line title would sit tighter to itself than
     to the line below it, which inverts the grouping. */
  .np-now-where {
    font-family: 'IBM Plex Mono', monospace; font-size: .75rem; letter-spacing: .03em;
    color: #55697a; margin: 9px 0 0;
  }
  .np-now-where b { font-weight: 400; color: #8fa8bd; }
  
  /* The thing the sheet can do that a bar can't. An artifact holds words and a
     recording in one object, and this is the only surface where both fit at
     once — so text isn't a detail view here, it's half the point.
  
     15px: iOS ships body text at 17 and Material at 16, and 16 is the usual
     floor for anything you'd actually read on a phone. Monospace costs width
     though — at 16 this column holds about 41 characters, under the 45 where
     prose starts to feel chopped. 15 buys a few back and still clears the
     floor by any reasonable reading of it. */
  .np-text {
    font-family: 'IBM Plex Mono', monospace; font-size: .9375rem; line-height: 1.7;
    color: #c6d5e2; margin: 14px 0 0; white-space: pre-wrap;
    padding: 14px 15px;
    background: rgba(255,255,255,.025);
    border: 1px solid rgba(255,255,255,.07); border-radius: 6px;
    position: relative;
  }
  /* Unbounded text buries the list — the sheet has two jobs and the second
     shouldn't fall below the fold because a song has four verses. About eight
     lines, with the last one fading rather than sliced, so the cut reads as
     "there's more" instead of as a layout bug. */
  .np-text.is-clamped {
    max-height: 228px; overflow: hidden;
    -webkit-mask-image: linear-gradient(#000 72%, transparent 100%);
            mask-image: linear-gradient(#000 72%, transparent 100%);
  }
  .np-nothing {
    font-family: 'IBM Plex Mono', monospace; font-size: .8125rem;
    color: #55697a; margin: 0; padding: 16px 0 4px;
  }
  .np-notext {
    font-family: 'IBM Plex Mono', monospace; font-size: .75rem;
    color: #55697a; margin: 20px 0 0;
  }
  
  /* ── the scope row ─────────────────────────────────────────────────────── */
  .np-filter {
    display: flex; gap: 6px; padding: 14px 16px 12px;
    border-top: 1px solid rgba(255,255,255,.07);
  }
  .np-chip {
    appearance: none; -webkit-appearance: none; cursor: pointer;
    font-family: 'IBM Plex Mono', monospace; font-size: .75rem; letter-spacing: .04em;
    color: #8fa8bd; background: rgba(255,255,255,.04);
    border: 1px solid rgba(255,255,255,.11); border-radius: 4px;
    padding: 7px 11px; min-height: 34px;
  }
  .np-chip[aria-pressed="true"] {
    color: #46e0c0; background: rgba(70,224,192,.09); border-color: rgba(70,224,192,.42);
  }
  .np-count {
    margin-left: auto; align-self: center;
    font-family: 'IBM Plex Mono', monospace; font-size: .6875rem; color: #55697a;
  }
  
  /* ── the list ──────────────────────────────────────────────────────────── */
  .np-list { list-style: none; padding: 0 8px 18px; margin: 0; }
  .np-item {
    width: 100%; display: flex; align-items: center; gap: 10px;
    appearance: none; -webkit-appearance: none; cursor: pointer; text-align: left;
    background: none; border: 0; border-radius: 6px;
    padding: 9px 8px; min-height: 56px;
    color: inherit;
  }
  .np-item:hover { background: rgba(255,255,255,.035); }
  .np-item-mark {
    flex: 0 0 28px; gap: 3px;
    display: flex; align-items: center; justify-content: center; color: #55697a;
  }
  .np-item.is-current .np-item-mark { color: #46e0c0; }
  
  /* Title and location on one line, separated by the bracket pair — the device
     already has a word for "container", in the search field, in an outlet's
     mark, in the gutter of every row. So the line reads as "gulls, which is in
     [ ] Field recordings" using a sign that's already been taught. Kept apart
     with an aperture for the same reason the outlet mark is: closed up, []
     reads as a checkbox rather than as a box with something in it.
  
     Flex, so the truncation is real: the title gives way first, since the
     outlet name is the shorter, more repeated thing and losing its start would
     be worse than losing a title's end. */
  .np-item-text {
    flex: 1 1 auto; min-width: 0;
    display: flex; align-items: baseline; gap: 8px;
  }
  .np-item-title {
    flex: 0 1 auto; min-width: 0;
    font-family: 'IBM Plex Mono', monospace; font-size: .875rem; color: #eaf6ff;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .np-item.is-current .np-item-title { color: #46e0c0; }
  .np-item-where {
    flex: 0 1 auto; min-width: 0;
    display: flex; align-items: baseline; gap: 5px;
    font-family: 'IBM Plex Mono', monospace; font-size: .6875rem; color: #55697a;
    overflow: hidden; white-space: nowrap;
  }
  .np-item-where span { overflow: hidden; text-overflow: ellipsis; }
  .np-where-bk { flex: 0 0 auto; display: inline-flex; font-size: .8125rem; }
  .np-where-ap { width: 2px; }
  .np-item-dur {
    flex: 0 0 auto; font-family: 'IBM Plex Mono', monospace; font-size: .6875rem;
    color: #55697a; font-variant-numeric: tabular-nums;
  }
  .np-mk-t { font-family: 'IBM Plex Mono', monospace; font-size: 1rem; line-height: 1; }
  
  /* ── the transport row ─────────────────────────────────────────────────────
     Bottom of the glass when collapsed, top of the sheet when open. Moved with
     flex `order`, not by moving the node — the <audio> lives in this subtree,
     and detaching it to re-insert it elsewhere would stop playback. Order
     reorders the painting, not the DOM, so the element is never disturbed. */
  .np-bar {
    order: 3;
    flex: 0 0 var(--np-h); align-self: stretch;
    display: flex; align-items: stretch; gap: 4px;
    padding: 0 4px 0 9px;
  }
  .is-open .np-bar { border-top: 1px solid rgba(255,255,255,.07); }
  
  /* No audio, no transport. A play button and a clock for an artifact that
     holds only words would be controls for something that isn't there. The
     head above keeps the close and the reveal, so the sheet still has its
     chrome. */
  .no-audio .np-bar { display: none; }
  
  .np-play {
    position: relative;
    flex: 0 0 44px; align-self: stretch;
    display: inline-flex; align-items: center; justify-content: center;
    appearance: none; -webkit-appearance: none; padding: 0; cursor: pointer;
    background: none; border: 0; color: #46e0c0;
    -webkit-tap-highlight-color: transparent;
  }
  .np-play::before {
    content: ""; position: absolute; inset: 7px; border-radius: 50%;
    background: rgba(70,224,192,.1); border: 1px solid rgba(70,224,192,.42);
  }
  .np-play:hover::before { background: rgba(70,224,192,.17); }
  .np-play svg { display: block; position: relative; }
  
  .np-scrub {
    flex: 1 1 auto; min-width: 0;
    display: flex; flex-direction: column; justify-content: center; gap: 4px;
    cursor: pointer; touch-action: none;
  }
  .np-line { display: flex; align-items: baseline; gap: 10px; }
  .np-title {
    flex: 1 1 auto; min-width: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
    font-family: 'IBM Plex Mono', monospace; font-size: .75rem; letter-spacing: .03em;
    color: #eaf6ff;
  }
  .np-time {
    flex: 0 0 auto; font-family: 'IBM Plex Mono', monospace; font-size: .6875rem;
    color: #8fa8bd; font-variant-numeric: tabular-nums;
  }
  /* The head names the artifact a few pixels up, so the bar stops. Collapsed it
     keeps the title, because there's no head down there to carry it. */
  .is-open .np-title { display: none; }
  .np-track { position: relative; height: 4px; border-radius: 2px; background: rgba(255,255,255,.12); }
  .np-fill { position: absolute; left: 0; top: 0; bottom: 0; width: 0%; border-radius: 2px; background: #46e0c0; }
  
  /* The scrub surface is deliberately the whole region right of the play
     button — padding there would create dead strips — so opening can't be "tap
     the bar" without taking the seek away. A chevron costs 36px of title width
     and keeps both. */
  .np-expand {
    flex: 0 0 36px; align-self: stretch;
    appearance: none; -webkit-appearance: none; cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center;
    background: none; border: 0; color: #55697a;
    -webkit-tap-highlight-color: transparent;
  }
  .np-expand:hover { color: #46e0c0; }
  .is-open .np-expand { display: none; }
  
  @media (prefers-reduced-motion: reduce) {
    .now-playing, .np-sheet { transition: none; }
  }

     /* ── the artifact row as a button ──────────────────────────────────────────
      The row used to be a div with one 44px control in its gutter. It's the
      control now, full width — the easiest target on the screen, and the first
      time that's been true of an artifact.
   
      A button doesn't inherit type, so the resets are the price of using the
      right element. Worth it: the row gets keyboard, focus and the disabled
      state for nothing. */
      button.row {
        width: 100%; text-align: left;
        appearance: none; -webkit-appearance: none;
        background: none; border: 0; padding: 0; margin: 0;
        font: inherit; color: inherit; cursor: pointer;
        -webkit-tap-highlight-color: transparent;
      }
      button.row:focus-visible { outline: 2px solid rgba(70,224,192,.7); outline-offset: -2px; }
      
      /* The mark isn't in a control any more, so it takes its colours from the
         row. Same teal on hover the slot had — the row inherited the behaviour
         along with the job. */
      .row.is-artifact > .mark { color: #55697a; transition: color .15s; }
      button.row:hover > .mark { color: #46e0c0; }
      
      /* Nothing to open: embed and launch, which the viewer doesn't hold yet. Not
         a button, so no hover and no target — the row says something is there and
         makes no offer it can't keep. */
      .row.is-inert { cursor: default; }
      .row.is-inert:hover { background: none; }
      
      /* An outlet with nothing in it has nothing for its bracket to reveal. It
         used to open a card as well, which is why this could be a live control
         before. Dimmed rather than hidden — the row still has a gutter and the
         mark still says "outlet". */
      .slot:disabled { cursor: default; opacity: .45; }
      .slot:disabled:hover { color: #55697a; }


       /* ── transport states ──────────────────────────────────────────────────────
      Three conditions the fake clock never had to have: the gap between
      pressing play and hearing anything, a file that won't load, and metadata
      that hasn't arrived.
   
      The glyph flips to pause the instant you press, before any sound — the
      press registered, and a button that stays on "play" while it buffers
      reads as a dead control. The ring pulsing underneath is what carries the
      difference between "started" and "starting". */
   @keyframes np-pulse { 0%, 100% { opacity: .3 } 50% { opacity: 1 } }
   
   .now-playing[data-state="loading"] .np-play::before {
     animation: np-pulse 1s ease-in-out infinite;
   }
   
   /* A file that won't load. Dimmed rather than hidden: the artifact still
      holds a recording, and pretending otherwise would be the same lie as
      showing a transport for words. */
   .now-playing[data-state="error"] .np-play { opacity: .4; cursor: default; }
   .now-playing[data-state="error"] .np-fill { background: #8a5a5a; }
   .now-playing[data-state="error"] .np-time { color: #b08a8a; }
   
   @media (prefers-reduced-motion: reduce) {
     .now-playing[data-state="loading"] .np-play::before { animation: none; opacity: .65; }
   }
/* ══════════════════════════════════════════════════════════════════════════
   THE ARTIFACT PAGE
   Replaces the earlier .artifact-* block wholesale — delete .artifact-tools,
   .artifact-textarea, and the old .artifact-field styling if you pasted them.
   ══════════════════════════════════════════════════════════════════════════ */

   /* The pencil sits beside the name, so the line is a flex row the way an
      outlet's title line is. Same control, same position, either kind of
      thing. */
      .artifact-title-line {
        display: flex; align-items: flex-start; gap: 10px;
      }
      .artifact-title {
        flex: 1 1 auto; min-width: 0;
        font-family: 'IBM Plex Mono', monospace; font-size: 1.25rem; font-weight: 500;
        letter-spacing: .5px; color: #eaf6ff; margin: 0; line-height: 1.3;
      }
      /* An artifact can be made without naming it. The placeholder reads as a
         placeholder rather than as a label that happens to say "untitled" — same
         treatment an unnamed outlet gets. */
      .artifact-title.is-empty {
        font-size: 1rem; text-transform: lowercase; color: #55697a;
      }
      .artifact-head {
        padding-bottom: 14px; margin-bottom: 16px;
        border-bottom: 1px solid rgba(255,255,255,.07);
      }
      .artifact-page .artifact-meta { margin: 9px 0 0; }
   
      /* Hands the file to the transport in the chrome. Sized like a tool chip
         because that's what it is — an act on the thing you're looking at. */
         .artifact-play {
          appearance: none; -webkit-appearance: none; cursor: pointer;
          display: inline-flex; align-items: center; gap: 8px;
          min-height: 40px; padding: 0 14px; margin: 0;
          font-family: 'IBM Plex Mono', monospace; font-size: .8125rem; letter-spacing: .04em;
          color: #46e0c0; background: rgba(70,224,192,.09);
          border: 1px solid rgba(70,224,192,.42); border-radius: 4px;
        }
        .artifact-play:hover { background: rgba(70,224,192,.17); }
        .artifact-play .ap-glyph { display: inline-flex; }
        .artifact-play .ap-dur {
          color: #55697a; font-size: .6875rem; font-variant-numeric: tabular-nums;
        }
  
        /* The row the two controls sit on. The 18px that used to hang off the
           play chip lives here now — on the chip it would put a gap under one
           control and not the other. */
        .artifact-actions {
          display: flex; align-items: center; gap: 8px;
          margin: 0 0 18px;
        }
  
        /* Square, outlined, no fill. Same height as the chip so they sit level,
           and quiet enough that play stays the act this screen is for. */
        .artifact-get {
          display: inline-flex; align-items: center; justify-content: center;
          width: 40px; min-height: 40px;
          color: #8fa8bd; background: transparent;
          border: 1px solid rgba(255,255,255,.12); border-radius: 4px;
          text-decoration: none;
        }
        .artifact-get:hover {
          color: #46e0c0;
          border-color: rgba(70,224,192,.42);
          background: rgba(70,224,192,.06);
        }
        .artifact-get:focus-visible {
          outline: 2px solid rgba(70,224,192,.7); outline-offset: 2px;
        }
      .artifact-play:hover { background: rgba(70,224,192,.17); }
      .artifact-play .ap-glyph { display: inline-flex; }
      .artifact-play .ap-dur {
        color: #55697a; font-size: .6875rem; font-variant-numeric: tabular-nums;
      }
   
      .artifact-empty {
        font-family: 'IBM Plex Mono', monospace; font-size: .8125rem;
        color: #55697a; margin: 0;
      }
   
   /* ══════════════════════════════════════════════════════════════════════════
      THE WRITING SURFACE
   
      Taken from the archived editor in auth_town/games/game_write.html, not from
      the outlet's identity form. An outlet's info is a caption and a boxed field
      suits it. An artifact's words ARE the artifact — a poem typed into a
      bordered rectangle labelled "Words" is being treated as metadata about
      itself.
   
      So no boxes and no labels on the two things that matter. The title is a
      borderless line at the size it'll be read at; the words fill everything
      below; the only chrome is a hairline and a count.
      ══════════════════════════════════════════════════════════════════════════ */
   
      /* Reads at the size it'll be read at, so what you type looks like what
         you'll get. The placeholder does a label's job and, unlike a label, goes
         away once there's something there. */
      .ae-title {
        width: 100%; display: block;
        padding: 4px 0 12px; margin: 0;
        font-family: 'IBM Plex Mono', monospace; font-size: 1.25rem; font-weight: 500;
        letter-spacing: .5px; line-height: 1.3;
        color: #eaf6ff; background: none;
        border: 0; border-bottom: 1px solid rgba(255,255,255,.07);
        border-radius: 0; outline: none;
      }
      .ae-title::placeholder { color: #55697a; }
      .ae-title:focus { border-bottom-color: rgba(70,224,192,.42); }

        .ae-byline {
          font-size: .9375rem;
          letter-spacing: .02em;
          color: #a8c0d4;
          margin-top: -6px;          /* pulls it up under the title's baseline */
        }
        .ae-byline::placeholder { color: #55697a; }
   
      /* Above the writing surface, because the surface is deliberately tall —
         after it would mean scrolling past everything you've written to swap a
         take. */
      .ae-audio { padding: 14px 0; border-bottom: 1px solid rgba(255,255,255,.07); }
      .ae-hint {
        display: block; font-family: 'IBM Plex Mono', monospace; font-size: .75rem;
        color: #55697a; line-height: 1.5;
      }
   
      .ae-counts {
        display: flex; justify-content: flex-end;
        padding: 10px 0 6px;
      }
      .ae-count {
        font-family: 'IBM Plex Mono', monospace; font-size: .6875rem;
        color: #55697a; font-variant-numeric: tabular-nums;
      }
   
      .ae-text {
        width: 100%; display: block;
        height: 30vh;
        padding: 0 0 20px; margin: 0;
        font-family: 'IBM Plex Mono', monospace; font-size: .9375rem; line-height: 1.7;
        color: #c6d5e2; background: none;
        border: 0; border-radius: 0; outline: none; resize: vertical;
        overflow-y: auto;
      }
      .ae-text::placeholder { color: #55697a; }
   
      .artifact-file {
        display: block; width: 100%; margin-top: 10px;
        font-family: 'IBM Plex Mono', monospace; font-size: .75rem;
        color: #8fa8bd;
      }
      .artifact-file::file-selector-button {
        font-family: 'IBM Plex Mono', monospace; font-size: .75rem;
        color: #8fa8bd; background: rgba(255,255,255,.04);
        border: 1px solid rgba(255,255,255,.11); border-radius: 4px;
        padding: 7px 11px; margin-right: 10px; cursor: pointer;
      }
      .artifact-file::file-selector-button:hover {
        color: #46e0c0; border-color: rgba(70,224,192,.42);
      }
   
      /* Destructive, so it says what it does rather than sitting as an unlabelled
         box. Only rendered when there's something to remove. */
      .artifact-check {
        display: flex; align-items: center; gap: 8px; margin-top: 12px;
        font-family: 'IBM Plex Mono', monospace; font-size: .75rem; color: #8fa8bd;
        cursor: pointer;
      }
      .artifact-check input { width: 16px; height: 16px; accent-color: #46e0c0; }
   
      /* htmx puts this on the save button for the length of the request. The only
         feedback a slow upload gets right now. */
      .outlet-save[disabled] { opacity: .5; cursor: default; }

   /* ── the settings panel ────────────────────────────────────────────────────
      Behind the gear, above the words. Things you set once and then don't look
      at, kept out of the way of reading — and above rather than below, because
      anything after a long piece of writing is unreachable. */
      .artifact-settings {
        margin: 0 0 20px; padding: 14px 15px;
        background: rgba(255,255,255,.025);
        border: 1px solid rgba(255,255,255,.07); border-radius: 6px;
      }
      .artifact-settings[hidden] { display: none; }
   
      .as-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
      .as-label {
        font-family: 'IBM Plex Mono', monospace; font-size: .75rem;
        letter-spacing: .04em; color: #8fa8bd;
      }
      .as-chips { display: flex; gap: 6px; margin-left: auto; }
      .as-chips form { margin: 0; }
   
      /* Same lit treatment the viewer's scope chips use — one pressed state for
         one kind of choice, wherever it appears. */
      .as-chips .tool-chip[aria-pressed="true"] {
        color: #46e0c0; background: rgba(70,224,192,.09);
        border-color: rgba(70,224,192,.42);
      }
   
      /* Says what the setting means, not what it's called. "Private" is a word;
         "only you" is the consequence. */
      .as-note {
        font-family: 'IBM Plex Mono', monospace; font-size: .6875rem; line-height: 1.6;
        color: #55697a; margin: 10px 0 0;
      }
   
      .as-danger {
        margin-top: 16px; padding-top: 14px;
        border-top: 1px solid rgba(255,255,255,.07);
      }
      .as-danger > [hidden] { display: none; }
   
      /* Warm rather than red at rest — it's a real option, not an alarm. The red
         arrives on hover, when you're actually reaching for it. */
      .tool-chip.is-danger { color: #b08a8a; }
      .tool-chip.is-danger:hover {
        color: #ff9d8a; background: rgba(255,157,138,.07);
        border-color: rgba(255,157,138,.35);
      }
   
      .artifact-confirm { display: flex; flex-direction: column; gap: 12px; }
      /* Names the thing. "Are you sure?" asks about something you have to
         remember; the label tells you what you're about to lose. */
      .ac-ask {
        font-family: 'IBM Plex Mono', monospace; font-size: .8125rem;
        line-height: 1.6; color: #c6d5e2; margin: 0;
      }
      .ac-acts { display: flex; align-items: center; gap: 8px; }
      .ac-acts form { margin: 0; }


/* ── artifact cards ─────────────────────────────────────────────────────────
   This level's artifacts. Rows are for panels — a gutter, a leader and a
   chevron earn their place in a recess where indentation means something.
   Here nothing is nested, so all of that was decoration competing for 372px.

   The surface is the profile's .outlet-card, unchanged: one fill, one
   hairline, one radius. The device already says "a thing at this level" that
   way and shouldn't learn a second spelling.

   Not a link. The play control lives inside, and a button inside an anchor is
   invalid — browsers split them apart. So the card is a block, the title is
   the link, and the slot is a sibling.

   DELETE from the previous step: .tail-dur and .level-artifacts. .row and its
   parts stay — panels still use them.
   ─────────────────────────────────────────────────────────────────────────── */

   .level-artifacts { list-style: none; padding: 0; margin: 16px 0 0; }

   .artifact-card {
     display: flex; align-items: center; gap: 10px;
     padding: 10px 12px; margin-bottom: 6px;
     background: rgba(255,255,255,.05);
     border: 1px solid rgba(255,255,255,.06);
     border-radius: 6px;
     list-style: none;
   }

   /* Always 30px, whatever it holds. That fixed width is what lines the titles
      and durations up down a mixed list — the job the leader dots were doing
      badly. */
   .ac-slot {
     flex: 0 0 30px; height: 30px;
     display: inline-flex; align-items: center; justify-content: center;
     font-family: 'IBM Plex Mono', monospace; font-size: .8125rem;
     color: #55697a; background: none; border: 0; border-radius: 4px;
   }
   .ac-slot.is-play {
     appearance: none; -webkit-appearance: none; cursor: pointer;
     color: #46e0c0;
     background: rgba(70,224,192,.09);
     border: 1px solid rgba(70,224,192,.42);
     -webkit-tap-highlight-color: transparent;
   }
   .ac-slot.is-play:hover { background: rgba(70,224,192,.17); }
   .ac-slot.is-play:focus-visible {
     outline: 2px solid rgba(70,224,192,.7); outline-offset: 2px;
   }

   /* Takes the slack, so everything to its right sits hard against the edge and
      a long name truncates rather than pushing the duration off. */
   .ac-title {
     flex: 1 1 auto; min-width: 0;
     font-family: 'IBM Plex Mono', monospace; font-size: .9375rem;
     color: #eaf6ff; text-decoration: none;
     overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
   }
   .ac-title:hover { color: #46e0c0; }
   .ac-title:focus-visible {
     outline: 2px solid rgba(70,224,192,.7); outline-offset: 2px; border-radius: 3px;
   }

   /* Only on a dual, where the slot has been taken over by the play control —
      otherwise the card would say "has sound" and lose the words. */
   .ac-mark {
     flex: 0 0 auto;
     font-family: 'IBM Plex Mono', monospace; font-size: .8125rem; color: #55697a;
   }

   .ac-dur {
     flex: 0 0 auto;
     font-family: 'IBM Plex Mono', monospace; font-size: .6875rem;
     color: #55697a; font-variant-numeric: tabular-nums;
   }

   /* Same ~ the rows and the slug line use. Kept rather than replaced with a
      word: a second spelling for one fact is worse than a terse one. */
   .ac-pub {
     flex: 0 0 10px;
     font-family: 'IBM Plex Mono', monospace; font-size: .8125rem;
     color: #8fa8bd; text-align: right;
   }

     /* Sits in the column ~ would have used, so nothing reflows. Quieter than
     the title and slightly tracked, so it reads as an attribution rather than
     as part of the name — at 372px the two are inches apart. */
  .ac-who {
    flex: 0 0 auto;
    font-family: 'IBM Plex Mono', monospace;
    font-size: .8125rem;        /* was .6875 — a size you read, not squint at */
    letter-spacing: .01em;      /* tracking hurts names; it's for codes */
    color: #b8cee0;             /* was #8fa8bd — closer to the title */
    max-width: 16ch;            /* was 10ch — "Christopher" fits now */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .np-item-who {
    color: #b8cee0;
    font-size: .8125rem;
    letter-spacing: .01em;
  }

  /* The artifact page has room, so no truncation and a little more presence —
     this is the one place the person's name is the point rather than a label
     on a row. */
  .artifact-by { color: #a8c0d4; }

  /* No bracket, so no hanging indent to align to. */
  .np-item-who { color: #8fa8bd; }

   .artifact-line {
    display: flex; align-items: center; gap: 10px;
    /* Vertical padding only. The horizontal inset is the panel's, and adding
       more would step the artifacts in from the outlet rows beside them as if
       they were nested inside one. */
    padding: 5px 0;
    min-height: 44px;
  }

   /* A panel is a box inside a box, so its name has ~260px where the header's
      has the full glass. Same 1.5rem there wraps to three lines and the panel
      becomes mostly title. */
      .outlet-panel .outlet-title {
        margin: 0; letter-spacing: .5px;
        font-size: 1.125rem; line-height: 1.35;
        /* Two lines, then cut. A name long enough to need three lines is a name
           you'll recognise from its first two. */
        display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
        overflow: hidden;
      }
      /* The name is the way in. The icon stays — two targets for one act is more
         to hit, not less — but the title is now the large one. */
      a.outlet-title { text-decoration: none; display: -webkit-box; }
      a.outlet-title:hover { color: #46e0c0; }
      a.outlet-title:focus-visible {
        outline: 2px solid rgba(70,224,192,.7); outline-offset: 2px; border-radius: 3px;
      }
   
      /* Two lines is a caption. Past that a panel stops being a thing on a page
         and becomes the page. The full text is on the outlet's own screen, one
         tap away. */
      .outlet-panel .outlet-description {
        font-size: .875rem; line-height: 1.55; margin: 6px 0 0;
        display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
        overflow: hidden;
      }
   
      /* Tighter box to match. */
      .outlet-panel { padding: 13px 13px 10px; }
      .outlet-panel .outlet-tools { margin-top: 12px; padding-top: 10px; }