/* USPTO case study: one file wrapper, replayed as a printed dossier.
   -------------------------------------------------------------------------
   Loaded ONLY by /products/uspto-bundle/case-study, after style.css and after
   the scrollcraft engine stylesheet, so plain element selectors here are page
   scoped by virtue of where the file is linked.

   Three jobs:
     1. Theme the engine and re-pin the site chrome, because the engine sets
        its own body ground and the header/footer would otherwise inherit it
        and go pale on pale under a dark system theme. Same fix, same reason,
        as the EDGAR case study and scripts/prepare_redline_demo.py.
     2. Carry the panel mockups. Every rule under .us-panel is copied from the
        real MCP App views:
            uspto_pfw_mcp/src/patent_filewrapper_mcp/ui/views.py
                SEARCH_RESULTS_HTML, FAMILY_VIEW_HTML, XML_VIEW_HTML,
                DOWNLOADS_HTML
            uspto_ptab_mcp/src/ptab_mcp/ui/views.py          SEARCH_RESULTS_HTML
            uspto_fpd_mcp/src/fpd_mcp/ui/views.py            SEARCH_RESULTS_HTML
            uspto_enriched_citation_mcp/.../citation_results_view.py
            uspto_enriched_citation_mcp/.../oa_citations_view.py
        The five servers share one skeleton and differ only in an accent
        triple, so the skeleton is written once and the accents are set per
        panel through --up-* custom properties. Copied deliberately, not
        imported: this page must render with no server and no connector.
     3. Drive the signature move, the as-of dial, off --sc-p. The engine is
        never edited.

   GRAMMAR: chaptered editorial. Chapters, not acts. Hard ground changes at
   chapter boundaries rather than an interpolated drift, a folio running head
   in the margin rather than a fixed progress pill, a title page rather than a
   surface already in a state, and a colophon at the end. No pinned act
   anywhere on the page. */

/* --- 1. Engine theme: paper, ink, one stamp-red accent -------------------- */
:root {
    --sc-canvas: #faf8f4;
    --sc-surface: #ffffff;
    --sc-ink: #1c1a17;
    --sc-ink-soft: #6b6459;
    --sc-accent: #7a2e1e;
    --sc-accent-ink: #ffffff;
    --sc-font-display: "Iowan Old Style", "Palatino Linotype", "Book Antiqua",
        Palatino, Georgia, "Times New Roman", serif;
    --sc-font-text: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
        Helvetica, Arial, sans-serif;
    --sc-font-mono: ui-monospace, SFMono-Regular, Consolas, "Liberation Mono", monospace;
    /* The engine's default block padding is written for a page of six long
       acts. This one carries a twelve-prompt transcript, so it buys its length
       back here. */
    --sc-section: clamp(0.6rem, 1.2vw, 1.1rem);
    --up-rule: #ddd6c9;
    --up-rule-soft: #ece6da;
    --up-paper: #faf8f4;
    --up-paper-alt: #f3f0e8;
    --up-paper-cool: #eef1f4;
}

/* Site chrome, pinned to the site's light palette on this page only. */
.site-header,
.notice-strip {
    background: #ffffff;
    color: #16181d;
    font-family: var(--sc-font-text);
}
.site-footer {
    background: #f6f7f4;
    color: #16181d;
    font-family: var(--sc-font-text);
}
.site-header .brand,
.site-header nav a,
.site-header nav .linklike,
.site-header nav summary,
.notice-strip,
.site-footer,
.site-footer a {
    color: #16181d;
}

/* The same light palette as tokens, so everything inside the chrome that reads
   them (the nav menus, the brand and menu marks, hover and focus colours)
   stays light when the reader's system is in dark mode. */
.site-header,
.notice-strip,
.site-footer {
    --color-bg: #ffffff;
    --color-text: #16181d;
    --color-muted: #5d6570;
    --color-border: #e3e6e2;
    --color-surface: #f6f7f4;
    --color-accent: #1f4d3d;
    --color-accent-strong: #163a2e;
    --color-accent-soft: #e9f2ec;
    --color-accent-contrast: #ffffff;
}

/* The chapters are full bleed; base.html's <main class="wrap"> is not. */
main.us-main {
    max-width: none;
    padding: 0;
    overflow-x: clip;
}

