    :root{
    /* Update bar (studio-version.js): 0 until a new build is detected, then the bar's
       height. Every full-height and position:fixed rule below subtracts it, so the app
       is pushed DOWN rather than hidden underneath. */
    --update-bar-h:0px;

      --purple-cta:#6c18ff; --purple-accent:#7c69fd; --purple-5:#f0e8ff; --purple-10:#deccff; --purple-chip:#f9f6ff;
      /* The hover a filled button takes. Already in this file further down as
         --an-normal, which is the analytics meter — same colour, different job. */
      --purple-hover:#9747FF;
      --text:#18181b; --muted:#6b7280; --faint:#888; --border:#e4e4e7; --bg:#ffffff; --panel:#fff; --green:#00c950;
      --blue:#7c69fd; --app-glyph:#3E3D3E; --grey-10:#E5E4E4; --grey-30:#D3D1D5; --grey-40:#B6B5B7; --grey-50:#8D8A92;
      --black-100:#0A090A;
      /* Paused: the one status drawn in red — the card's pill and Home's Paused chip. */
      --paused:#dc2626;
      /* A field's border under the pointer and while you type — the rest of Myop's
         search field, over the --purple-chip fill. */
      --purple-line:#B996F6; font-family:'DM Sans',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;

      /* ── The design system's type scale and ink roles ──────────────────────
         Straight from the design file, and named exactly as editor.css names
         them, so the two stylesheets speak one language. (They are separate
         sheets — gallery.html never loads editor.css — so the duplication is
         deliberate, the same arrangement the --an-* analytics ramp uses below.)
         Reach for a ROLE token rather than typing 13px/18px into a rule. */
      --font:'DM Sans',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
      --font-mono:'DM Mono',ui-monospace,SFMono-Regular,Menlo,monospace;
      /* Each one ENDS IN var(--font), and has to: these are used as `font:` and the
         shorthand is invalid without a family — the browser drops the whole
         declaration, silently, and the text falls back to whatever it inherited.
         weight size/line-height family. */
      --t-h1:500 24px/32px var(--font);        /* a dialog's title */
      --t-body-lg:400 16px/24px var(--font);   /* the line under it */
      --t-body:400 13px/18px var(--font);      /* the workhorse: fields, buttons */
      --t-tiny:400 12px/14px var(--font);      /* a note under a field */
      --t-label:600 11px/14px var(--font);     /* uppercase labels, with the tracking below */
      --t-label-tracking:1px;
      /* Three inks, by role rather than by shade. */
      --ink:#3E3D3E;               /* headings and body */
      --ink-muted:#6A696A;         /* labels, placeholders, secondary notes */
      --ink-strong:#0A090A;        /* what you have typed */
      /* The hairline every field and button is drawn with. Same value as
         --grey-30; this is the name that says what it is for. */
      --line-grey:#D3D1D5;

      /* Home: a soft diagonal wash over the hero, and the white sheet that rides up over
         it. --wash-base is the flat colour of the strip beside and below the sheet, so
         the page stays one colour however long it gets. (There was a --wash-h here that
         set how far the diagonal ran in PIXELS; see .hwash for why it is gone.) */
      --wash-1:#ece2ff; --wash-2:#ffffff; --wash-3:#ffe6da; --wash-base:#fbf9ff;
      /* --sheet-gutter is the strip of wash left showing down each side of the sheet.
         One number controls both edges; the sheet takes every pixel that isn't it. */
      --sheet-gutter:14px; --sheet-r:10px;
      --read-w:1080px; --home-pad-top:clamp(64px,9vh,110px); --home-pad-bot:56px;
      /* The starter icon tiles under the Home prompt box. */
      --starter-size:50.4px; --starter-r:12.6px; --starter-gap:22px;
      /* ── Design system: the input field ────────────────────────────────────
         One field, drawn one way: Purple 2 ground, a Purple 40 hairline, 4px corners,
         4px/12px padding, body type. Any text field on these screens uses .ds-input
         (or these tokens), never its own border, padding or radius. */
      --purple-2:#F9F6FF; --purple-40:#B996F6;
      /* The quiet icon control: no border, no fill at rest, Purple 5 under the cursor, the
         mark keeps its ink. The Home ＋ is the round one; the menu's back step is the
         square one. Same hover on both, from here. */
      --quiet-hover:var(--purple-5); --quiet-ink:#3E3D3E;
      /* Design system: the menu. Rows are Body type in Grey 90, 36px tall, 16px in,
         4px corners, 2px apart. Hovered is Purple 10; selected is Purple 5. */
      --grey-90:#3E3D3E;
      --menu-item-pad:9px 16px; --menu-item-r:4px; --menu-item-gap:2px;
      --menu-hover:var(--purple-10); --menu-selected:var(--purple-5);
      --field-bg:var(--purple-2); --field-line:var(--purple-40); --field-r:4px; --field-pad:4px 12px;

      /* App card: the preview fills it, and the info bar sits on top of the preview —
         --card-bar collapsed (name + status), --card-bar-open once you hover. */
      --card-h:300px; --card-r:12px; --card-bar:42px; --card-bar-open:184px;
      /* --card-min is the narrowest an app card may get before the grid drops a column,
         so it sets how many you see across. Home overrides it (see .wrap-home) because
         Home is the only view that breaks out of the 1080px reading box — the same
         number here would leave Apps with two enormous cards instead of three.
         Starter ideas use the same number, so a starter card is the same size as an
         app card and the two rows line up. */
      --card-min:300px;
      /* How far the preview's own controls (dots, arrows) sit in from the card edge. */
      --wgt-inset:14px;
      /* Starters carry no sizing of their own any more — same --card-h, --card-r,
         --card-min and gutter as an app card. */
    }
    *{box-sizing:border-box} body{margin:0;background:var(--bg);color:var(--text);font-size:14px;-webkit-font-smoothing:antialiased;display:flex;height:calc(100vh - var(--update-bar-h));margin-top:var(--update-bar-h);overflow:hidden}
    /* On a phone, 100vh is the LARGE viewport — the height the page would have if the
       address bar and bottom toolbar were hidden. The body doesn't scroll (the column
       inside it does), so those last ~100px sat under the browser chrome with no way to
       reach them: the page stopped mid-card. 100dvh is the height actually on screen.
       Second declaration, so a browser that doesn't know the unit keeps the vh line. */
    body{height:calc(100dvh - var(--update-bar-h))}
    button{font-family:inherit;cursor:pointer;border:none;background:none}
    /* The grid, the starter row and the app cards all set their own `display`, which
       beats the browser's rule for [hidden]. Home hides tiles rather than deleting them
       (a removed preview iframe reloads), so hidden has to actually win. */
    [hidden]{display:none!important}
    a{color:inherit}

    /* ── Main scroll area (rail is injected app-level by studio-sidebar.js) ── */
    .main{flex:1;min-width:0;height:calc(100vh - var(--update-bar-h));overflow-y:auto}
    .main{height:calc(100dvh - var(--update-bar-h))}
    .btn{display:inline-flex;align-items:center;gap:7px;font-size:13px;font-weight:600;padding:9px 15px;border-radius:8px;border:1px solid var(--border);background:#fff;color:var(--text);transition:.12s}
    .btn:hover{background:var(--panel)} .btn.primary{background:var(--text);color:#fff;border-color:var(--text)} .btn.primary:hover{background:#000}
    /* The reading column takes the whole width of the main area now, rather than
       stopping at 1080px. The side padding is what keeps content off the edges. */
    /* --wrap-pad is the page's side padding, exposed so anything that breaks OUT of
       the reading column (the analytics band) can pull by exactly that much. Two
       numbers drifting apart is what put a horizontal scrollbar on Analytics. */
    /* ── Leaving Home for a connector ─────────────────────────────────────────
       Opening a connector is a FULL PAGE NAVIGATION, and the studio can take seconds
       to answer it. Until this existed the press did nothing you could see: Home sat
       there, unchanged and still scrollable, until the new document replaced it — so
       the honest reading of the screen was "the click missed".

       This is the editor's own boot cover (dashboard-ui/index.html #appLoading, styled
       in src/editor.css), raised HERE, before the navigation starts. The two pages then
       show the same cover with the same mark sweeping at the same rate, so the document
       swap happens underneath a picture that never changes and the wait reads as one
       continuous load rather than two.

       KEEP THESE RULES AND src/editor.css's .app-loading IN STEP — they are the same
       object drawn by two stylesheets, the same arrangement --an-* and the type scale
       already use. The markup and the SVG are built in gallery.js (navCover). */
    .app-loading{position:fixed;inset:var(--update-bar-h) 0 0 0;z-index:9999;background:var(--bg,#f5f5f5);
      display:none;flex-direction:column;align-items:center;justify-content:center}
    .app-loading.on{display:flex}
    .app-loading .mk{display:grid;place-items:center;line-height:0}
    .app-loading .mk svg{height:46px;width:auto;display:block}
    /* The band is a static repeating gradient clipped to the mark; sliding it is what
       makes the colour travel. CSS, not SMIL — iOS Safari runs SMIL without ever
       repainting it, so the loader was frozen on every iPhone. docs/LOADER-ANIMATION.md.
       230px is one period of the gradient, so the loop has no seam. */
    .app-loading .mk-band{animation:mk-sweep 2.6s linear infinite}
    @keyframes mk-sweep{from{transform:translateX(0)}to{transform:translateX(230px)}}
    @media (prefers-reduced-motion:reduce){ .app-loading .mk-band{animation:none} }
    .app-loading .mk-slow{margin:18px 0 0;font:400 13px/1.5 var(--font);color:var(--ink-muted);
      line-height:1.5;text-align:center}

    .wrap{--wrap-pad:32px;max-width:100%;margin:0 auto;padding:34px var(--wrap-pad) 60px}
    /* .htpls (the starter row on Home) rides on this same rule rather than a copy of
       it: one grid definition, so the starters can't drift out of step with the apps. */
    .grid,.htpls{display:grid;grid-template-columns:repeat(auto-fill,minmax(var(--card-min,340px),1fr));gap:16px}
    /* The grid's floor while a search matches nothing is set in JS (renderCards), to the
       height it had a keystroke ago — a static minimum cannot hold an arbitrary scroll
       position, it only makes the jump smaller. */
    /* ── App card ──────────────────────────────────────────────────────────────
       The widget preview IS the card. Name and status ride a thin bar along the
       bottom edge; the slug, description, counts and who touched it last are one
       hover away. A grid then reads as a wall of the actual product rather than a
       wall of metadata — which is the whole point of building UI into a connector. */
    .card{position:relative;height:var(--card-h);background:var(--panel);border:1px solid var(--border);
      border-radius:var(--card-r);overflow:hidden;cursor:pointer;text-align:left;
      transition:border-color .16s,box-shadow .16s}
    .card:hover{border-color:var(--purple-10);box-shadow:0 12px 28px -16px rgba(108,24,255,.3)}
    /* The app glyph. One mark for every app — which is exactly why the CARD no longer
       carries it: a mark that is identical on all of them distinguishes nothing, and it
       held the name 27px in while the description beneath it started at the padding edge.
       It stays where it stands in for an app that isn't otherwise pictured: the analytics
       row and the plan modal. */
    .pm-av,.an-tile{color:var(--app-glyph);flex-shrink:0;display:grid;place-items:center}
    .pm-av svg,.an-tile svg{display:block;width:16px;height:16px}
    .cmeta{font-size:11px;color:var(--faint);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0}
    .cmeta .cby{color:var(--muted)}
    .cby{cursor:default;text-decoration:underline dotted transparent}
    .cby:hover{text-decoration-color:var(--faint)}

    /* The info bar. Collapsed it is one line; hovering the card opens it over the
       bottom of the preview. Hovering the carousel controls holds it shut — driving
       the arrows shouldn't drag a panel across the thing you're trying to look at. */
    .hc-info{position:absolute;left:0;right:0;bottom:0;z-index:4;padding:9px 13px;
      background:rgba(255,255,255,.94);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
      border-top:1px solid var(--border);max-height:var(--card-bar);overflow:hidden;
      transition:max-height .26s cubic-bezier(.22,1,.36,1)}
    .card:hover .hc-info,.card:focus-within .hc-info{max-height:var(--card-bar-open)}
    .card:has(.wgt-nav:hover) .hc-info,.card:has(.wgt-dot:hover) .hc-info{max-height:var(--card-bar)}
    .hc-head{display:flex;align-items:center;gap:4px}
        .hc-name{font-family:'DM Sans',-apple-system,system-ui,sans-serif;font-size:14px;font-weight:600;
      color:var(--text);flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
    .hc-state{flex:none;font-size:11px;font-weight:600;letter-spacing:.02em;color:var(--muted)}
    /* Live is weight and a dot rather than green — the greens weren't in the system,
       and at 11px a colour shift is a weaker signal than a mark you can see. */
    .hc-state.live{color:#3E3D3E;font-weight:800;display:inline-flex;align-items:center;gap:5px}
    .hc-state.live::before{content:"";width:5px;height:5px;border-radius:50%;background:currentColor;flex:none}
    /* Draft isn't a warning, it's just "not released yet" — so it reads as plain
       text at 400 rather than amber. */
    .hc-state.draft{color:#3E3D3E;font-weight:400}
    /* Switched off: the endpoint answers 503, so this outranks Live/Draft. */
    .hc-state.stopped{color:var(--paused)}
    .hc-body{opacity:0;transition:opacity .2s ease .04s}
    .card:hover .hc-body,.card:focus-within .hc-body{opacity:1}
    .card:has(.wgt-nav:hover) .hc-body,.card:has(.wgt-dot:hover) .hc-body{opacity:0;transition:none}
    .hc-desc{font-size:12px;line-height:1.5;color:var(--muted);margin-top:6px;
      display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
    /* Counts as a sentence. They were two display figures with uppercase labels —
       a lot of design for "3 tools", on a card whose subject is the preview above. */
    .hc-metrics{font-size:12px;line-height:1.5;color:var(--muted);margin-top:8px}
    .hc-foot{display:flex;align-items:center;gap:8px;margin-top:9px}
    .hc-foot .mini{margin-left:auto}
    /* Widget carousel — it fills the whole card, so it carries the card's own tint
       rather than the old chequerboard, which at this size read as noise. */
    .wgt-stage{position:absolute;inset:0;overflow:hidden;
      background:linear-gradient(155deg,rgba(240,232,255,.5) 0%,#fff 52%,rgba(255,237,231,.45) 100%)}
    .wgt-stage.empty{display:flex;align-items:center;justify-content:center;padding-bottom:var(--card-bar)}
    /* Pending: the detail fetch hasn't answered yet, so we don't know if there are
       widgets. Shimmer instead of claiming "No UI widgets" and retracting it. */
    .wgt-stage.loading{display:flex;align-items:center;justify-content:center}
    .wgt-stage.loading::after{content:"";position:absolute;inset:0;
      background:linear-gradient(90deg,#f2f2f2 25%,#f8f8f8 37%,#f2f2f2 63%);background-size:400% 100%;animation:hsk 1.3s ease infinite}
    .wgt-empty{color:var(--faint);font-size:12px}
    .wgt-scale{position:absolute;top:0;left:0;width:200%;height:200%;transform:scale(.5);transform-origin:top left;pointer-events:none}
    .wgt-frame{width:100%;height:100%;border:0;background:transparent}
    /* The two arrows sit together in the top-right corner; the dots sit opposite them in
       the top-left. Always visible, because a preview you can flip through only helps if
       you can see that you can. The arrows keep their white chips: a widget can be any
       colour, and a control that's only legible over some of them is one people stop
       trusting. The dots are bare marks — small enough not to need a plate. */
    .wgt-ctl{position:absolute;top:var(--wgt-inset);right:var(--wgt-inset);z-index:3;
      display:flex;align-items:center;gap:4px}
    /* padding:0 matters — a <button> carries UA padding of 1px 6px, which squeezed the
       26px box to a 12px content box and pushed the chevron ~2px right of centre. */
    .wgt-nav{width:26px;height:26px;border-radius:8px;padding:0;
      background:rgba(255,255,255,.88);border:1px solid var(--border);color:#3E3D3E;
      font-size:16px;line-height:1;display:grid;place-items:center;
      backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);
      box-shadow:0 1px 3px rgba(10,9,10,.06);transition:.14s}
    .wgt-nav svg{display:block;width:16px;height:16px}
    .wgt-nav:hover{background:var(--purple-5);border:1px solid var(--purple-10)}
    /* Each dot is a 5px mark inside a 10×16 target: the mark is the size it should look,
       the button is the size it has to be to actually hit. The targets touch, so the
       marks sit 5px apart and read as one tight row. No plate behind them — a faint
       white ring on each mark is what keeps them readable over a dark widget. */
    /* Top edge of the marks lines up with the top edge of the arrow chips opposite —
       the 5.5px back-off is the dot target's own padding above its mark, same idea as
       the 2.5px on the left. */
    .wgt-dots{position:absolute;top:calc(var(--wgt-inset) - 5.5px);left:calc(var(--wgt-inset) - 2.5px);
      z-index:3;display:flex;align-items:center;gap:0}
    .wgt-dot{width:10px;height:16px;border-radius:999px;padding:0;background:none;display:grid;place-items:center}
    .wgt-dot::after{content:"";width:5px;height:5px;border-radius:50%;background:rgba(60,61,62,.32);
      box-shadow:0 0 0 1.5px rgba(255,255,255,.7);transition:background .14s}
    /* Hover changes colour only. A dot that grows under the cursor moves the row it
       sits in, and these are only a few pixels apart. */
    .wgt-dot:hover::after{background:#CAACFF}
    .wgt-dot.on::after{background:var(--purple-cta)}
    /* Keyboard: the card is focusable, and ←/→ flip the preview from there. */
    .card:focus-visible{outline:2px solid var(--purple-cta);outline-offset:2px}
    .wgt-nav:focus-visible,.wgt-dot:focus-visible,.mini:focus-visible{outline:2px solid var(--purple-cta);outline-offset:1px}
    @media (prefers-reduced-motion:reduce){ .wgt-dot:hover::after{transform:none} }
    /* Copy the address — the link mark alone, no pill around it and no word beside it.
       Sized to a real touch target while the mark inside stays 14.4px. */
    .mini{display:grid;place-items:center;padding:0;
      width:24px;height:24px;border-radius:80px;flex:none;
      color:#3E3D3E;transition:color .12s}
    .mini-ic{width:14.4px;height:14.4px;flex:none;display:block}
    .mini-ic svg{display:block;width:14.4px;height:14.4px}
    .mini:hover{color:var(--purple-cta)}
    /* The line under the New-app tile on an empty grid. A muted placeholder, not a
       message: it sits below the tile (order:1 — the tile is order 0), takes the full
       row so it starts at the left edge under it, and ignores the pointer so it can
       never look clickable. */
    .empty{grid-column:1/-1;order:1;padding:14px 2px 0;text-align:left;
      font-family:'DM Sans',sans-serif;font-size:14px;font-weight:400;line-height:20px;
      color:var(--ink-muted);text-wrap:pretty;pointer-events:none}
    /* …except the one way out of an empty search or filter. */
    .empty-clear{pointer-events:auto;margin:0;padding:0;border:0;background:none;font:inherit;
      color:var(--purple-cta);text-decoration:underline;text-underline-offset:3px;cursor:pointer}
    .empty-clear:hover{color:var(--purple-hover)}
    .empty-clear:focus-visible{outline:2px solid var(--purple-cta);outline-offset:2px;border-radius:2px}
    /* "New app" — no longer a dashed outline with a ＋ in a box. It's a filled card
       like the real ones, on the same wash as Home, carrying an open (dashed) version
       of the app mark. Height stays on --card-h so it lines up with its neighbours. */
    .new-card{height:var(--card-h);max-width:100%;box-sizing:border-box;
      display:flex;flex-direction:column;justify-content:center;align-items:center;gap:10px;
      border:1px solid var(--purple-10);border-radius:12px;
      background-image:linear-gradient(-4deg,rgba(240,232,255,.6) -6.17%,rgba(255,255,255,.6) 76.6%,rgba(255,237,231,.6) 95.9%);
      font-family:'DM Sans',-apple-system,system-ui,sans-serif;color:var(--text);
      transition:background .2s,border-color .2s}
    /* Hover flips the wash top-to-bottom — same three colours, other way up (-4deg
       turned through 180°), so the card answers without changing colour. */
    .new-card:hover{border-color:var(--purple-cta);box-shadow:none;
      background-image:linear-gradient(176deg,rgba(240,232,255,.6) -6.17%,rgba(255,255,255,.6) 76.6%,rgba(255,237,231,.6) 95.9%)}
    .new-card .new-mark{display:grid;place-items:center;flex:none}
    .new-card .new-mark svg{display:block;width:70px;height:70px}
    /* Marching ants: the dashes travel around the four open squares on hover. Only
       the dashed outlines move — the plus badge is a fixed point, and animating it
       would read as the whole mark wobbling. */
    .new-card .new-mark .ants{stroke-dasharray:4 2;stroke-dashoffset:0;transition:stroke-dashoffset .6s ease}
    .new-card:hover .new-mark .ants{stroke-dashoffset:24}
    @media (prefers-reduced-motion:reduce){
      .new-card .new-mark .ants,.new-card:hover .new-mark .ants{transition:none;stroke-dashoffset:0}
    }
    .new-card .new-lbl{font-family:'DM Sans',-apple-system,system-ui,sans-serif;
      font-size:13px;letter-spacing:-.13px;color:#3E3D3E}
    /* Touch: there is no hover, so the card unrolls instead of hiding half of itself. */
    @media (hover:none){
      .card,.htpl-card{height:auto}
      /* A template card's info bar is already open, so it needs nothing here — only the
         stage has to stop being absolutely positioned. */
      .wgt-stage{position:relative;inset:auto;aspect-ratio:16/10}
      .hc-info{position:static;max-height:none;backdrop-filter:none;-webkit-backdrop-filter:none;background:var(--panel)}
      .hc-body{opacity:1}
      .new-card{height:200px}
    }
    @media (prefers-reduced-motion:reduce){ .hc-info,.hc-body{transition:none} }

    /* ── Home view ─────────────────────────────────────────────────────────────
       A wash behind a quiet greeting, then a white sheet that rides up over it and
       carries the work. Home is the one view that breaks out of the 1080px reading
       box — setView() adds .wrap-home for exactly as long as Home is on screen. */
    /* Home has the full width to spend, so it spends it on the app cards. */
    .wrap.wrap-home{max-width:none;padding:0;--card-min:340px}
    /* Four across on Home, as a fixed count rather than auto-fill: the number of apps
       in a row is then the same on every monitor, instead of a fifth column appearing
       on a wide one and the cards shrinking on a narrow one. It steps down 4 → 3 → 2 → 1
       at the widths where a card would otherwise fall under ~300px. */
    #homeView .grid{grid-template-columns:repeat(4,minmax(0,1fr))}
    @media (max-width:1400px){ #homeView .grid{grid-template-columns:repeat(3,minmax(0,1fr))} }
    @media (max-width:1100px){ #homeView .grid{grid-template-columns:repeat(2,minmax(0,1fr))} }
    @media (max-width:700px){ #homeView .grid{grid-template-columns:minmax(0,1fr)} }
    /* The wash lives here, not on .main: .main is the scroll container (a background
       there would never scroll) and studio-feedback.js clones and translates it to
       take bug screenshots, which a background on .main itself wouldn't follow. */
    #homeView{position:relative;min-height:calc(100vh - var(--update-bar-h));
      min-height:calc(100dvh - var(--update-bar-h));
      background:var(--wash-base)}
    /* THE GRADIENT IS SIZED BY THE HERO, NOT BY A NUMBER.
       It used to be a layer on #homeView cut off at a fixed --wash-h (720px, 520px on a
       phone) with --wash-base showing below. That is a guess about how tall the hero is,
       and it was wrong exactly when the hero was most visible: a long prompt in the box
       plus four chips stacked one per row on a phone runs past 520px, and what appeared
       under the cut was a hard horizontal edge into flat grey, mid-hero.
       This wrapper is full-bleed and wraps only the hero, so `100% 100%` means the hero —
       whatever its height, including while the textarea is growing as somebody types. */
    .hwash{background:linear-gradient(158deg,var(--wash-1) 0%,var(--wash-2) 48%,var(--wash-3) 100%)
      no-repeat 0 0/100% 100%}
    /* The hero is the input plus its row of examples, and they read as ONE thing — so
       the air under the examples is much tighter than the air above the greeting. It used
       to be 115px, which left a band of empty wash between the examples and the sheet
       wide enough to look like the page had ended. */
    .hhero{max-width:var(--read-w);margin:0 auto;padding:var(--home-pad-top) 32px var(--home-pad-bot);text-align:center}
    /* The one line above the prompt: a 600px measure, centred, under 30px of air.
       Grey-90 at 400 — it's an invitation to type, not a headline, so it sits back
       and lets the prompt box be the loud thing. Width capped rather than fixed so
       it doesn't overflow a narrow window. */
    /* min-height, not height: the line now carries a name, and a long one wraps to two
       lines on a narrow window rather than being clipped. */
    .hh-greet{width:100%;max-width:600px;min-height:62px;margin:30px auto 12px;padding-top:30px;
      font-family:'DM Sans',sans-serif;font-size:24px;font-weight:400;line-height:32px;
      text-align:center;color:#3E3D3E;box-sizing:border-box}
    /* The sheet. No bottom radius and no bottom margin — it runs off the end of the
       scroll area rather than floating in the middle of it. */
    .hsheet{width:calc(100% - var(--sheet-gutter)*2);margin:0 auto;background:var(--panel);
      border-radius:var(--sheet-r) var(--sheet-r) 0 0;box-shadow:0 -4px 24px rgba(0,0,0,.06);
      padding-bottom:72px}
    /* No measure cap: the sheet is the workspace, so the grid uses all of it and the
       cards fill the width rather than sitting in a column with white on both sides. */
    .hsheet-in{padding:30px 32px 0}
    /* The counts, at the top of the sheet above the tab row. One nowrap row starting
       at the left edge, fluid gap, label above value. No rule under them and no card
       around them — they're a readout, so the weight comes off and they stay quiet. */
    .hstats{display:flex;align-items:center;justify-content:flex-start;flex-wrap:wrap;
      gap:clamp(20px,3.5vw,48px);padding:5px 0 24px;margin:0}
    .hstat{text-align:left;min-width:60px}
    .hstat-l{font-family:'DM Sans',-apple-system,BlinkMacSystemFont,'Segoe UI','Roboto','Helvetica','Arial',sans-serif;
      font-size:12px;color:#6A696A;text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px;white-space:nowrap;
      text-align:left}
    .hstat-v{font-family:'DM Sans',-apple-system,BlinkMacSystemFont,'Segoe UI','Roboto','Helvetica','Arial',sans-serif;
      font-size:26px;font-weight:400;color:#3E3D3E;text-align:left;line-height:1.15}
    /* Live is the one that matters, so it's the heavier one. It used to be green,
       which is a colour the system doesn't otherwise use. */
    .hstat-v.live{font-weight:800} .hstat-v.draft{color:#6A696A}
    /* Each number slides up as it lands, one after the next — the counts arrive after
       the connectors do, so this is the moment they appear rather than page load. */
    @keyframes slideUp{ 0%{opacity:0;transform:translateY(8px)} 100%{opacity:1;transform:translateY(0)} }
    .hstat-v.animate{animation:slideUp .6s ease-out both}
    .hstat:nth-child(1) .hstat-v.animate{animation-delay:.05s}
    .hstat:nth-child(2) .hstat-v.animate{animation-delay:.1s}
    .hstat:nth-child(3) .hstat-v.animate{animation-delay:.15s}
    .hstat:nth-child(4) .hstat-v.animate{animation-delay:.2s}
    @media (prefers-reduced-motion:reduce){ .hstat-v.animate{animation:none} }
    /* section headers inside the sheet */
    .hsec-h{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0 0 16px}
    .hsec-h h2{font-size:16px;font-weight:700;margin:0;letter-spacing:-.01em;flex-shrink:0}
    .hsec-h .right{display:flex;align-items:center;gap:14px;min-width:0}
    .hsec-h+.hsec-h,.grid+.hsec-h,.more-sentinel+.hsec-h{margin-top:34px}
    /* prompt hero */
    .phero{margin-top:22px}
    /* Starters: four categories under the box, an icon tile with its name beneath.
       Frosted white on the wash so they sit IN the hero rather than on top of it.
       Monochrome ink until you're on one, then brand purple. No motion on hover: the
       colour change is the whole response. `.on` is the category whose first line
       opens the box right now. */
    .phero-starters{display:flex;justify-content:center;gap:var(--starter-gap);margin-top:20px}
    .phero-starter{display:flex;flex-direction:column;align-items:center;gap:8px;padding:0;border:none;background:none;
      cursor:pointer;font:var(--t-tiny);color:var(--ink-muted)}
    .phero-starter-tile{width:var(--starter-size);height:var(--starter-size);display:grid;place-items:center;
      border-radius:var(--starter-r);border:1px solid rgba(255,255,255,.95);background:rgba(255,255,255,.6);
      -webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);color:var(--ink);
      box-shadow:0 1px 2px rgba(24,16,48,.04),0 10px 24px -16px rgba(80,40,160,.38)}
    .phero-starter-tile svg{width:17.82px;height:17.82px;display:block}   /* 22px, less 10%, less 10% again */
    .phero-starter-name{font-weight:500}
    .phero-starter:hover .phero-starter-tile{background:#fff;color:var(--purple-cta);border-color:var(--purple-10)}
    .phero-starter:hover,.phero-starter.on{color:var(--ink-strong)}
    .phero-starter.on .phero-starter-tile{background:var(--purple-5);border-color:var(--purple-10);color:var(--purple-cta)}
    .phero-starter:focus-visible{outline:none}
    .phero-starter:focus-visible .phero-starter-tile{outline:2px solid var(--purple-cta);outline-offset:3px}
    @media (max-width:520px){ .phero-starters{gap:12px} .phero-starter-tile{width:43.2px;height:43.2px} }

    /* "New connector" scrolls to the box and focuses it — invisible when the box is
       already on screen, which is the no-op that got pressed six times in one session.
       The ring fires in every case, including a repeat press. */
    @keyframes pheroRing{
      0%{box-shadow:0 0 0 0 rgba(108,24,255,.42)}
      70%{box-shadow:0 0 0 14px rgba(108,24,255,0)}
      100%{box-shadow:0 0 0 0 rgba(108,24,255,0)}
    }
    .phero-box.phero-ring{border-color:var(--purple-cta);animation:pheroRing .85s ease-out 2}
    @media (prefers-reduced-motion:reduce){
      .phero-box.phero-ring{animation:none;box-shadow:0 0 0 4px rgba(108,24,255,.28)}
    }

    .phero-box{max-width:600px;margin:0 auto;background:var(--panel);border:1px solid var(--border);border-radius:14px;padding:14px 16px;text-align:left;
      box-shadow:0 1px 4px rgba(0,0,0,.04),0 18px 40px -28px rgba(80,40,160,.45);transition:.15s}
    /* No focus treatment: clicking into the box shouldn't change it. Listening and
       dragging still light it up (below) — those are states, not just a caret. */
    .phero-ta{width:100%;border:none;outline:none;resize:none;font-family:inherit;font-size:16px;line-height:1.5;color:var(--text);background:transparent;min-height:48px;max-height:170px;display:block}
    .phero-ta::placeholder{color:#a1a1aa}
    .phero-row{display:flex;align-items:center;justify-content:space-between;margin-top:8px}
    /* Submit — a labelled button that says what pressing it does. Grey-90 by default
       so it reads as the one committing action here; purple only under the cursor. */
    .phero-send{display:flex;align-items:center;justify-content:center;gap:4.8px;
      height:30px;padding:0 11.2px;border:none;border-radius:6px;background:#3e3d3e;color:#fff;
      font-family:'DM Sans',sans-serif;font-size:13px;font-weight:400;cursor:pointer;flex-shrink:0;
      transition:background-color .15s}
    .phero-send:hover:not(:disabled){background:#9747FF}
    /* Disabled reads as its own flat grey rather than a faded version of the live
       button — and the arrow drops to the same grey as the label, since white on
       Grey 10 is all but invisible. */
    .phero-send:disabled{background:var(--grey-10);color:var(--grey-40);cursor:default}
    .phero-send:disabled svg path{stroke:var(--grey-40)}
    .phero-send span{display:flex;align-items:center}
    .phero-send .ic{display:flex;align-items:center}
    .phero-send svg{width:11.2px;height:11.2px;display:block}
    .phero-send.busy{opacity:.6;cursor:default}

    /* ── The prompt handoff from myop.dev (src/handoff.ts, gallery.js handoff()) ──
       Two classes and four rules. Everything else the sequence uses is already here:
       the box, the button, .phero-ring and the nav cover.

       THE POINTER IS DRAWN. A page cannot move the system cursor, and moving somebody's
       pointer is a thing software must not do — so this is an obvious stand-in, slightly
       oversized, with a white keyline so it reads on both the wash and the white box and
       is never mistaken for their own. */
    .hoff-cursor{position:fixed;left:0;top:0;z-index:1200;width:26px;height:26px;
      pointer-events:none;opacity:0;filter:drop-shadow(0 3px 7px rgba(10,9,10,.28));
      transition:opacity .16s ease}
    .hoff-cursor.on{opacity:1}
    .hoff-cursor svg{display:block;width:26px;height:26px}
    /* The press: the pointer dips, the way a real click looks. */
    .hoff-cursor.press svg{animation:hoffDip .18s ease-out}
    @keyframes hoffDip{50%{transform:scale(.86)}100%{transform:scale(1)}}
    /* Drawn at the point of contact so the press has a consequence you can see. */
    .hoff-ripple{position:fixed;z-index:1199;width:12px;height:12px;margin:-6px 0 0 -6px;
      border-radius:50%;border:2px solid var(--purple-cta);pointer-events:none;
      animation:hoffRipple .42s cubic-bezier(.2,.75,.3,1) forwards}
    @keyframes hoffRipple{0%{transform:scale(.4);opacity:.9}100%{transform:scale(4.6);opacity:0}}
    /* :hover and :active belong to the REAL pointer and will never fire for a cursor we
       drew, so the two states get classes that reproduce them exactly. Keep these in step
       with .phero-send:hover above — they are the same button under a different pointer. */
    .phero-send.hoff-hover:not(:disabled){background:var(--purple-hover)}
    .phero-send.hoff-press:not(:disabled){background:var(--purple-hover);transform:scale(.97)}
    @media (prefers-reduced-motion:reduce){
      .hoff-cursor,.hoff-cursor svg,.hoff-ripple{animation:none;transition:none}
    }
    /* Attach (+) — drop PRDs, design docs/images, HTML, API docs. No chrome at rest:
       it's a bare mark that fills in with Purple 5 under the cursor, and stays filled
       while its drawer is open (.active) so the open panel has a visible owner. */
    .phero-attach{display:flex;align-items:center;justify-content:center;
      width:24px;height:24px;padding:0;border-radius:50%;border:1px solid transparent;background:transparent;
      cursor:pointer;flex-shrink:0;transition:background .15s,border-color .15s}
    .phero-attach svg{width:15px;height:15px;display:block}
    .phero-attach svg path{stroke:#3E3D3E;transition:stroke .15s}
    .phero-attach:hover{background:var(--quiet-hover);border-color:transparent}
    .phero-attach:hover svg path{stroke:#3E3D3E}
    .phero-attach.active{background:var(--purple-5);border-color:transparent}
    .phero-left{display:flex;align-items:center;align-self:center;gap:8px;min-width:0}
    /* ── The + drawer ─────────────────────────────────────────────────────────
       Opens DOWNWARD from the +: search on top, sectioned rows, one highlighted
       line. Deliberately quiet — no keyboard legend, no chrome explaining itself.
       The shortcuts are the standard palette ones; people find them by trying, and
       a row of kbd chips costs every user attention to teach a few of them. */
    /* The + and its drawer. This wrapper is a plain block, so if anything ever stretches
       it to the row's full height the 24px button inside it sits at the TOP of that
       height rather than on the row's centreline. Centring the button within the wrapper
       AND the wrapper within the row makes it hold either way. */
    .phero-plus{position:relative;flex-shrink:0;align-self:center;
      display:flex;align-items:center;justify-content:center}
    .phero-menu{display:none;position:absolute;top:calc(100% + 8px);left:0;z-index:60;width:308px;
      background:var(--panel);border:1px solid var(--border);border-radius:12px;overflow:hidden;
      box-shadow:0 20px 48px -20px rgba(24,16,48,.3),0 3px 10px rgba(24,16,48,.06)}
    /* Opens as it is: no grow, fade or slide, on opening or when moving between its
       screens. (pmin is kept: Home's sort and filter menus still use it.) */
    .phero-menu.open{display:block}
    @keyframes pmin{from{opacity:0;transform:translateY(-6px) scale(.98)}to{opacity:1;transform:none}}
    /* Two classes, so this wins over .pm-field's padding regardless of source order —
       that padding is what pushed the search text off the row-label line. */
    /* The search sits on the SAME two columns as the rows below it: the magnifier is
       centred over the icon tiles (body 5 + item 8 + tile 26/2 = 26px) and the text
       starts on the row-label line (5 + 8 + 26 + 9 = 48px). Anything else makes the
       first thing you see look bolted onto the list rather than part of it. */
    /* No height cap and no scroll. What goes in here is already bounded (four actions,
       three recent connectors, six when searching), so a cap only ever hid the last
       rows behind a scrollbar inside a menu that had room to be its full height. */
    .pm-body{padding:4px}
    /* Section heading: sentence case, Body type at semibold, the item's ink. */
    .pm-sec{font:600 13px/18px var(--font);color:var(--grey-90);padding:12px 16px 4px}
    /* The line between the menu's two groups: a full-width hairline with 4px of air on
       each side. (The row above's 2px gap collapses into the 4px here, so it stays 4.) */
    .pm-sep{height:1px;background:var(--border);margin:4px -4px}
    .pm-item,.pm-row{display:flex;align-items:center;gap:8px;width:100%;padding:var(--menu-item-pad);border:none;background:none;
      margin:0 0 var(--menu-item-gap);border-radius:var(--menu-item-r);cursor:pointer;text-align:left}
    .phero-menu:focus{outline:none}
    /* ONE selection model: hover and the arrow keys both land on .hi, so the mouse
       and the keyboard can never disagree about what Enter would do. */
    .pm-item.sel,.pm-row.sel{background:var(--menu-selected)}
    .pm-item.hi,.pm-row.hi{background:var(--menu-hover)}
    .pm-item.locked{opacity:.72}
    /* The mark alone — no chip behind it, no purple. The 26px box stays so these
       rows line up with the app rows below them. */
    .pm-ic{width:16px;height:16px;flex-shrink:0;display:grid;place-items:center;color:var(--grey-90)}
    .pm-ic svg{width:16px;height:16px;display:block}
    .pm-txt{display:flex;flex-direction:column;gap:1px;min-width:0}
    /* Item type, as specified: Body (DM Sans 13/18, 400), Grey 90, one line with an ellipsis. */
    .pm-l{font-family:"DM Sans",var(--font);font-size:13px;font-style:normal;font-weight:400;line-height:18px;
      color:var(--grey-90);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
    .pm-s{font:var(--t-tiny);color:var(--ink-muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
    .pm-lock{margin-left:auto;color:var(--faint);flex-shrink:0;display:grid;place-items:center}
    .pm-lock svg{width:12px;height:12px}
    /* Sub-panel header, with the back step */
    /* 8px gutter, the same as .pm-field below it — the back button, the input and
       the note all start on one line. At 10px the button sat 2px right of the field. */
    .pm-head{display:flex;align-items:center;gap:8px;padding:8px;border-bottom:1px solid var(--border)}
    .pm-ht{font-size:12.5px;font-weight:650;color:var(--text)}
    /* padding:0 is load bearing. A button keeps the UA default padding:1px 6px, which
       at 22px leaves an 8px content box — narrower than the 12px icon, so it overflows
       and grid alignment pins it to the start instead of centring it. That is the 2px
       rightward lean; the bigger buttons hide it only because their box stays wider
       than their icon. */
    /* The quiet icon control, square: the Home ＋ with corners instead of a circle, and
       the same size (24px). No border; Purple 5 under the cursor; the arrow keeps its ink. */
    .pm-back{width:24px;height:24px;padding:0;border-radius:6px;border:none;background:transparent;
      color:var(--quiet-ink);display:grid;place-items:center;cursor:pointer;flex-shrink:0;transition:background .15s}
    .pm-back:hover{background:var(--quiet-hover);color:var(--quiet-ink)}
    .pm-back:focus-visible{outline:2px solid var(--purple-cta);outline-offset:2px}
    .pm-back svg{width:12px;height:12px}
    /* Sub-panels share ONE 8px gutter: back button, field, list rows and notes all start
       and end on the same two lines. They used to mix 5px and 8px, so the field and the
       highlighted row below it had different edges. */
    .pm-field{display:flex;align-items:stretch;gap:6px;padding:8px}
    .ds-input,.pm-inp{display:flex;align-items:center;align-self:stretch;flex:1;min-width:0;
      padding:var(--field-pad);border-radius:var(--field-r);border:1px solid var(--field-line);
      background:var(--field-bg);font:var(--t-body);color:var(--ink-strong);outline:none}
    .ds-input::placeholder,.pm-inp::placeholder{color:var(--ink-muted)}
    .pm-note{font:var(--t-tiny);color:var(--ink-muted);line-height:1.45;padding:0 8px 10px}
    .pm-err{font:var(--t-tiny);color:var(--accent-red,#ef4444);padding:0 8px 8px}
    .pm-err:empty{display:none}
    .pm-empty{font:var(--t-body);color:var(--ink-muted);padding:9px 16px;text-align:center}
    .pm-list{max-height:216px;overflow-y:auto;padding:0 8px 8px}
    .pm-av{width:16px;height:16px}
    /* A connector is one line: name on the left, its size on the right. The actions
       above keep two lines because their second line explains them; a tool count
       doesn't need a line of its own. */
    .pm-row .pm-txt{flex:1;flex-direction:row;align-items:baseline;justify-content:space-between;gap:10px}
    .pm-row .pm-s{flex-shrink:0}
    /* Context chips sit alongside file chips but read as structural, not decorative. */
    .phero-chip.ctx{background:var(--purple-chip);border-color:var(--purple-10)}
    .phero-chip.ctx .ic{background:var(--purple-cta)}
    .phero-chip.ctx .ic svg{color:#fff;width:11px;height:11px}
    .phero-right{display:flex;align-items:center;gap:12px;flex-shrink:0}   /* mic ↔ Build it: 8px + 4px */
    /* ── Mic (dictation) ──────────────────────────────────────────────────────
       Tap = latched, hold = push-to-talk. Idle it matches the attach button; live
       it becomes a filled stop with two breathing rings, and the hint text on the
       left swaps for a real-level waveform + timer. */
    /* 28px, not 34. It sits between a 24px ＋ on the far side of the row and the send
       button, and at 34 with a ring and a white fill it was the loudest thing in a row
       whose other icon wears neither — the eye went to dictation before the input. It
       KEEPS the ring: unlike the ＋, this is a mode you switch on and stay in, and the
       border is what the live and arming states grow out of. */
    .phero-mic{position:relative;width:28px;height:28px;border-radius:50%;border:1px solid var(--border);background:var(--panel);
      color:var(--muted);display:grid;place-items:center;cursor:pointer;flex-shrink:0;touch-action:none;
      transition:background .16s,color .16s,border-color .16s,transform .12s}
    .phero-mic svg{width:14px;height:14px;position:relative;z-index:1}
    .phero-mic:hover{border-color:var(--purple-10);color:var(--purple-cta);background:var(--purple-5)}
    .phero-mic:active{transform:scale(.94)}
    .phero-mic.live{background:var(--purple-cta);border-color:transparent;color:#fff}
    .phero-mic.live.ptt{background:#e11d48}
    /* While the press is arming, a red halo grows over exactly HOLD_MS. Let go before
       it lands and you keep the latched toggle; hold through it and it is push-to-talk.
       The gesture is legible mid-press instead of only after you release. */
    .phero-mic.live.arming{animation:micarm var(--hold,700ms) linear forwards}
    @keyframes micarm{from{box-shadow:0 0 0 0 rgba(225,29,72,0)}to{box-shadow:0 0 0 7px rgba(225,29,72,.3)}}
    /* Two rings, offset in time, so the pulse never has a dead beat. */
    .phero-mic.live::before,.phero-mic.live::after{content:"";position:absolute;inset:0;border-radius:50%;
      border:2px solid currentColor;opacity:.55;animation:micring 1.8s cubic-bezier(.22,.61,.36,1) infinite;pointer-events:none}
    .phero-mic.live::after{animation-delay:.9s}
    @keyframes micring{0%{transform:scale(1);opacity:.5}70%{opacity:0}100%{transform:scale(1.85);opacity:0}}
    /* HUD: hidden until listening, then it fills the left of the bar. */
    .phero-mic-hud{display:none;align-items:center;gap:10px;font-size:12px;color:var(--purple-cta);font-weight:600;
      animation:hudin .22s ease both}
    .phero-box.listening .phero-mic-hud{display:flex}
    @keyframes hudin{from{opacity:0;transform:translateX(-6px)}to{opacity:1;transform:none}}
    .mic-wave{display:flex;align-items:center;gap:3px;height:20px}
    .mic-bar{width:3px;height:20px;border-radius:2px;background:currentColor;transform:scaleY(.22);
      transform-origin:center;transition:transform .06s linear}
    /* Push-to-talk paints the whole HUD the same red as the button, so the two
       gestures never look alike mid-recording. */
    .phero-box.listening .phero-mic-hud.ptt{color:#e11d48}
    .mic-time{font-variant-numeric:tabular-nums;color:var(--muted);font-weight:600}
    .mic-mode{color:var(--muted);font-weight:500}
    .mic-esc{color:var(--faint);font-weight:500}
    /* Whole box takes on the listening state — same language as .dragging. */
    .phero-box.listening{border-color:var(--purple-cta);box-shadow:0 0 0 3px var(--purple-10),0 14px 36px -22px rgba(80,40,160,.5)}
    .phero-box.listening::after{content:"";position:absolute;inset:-1px;border-radius:14px;pointer-events:none;
      box-shadow:0 0 0 6px var(--purple-5);animation:boxpulse 2.2s ease-in-out infinite}
    .phero-box{position:relative}
    @keyframes boxpulse{0%,100%{opacity:.35}50%{opacity:.9}}
    @media (prefers-reduced-motion:reduce){
      .phero-mic.live,.phero-mic.live::before,.phero-mic.live::after,.phero-box.listening::after{animation:none}
      .phero-mic.live.arming{animation:micarm var(--hold,700ms) linear forwards}
    }
    /* File chips */
    .phero-files{display:flex;flex-wrap:wrap;gap:6px;margin:2px 0 8px}
    .phero-files:empty{display:none}
    .phero-chip{display:inline-flex;align-items:center;gap:7px;max-width:220px;padding:5px 8px 5px 6px;border-radius:8px;
      background:var(--purple-5);border:1px solid var(--purple-10);font-size:12px;color:var(--text)}
    .phero-chip .ic{width:20px;height:20px;border-radius:5px;flex-shrink:0;display:grid;place-items:center;background:#fff;overflow:hidden}
    .phero-chip .ic img{width:100%;height:100%;object-fit:cover}
    .phero-chip .ic svg{width:12px;height:12px;color:var(--purple-cta)}
    .phero-chip .nm{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:500}
    .phero-chip .sz{color:var(--faint);flex-shrink:0}
    .phero-chip .x{cursor:pointer;color:var(--faint);flex-shrink:0;line-height:1;padding:0 2px;border:none;background:none;font-size:14px}
    .phero-chip .x:hover{color:var(--accent-red,#ef4444)}
    /* Drag-over highlight on the whole box */
    .phero-box.dragging{border-color:var(--purple-cta);box-shadow:0 0 0 3px var(--purple-10),0 14px 36px -22px rgba(80,40,160,.4);background:var(--purple-5)}
    .phero-drophint{font-size:12px;color:var(--purple-cta);font-weight:600}
    /* ── Starter ideas ─────────────────────────────────────────────────────────
       Four shapes people actually build. Clicking one writes the prompt for you.
       The mock widget on top is what that connector would render in Claude — the
       whole point of Myop is the UI, so a starter showing only a title undersells it. */
    /* Built like an app card: same panel, same border, same --card-r corner, same
       --card-h height, the same widget-stage wash behind the preview, and the same
       hover. A starter and an app sit in the same sheet, so they read as one family. */
    /* The line above the starters. It says what the section is honestly — templates
       aren't built yet — so an account with none doesn't read the tab as broken. */
    .htpl-note{margin:0 0 18px;font-size:13.5px;line-height:20px;color:var(--muted);max-width:70ch}
    /* Shown only when there is nothing at all to start from: the way back to the one
       thing that does work. Same shape as the suggestion pills, one size up. */
    .htpl-cta{display:inline-flex;align-items:center;justify-content:center;height:36px;padding:8px 16px;
      background:#fff;border:1px solid var(--grey-30);border-radius:6px;
      font-family:'DM Sans',sans-serif;font-size:13px;font-weight:400;line-height:18px;color:var(--black-100);
      cursor:pointer;transition:border-color .2s,background .2s}
    .htpl-cta:hover{background:var(--purple-5);border-color:var(--purple-10)}
    .htpl-cta:focus-visible{outline:2px solid var(--purple-cta);outline-offset:2px}
    /* ── A template card ──────────────────────────────────────────────────────
       Same shell as an app card, because it IS one — a real connector you don't own yet.
       The preview is the template's own component running in an iframe (.wgt-stage /
       .wgt-scale, shared with the app cards), so the card shows what the thing draws
       rather than a drawing of what it might draw. */
    .htpl-card{position:relative;height:var(--card-h);display:flex;flex-direction:column;
      background:var(--panel);border:1px solid var(--border);border-radius:var(--card-r);
      overflow:hidden;text-align:left;cursor:pointer;padding:0;
      transition:border-color .16s,box-shadow .16s,opacity .16s}
    .htpl-card:hover{border-color:var(--purple-10);box-shadow:0 12px 28px -16px rgba(108,24,255,.3)}
    .htpl-card:focus-visible{outline:2px solid var(--purple-cta);outline-offset:2px}
    /* An app card layers its info bar OVER the preview and slides it open on hover, so
       .wgt-stage there is inset:0. A template card stacks instead — the description is
       the only thing telling these three apart, so it can't be behind a hover — which
       means the stage has to come out of absolute positioning and take its share of a
       flex column. */
    .htpl-card .wgt-stage{position:relative;inset:auto;flex:1 1 auto;min-height:0}
    /* Creating takes a round trip. Say so on the card you pressed, so a slow network
       doesn't read as a card that ignored you. */
    .htpl-card.busy{opacity:.55;pointer-events:none}
    /* The info bar, always open: unlike an app card there is nothing here you already
       know, so hiding the description behind a hover would hide the only thing that tells
       these three apart. */
    .htpl-info{flex:0 0 auto;padding:11px 13px 13px;border-top:1px solid var(--border);
      background:var(--panel)}
    .htpl-n{font-size:14px;font-weight:600;color:var(--text);
      white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
    .htpl-d{font-size:12px;line-height:1.5;color:var(--muted);margin-top:3px}
    .htpl-m{font-size:11px;color:var(--faint);margin-top:6px}
    @media (prefers-reduced-motion:reduce){ .htpl-card{transition:none} }
    /* home loading skeletons (until connectors load) */
    .hsk{display:inline-block;border-radius:6px;background:linear-gradient(90deg,#ececec 25%,#f5f5f5 37%,#ececec 63%);background-size:400% 100%;animation:hsk 1.3s ease infinite}
    .hsk-n{width:42px;height:26px}
    .hsk-card{height:var(--card-h);border:1px solid var(--border);background:linear-gradient(90deg,#f2f2f2 25%,#f8f8f8 37%,#f2f2f2 63%);background-size:400% 100%;animation:hsk 1.3s ease infinite}
    @keyframes hsk{0%{background-position:100% 0}100%{background-position:0 0}}
    /* apps section */
    .home-search{width:200px;max-width:44vw;padding:7px 12px 7px 31px;border:1px solid var(--border);border-radius:8px;
      background:var(--panel) url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23a1a1aa' stroke-width='2' stroke-linecap='round'><circle cx='11' cy='11' r='7'/><path d='M20 20l-3-3'/></svg>") no-repeat 10px center;
      font-size:13px;color:var(--text);outline:none;transition:.14s}
    .home-search:focus{border-color:var(--purple-10);box-shadow:0 0 0 3px var(--purple-5)}
    .home-search::placeholder{color:var(--faint)}
    .link-btn{font-size:13px;font-weight:600;color:var(--purple-cta);background:none;border:none;cursor:pointer;flex-shrink:0}
    .link-btn:hover{text-decoration:underline}
    /* Infinite-scroll trip wire under the Home grid — must occupy layout to be observable. */
    .more-sentinel{height:8px;margin-top:8px}
    /* ── Mobile ──────────────────────────────────────────────────────────────
       900px matches the rail's own breakpoint (studio-sidebar.js), so the rail
       stops taking 68px of the row at exactly the width this starts reclaiming.

       Measured at 390px before this existed: .main had 322px of width, .wrap ate
       64px of it in padding, leaving 258px — while .grid demanded a 300px minimum
       column. Home overflowed by 63px horizontally, Apps by 10px (which is why
       Apps looked "fine" and Home looked broken; both were wrong).                */
    /* No breakpoint of its own any more: .htpls auto-fills on the same minimum as the
       app grid, so it reflows to 3, 2, 1 columns wherever .grid does. */
    @media (max-width:900px){
      /* Here the rail is a floating 68x66 card again (studio-sidebar.js, same 900px
         breakpoint), so the top of the page has to clear it. */
      .wrap{--wrap-pad:16px;padding:78px var(--wrap-pad) 48px}
      /* Same +20% as the desktop hero, at phone proportions. */
      .hhero{padding:96px 16px 44px}
      .hh-greet{font-size:22px}
      .hstats{gap:20px;padding:14px 0 18px}
      .hsheet{width:100%;border-radius:14px 14px 0 0;min-height:auto;padding-bottom:48px}
      .hsheet-in{padding:22px 16px 0}
      /* One column. auto-fill with a 300px minimum cannot fit a phone, and a grid
         column never shrinks below its minimum — that was the horizontal scroll. */
      .grid,.htpls{grid-template-columns:1fr;gap:14px}
      /* Stats: 4 columns of text don't fit at this width; wrap to 2×2. */
      .hstat{flex:1 0 40%}
      .hstat-v{font-size:22px}
      /* Title + search + "View all" on one row pushed the button off-screen. */
      .hsec-h{flex-direction:column;align-items:stretch;gap:10px}
      .hsec-h .right{width:100%;justify-content:space-between}
      .home-search{flex:1;min-width:0;max-width:none}
    }

    /* ── The New app dialog ───────────────────────────────────────────────────
       Drawn to the design system's dialog spec: a 100px illustration over a
       24px/32 title and one 16px/24 line, then fields — an 11px uppercase label
       4px above a white field on a grey hairline at a 4px corner — and two 30px
       buttons at the bottom right.

       NOTE for whoever edits this next: nothing in here may become a flex child
       with a zero basis (`flex:1 1 0`). The box sizes to its content, so there is
       no free space to hand out, and a zero-basis child collapses to nothing —
       which once made both fields vanish while the head and buttons stayed put,
       so it read as a deliberately empty dialog rather than as a bug. If the
       window is too short, .modal-box scrolls as a plain block instead. */
    :root{
      --dlg-w:560px;    /* dialog width, before the 100% clamp */
      --dlg-r:12px;     /* the dialog's own corner; fields are sharper */
      --dlg-pad:32px;   /* side padding, shared by the head and the body */
      --dlg-gap:24px;   /* between one field and the next */
      --fld-r:4px; --fld-h:44px;
      --btn-h:30px; --btn-r:6px;
    }
    .modal-ov{position:fixed;inset:var(--update-bar-h) 0 0 0;z-index:100;display:flex;align-items:center;justify-content:center;padding:24px;
      background:rgba(20,18,28,.28);backdrop-filter:blur(6px) saturate(1.1);-webkit-backdrop-filter:blur(6px) saturate(1.1);
      opacity:0;transition:opacity .16s ease}
    .modal-ov.show{opacity:1}
    .modal-box{width:min(var(--dlg-w),100%);max-height:calc(100vh - 48px);overflow-y:auto;
      background:var(--panel);border:1px solid var(--border);border-radius:var(--dlg-r);
      box-shadow:0 24px 64px -18px rgba(10,9,10,.4);transform:translateY(8px) scale(.99);transition:transform .18s ease}
    .modal-ov.show .modal-box{transform:none}

    /* Head: the illustration, 20px of air, then the title block. No tinted tile
       behind a glyph — the illustration is the whole picture. */
    .modal-h{display:flex;flex-direction:column;align-items:flex-start;gap:20px;padding:24px var(--dlg-pad)}
    .modal-h .mi{width:100px;height:100px;flex-shrink:0}
    .modal-h .mi svg{width:100px;height:100px;display:block}
    .modal-h .tt{display:flex;flex-direction:column;align-items:flex-start;gap:4px;width:100%}
    .modal-h h3{margin:0;font:var(--t-h1);color:var(--ink)}
    .modal-h p{margin:0;font:var(--t-body-lg);color:var(--ink)}

    /* Body: a plain column of fields. See the note above about flex. */
    .modal-body{display:flex;flex-direction:column;gap:var(--dlg-gap);padding:0 var(--dlg-pad) 24px}
    /* A label and the field it names are one group, 4px apart; --dlg-gap is the
       distance between groups, not between a label and its own field. */
    .fld{display:flex;flex-direction:column;align-items:flex-start;gap:4px;width:100%}
    .lab{display:block;font:var(--t-label);letter-spacing:var(--t-label-tracking);
      text-transform:uppercase;color:var(--ink-muted)}
    .inp{width:100%;height:var(--fld-h);padding:8px 12px;background:#fff;
      border:1px solid var(--line-grey);border-radius:var(--fld-r);
      font:var(--t-body);font-style:italic;color:var(--ink-strong);outline:none}
    /* An empty field reads as an example: italic, and upright the moment you type.
       The opacity is load-bearing — it defeats Firefox's own placeholder fade, so
       --ink-muted is the colour that actually renders. */
    .inp.has-value{font-style:normal}
    .inp::placeholder{font-style:italic;color:var(--ink-muted);opacity:1}
    /* The hairline stays put on focus, per the design. The ring is the studio's
       own convention and only shows for the keyboard, never for the mouse. */
    .inp:focus{border-color:var(--line-grey)}
    .inp:focus-visible{outline:2px solid var(--purple-cta);outline-offset:2px}
    /* 104px = the reference's 86px of text plus its 8px padding and hairline. We
       carry the border on the textarea rather than on a wrapper, so the number has
       to include them; the rendered box is the same height either way. */
    .gen-desc{height:104px;resize:none;overflow-y:auto;
      scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.15) transparent}
    .gen-desc::-webkit-scrollbar{width:4px}
    .gen-desc::-webkit-scrollbar-track{background:transparent}
    .gen-desc::-webkit-scrollbar-thumb{background:rgba(0,0,0,.15);border-radius:4px}
    /* The "or" between the two ways in, set as a label so it belongs to the same
       family as the field labels above and below it. */
    .or-div{display:flex;align-items:center;gap:10px;font:var(--t-label);
      letter-spacing:var(--t-label-tracking);text-transform:uppercase;color:var(--ink-muted)}
    .or-div::before,.or-div::after{content:"";flex:1;height:1px;background:var(--line-grey)}
    .slug-prev{font:var(--t-tiny);color:var(--ink-muted);margin-top:4px}
    .slug-prev b{color:var(--purple-cta);font-family:var(--font-mono);font-weight:500}
    /* The address you'd get instead, when the name is already taken. A plain note
       in the same ink as the rest — the studio has no amber left to spend. */
    .slug-warn{color:var(--ink-muted)}

    /* Two buttons, one shape: 30px, as everywhere else in the studio. Outline
       cancels, filled commits, and purple is the hover on both. Scoped to the
       footer so the page's other buttons are untouched. */
    .modal-foot{display:flex;justify-content:flex-end;align-items:center;gap:16px}
    .modal-foot .btn{display:inline-flex;justify-content:center;align-items:center;gap:8px;
      height:var(--btn-h);padding:6px 12px;border-radius:var(--btn-r);
      font:var(--t-body);
      background:#fff;border:1px solid var(--line-grey);color:var(--ink-strong);transition:all .15s ease}
    .modal-foot .btn:hover:not(:disabled){background:var(--purple-5);border-color:var(--purple-10)}
    .modal-foot .btn.primary{background:var(--ink);border-color:var(--ink);color:#fff}
    .modal-foot .btn.primary:hover:not(:disabled){background:var(--purple-hover);border-color:var(--purple-hover)}
    .modal-foot .btn:disabled{opacity:.4;cursor:default}

/* ── Analytics, across every app ──────────────────────────────────────────────
   Decision 4: this used to be two clicks deep inside one app. Same visual language as
   the per-app Usage screen so the drill-down doesn't feel like a different product.

   The language itself is the Myop analytics one: a warm header band, an insight strip,
   metric cards, and panels of flat white on a #E5E4E4 hairline. Everything here is
   tokenised at the top so "warmer", "flatter", "tighter" stays one edit.

   Tokens are duplicated on :root in editor.css — the two files are separate
   stylesheets and the per-app Usage tab needs the same ramp. */
:root{
  --an-line:#E5E4E4;      /* card + row hairline */
  --an-track:#E5E4E4;     /* meter track */
  --an-dim:#6A696A;       /* secondary text */
  --an-ink:#3E3D3E;       /* primary text on this surface */
  --an-chip:#F5F3F3;      /* rank chip */
  --an-mute-bar:#B6B5B7;  /* every bar that isn't the leader */
  --an-normal:#9747FF;    /* the single-series meter */
  --an-special:linear-gradient(237deg,#FF7133 19.7%,#7C69FD 75.96%);
  --an-wash-head:linear-gradient(86deg,rgba(240,232,255,.4) -6.17%,rgba(255,255,255,.4) 76.6%,rgba(255,237,231,.4) 95.9%);
  --an-wash-soft:linear-gradient(90deg,rgba(240,232,255,.40) 0%,rgba(255,255,255,.30) 55%,rgba(255,237,231,.42) 100%);
}
/* Header band. Breaks the reading column's padding so the wash runs edge to edge,
   the way it does in the analytics component. */
.an-band{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:16px;
  margin:-34px calc(var(--wrap-pad,32px) * -1) 24px;padding:28px var(--wrap-pad,32px);
  background:var(--an-wash-head);border-bottom:1px solid #F5F3F3}
.an-brand{display:flex;align-items:center;gap:16px;min-width:0}
.an-brand>span{display:flex;flex:none}
.an-brand svg{width:56px;height:56px;flex:none;display:block}
.an-brand h1{margin:0;font-size:20px;font-weight:400;line-height:28px;color:#1C1B1F;letter-spacing:0}
.an-brand p{margin:0;font-size:13px;font-weight:300;line-height:18px;color:#1C1B1F}
.an-body{display:flex;flex-direction:column;gap:16px}

/* Insight strip */
.an-insight{display:flex;align-items:center;flex-wrap:wrap;gap:8px 20px;padding:14px 20px;
  background:var(--an-wash-soft);border:1px solid var(--an-line);border-radius:12px;
  font-size:13px;line-height:18px;color:var(--an-dim)}
.an-insight b{font-weight:600;color:var(--an-ink)}
.an-sep{width:4px;height:4px;border-radius:50%;background:#D3D1D5;flex:none}

/* Metric cards */
.an-kpis{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:16px}
.an-kpi{display:flex;flex-direction:column;min-height:118px;min-width:0;padding:16px;
  background:#fff;border:1px solid var(--an-line);border-radius:12px}
.an-kpi-l{font-size:11px;font-weight:600;line-height:14px;text-transform:uppercase;letter-spacing:1px;color:var(--an-dim)}
.an-kpi-v{font-size:24px;font-weight:600;line-height:32px;letter-spacing:-.01em;margin-top:8px;color:var(--an-ink)}
.an-kpi-c{margin-top:auto;padding-top:10px;font-size:13px;line-height:18px;color:var(--an-dim);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* Ratio variant — a share of a known whole reads better as a meter than as a caption. */
.an-kpi-r{margin-top:auto;padding-top:12px}
.an-kpi-rt{height:6px;background:var(--an-track);border-radius:4px;overflow:hidden}
.an-kpi-rf{height:100%;background:var(--an-normal);border-radius:4px}
.an-kpi-rc{margin-top:8px;font-size:13px;line-height:18px;color:var(--an-dim)}

/* Panels */
.an-cols{display:grid;grid-template-columns:1.6fr 1fr;gap:16px}
.an-panel{display:flex;flex-direction:column;min-width:0;padding:20px;
  background:#fff;border:1px solid var(--an-line);border-radius:12px}
.an-ph h3{margin:0;font-size:13px;font-weight:600;line-height:18px;color:var(--an-ink)}
.an-ph p{margin:2px 0 0;font-size:13px;font-weight:400;line-height:18px;color:var(--an-dim)}
.an-pb{margin-top:16px;flex:1;min-width:0;display:flex;flex-direction:column}

/* Hero row — the leading app, then the rest as a quiet list */
.an-hero{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px;
  margin-bottom:6px;border:1px solid var(--an-line);border-radius:8px;text-decoration:none;color:inherit;
  background:linear-gradient(90deg,rgba(255,113,51,.07) 0%,rgba(124,105,253,.07) 100%)}
.an-hero:hover{border-color:#CAACFF}
.an-hero-l{display:flex;align-items:center;gap:12px;min-width:0}
.an-hero-r{width:26px;height:26px;border-radius:50%;flex:none;display:grid;place-items:center;
  background:var(--an-special);color:#fff;font-size:12px;font-weight:600}
.an-hero-n{font-size:15px;font-weight:600;line-height:20px;color:var(--an-ink);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.an-hero-m{margin-top:1px;font-size:13px;line-height:18px;color:var(--an-dim)}
.an-hero-v{font-size:20px;font-weight:600;line-height:24px;color:var(--an-ink);flex:none}
.an-list{display:flex;flex-direction:column}
.an-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 2px;
  border-bottom:1px solid var(--an-line);text-decoration:none;color:inherit}
.an-row:last-child{border-bottom:none}
/* Two gaps, not one: the mark belongs to the name, so it sits closer to it than to
   the rank number in front of it — otherwise the row reads as three loose columns. */
.an-row-l{display:flex;align-items:center;gap:8px;min-width:0}
.an-row-l>:first-child{margin-right:4px}
.an-row-r{display:flex;align-items:center;gap:16px;flex:none}
.an-rank{width:22px;height:22px;border-radius:50%;flex:none;display:grid;place-items:center;
  background:var(--an-chip);color:var(--an-ink);font-size:11px;font-weight:600}
.an-nm{font-size:13px;line-height:18px;color:var(--an-ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.an-n{font-size:13px;font-weight:600;color:var(--an-ink);font-variant-numeric:tabular-nums}
.an-share{min-width:34px;text-align:right;font-size:13px;color:var(--an-dim);font-variant-numeric:tabular-nums}
.an-when{font-size:13px;color:var(--an-dim);white-space:nowrap}
.an-app{font-size:13px;color:var(--an-dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:150px}
.an-tile{width:26px;height:26px;flex:none;display:grid;place-items:center;color:var(--an-ink)}
/* Activity marks come out of this page's own two-tone ranking — the purple the meters
   use for a connection, the quiet grey every non-leading bar uses for a tool call.
   No third colour is introduced for a distinction this small. */
.an-dot{width:8px;height:8px;border-radius:50%;flex:none;background:var(--an-mute-bar)}
.an-dot.c{background:var(--an-normal)} .an-dot.t{background:var(--an-mute-bar)}

/* Composition bar — one row of proportions, the way Environments reads */
.an-comp{display:flex;height:8px;border-radius:4px;overflow:hidden;background:var(--an-track)}
.an-comp i{display:block;height:100%}
.an-comp-l{display:flex;flex-direction:column;gap:12px;margin-top:16px}
.an-comp-r{display:flex;align-items:center;justify-content:space-between;gap:12px;font-size:13px;color:var(--an-ink)}
.an-comp-n{display:inline-flex;align-items:center;gap:8px;min-width:0}
.an-comp-v{color:var(--an-dim);white-space:nowrap}

.an-empty{display:flex;flex-direction:column;gap:6px;align-items:center;text-align:center;
  padding:44px 20px;background:#fff;border:1px solid var(--an-line);border-radius:12px}
.an-empty b{font-size:16px;font-weight:700;color:#0A090A}
.an-empty span{font-size:13px;color:var(--an-ink);max-width:52ch;line-height:18px}
.an-empty.sm{padding:22px;border:0;background:transparent;font-size:13px;color:var(--an-dim)}
/* Say what the screen can't do rather than letting it look complete. */
.an-note{margin:2px 0 0;font-size:13px;color:var(--an-dim);line-height:18px}
@media (max-width:900px){
  .an-kpis{grid-template-columns:repeat(2,minmax(0,1fr))}
  .an-cols{grid-template-columns:minmax(0,1fr)}
}
@media (max-width:720px){
  .an-band{margin-bottom:20px;padding-top:24px;padding-bottom:24px;gap:20px}
  .an-kpis{grid-template-columns:minmax(0,1fr)}
  .an-app{display:none}
  .hstats{gap:12px}
  .hstat-l{font-size:11px}
  .hstat{min-width:0}
}

/* The way into cross-app Analytics is the Analytics row in the left rail; the counts
   at the top of the sheet are just the counts. */

/* ── Home tabs ────────────────────────────────────────────────────────────────
   One pill bar in place of a heading + its own search box + a "View all" link + a
   separate templates section. Search expands into the bar rather than living in a
   corner permanently competing with the heading. */
/* Two lines at the top of the sheet, not one: the counts, then a hairline, then the
   pill bar under it. The rule is what separates the readout above from the control
   below — so it belongs to this row, and the counts keep their own air above it. */
/* The rule above the tabs separates the counts from the grid. On a first visit there are
   no counts — the strip is dropped — so it would be a line under nothing; 35px of air
   above it made that read as a gap the page forgot to fill. */
.htabs-row{display:flex;align-items:center;justify-content:flex-start;flex-wrap:wrap;
  gap:12px 24px;border-top:1px solid var(--border);padding-top:24px;margin:0 0 22px}
/* First visit: the counts are dropped (four zeros are noise), so the tab row is the top
   of the sheet and its rule would be a line under nothing. */
.hsheet-in > .htabs-row:first-child{border-top:0;padding-top:0}
.htabs-row .htabs{margin:0;min-width:0}
.htabs{display:inline-flex;align-items:center;gap:2px;padding:4px;margin:0 0 20px;
  background:#F9F6FF;border-radius:100px;max-width:100%}
/* While the bar is opening or closing its width is driven from JS (animateWidth).
   Clipping only for that stretch — the selected pill's shadow needs to escape the
   rest of the time. */
.htabs.animating{overflow:hidden}
.htabs.animating .htab,.htabs.animating .htab-x{white-space:nowrap;flex-shrink:0}
.htab{display:inline-flex;align-items:center;gap:8px;height:36px;padding:2px 16px;border:1px solid transparent;
  border-radius:100px;font-size:13px;line-height:18px;font-weight:400;color:#3E3D3E;white-space:nowrap;
  background:transparent;transition:background .12s,color .12s,box-shadow .12s}
.htab:hover{background:#F9F6FF}
.htab.on{background:#fff;color:#0A090A;box-shadow:0 0 8px rgba(0,0,0,.16)}
.htab:focus-visible{outline:2px solid #6C18FF;outline-offset:2px}
.htab-ico{display:grid;place-items:center;flex-shrink:0}
.htab-ico svg{width:16px;height:16px;display:block}
@media (prefers-reduced-motion:reduce){.htab{transition:none}}

/* ── Search, sort and filter, on the right of the tab row ─────────────────────
   A plain bordered search field, then the component panel's two 32×32 icon buttons,
   6px corners, 8px apart. The menus they open are the + menu's
   surface (same border, radius, shadow, entrance), so Home has one kind of dropdown. */
.hcount{font:var(--t-body);color:var(--ink-muted);white-space:nowrap;font-variant-numeric:tabular-nums}
.hctl{display:flex;align-items:center;gap:8px;margin-left:auto}
/* Search: an icon button like its neighbours until pressed, then it opens into the
   plain field the rest of Myop uses, the magnifier kept at its left edge and no
   placeholder. The BOX carries the look — white with a hairline at rest, the lilac fill
   and purple border under the pointer and while you type — and the input inside it is
   bare. No grey. 219 wide, 32px tall (the icon buttons' height, so the row keeps one
   line open or closed), 4px corners. */
.hsearch{display:flex;align-items:center;border-radius:4px;transition:background .12s,border-color .12s}
.hsearch.open{width:219px;height:32px;border:1px solid var(--border);background:var(--panel);
  animation:hsearchin .16s ease-out both}
.hsearch.open:hover,.hsearch.open:focus-within{background:var(--purple-chip);border-color:var(--purple-line)}
@keyframes hsearchin{from{opacity:0;transform:translateX(6px)}to{opacity:1;transform:none}}
/* Inside the box the magnifier is its label, not a button to hover: no fill, full height. */
.hsearch.open .hsearch-btn{width:30px;height:100%;background:transparent;border-color:transparent;cursor:text}
.hsearch-in{display:none;flex:1;min-width:0;height:100%;padding:0 10px 0 0;margin:0;border:0;
  background:transparent;font:var(--t-body);color:var(--ink-strong);outline:none}
.hsearch.open .hsearch-in{display:block}
.hsearch-in::-webkit-search-cancel-button{display:none}
.hctl-wrap{position:relative}
/* The border is there at rest, transparent, so the hover can colour it without the
   button growing a pixel. Hover and menu-open are the search box's own look — lilac
   fill, purple border — so the three controls answer the pointer the same way. */
.hctl-btn{position:relative;display:grid;place-items:center;width:32px;height:32px;padding:0;
  border:1px solid transparent;border-radius:6px;background:transparent;color:var(--ink);cursor:pointer;
  transition:background .12s,border-color .12s}
.hctl-btn svg{width:16px;height:16px;display:block}
.hctl-btn:hover,.hctl-btn[aria-expanded="true"]{background:var(--purple-chip);border-color:var(--purple-line)}
.hctl-btn:focus-visible{outline:2px solid var(--purple-cta);outline-offset:2px}
/* A filter is hiding connectors: say so on the button, not only in the count. */
.hctl-btn.active::after{content:"";position:absolute;top:6px;right:6px;width:6px;height:6px;border-radius:50%;
  background:var(--purple-cta);box-shadow:0 0 0 2px var(--panel)}
.hmenu{position:absolute;top:calc(100% + 6px);right:0;z-index:60;min-width:188px;padding:4px;
  background:var(--panel);border:1px solid var(--border);border-radius:12px;
  box-shadow:0 20px 48px -20px rgba(24,16,48,.3),0 3px 10px rgba(24,16,48,.06);
  animation:pmin .14s cubic-bezier(.2,.75,.3,1) both;transform-origin:top right}
.hmenu-h{padding:8px 8px 4px;font:var(--t-label);letter-spacing:var(--t-label-tracking);
  text-transform:uppercase;color:var(--ink-muted)}
/* One highlight: hovering a row focuses it (menuButton), so the mouse and the arrow
   keys can never light up two different rows — the + menu's rule too. */
.hmenu-i{display:flex;align-items:center;gap:12px;width:100%;padding:8px;border:0;border-radius:8px;
  background:none;font:var(--t-body);color:var(--ink);text-align:left;cursor:pointer;outline:none}
.hmenu-i:focus{background:var(--purple-5);color:var(--ink-strong)}
.hmenu-l{display:inline-flex;align-items:center;gap:6px}
/* The card pill's marks: a dot before Live, red for Paused. */
.hmenu-live .hmenu-l::before,.hmenu-paused .hmenu-l::before{content:"";width:5px;height:5px;border-radius:50%;
  background:currentColor;flex:none}
.hmenu-paused .hmenu-l::before{background:var(--paused)}
.hmenu-tick{display:grid;place-items:center;margin-left:auto;color:var(--purple-cta);visibility:hidden}
.hmenu-tick svg{width:16px;height:16px;display:block}
.hmenu-i[aria-checked="true"] .hmenu-tick{visibility:visible}
@media (prefers-reduced-motion:reduce){.hctl-btn,.hsearch{transition:none}.hmenu,.hsearch.open{animation:none}}
/* Fingers get a bigger target than the pointer's 32px. */
@media (pointer:coarse){.hsearch.open{height:40px}.hctl-btn{width:40px;height:40px}}
/* Phone: the tab bar scrolls sideways rather than wrapping. */
@media (max-width:900px){
  .htabs-row .htabs{flex-shrink:1;overflow-x:auto;scrollbar-width:none}
  .htabs-row .htabs::-webkit-scrollbar{display:none}
  /* Its own line under the tabs: the field takes the width, the buttons stay square. */
  .hctl{margin-left:0;width:100%}
  .hsearch.open{flex:1;min-width:0;width:auto}
  /* The menus hang off the ROW, not their button. On desktop the row sits at the right
     edge, so right:0 on the button opens inward; on a phone the row starts at the left
     edge and right:0 pushed Sort's and Filter's menus off the screen. Anchored to the row,
     a menu opens from the row's left while the buttons sit there, and from its right once
     the open search field has pushed them to the far end. */
  .hctl{position:relative}
  .hctl-wrap{position:static}
  .hmenu{left:0;right:auto;transform-origin:top left}
  .hctl:has(.hsearch.open) .hmenu{left:auto;right:0;transform-origin:top right}
  /* 16px or iOS Safari zooms the page on focus — same floor and same reasoning as the
     editor's (see the block at the top of editor.css's 900px media query). The hero's
     own prompt box is already 16px, so search was the last control on Home that jumped. */
  .hsearch-in{font-size:16px}
}

    /* ── Template posters ────────────────────────────────────────────────────
       The card itself is an ordinary .card — hover, shadow, the .hc-info slide, all
       inherited. The only thing template-specific is that its stage holds a still
       instead of a live widget iframe.

       NO object-fit HERE, deliberately. `cover` scales the image until it fills BOTH
       axes, so on a wide card (a two-column layout, or one column on a narrow window)
       it zooms past 1:1 and shows a cropped, magnified sliver — the widget's own title
       running off both edges. A poster is a picture of a screen; magnifying it is the
       one thing it must never do.

       Width 100% + height auto instead: the shot is drawn at exactly the card's width,
       whatever that is, and the stage clips whatever does not fit BELOW. The sides are
       never cut, the scale never exceeds the card, and what you see is the top of the
       widget — which is where every one of these puts its identity. */
    .tpl-shot{display:block;width:100%;height:auto}
    /* The stage's gradient is there for a widget that has not painted yet; a poster
       always covers the top of it, and the card's own panel colour is the right thing
       to show under a short one. */
    .tpl-card .wgt-stage{background:var(--panel)}