/* --- 2. Chapters, folio, measure ----------------------------------------- */
/* A chapter is the unit. Each lands on its own ground and holds it: a hard
   change at the boundary, never an interpolated gradient. */
.up-chapter {
    position: relative;
    background: var(--up-paper);
    border-top: 1px solid var(--up-rule);
    /* One chapter's dirty subtree cannot pull the other fourteen into the
       relayout the engine forces when it reads document.scrollHeight. */
    contain: layout;
    /* Set one step down from the site's reading size. This is a dossier of
       twelve prompts and twelve verbatim answers, and at the site's default
       size it runs past sixty viewport-heights; every point of type here is a
       viewport-height of a reader's patience. */
    font-size: 15px;
}
.up-chapter--alt { background: var(--up-paper-alt); }
.up-chapter--cool { background: var(--up-paper-cool); }
.up-chapter--title { border-top: 0; }

.up-wrap {
    width: 100%;
    max-width: 74rem;
    margin-inline: auto;
    padding-inline: clamp(1rem, 4vw, 2.5rem);
    display: grid;
    grid-template-columns: 9.5rem minmax(0, 1fr);
    gap: clamp(1rem, 3vw, 2.5rem);
    align-items: start;
}
.up-wrap--full { grid-template-columns: minmax(0, 1fr); }
.up-col { min-width: 0; }

/* The folio: a printed running head set in the margin. Not a fixed pill, not a
   progress readout: it says which chapter you are in and which connector
   answered it, and it leaves with the chapter.

   NOT STICKY, and this is the last of the six things that had to change to
   stop the desktop renderer dying. Sticky positioning puts every one of these
   on the compositor and makes its position a constraint the browser re-solves
   on every scroll; fifteen of them on a 46,000px page was the difference
   between a page that survives a sustained 600px-every-60ms scroll and one
   that does not. Measured in a real Chrome: with them sticky the browser could
   not run a script at all during that scroll; with them static it can. The
   EDGAR case study has no sticky element anywhere.

   It is also the more correct reading of the grammar. A folio in a printed
   feature does not follow the reader down the page; it sits at the head of its
   chapter and stays there. */
.up-folio {
    font-family: var(--sc-font-mono);
    font-size: 11px;
    line-height: 1.55;
    color: var(--sc-ink-soft);
    border-top: 2px solid var(--sc-accent);
    padding-top: 8px;
}
.up-folio__num {
    display: block;
    font-size: 13px;
    font-weight: 700;
    letter-spacing: 0.08em;
    color: var(--sc-accent);
}
.up-folio__title {
    display: block;
    font-family: var(--sc-font-display);
    font-size: 15px;
    line-height: 1.3;
    color: var(--sc-ink);
    margin: 2px 0 6px;
}
.up-folio__server {
    display: block;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    font-size: 10px;
}
@media (max-width: 900px) {
    .up-wrap { grid-template-columns: minmax(0, 1fr); }
    .up-folio {
        display: flex;
        flex-wrap: wrap;
        align-items: baseline;
        gap: 10px;
        margin-bottom: 10px;
    }
    .up-folio__title { margin: 0; }
}

/* --- 3. Title page ------------------------------------------------------- */
/* Chaptered editorial opens on type set on the paper ground. No panel, no
   media above the fold: the first panel belongs to chapter one. */
.up-title {
    padding-block: clamp(2rem, 7vh, 4.5rem);
}
.up-title__rule {
    border: 0;
    border-top: 3px solid var(--sc-accent);
    width: 6rem;
    margin: 0 0 1.2rem;
}
.up-kicker {
    font-family: var(--sc-font-mono);
    font-size: 11px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--sc-accent);
    margin: 0 0 10px;
}
h1.up-h1 {
    font-family: var(--sc-font-display);
    font-size: clamp(2.1rem, 6vw, 4.1rem);
    line-height: 1.04;
    letter-spacing: -0.015em;
    margin: 0 0 0.5rem;
    max-width: 20ch;
}
.up-standfirst {
    font-family: var(--sc-font-display);
    font-size: clamp(1.05rem, 1.8vw, 1.4rem);
    line-height: 1.5;
    color: #35312b;
    max-width: 46ch;
    margin: 0 0 1.2rem;
}
h2.up-h2 {
    font-family: var(--sc-font-display);
    font-size: clamp(1.3rem, 2.4vw, 1.9rem);
    line-height: 1.2;
    margin: 0 0 0.5rem;
}
h3.up-h3 {
    font-family: var(--sc-font-display);
    font-size: 1.06rem;
    line-height: 1.25;
    margin: 0.9rem 0 0.35rem;
}
.up-body { max-width: 78ch; }

/* The verification plate: what the page is, when it was captured, and the one
   thing that makes it different from the EDGAR case study. */
.up-plate {
    border: 1px solid var(--up-rule);
    border-left: 3px solid var(--sc-accent);
    background: #fff;
    padding: 12px 16px;
    font-size: 0.93em;
    line-height: 1.6;
    color: #35312b;
    max-width: 58ch;
}
.up-plate p { margin: 0 0 0.6em; }
.up-plate p:last-child { margin-bottom: 0; }

/* An intertitle: a full stop between chapters, and the silence before the
   peak. Type on the ground, nothing else on screen. */
.up-intertitle {
    padding-block: clamp(2.5rem, 11vh, 6rem);
    text-align: left;
}
.up-intertitle p {
    font-family: var(--sc-font-display);
    font-size: clamp(1.3rem, 3.2vw, 2.2rem);
    line-height: 1.35;
    max-width: 24ch;
    margin: 0;
    color: #4a443b;
}

/* Sections 4, 5, 6 and 6c moved to ../panels.css, which BOTH the case study
   and the walkthrough link, because templates/_uspto_panels.html is shared.
   This file keeps what is the case study's alone: the engine theme, the
   chapters, the title page, the figures, the acts, the dial and the
   colophon. panels.css is linked first, so anything below overrides it. */

/* --- 6. Figures, and where a reveal is allowed to land ------------------- */
/* A REVEAL NEVER WRAPS A PANEL. The engine drives `data-sc-reveal` by writing
   an inline `clip-path: inset(...)` on every frame the act is live. A changing
   clip-path cannot be composited: it is a main-thread repaint of the whole
   box, every frame. On a small caption that is nothing. On a 912x1531 panel it
   is fatal, and it was: with the three reveal figures neutralised the page
   held a steady 17ms frame from 20,000px to the end, and with them wrapping
   the panels the renderer hung there.

   So the panel sits in the figure and arrives plainly, and the wipe belongs to
   the figcaption. The grammar wanted the caption anyway: chaptered editorial
   puts media in its own column with a caption under it. */
.up-figure { margin: 0 0 14px; }
.up-figure > figcaption {
    font-size: 0.88em;
    line-height: 1.55;
    color: var(--sc-ink-soft);
    border-left: 2px solid var(--up-rule);
    padding-left: 10px;
    margin-top: 8px;
    max-width: 62ch;
}

/* --- 6a. The entrance cue, bounded --------------------------------------- */
/* THIS IS WHAT KILLED THE DESKTOP RENDERER, and it is worth the whole comment.
   The engine's stylesheet writes the entrance pre-state as

       [data-sc-in], [data-sc-stagger] > * {
         opacity: 0; transform: translate3d(0, 14px, 0);
         transition: opacity 620ms, transform 620ms;
       }

   A 3D transform forces a compositor layer, and it forces it for the life of
   the page, not for the life of the animation. On a six-act marketing page
   that is a handful of small boxes. Here every stagger wrapper is a whole
   chapter's content column, so its direct children are the panels, the tables,
   the folds and the assistant's answers: measured in a real Chrome, 125 boxes
   totalling 49.2 megapixels, about 188 MB of layer backing store, 30 of them
   over half a megapixel and the largest 912x3466. The EDGAR case study, on the
   same engine, has 34 boxes and 19.0 megapixels. The renderer hung on load.

   The fix is opt-in rather than opt-out, and it never touches the engine:
   neutralise the pre-state for everything on this page, then hand the entrance
   back to the small blocks only, as a fade with no transform at all.

   It also fixes a correctness bug the same rule caused: with scripting off
   nothing ever adds .sc-in, so the engine's pre-state would have left most of
   the transcript at opacity 0. Everything below is gated on .sc-ready. */
.up-chapter [data-sc-in],
.up-chapter [data-sc-stagger] > * {
    opacity: 1;
    transform: none;
    transition: none;
}
.sc-ready .up-chapter [data-sc-in].up-title,
.sc-ready .up-chapter [data-sc-in].up-intertitle,
.sc-ready .up-chapter [data-sc-stagger] > .up-turn--user,
.sc-ready .up-chapter [data-sc-stagger] > .up-tools,
.sc-ready .up-chapter [data-sc-stagger] > .up-src,
.sc-ready .up-chapter [data-sc-stagger] > .up-body,
.sc-ready .up-chapter [data-sc-stagger] > h2,
.sc-ready .up-chapter [data-sc-stagger] > h3 {
    opacity: 0;
    transition: opacity 620ms var(--sc-ease-out);
}
.sc-ready .up-chapter [data-sc-in].sc-in,
.sc-ready .up-chapter [data-sc-stagger] > .sc-in { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
    .sc-ready .up-chapter [data-sc-in],
    .sc-ready .up-chapter [data-sc-stagger] > * { opacity: 1; transition: none; }
}

/* --- 6b. Acts, and what the engine promotes ------------------------------ */
/* AN ACT IS A TIGHT WRAPPER, NEVER A CHAPTER. The engine writes --sc-p to
   every act element on every scroll frame, and a custom-property write
   invalidates style for the element's whole subtree. With the act on the
   section, fifteen writes a frame invalidated all 3,500 nodes on the page;
   with it on a wrapper around only the thing the device drives, the same
   fifteen writes touch a few hundred. This is the single largest cost on the
   page and it is why the desktop renderer died. */
.up-act { display: block; }
.up-act--dial { display: block; }

/* The engine hints `will-change` on anything it animates, which is right for
   a page with a handful of small animated boxes and wrong here: `will-change`
   never expires, so a hint on a panel-sized box pins a compositor layer the
   size of that box for the life of the page. The engine writes the property
   inline every frame either way, so dropping the hint costs nothing but the
   layer. Never fix this by editing the engine. */
.up-chapter [data-sc-reveal],
.up-chapter [data-sc-parallax] { will-change: auto; }

/* The caption the parallax drives, so the device never touches a panel. */
.up-caption { max-width: 62ch; }

/* --- 7. The signature move: the as-of dial ------------------------------- */
/* Twenty years of obviousness law on one spine, and a marker the reader
   drives. Every card is positioned at its own effective date, taken from the
   hypergraph capture; crossing a card's date moves it out of "not yet in
   effect" and into force. The marker halts at 2010-10-13, which is the date
   the connector was actually asked about, and the three buckets read out
   beside it. Driven entirely from --sc-p; the engine is untouched. */
.up-dial {
    background: #fff;
    border: 1px solid var(--up-rule);
    padding: 16px 18px 18px;
    margin: 0 0 16px;
}
.up-dial__head {
    display: flex;
    align-items: baseline;
    gap: 12px;
    flex-wrap: wrap;
    margin-bottom: 14px;
}
.up-dial__label {
    font-family: var(--sc-font-mono);
    font-size: 10px;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: #9a9184;
}
.up-dial__date {
    font-family: var(--sc-font-mono);
    font-size: clamp(1.1rem, 2.6vw, 1.6rem);
    font-weight: 700;
    color: var(--sc-accent);
    font-variant-numeric: tabular-nums;
}
.up-dial__note { font-size: 0.88em; color: var(--sc-ink-soft); }

.up-spine {
    position: relative;
    height: 46px;
    border-bottom: 2px solid #cbc2b2;
    margin-bottom: 6px;
}
.up-spine__tick {
    position: absolute;
    bottom: 0;
    transform: translateX(-50%);
    font-family: var(--sc-font-mono);
    font-size: 10px;
    color: #9a9184;
    white-space: nowrap;
    padding-bottom: 3px;
    border-left: 1px solid #d9d1c2;
    padding-left: 4px;
}
.up-spine__tick--start { transform: none; border-left: 0; padding-left: 0; font-weight: 700; color: #6b6459; }
.up-spine__tick--end { transform: translateX(-100%); border-left: 0; border-right: 1px solid #d9d1c2; padding-left: 0; padding-right: 4px; font-weight: 700; color: #6b6459; }
/* The marker travels on `transform`, never on `left`. `left` is a layout
   property: moving it on every scroll frame dirties layout inside the spine,
   and the engine reads document.scrollHeight on the next frame, which turns
   that into a forced synchronous layout of a 43,000px document. `translateX`
   is composited and touches nothing. */
.up-spine__marker {
    position: absolute;
    top: 0;
    bottom: -6px;
    left: 0;
    width: 2px;
    background: var(--sc-accent);
    transform: translateX(calc(var(--up-dial-x, 100%) - 1px));
    will-change: auto;
}
.up-spine__marker::after {
    content: "";
    position: absolute;
    top: -5px;
    left: -4px;
    width: 10px;
    height: 10px;
    background: var(--sc-accent);
    border-radius: 50%;
}
/* The dial's own clock. --sc-p runs across the chapter's whole visible life,
   so it is geared up by 1.35: the marker reaches the office action's date
   around the point a reader is looking at the buckets, and finishes at the
   right-hand end of the spine while the chapter is still on screen.

   With no script --sc-p never arrives, the marker parks past the office action
   date, and every card is at full opacity carrying its own effective date. The
   section reads either way; scrolling is what makes it move. */
.up-dial { --up-dial-p: 1; --up-dial-x: 100cqw; --up-dial-step: 1; }
.up-spine { container-type: inline-size; }
.sc-ready .up-dial {
    /* min() and max() rather than clamp(): a clamp() nested inside another
       clamp() inside a calc(), recomputed on seventy-six elements every scroll
       frame, crashed the headless renderer outright. One level of nesting is
       cheap and does the same job, because --sc-p is never negative and
       opacity clamps its own upper bound. */
    --up-dial-p: min(1, calc(var(--sc-p, 1) * 1.35));
    --up-dial-x: calc(var(--up-dial-p) * 100cqw);
    /* The marker moves every frame; the CARDS must not. Thirty-two elements
       recomputing an opacity on every scroll frame is thirty-two composited
       layers created and thrown away sixty times a second, and it is the
       difference between a chapter that scrolls and one that crawls. The
       stepped value changes twenty-four times across the whole sweep instead,
       so the cards light in small groups, which is what the eye sees anyway. */
    --up-dial-step: calc(round(down, var(--up-dial-p) * 24, 1) / 24);
}

.up-buckets {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
    gap: 12px;
    margin-top: 16px;
    align-items: start;
}
.up-bucket {
    border: 1px solid var(--up-rule);
    background: #fdfcf9;
    padding: 12px 14px;
}
.up-bucket h4 {
    font-family: var(--sc-font-mono);
    font-size: 10px;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    margin: 0 0 4px;
}
.up-bucket--force { border-left: 4px solid #2e6b45; }
.up-bucket--force h4 { color: #2e6b45; }
.up-bucket--future { border-left: 4px solid #8a7433; }
.up-bucket--future h4 { color: #8a7433; }
.up-bucket--past { border-left: 4px solid #7c7268; }
.up-bucket--past h4 { color: #7c7268; }
.up-bucket__note { font-size: 0.85em; color: var(--sc-ink-soft); margin: 0 0 8px; }
/* A bucket is a list, not an essay: one dense line per rule, and the list
   scrolls inside its own column rather than growing the page. A thirty-two
   rule regime is the point of the act, so all of it is here; what is capped is
   the vertical space it is allowed to take. */
/* NOT a scroll container. See the scroll-container note below: nine capped
   bucket lists were nine composited scrolling layers, and on a page this long
   they were most of what killed the desktop renderer. The buckets are simply
   as tall as their contents now, which costs chapter VI about 1,500px and buys
   the peak more scroll room, which it wanted anyway. */
.up-rules {
    list-style: none;
    margin: 0;
    padding: 0;
}
.up-rule-card {
    border-top: 1px solid var(--up-rule-soft);
    padding: 5px 0;
    font-size: 0.84em;
    line-height: 1.4;
}
.up-rule-card:first-child { border-top: 0; padding-top: 0; }
.up-rule-card b { display: block; }
.up-rule-card .cite {
    display: block;
    color: var(--sc-ink-soft);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.up-rule-card .when {
    font-family: var(--sc-font-mono);
    font-size: 10px;
    color: #9a9184;
    font-variant-numeric: tabular-nums;
}
/* The state change the dial drives: a rule whose own date is ahead of the
   marker is dimmed and struck through the "in force" reading. Each card
   carries its own --up-at (its position on the spine, 0 to 1). */
/* No transition here on purpose. The value already changes continuously with
   scroll, so a transition adds nothing a reader can see and makes the
   compositor animate seventy-six elements on every frame, which is enough to
   crash a headless renderer outright. Found by the verification pass. */
@media (prefers-reduced-motion: no-preference) {
    .sc-ready .up-rule-card {
        /* The 0.03 lead is why the last card on the spine ever reaches full
           opacity: without it a rule sitting at at = 1 needs the marker to
           land exactly on 1 and never quite gets there. */
        opacity: max(0.34, calc((var(--up-dial-step, 1) - var(--up-at, 0) + 0.05) * 26 + 0.34));
    }
}
/* A rule that has since been displaced is struck on its own date line. */
.up-rule-card--superseded .when { text-decoration: line-through; }

/* --- 8. The colophon ----------------------------------------------------- */
.up-colophon {
    border-top: 3px double var(--up-rule);
    padding-top: 18px;
}
.up-composer {
    border: 1px solid var(--up-rule);
    background: #fff;
    padding: 14px 16px;
    max-width: 62ch;
}
.up-composer label {
    display: block;
    font-family: var(--sc-font-mono);
    font-size: 10px;
    letter-spacing: 0.07em;
    text-transform: uppercase;
    color: var(--sc-ink-soft);
    margin-bottom: 6px;
}
.up-composer input {
    width: 100%;
    font: inherit;
    font-size: 0.92em;
    padding: 9px 11px;
    border: 1px solid var(--up-rule);
    background: #fbfaf6;
    color: var(--sc-ink);
}
.up-composer p { margin: 8px 0 0; font-size: 0.9em; color: var(--sc-ink-soft); }
.up-composer button {
    font: inherit;
    font-size: 0.9em;
    background: none;
    border: 0;
    padding: 0;
    color: var(--sc-accent);
    text-decoration: underline;
    cursor: pointer;
}
.up-links { display: flex; flex-wrap: wrap; gap: 6px 20px; margin: 0; padding: 0; list-style: none; font-size: 0.94em; }

.up-footnote {
    font-size: 0.86em;
    color: var(--sc-ink-soft);
    line-height: 1.6;
    max-width: 74ch;
    border-top: 1px solid var(--up-rule);
    padding-top: 12px;
}

/* --- 9. Focus, motion, phones -------------------------------------------- */

/* The engine's stack rhythm is written for a six-act page with a lot of air.
   A twelve-prompt transcript needs the air back: this is the single largest
   lever on total page length, and length is the reader's patience. */
.up-chapter .sc-stack > * + * { margin-top: var(--sc-2); }
.up-chapter .sc-stack > :is(h1, h2, h3) { margin-top: var(--sc-4); }
/* The scrollbar-oscillation note, and the two rules it explains, moved with
   the panels to ../panels.css. */
.up-chapter .sc-stack > :is(h1, h2, h3):first-child { margin-top: 0; }

@media (max-width: 900px) {
    .up-spine__tick { font-size: 9px; }
    /* On a phone the spine is about 320px wide, so six dated ticks collide
       into each other. Keep the two ends and the one date the chapter is
       about. */
    .up-spine__tick.up-spine__tick--dense { display: none; }
    .up-spine__tick--key { font-weight: 700; color: var(--sc-accent); }
}
@media (prefers-reduced-motion: reduce) {
    .sc-ready .up-rule-card { opacity: 1; }
}

/* The dial's control. The move is scroll-driven, which is the point and also
   the problem: a reader who stops to read what just lit stops the marker too.
   So the value gets a handle. Set wide and quiet, under the spine, in the same
   monospace as the dates it moves between. */
.up-dial__control {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr) max-content;
    align-items: center;
    gap: 10px;
    margin: 10px 0 4px;
}
.up-dial__control-label {
    font-family: var(--sc-font-mono);
    font-size: 10px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--sc-ink-soft);
}
.up-dial__control input[type="range"] {
    width: 100%;
    accent-color: var(--sc-accent);
    margin: 0;
    cursor: pointer;
}
.up-dial__readout {
    font-family: var(--sc-font-mono);
    font-size: 13px;
    font-weight: 700;
    color: var(--sc-accent);
    font-variant-numeric: tabular-nums;
}
@media (max-width: 640px) {
    .up-dial__control { grid-template-columns: minmax(0, 1fr) max-content; }
    .up-dial__control-label { grid-column: 1 / -1; }
}
