/* ---------------------------------------------------------------------------
 * Spectrum OS 4.0 launch page.
 *
 * kf-about.css is the DEPENDENCY, not a nicety — .kf-container, .kf-band, .kf-eyebrow, .kf-h2,
 * .kf-lede, .kf-btn and every token live there. This file carries only what is specific to this
 * page: the five interactive modules. Nothing here restates the type scale, the button pill or
 * --kf-card-radius; where a component needs one it uses the token.
 * ------------------------------------------------------------------------- */

.kf-sp4, .kf-sp4-hero, .kf-sp4-sec, .kf-sp4-marquee, .kf-sp4-nav,
.kf-sp4-readout, .kf-sp4-verdict, .kf-sp4-curve {
    --sp4-panel: #171715; --sp4-card: #232320; --sp4-hair: #3a3a36;
}

/* --- grounds -------------------------------------------------------------- */
.kf-sp4-sec--dark  { background: #1C1C1A; }
.kf-sp4-sec--ink   { background: #000; }
.kf-sp4-sec--light { background: #F7F7F5; }
.kf-sp4-sec        { padding-block: 96px; }

/* --- hero ----------------------------------------------------------------- */
.kf-sp4-hero { position: relative; min-height: 640px; display: flex; align-items: center;
    background: #000; overflow: hidden; }
.kf-sp4-hero__media { position: absolute; inset: 0; background: #2a2a2a; }
.kf-sp4-hero__media::after { content: ''; position: absolute; inset: 0;
    background: linear-gradient(90deg, rgba(0,0,0,.86) 0%, rgba(0,0,0,.66) 45%, rgba(0,0,0,.42) 100%); }
.kf-sp4-hero__inner { position: relative; width: 100%; padding-block: 80px; }
.kf-sp4-rule { width: 64px; height: 3px; background: var(--kf-yellow); margin-bottom: 28px; }
.kf-sp4-logo { display: inline-flex; align-items: center; justify-content: center;
    width: 300px; height: 68px; background: var(--sp4-card); color: var(--kf-mute);
    font: 500 11px/14px var(--kf-body); letter-spacing: 1.6px; text-transform: uppercase;
    margin-bottom: 28px; border-radius: var(--kf-card-radius); }
.kf-sp4-hero__a { font-family: var(--kf-display); font-weight: 700; text-transform: uppercase;
    font-size: 56px; line-height: 60px; color: #fff; margin: 0; }
.kf-sp4-hero__b { font-family: var(--kf-display); font-weight: 700; text-transform: uppercase;
    font-size: 32px; line-height: 38px; color: var(--kf-yellow); margin: 10px 0 0; max-width: 18em; }
.kf-sp4-actions { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 34px; }

/* --- sticky scrollspy nav -------------------------------------------------- */
/* The builder wraps every element in section > container > grid > width div, and all four of those
   boxes are exactly as tall as the nav inside them. `position: sticky` can only travel inside its
   own containing block, so the nav had nowhere to go and simply scrolled away -- it sticks on the
   prototype only because there it is a direct child of a 6800px <body>. Collapse the wrappers for
   this one band so the containing block becomes <main>. The band holds nothing but the nav, so the
   wrappers carry no padding or background worth keeping, and :has() scopes it to that section
   alone. Browsers without :has() just get a nav that scrolls away, which is what happens today. */
.uk-section:has(.kf-sp4-nav),
.uk-section:has(.kf-sp4-nav) .uk-container,
.uk-section:has(.kf-sp4-nav) .uk-grid,
.uk-section:has(.kf-sp4-nav) [class*="uk-width"] { display: contents; }

.kf-sp4-nav { position: sticky; top: var(--sp4-chrome, 0px); z-index: 40; background: var(--kf-ink);
    border-bottom: 1px solid var(--sp4-hair); }
.kf-sp4-nav__inner { display: flex; align-items: center; gap: 30px; height: 64px; }
.kf-sp4-nav__link { position: relative; font: 700 12px/1 var(--kf-body); letter-spacing: 1.1px;
    text-transform: uppercase; color: var(--kf-on-dark); text-decoration: none; padding-block: 10px;
    white-space: nowrap; transition: color .15s ease-out; }
.kf-sp4-nav__link:hover { color: #fff; }
/* active is an UNDERLINE, not a yellow chip — the chip collided with the yellow CTA beside it */
.kf-sp4-nav__link::after { content: ''; position: absolute; left: 0; right: 0; bottom: 0;
    height: 3px; background: var(--kf-yellow); transform: scaleX(0); transform-origin: left;
    transition: transform .2s ease-out; }
.kf-sp4-nav__link.is-active { color: #fff; }
.kf-sp4-nav__link.is-active::after { transform: scaleX(1); }
.kf-sp4-nav__cta { margin-left: auto; min-height: 44px; }

/* --- marquee -------------------------------------------------------------- */
.kf-sp4-marquee { background: var(--kf-yellow); overflow: hidden; }
.kf-sp4-marquee__track { display: flex; width: max-content; animation: kf-sp4-run 28s linear infinite; }
.kf-sp4-marquee__run { display: flex; gap: 48px; padding: 22px 24px; margin: 0; list-style: none; }
.kf-sp4-marquee__el { font-family: var(--kf-display); font-weight: 700; text-transform: uppercase;
    font-size: 16px; line-height: 24px; color: var(--kf-ink); white-space: nowrap; }
@keyframes kf-sp4-run { to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) { .kf-sp4-marquee__track { animation: none; } }

/* --- shared section furniture --------------------------------------------- */
/* Centre the two columns against each other. The media side (the ballast, the port photo, the
   picker) is always much taller than the copy, and top-aligning left the heading and lede stranded
   against the top edge with a few hundred px of empty column under them. */
.kf-sp4-split { display: grid; grid-template-columns: minmax(0,1fr) 580px; gap: 60px; align-items: center; }
.kf-sp4-split--flip { grid-template-columns: 560px minmax(0,1fr); }
.kf-sp4-bullets { margin: 40px 0 0; padding: 0; list-style: none; }
.kf-sp4-bullets li { border-left: 3px solid var(--kf-yellow); padding: 0 0 0 18px; margin-bottom: 24px; }
.kf-sp4-bullets b { display: block; font-family: var(--kf-display); font-weight: 700;
    text-transform: uppercase; font-size: 18px; line-height: 22px; color: #fff; }
.kf-sp4-bullets span { display: block; font: 400 13px/20px var(--kf-body); color: var(--kf-on-dark); margin-top: 4px; }
.kf-sp4-sec--light .kf-sp4-bullets b { color: var(--kf-ink); }
.kf-sp4-sec--light .kf-sp4-bullets span { color: var(--kf-ink-3); }
.kf-sp4-anno { font: 500 11px/16px var(--kf-body); color: var(--kf-mute); margin-top: 16px; }
/* Block margins below were never declared, so they came from the browser: 1em on the <p>s that sit
   outside a .kf-band (kf-about zeroes the ones inside it) and 1em on this <dl>. A theme that styles
   `p` and `dl` supplies 20px instead and every panel ending in one grew. Say the 1em out loud so the
   spacing is the design's, not the host page's -- and restate the .kf-band case, which is zero. */
p.kf-sp4-anno { margin-bottom: 1em; }
.kf-band p.kf-sp4-anno { margin-bottom: 0; }

/* --- interactive panel (shared shell) -------------------------------------- */
.kf-sp4-panel { background: var(--sp4-panel); border: 1px solid var(--sp4-hair);
    border-radius: var(--kf-card-radius); padding: 26px 28px 28px; }
.kf-sp4-panel__title { font: 500 11px/14px var(--kf-body); letter-spacing: 1.6px;
    text-transform: uppercase; color: var(--kf-yellow); margin: 0; }
.kf-sp4-panel__hint { font: 400 12px/18px var(--kf-body); color: var(--kf-mute); margin: 6px 0 0; }

/* --- CIE picker ------------------------------------------------------------ */
.kf-sp4-cie { display: grid; grid-template-columns: 330px minmax(0,1fr); gap: 26px; margin-top: 20px; }
.kf-sp4-cie__stage { position: relative; width: 330px; height: 330px; touch-action: none; cursor: crosshair; }
.kf-sp4-cie__canvas { width: 330px; height: 330px; display: block; }
.kf-sp4-cie__target { position: absolute; width: 22px; height: 22px; margin: -11px 0 0 -11px;
    border: 2px solid #fff; border-radius: 50%; pointer-events: none;
    box-shadow: 0 0 0 1px rgba(0,0,0,.55); }
.kf-sp4-cie__target::after { content: ''; position: absolute; inset: 6px; border-radius: 50%;
    background: var(--kf-yellow); }
.kf-sp4-cie__stage:focus-visible { outline: 2px solid var(--kf-yellow); outline-offset: 3px; }
.kf-sp4-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.kf-sp4-chip { font: 700 10px/1 var(--kf-body); letter-spacing: .8px; text-transform: uppercase;
    color: var(--kf-on-dark); background: none; border: 1px solid var(--sp4-hair);
    border-radius: 13px; padding: 8px 14px; cursor: pointer; transition: all .15s ease-out; }
.kf-sp4-chip:hover { border-color: var(--kf-yellow); color: #fff; }
.kf-sp4-chip.is-active { background: var(--kf-yellow); border-color: var(--kf-yellow); color: var(--kf-ink); }
.kf-sp4-read { display: grid; grid-template-columns: repeat(4,1fr); gap: 10px;
    background: var(--sp4-card); border-radius: var(--kf-card-radius); padding: 16px;
    margin-top: 16px; margin-bottom: 1em; }   /* 1em: see the note on p.kf-sp4-anno above */
.kf-sp4-read dt { font: 500 10px/14px var(--kf-body); letter-spacing: 1.2px;
    text-transform: uppercase; color: var(--kf-mute); }
.kf-sp4-read dd { font-family: var(--kf-display); font-weight: 700; font-size: 20px; line-height: 24px;
    color: #fff; margin: 2px 0 0; font-variant-numeric: tabular-nums; }
.kf-sp4-preview { margin-top: 16px; }
.kf-sp4-preview__label { font: 500 10px/14px var(--kf-body); letter-spacing: 1.2px;
    text-transform: uppercase; color: var(--kf-mute); }
.kf-sp4-preview__swatch { height: 94px; margin-top: 8px; border-radius: var(--kf-card-radius);
    background: #fff; transition: background-color .12s linear; }

/* --- dimming curve switcher ------------------------------------------------ */
/* A full-width strip rather than a card: the background reaches both page edges while the three
   columns stay on the same 1200px grid as every other band, so it reads as its own section instead
   of a panel floating inside one. The columns are centred to each other -- the plot and the ramp are
   taller than the copy, and top-aligning left the title stranded at the top of a 200px row. */
.kf-sp4-curve { background: var(--sp4-card); padding-block: 72px; }
/* Review asked for this section to be bigger (Alan, Oct 10). The plot carries the whole point of
   the band -- four curve shapes you can tell apart -- and at 240x112 it was the smallest thing on
   a full-width strip. */
.kf-sp4-curve__grid {
    display: grid; grid-template-columns: minmax(0,1fr) 420px 300px; gap: 44px; align-items: center; }
.kf-sp4-curve__tabs { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; }
/* content-box on purpose: the theme sets a global border-box, which made the declared size
   include the hairline and shrank the drawing area to 238x110. The numbers here are the PLOT, so
   the rule has to say which box they describe rather than inherit that answer from the host page. */
.kf-sp4-curve__plot { box-sizing: content-box; border: 1px solid var(--sp4-hair); display: block;
    width: 420px; height: 210px; }
.kf-sp4-ramp { display: flex; gap: 4px; height: 110px; }
.kf-sp4-ramp i { flex: 1; background: #fff; display: block; transition: opacity .12s linear; }
/* margin-bottom was never declared, so the design took the browser's default 1em on a <p> -- 9px
   at this font size. A theme that styles `p` instead supplied 20px and stretched every panel that
   ends in an axis caption. State the 1em explicitly: same result, and it no longer depends on who
   styles paragraphs on the host page. */
.kf-sp4-axis { font: 500 9px/12px var(--kf-body); letter-spacing: 1px; color: var(--kf-mute);
    margin-top: 8px; margin-bottom: 1em; }

/* --- hardware toggle ------------------------------------------------------- */
.kf-sp4-toggle { display: inline-flex; background: #fff; border: 1px solid #DCDCD6;
    border-radius: 18px; padding: 3px; position: relative; }
.kf-sp4-toggle__btn { position: relative; z-index: 1; border: 0; background: none; cursor: pointer;
    font: 700 11px/1 var(--kf-body); letter-spacing: 1px; text-transform: uppercase;
    color: var(--kf-mute); padding: 9px 22px; border-radius: 15px; transition: color .18s ease-out; }
.kf-sp4-toggle__btn.is-active { color: #fff; }
.kf-sp4-toggle__thumb { position: absolute; top: 3px; bottom: 3px; background: var(--kf-ink);
    border-radius: 15px; transition: transform .24s cubic-bezier(.4,0,.2,1), width .24s cubic-bezier(.4,0,.2,1); }
.kf-sp4-lineup { display: grid; grid-template-columns: repeat(3,1fr); gap: 21px; margin-top: 34px; }
.kf-sp4-prod { background: #fff; border-radius: var(--kf-card-radius); overflow: hidden;
    opacity: 1; transform: none; transition: opacity .22s ease-out, transform .22s ease-out; }
.kf-sp4-prod[hidden] { display: none; }
.kf-sp4-prod.is-entering { opacity: 0; transform: translateY(8px); }
.kf-sp4-prod__img { height: 108px; background: #E4E4E0; display: flex; align-items: center;
    justify-content: center; font: 500 10px/14px var(--kf-body); letter-spacing: 1.2px;
    text-transform: uppercase; color: var(--kf-mute); text-align: center; padding: 0 16px; }
.kf-sp4-prod__body { padding: 18px 20px 22px; }
.kf-sp4-prod__name { font-family: var(--kf-display); font-weight: 700; text-transform: uppercase;
    font-size: 21px; line-height: 25px; color: var(--kf-ink); margin: 0; }
.kf-sp4-prod__kick { font: 400 12px/18px var(--kf-body); color: var(--kf-mute); margin: 4px 0 0; }
.kf-sp4-prod__desc { font: 400 13px/20px var(--kf-body); color: var(--kf-ink-3); margin: 8px 0 0; }

/* --- power scrubber -------------------------------------------------------- */
.kf-sp4-scrub { display: grid; grid-template-columns: 232px minmax(0,1fr); gap: 28px; margin-top: 22px; }
.kf-sp4-scrub__swatch { height: 150px; border-radius: var(--kf-card-radius); background: #F0A24A; }
.kf-sp4-scrub__big { font-family: var(--kf-display); font-weight: 700; font-size: 64px; line-height: 68px;
    color: #fff; margin: 0; font-variant-numeric: tabular-nums; }
.kf-sp4-scrub__duv { font-family: var(--kf-display); font-weight: 700; font-size: 24px; line-height: 28px;
    color: var(--kf-yellow); margin: 4px 0 0; }
.kf-sp4-range { -webkit-appearance: none; appearance: none; width: 100%; height: 6px;
    border-radius: 3px; margin-top: 26px; cursor: pointer;
    background: linear-gradient(90deg, var(--kf-yellow) 0 var(--kf-fill,100%), var(--sp4-hair) var(--kf-fill,100%) 100%); }
.kf-sp4-range::-webkit-slider-thumb { -webkit-appearance: none; width: 22px; height: 22px;
    border-radius: 50%; background: #fff; border: 0; cursor: grab; }
.kf-sp4-range::-moz-range-thumb { width: 22px; height: 22px; border-radius: 50%; background: #fff; border: 0; }
.kf-sp4-ticks { display: flex; justify-content: space-between; font: 500 10px/14px var(--kf-body);
    color: var(--kf-mute); margin-top: 10px; }

/* --- network signal flow --------------------------------------------------- */
.kf-sp4-flow { display: grid; grid-template-columns: repeat(4,1fr); gap: 80px; margin-top: 40px;
    position: relative; }
.kf-sp4-node { background: #fff; border: 1px solid #DCDCD6; border-top: 3px solid var(--kf-yellow);
    border-radius: var(--kf-card-radius); padding: 24px 18px; cursor: pointer; position: relative;
    text-align: left; font: inherit; transition: background-color .18s ease-out, color .18s ease-out; }
.kf-sp4-node__name { font-family: var(--kf-display); font-weight: 700; text-transform: uppercase;
    font-size: 19px; line-height: 23px; color: var(--kf-ink); }
.kf-sp4-node__sub { font: 400 12px/18px var(--kf-body); color: var(--kf-mute); margin-top: 4px; }
.kf-sp4-node.is-active, .kf-sp4-node:hover { background: var(--kf-ink); border-color: var(--kf-ink); }
.kf-sp4-node.is-active .kf-sp4-node__name, .kf-sp4-node:hover .kf-sp4-node__name { color: #fff; }
.kf-sp4-node.is-active .kf-sp4-node__sub,  .kf-sp4-node:hover .kf-sp4-node__sub  { color: var(--kf-on-dark); }
.kf-sp4-node:not(:last-child)::after { content: ''; position: absolute; top: 50%; left: 100%;
    width: 80px; height: 2px; background: var(--sp4-hair); }
.kf-sp4-node:not(:last-child)::before { content: ''; position: absolute; top: calc(50% - 1px); left: 100%;
    width: 0; height: 4px; background: var(--kf-yellow); z-index: 1;
    transition: width .5s cubic-bezier(.4,0,.2,1); }
.kf-sp4-flow.is-live .kf-sp4-node:not(:last-child)::before { width: 80px; }
.kf-sp4-flow.is-live .kf-sp4-node:nth-child(2)::before { transition-delay: .35s; }
.kf-sp4-flow.is-live .kf-sp4-node:nth-child(3)::before { transition-delay: .7s; }
.kf-sp4-netcards { display: grid; grid-template-columns: repeat(3,1fr); gap: 21px; margin-top: 44px; }
.kf-sp4-netcard { background: #fff; border-top: 3px solid #DCDCD6; border-radius: var(--kf-card-radius);
    padding: 24px 20px 26px; transition: border-color .18s ease-out, box-shadow .18s ease-out; }
.kf-sp4-netcard.is-active { border-top-color: var(--kf-yellow); box-shadow: 0 2px 0 rgba(0,0,0,.06); }

/* --- LiteDimmer hotspots --------------------------------------------------- */
.kf-sp4-ports { position: relative; min-height: 500px; background: #2a2a2a; }
.kf-sp4-hot { position: absolute; width: 22px; height: 22px; margin: -11px 0 0 -11px;
    border: 2px solid var(--kf-yellow); border-radius: 50%; background: none; cursor: pointer; padding: 0; }
.kf-sp4-hot::after { content: ''; position: absolute; inset: 5px; border-radius: 50%; background: var(--kf-yellow); }
.kf-sp4-hot::before { content: ''; position: absolute; inset: -6px; border-radius: 50%;
    border: 2px solid var(--kf-yellow); opacity: 0; }
.kf-sp4-hot.is-pulsing::before { animation: kf-sp4-pulse 1.4s ease-out; }
@keyframes kf-sp4-pulse { from { opacity: .9; transform: scale(.7); } to { opacity: 0; transform: scale(1.7); } }
.kf-sp4-hot__label { position: absolute; left: 30px; top: 50%; transform: translateY(-50%);
    white-space: nowrap; font: 700 11px/1 var(--kf-body); letter-spacing: .9px; text-transform: uppercase;
    color: #fff; background: rgba(0,0,0,.72); padding: 6px 10px; border-radius: 3px;
    opacity: 0; transition: opacity .15s ease-out; pointer-events: none; }
.kf-sp4-hot:hover .kf-sp4-hot__label, .kf-sp4-hot:focus-visible .kf-sp4-hot__label { opacity: 1; }

/* --- OS 3 ballast simulator ------------------------------------------------ */
.kf-sp4-ballast { display: grid; grid-template-columns: 340px 150px; gap: 28px; }
.kf-sp4-screen { background: #000; border: 1px solid var(--sp4-hair); border-radius: var(--kf-card-radius);
    padding: 18px 20px; min-height: 160px; }
.kf-sp4-screen__cue { font: 500 11px/14px var(--kf-body); letter-spacing: 1.4px;
    text-transform: uppercase; color: var(--kf-yellow); }
.kf-sp4-screen__big { font-family: var(--kf-display); font-weight: 700; font-size: 44px; line-height: 48px;
    color: #fff; margin: 6px 0 10px; font-variant-numeric: tabular-nums; }
.kf-sp4-screen__row { font: 500 13px/20px var(--kf-body); color: var(--kf-on-dark);
    font-variant-numeric: tabular-nums; }
.kf-sp4-cues { display: flex; gap: 10px; margin-top: 22px; }
.kf-sp4-cue { flex: 1; border: 1px solid var(--sp4-hair); background: none; border-radius: 17px;
    padding: 10px 0; cursor: pointer; font: 700 11px/1 var(--kf-body); letter-spacing: .9px;
    text-transform: uppercase; color: var(--kf-on-dark); transition: all .15s ease-out; }
.kf-sp4-cue:hover { border-color: var(--kf-yellow); color: #fff; }
.kf-sp4-cue.is-active { background: var(--kf-yellow); border-color: var(--kf-yellow); color: var(--kf-ink); }
.kf-sp4-side { display: flex; flex-direction: column; gap: 8px; }
.kf-sp4-side__btn { border: 1px solid var(--sp4-hair); background: none; border-radius: 13px;
    padding: 7px 0; cursor: pointer; font: 700 10px/1 var(--kf-body); letter-spacing: .8px;
    text-transform: uppercase; color: var(--kf-on-dark); transition: all .15s ease-out; }
.kf-sp4-side__btn:hover { border-color: var(--kf-yellow); color: #fff; }
.kf-sp4-side__btn.is-on { background: var(--kf-yellow); border-color: var(--kf-yellow); color: var(--kf-ink); }
.kf-sp4-oslist { margin: 0; padding: 0; list-style: none; display: grid; gap: 10px; }
.kf-sp4-oslist li { background: var(--sp4-card); border-radius: var(--kf-card-radius);
    padding: 14px 18px; display: grid; grid-template-columns: 32px minmax(0,1fr); gap: 16px; align-items: start; }
.kf-sp4-oslist i { width: 32px; height: 32px; border: 2px solid var(--kf-yellow); display: block; }
.kf-sp4-oslist b { font-family: var(--kf-display); font-weight: 700; text-transform: uppercase;
    font-size: 17px; line-height: 21px; color: #fff; }
.kf-sp4-oslist span { display: block; font: 400 12px/18px var(--kf-body); color: var(--kf-on-dark); margin-top: 2px; }

/* --- responsive ------------------------------------------------------------ */
@media (max-width: 1200px) {
    .kf-sp4-split, .kf-sp4-split--flip { grid-template-columns: minmax(0,1fr); gap: 44px; }
    .kf-sp4-curve__grid { grid-template-columns: minmax(0,1fr); }
    .kf-sp4-flow { grid-template-columns: repeat(2,1fr); gap: 20px; }
    .kf-sp4-node:not(:last-child)::after, .kf-sp4-node:not(:last-child)::before { display: none; }
}
@media (max-width: 960px) {
    .kf-sp4-lineup, .kf-sp4-netcards { grid-template-columns: 1fr; }
    .kf-sp4-cie { grid-template-columns: minmax(0,1fr); }
    .kf-sp4-ballast { grid-template-columns: minmax(0,1fr); }
    .kf-sp4-hero__a { font-size: 40px; line-height: 44px; }
    .kf-sp4-hero__b { font-size: 24px; line-height: 30px; }
    .kf-sp4-nav__inner { gap: 18px; overflow-x: auto; }
}
@media (max-width: 640px) {
    .kf-sp4-scrub { grid-template-columns: minmax(0,1fr); }
    .kf-sp4-read { grid-template-columns: repeat(2,1fr); }
    .kf-sp4-flow { grid-template-columns: 1fr; }
}

/* --- reused OS 3 assets ----------------------------------------------------- */
/* The rear-panel PNG is transparent and 723×622; the hotspots are positioned as a percentage of
   the FIGURE, so the figure has to be the image's own box and nothing wider. */
.kf-sp4-ports__fig { position: relative; display: block; margin: 0; max-width: 723px; }
.kf-sp4-ports__fig img { display: block; width: 100%; height: auto; }
/* Video mode is full bleed at the clip's own 16:9: no box, no letterbox, and no object-fit crop
   between a hotspot percentage and the pixel it is pointing at. The band supplies its own padding
   normally, so that is removed here and the copy is laid over the empty left of frame instead. */
.kf-sp4-sec--ink:has(.kf-sp4-ports__stage) { padding-block: 0; }
.kf-sp4-ports__stage { position: relative; width: 100%; aspect-ratio: 16 / 9; overflow: hidden; }
.kf-sp4-ports__video { position: absolute; inset: 0; width: 100%; height: 100%;
    object-fit: cover; display: block; }
/* The stick travels left to right across the frame and passes straight under the copy, which the
   still never did. Darken the left so the heading holds whatever is behind it; it clears before the
   hardware so nothing is dimmed that the callouts point at. */
.kf-sp4-ports__stage::before { content: ''; position: absolute; inset: 0; z-index: 2;
    pointer-events: none;
    background: linear-gradient(90deg, #000 0%, rgba(0,0,0,.88) 30%, rgba(0,0,0,.45) 48%, transparent 62%); }
.kf-sp4-ports__copy { position: absolute; inset: 0; display: flex; align-items: center;
    pointer-events: none; z-index: 3; }
/* the copy is over black in the clip, but keep it readable if the framing ever shifts */
.kf-sp4-ports__copy > .kf-container { width: 100%; }
.kf-sp4-ports__copy .kf-lede, .kf-sp4-ports__copy .kf-sp4-anno { max-width: 34em; }
.kf-sp4-ports__stage .kf-sp4-hot { z-index: 4; }
@media (max-width: 860px) {
    /* 16:9 is too short to hold the copy over the clip, so the stage stacks. The dots STAY on the
       ports -- they are what makes the image legible as a diagram -- but at this size they are far
       too tight to tap (25px apart at 390px wide), so the list below becomes the control and each
       row flashes its own dot. */
    .kf-sp4-sec--ink:has(.kf-sp4-ports__stage) { padding-block: 64px 48px; }
    .kf-sp4-ports__stage { display: flex; flex-wrap: wrap; aspect-ratio: auto; overflow: visible; }
    .kf-sp4-ports__stage::before { display: none; }
    .kf-sp4-ports__copy { order: 1; flex: 0 0 100%; position: static; display: block; margin-bottom: 20px; }
    .kf-sp4-ports__media { order: 2; flex: 0 0 100%; position: relative; }
    .kf-sp4-ports__video { position: relative; aspect-ratio: 16 / 9; border-radius: 4px; }
    /* smaller dots, since they are now indicators rather than hit targets */
    .kf-sp4-ports__media .kf-sp4-hot { width: 15px; height: 15px; margin: -7.5px 0 0 -7.5px; border-width: 2px; }
    .kf-sp4-ports__media .kf-sp4-hot::after { inset: 3px; }
    .kf-sp4-ports__media .kf-sp4-hot__label { display: none; }
    /* the hover hint is not true here, and the list needs no instruction */
    .kf-sp4-ports__copy .kf-sp4-anno { display: none; }
}

/* The mobile port list: built from the hotspots themselves, so there is one source of truth for
   the names and the positions. Tapping a row flashes the matching dot on the clip. Above the
   stacking breakpoint the callouts work on their own, so the list is not rendered. */
.kf-sp4-ports__list { display: none; }
@media (max-width: 860px) {
.kf-sp4-ports__list { display: block; order: 3; flex: 0 0 100%; list-style: none; margin: 18px 0 0;
    padding: 0; border-top: 1px solid rgba(255,255,255,.08); }
.kf-sp4-ports__list li { border-bottom: 1px solid rgba(255,255,255,.08); }
.kf-sp4-ports__list button { width: 100%; display: flex; align-items: center; gap: 12px;
    min-height: 44px; padding: 4px 20px; background: none; border: 0; cursor: pointer;
    text-align: left; font: 500 12px/17px var(--kf-body); letter-spacing: .4px;
    color: var(--kf-on-dark, rgba(255,255,255,.82)); }
.kf-sp4-ports__list button::before { content: ''; width: 9px; height: 9px; flex: none;
    border-radius: 50%; background: var(--kf-yellow); }
.kf-sp4-ports__list button:hover, .kf-sp4-ports__list button.is-active { color: #fff; }
/* the dot being pointed at lifts above its neighbours and stays lit while the row is held */
}
.kf-sp4-hot.is-called { z-index: 6; }
.kf-sp4-hot.is-called::after { box-shadow: 0 0 0 3px rgba(254,210,8,.35), 0 0 16px rgba(254,210,8,.9); }

/* Chips are the page's smallest controls and 28px is under any touch guideline. */
@media (max-width: 760px) {
    .kf-sp4-chip { min-height: 40px; display: inline-flex; align-items: center; }
}
/* OS 3 icons are yellow line-art on transparent — they drop straight onto the dark grounds. */
.kf-sp4-osicon { display: block; width: 32px; height: 32px; object-fit: contain; }
.kf-sp4-neticon { display: block; width: 40px; height: 40px; object-fit: contain; margin-bottom: 14px; }

/* --- corrections after first render ---------------------------------------- */
/* The flow node's name and sub are both spans; inline they ran together as "CONSOLEsACN". */
.kf-sp4-node__name, .kf-sp4-node__sub { display: block; }
/* The picker's controls were sharing a 224px column with a 330px canvas, which squeezed the
   readout until "6508 K" wrapped. Stack instead: the diagram gets its full width and the
   readout gets four honest columns. */
.kf-sp4-cie { grid-template-columns: minmax(0, 1fr); justify-items: stretch; }
.kf-sp4-cie__stage { margin-inline: auto; }
/* The diagram is a fixed 330px square, which overflowed its column on a phone and had its right
   edge cut off. Let it shrink to the space available and stay square; the picker maps pointer
   position proportionally and the marker is placed in percent, so it tracks at any rendered size. */
@media (max-width: 420px) {
    .kf-sp4-cie__stage { width: 100%; max-width: 330px; height: auto; aspect-ratio: 1; }
    .kf-sp4-cie__canvas { width: 100%; height: 100%; }
}
/* OS 3's icons are not square (the RDM mark is 300×145). Give the network cards a fixed icon
   BOX and let each mark sit inside it at its own aspect, left-aligned with the text. */
.kf-sp4-neticon { width: auto; max-width: 56px; height: 34px; object-fit: contain; object-position: left center; }

/* kf-about.css puts the body font on `.kf-band p`, not on `.kf-lede` / `.kf-sub` themselves, so
   those classes inherit — and outside a `.kf-band` wrapper they inherited the browser's serif.
   Set it once at the page root rather than wrapping every section in a class it does not need. */
.kf-sp4, .kf-sp4-hero, .kf-sp4-sec, .kf-sp4-marquee, .kf-sp4-nav,
.kf-sp4-readout, .kf-sp4-verdict, .kf-sp4-curve {
    font-family: var(--kf-body);
    /* The prototype sat on a bare <body>; a theme page brings its own line-height (28px), which
       every container here inherits and hands to any child that does not set one. That changed the
       row height of .kf-sp4-pers and the LCD. Reset it at the band boundary so the build matches
       the design regardless of the host page's typography. */
    line-height: normal;
}

/* Inherited colour is the same leak (the theme body is #58595B). Nothing in the design relies on
   inheriting it, so this is a guard rather than a visible fix: give each ground a legible default
   so text added later is readable instead of near-black on black, which is what the prototype's
   bare body would have produced. */
.kf-sp4-sec--light, .kf-sp4-marquee { color: var(--kf-ink); }
.kf-sp4-sec--dark, .kf-sp4-sec--ink, .kf-sp4-hero, .kf-sp4-nav,
.kf-sp4-readout, .kf-sp4-verdict, .kf-sp4-curve { color: #fff; }

/* The hotspots are siblings, so a label was painted UNDER every dot later in DOM order — the
   LAN 1 tooltip had DMX IN and DMX THROUGH sitting on top of it. Raise the whole hotspot (the
   label is its child) above its siblings while it is the active one. z-index needs a positioned
   element, which these already are. */
.kf-sp4-hot { z-index: 1; }
.kf-sp4-hot:hover, .kf-sp4-hot:focus-visible { z-index: 5; }
/* Hotspots on the right-hand half would push their label off the image; flip those to the left so
   the tooltip always resolves inside the figure. */
.kf-sp4-hot[data-flip] .kf-sp4-hot__label { left: auto; right: 30px; }
/* The labels are nowrap, so the longest one (DMX THROUGH · GATEWAY OUTPUT, 228px) could not fit
   beside its dot once the photo shrank to phone width -- it ran off the figure and off the screen.
   Let them wrap and cap the width on narrow screens; every hotspot then resolves inside the photo. */
@media (max-width: 760px) {
    .kf-sp4-hot__label { white-space: normal; max-width: 140px; }
}

/* --- rig comparison (replaces the old flow chart) ---------------------------- */
.kf-sp4-rigswitch { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-top: 34px; }
.kf-sp4-sec--light .kf-sp4-chip { color: var(--kf-ink-3); border-color: #DCDCD6; }
.kf-sp4-sec--light .kf-sp4-chip:hover { border-color: var(--kf-ink); color: var(--kf-ink); }
.kf-sp4-sec--light .kf-sp4-chip.is-active { background: var(--kf-ink); border-color: var(--kf-ink); color: #fff; }
.kf-sp4-rig { display: flex; align-items: stretch; gap: 0; margin-top: 30px; flex-wrap: wrap; }
.kf-sp4-rignode { flex: 1 1 0; min-width: 150px; background: #fff; border: 1px solid #DCDCD6;
    border-top: 3px solid var(--kf-yellow); border-radius: var(--kf-card-radius);
    padding: 20px 18px; display: flex; flex-direction: column; gap: 4px; position: relative; }
.kf-sp4-rignode.is-in { animation: kf-sp4-pop .34s cubic-bezier(.4,0,.2,1) both; }
@keyframes kf-sp4-pop { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.kf-sp4-rignode__name { font-family: var(--kf-display); font-weight: 700; text-transform: uppercase;
    font-size: 18px; line-height: 22px; color: var(--kf-ink); }
.kf-sp4-rignode__sub { font: 400 12px/17px var(--kf-body); color: var(--kf-mute); }
/* the box OS 4.0 removes — called out as the thing you stop rigging */
.kf-sp4-rignode--extra { border-color: #C9452F; border-top-color: #C9452F; background: #FFF4F2; }
.kf-sp4-rignode--extra .kf-sp4-rignode__sub { color: #9A3526; }
.kf-sp4-rignode__tag { position: absolute; top: -11px; right: 12px; background: #C9452F; color: #fff;
    font: 700 9px/1 var(--kf-body); letter-spacing: .9px; text-transform: uppercase;
    padding: 5px 8px; border-radius: 10px; }
.kf-sp4-rignode--dimmer { background: var(--kf-ink); }
.kf-sp4-rignode--dimmer .kf-sp4-rignode__name { color: #fff; }
.kf-sp4-rignode--dimmer .kf-sp4-rignode__sub { color: var(--kf-on-dark); }
.kf-sp4-riglink { flex: 0 0 34px; align-self: center; height: 3px; background: var(--kf-yellow); }
.kf-sp4-rigstats { display: grid; grid-template-columns: repeat(4,1fr); gap: 21px; margin: 30px 0 0; }
.kf-sp4-rigstat dt { font: 500 10px/14px var(--kf-body); letter-spacing: 1.2px;
    text-transform: uppercase; color: var(--kf-mute); }
.kf-sp4-rigstat dd { margin: 4px 0 0; font-family: var(--kf-display); font-weight: 700;
    font-size: 34px; line-height: 38px; color: var(--kf-ink); }
.kf-sp4-rigstat dd i { display: block; font: 700 11px/16px var(--kf-body); font-style: normal;
    letter-spacing: .4px; color: #1C7A3E; text-transform: none; }

/* --- ballast: the fixture preview is the point ------------------------------- */
.kf-sp4-fixture { height: 190px; border-radius: var(--kf-card-radius); background: #FFF3DC;
    transition: background-color .35s ease-out, box-shadow .35s ease-out; }
.kf-sp4-fixnote { font: 500 12px/18px var(--kf-body); color: var(--kf-mute); margin: 10px 0 0; }
.kf-sp4-tray { margin-top: 18px; border-top: 1px solid var(--sp4-hair); padding-top: 16px; }
.kf-sp4-tray__label { font: 500 10px/14px var(--kf-body); letter-spacing: 1.2px;
    text-transform: uppercase; color: var(--kf-mute); margin: 0 0 10px; }
.kf-sp4-tray__chips { display: flex; flex-wrap: wrap; gap: 8px; }
.kf-sp4-monitor { font: 400 11px/18px ui-monospace, SFMono-Regular, Menlo, monospace;
    color: var(--kf-yellow); background: #000; border-radius: var(--kf-card-radius);
    padding: 12px 14px; margin: 0; white-space: pre; overflow-x: auto; }
@media (max-width: 1200px) { .kf-sp4-rigstats { grid-template-columns: repeat(2,1fr); }
    .kf-sp4-riglink { display: none; } .kf-sp4-rig { gap: 12px; } }

/* --- RDM discovery (replaces the three restating cards) ---------------------- */
/* Dark, like every other panel on this page that demonstrates functionality. A white card in a
   light band read as a document; the instrument panels (picker, scrub demo, ballast) are all dark,
   and the lamp colours and IDENTIFY flash only read properly against a dark ground anyway. */
.kf-sp4-rdm { background: var(--sp4-panel); border: 1px solid var(--sp4-hair); border-top: 3px solid var(--kf-yellow);
    border-radius: var(--kf-card-radius); padding: 26px 28px 28px; margin-top: 44px; }
.kf-sp4-rdm__head { display: flex; align-items: center; justify-content: space-between;
    gap: 24px; flex-wrap: wrap; }
/* The sweep: a line crossing the card while the universes are polled. It is the only thing on
   screen between pressing (or arriving) and the first fixture answering, so it carries the sense
   that something is being probed rather than loaded. */
.kf-sp4-rdmscan { position: relative; height: 2px; margin: 22px 0 0; overflow: hidden;
    background: rgba(255,255,255,.08); opacity: 0; transition: opacity .2s ease-out; }
.kf-sp4-rdm.is-scanning .kf-sp4-rdmscan { opacity: 1; }
.kf-sp4-rdmscan span { position: absolute; inset: 0 auto 0 0; width: 34%;
    background: linear-gradient(90deg, transparent, var(--kf-yellow), transparent); }
.kf-sp4-rdm.is-scanning .kf-sp4-rdmscan span { animation: kf-sp4-sweep 1.7s ease-in-out infinite; }
@keyframes kf-sp4-sweep { from { transform: translateX(-100%); } to { transform: translateX(394%); } }
/* Hold the list's final height from the start so arriving rows do not shunt the page. Sized for
   the three devices in RDM_DEVICES at ~51px each; on a phone the rows wrap to two lines (~95px)
   once the uid and personality columns drop out. Both numbers follow the device count -- if that
   list changes again, re-measure rather than leaving the old reservation to open a gap. */
.kf-sp4-rdmlist { min-height: 156px; }
@media (max-width: 860px) {
    .kf-sp4-rdmlist { min-height: 290px; }
}
.kf-sp4-rdm .kf-sp4-panel__hint { max-width: 52ch; }
.kf-sp4-rdmlist { list-style: none; margin: 24px 0 0; padding: 0; }
.kf-sp4-rdmrow { display: grid; align-items: center; gap: 16px;
    grid-template-columns: 14px minmax(0,1.5fr) minmax(0,1.2fr) 86px minmax(0,1fr) 104px;
    padding: 12px 0; border-top: 1px solid rgba(255,255,255,.09);
    animation: kf-sp4-rowin .3s ease-out both; }
@keyframes kf-sp4-rowin { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.kf-sp4-rdmrow__lamp { width: 14px; height: 14px; border-radius: 50%; display: block;
    box-shadow: 0 0 0 1px rgba(0,0,0,.12) inset; }
.kf-sp4-rdmrow__name { font-family: var(--kf-display); font-weight: 700; text-transform: uppercase;
    font-size: 16px; line-height: 20px; color: #fff; }
.kf-sp4-rdmrow__uid, .kf-sp4-rdmrow__patch, .kf-sp4-rdmrow__pers {
    font: 400 12px/18px ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--kf-mute); }
.kf-sp4-rdmrow__id { justify-self: end; border: 1px solid rgba(255,255,255,.4); background: none;
    border-radius: 15px; padding: 7px 16px; cursor: pointer;
    font: 700 10px/1 var(--kf-body); letter-spacing: .9px; text-transform: uppercase;
    color: #fff; transition: background-color .15s ease-out, color .15s ease-out; }
.kf-sp4-rdmrow__id:hover { background: var(--kf-yellow); border-color: var(--kf-yellow); color: var(--kf-ink); }
/* IDENTIFY — the fixture flashes so you can find it on the truss */
.kf-sp4-rdmrow.is-identifying { background: rgba(254,210,8,.10); }
.kf-sp4-rdmrow.is-identifying .kf-sp4-rdmrow__lamp { animation: kf-sp4-identify .36s steps(1) 5; }
@keyframes kf-sp4-identify { 0%,49% { background: #fff !important; box-shadow: 0 0 0 6px rgba(254,210,8,.55); }
    50%,100% { box-shadow: 0 0 0 1px rgba(0,0,0,.12) inset; } }
.kf-sp4-rdmstatus { font: 400 12px/18px ui-monospace, SFMono-Regular, Menlo, monospace;
    color: var(--kf-slate); margin: 16px 0 0; min-height: 18px; }
/* --kf-slate and --kf-mute are tuned for a light ground. The panel is dark now, and both the status
   line and the qualifying note were effectively unreadable against it. */
.kf-sp4-rdm .kf-sp4-rdmstatus { color: rgba(255,255,255,.62); }
/* The status line wraps to two lines at phone width for the longer messages ("... 1 found",
   "Identify - <name> flashing on the truss") and back to one for the short ones, stepping the card
   18px several times per sequence. Reserve both lines. Must sit after the base rule above: same
   specificity, so order decides. */
@media (max-width: 860px) { .kf-sp4-rdmstatus { min-height: 36px; } }
.kf-sp4-rdm .kf-sp4-anno { color: rgba(255,255,255,.55); }
.kf-sp4-rdm .kf-sp4-panel__hint { color: var(--kf-on-dark, rgba(255,255,255,.78)); }
@media (max-width: 960px) {
    /* Three columns were declared but four children remain visible once the uid and personality
       are hidden, so IDENTIFY wrapped onto an implicit second row in column 1 -- and justify-self:
       end then parked it at left:-17, outside the card. Place all four explicitly: the name takes
       its own line, the patch and the button share the next one. */
    .kf-sp4-rdmrow { grid-template-columns: 14px minmax(0,1fr) auto; row-gap: 10px; align-items: center; }
    .kf-sp4-rdmrow__uid, .kf-sp4-rdmrow__pers { display: none; }
    .kf-sp4-rdmrow__lamp  { grid-column: 1; grid-row: 1; }
    .kf-sp4-rdmrow__name  { grid-column: 2 / span 2; grid-row: 1; }
    .kf-sp4-rdmrow__patch { grid-column: 2; grid-row: 2; }
    .kf-sp4-rdmrow__id    { grid-column: 3; grid-row: 2; min-height: 40px; }
}

/* --- hero: live CIE walk along the Planckian locus --------------------------- */
.kf-sp4-hero__glow { position: absolute; inset: 0; pointer-events: none; mix-blend-mode: screen; }
.kf-sp4-ticker { display: flex; align-items: center; gap: 26px; flex-wrap: wrap;
    margin-top: 44px; padding-top: 20px; border-top: 1px solid rgba(255,255,255,.14); }
.kf-sp4-ticker__tag { font: 500 10px/14px var(--kf-body); letter-spacing: 1.4px;
    text-transform: uppercase; color: var(--kf-yellow); }
.kf-sp4-ticker__kv { font: 400 12px/18px var(--kf-body); letter-spacing: .4px;
    text-transform: uppercase; color: var(--kf-on-dark-mute); }
.kf-sp4-ticker__kv b { font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 13px; color: #fff; font-variant-numeric: tabular-nums; margin-left: 6px; }
.kf-sp4-ticker__ramp { display: inline-flex; gap: 6px; }
.kf-sp4-ticker__ramp i { width: 26px; height: 26px; display: block; background: #fff;
    transition: background-color .3s linear; }

/* --- engine / personality browser ------------------------------------------- */
/* --- the family, read as two generations ------------------------------------------------------
 * The numbers first, then the engines grouped by generation, then the patch list on request. The
 * old two-column layout put a five-item list next to a seventeen-row table and asked the reader to
 * work out the relationship between them.
 */
/* Yellow, because white cards on a near-white band made the headline numbers read as just more of
   the same list below them. This is the band's claim stated once, so it gets the brand ground. */
.kf-sp4-engines__stats { display: grid; grid-template-columns: repeat(3, minmax(0,1fr));
    margin: 30px 0 0; background: var(--kf-yellow);
    border-radius: var(--kf-card-radius); overflow: hidden; }
/* Divide with a border on the cells, not a gap. A gap shows the PARENT's background, which
   composites over the page rather than over the yellow and came out a pale grey line; a border
   sits on the yellow itself and reads as a darker amber rule. */
.kf-sp4-engines__stats > div { background: var(--kf-yellow); padding: 20px 22px; display: grid; gap: 4px; }
.kf-sp4-engines__stats > div + div { border-left: 1px solid rgba(0,0,0,.22); }
.kf-sp4-engines__stats dt { font: 700 10px/14px var(--kf-body); letter-spacing: 1.4px;
    text-transform: uppercase; color: var(--kf-ink-2, #3a3a36); order: 2; }
.kf-sp4-engines__stats dd { margin: 0; order: 1; font-family: var(--kf-display); font-weight: 700;
    font-size: 38px; line-height: 42px; color: var(--kf-ink); }

/* The generation grouping and its labels came out in review (Oct 5/9): the band now states plain
   compatibility with three engines, so there is one flat row and no group heading to style. */
.kf-sp4-engines__picker { display: grid; margin-top: 26px; }
.kf-sp4-engines__row { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    gap: 10px; }

/* the patch list is reference material, so it opens on request rather than filling the band */
.kf-sp4-pers__disc { margin-top: 28px; border: 1px solid #DCDCD6;
    border-radius: var(--kf-card-radius); background: #fff; }
.kf-sp4-pers__summary { cursor: pointer; list-style: none; padding: 16px 20px;
    display: flex; align-items: center; justify-content: space-between; gap: 16px;
    font: 500 13px/18px var(--kf-body); color: var(--kf-ink-3); }
.kf-sp4-pers__summary::-webkit-details-marker { display: none; }
.kf-sp4-pers__summary b { font-weight: 700; color: var(--kf-ink); }
.kf-sp4-pers__summary::after { content: '+'; font-size: 18px; line-height: 1; color: var(--kf-ink); }
.kf-sp4-pers__disc[open] .kf-sp4-pers__summary::after { content: '\2212'; }
.kf-sp4-pers__count { font: 400 12px/16px var(--kf-body); color: var(--kf-mute); margin-left: auto; }
.kf-sp4-pers__disc .kf-sp4-pers { border: 0; border-top: 1px solid #DCDCD6; border-radius: 0; }
.kf-sp4-pers__disc .kf-sp4-pers__sum { padding: 0 20px 18px; }
.kf-sp4-engine { text-align: left; background: #fff; border: 1px solid #DCDCD6;
    border-radius: var(--kf-card-radius); padding: 14px 16px; cursor: pointer; display: grid; gap: 2px;
    transition: border-color .15s ease-out, background-color .15s ease-out; }
.kf-sp4-engine:hover { border-color: var(--kf-ink); }
.kf-sp4-engine.is-active { background: var(--kf-ink); border-color: var(--kf-ink); }
.kf-sp4-engine.is-active .kf-sp4-engine__name { color: #fff; }
.kf-sp4-engine.is-active .kf-sp4-engine__sub { color: var(--kf-on-dark); }
.kf-sp4-engine__name { font-family: var(--kf-display); font-weight: 700; text-transform: uppercase;
    font-size: 17px; line-height: 21px; color: var(--kf-ink); }
.kf-sp4-engine__sub { font: 400 12px/17px var(--kf-body); color: var(--kf-mute); }
.kf-sp4-engine__era { justify-self: start; font: 700 9px/1 var(--kf-body); letter-spacing: .9px;
    text-transform: uppercase; padding: 4px 7px; border-radius: 9px; margin-bottom: 4px; }
.kf-sp4-engine__era--new { background: var(--kf-yellow); color: var(--kf-ink); }
.kf-sp4-engine__era--legacy { background: #E6E6E1; color: var(--kf-ink-3); }
.kf-sp4-pers { list-style: none; margin: 0; padding: 0; background: #fff;
    border: 1px solid #DCDCD6; border-radius: var(--kf-card-radius); }
.kf-sp4-pers__row { display: grid; grid-template-columns: 54px minmax(0,1fr) 92px 164px;
    gap: 14px; align-items: center; padding: 11px 18px; border-top: 1px solid #F0F0EC; }
.kf-sp4-pers__row:first-child { border-top: 0; }
.kf-sp4-pers__row.is-new { background: #FFFCEF; }
.kf-sp4-pers__n { font: 400 12px/18px ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--kf-mute); }
.kf-sp4-pers__name { font-family: var(--kf-display); font-weight: 700; text-transform: uppercase;
    font-size: 15px; line-height: 19px; color: var(--kf-ink); }
.kf-sp4-pers__ch { font: 400 12px/18px ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--kf-ink-3); }
.kf-sp4-pers__tag { justify-self: end; font: 700 9px/1 var(--kf-body); letter-spacing: .8px;
    text-transform: uppercase; color: var(--kf-mute); }
.kf-sp4-pers__row.is-new .kf-sp4-pers__tag { color: #8A6D00; }
/* REVIEW §7.1: the rows we cannot yet publish are a row of their own rather than an omission,
   so the count on the page and the count in the firmware never silently disagree again. */
.kf-sp4-pers__row--pending { background: repeating-linear-gradient(135deg,
        #FAFAF7 0 8px, #F2F2EC 8px 16px); }
.kf-sp4-pers__row--pending .kf-sp4-pers__name,
.kf-sp4-pers__row--pending .kf-sp4-pers__ch { color: var(--kf-mute); font-style: italic; }
.kf-sp4-pers__row--pending .kf-sp4-pers__name { font-style: normal; }
.kf-sp4-pers__ch { white-space: nowrap; }
.kf-sp4-pers__sum { font: 400 13px/20px var(--kf-body); color: var(--kf-ink-3); margin: 14px 0 0; }
.kf-sp4-pers__sum strong { font-family: var(--kf-display); font-size: 16px; color: var(--kf-ink); }

/* --- ballast: feature copy now rides on the control that performs it ---------- */
.kf-sp4-cuehead { margin-top: 18px; }
.kf-sp4-tray__head { display: grid; grid-template-columns: 28px minmax(0,1fr); gap: 14px;
    align-items: start; margin-bottom: 14px; }
.kf-sp4-tray__icon { width: 28px; height: 28px; object-fit: contain; }
.kf-sp4-tray__head b { display: block; font-family: var(--kf-display); font-weight: 700;
    text-transform: uppercase; font-size: 15px; line-height: 19px; color: #fff; }
.kf-sp4-tray__head span { display: block; font: 400 12px/18px var(--kf-body); color: var(--kf-on-dark); margin-top: 2px; }
.kf-sp4-cuehead .kf-sp4-tray__head { margin-bottom: 10px; }
.kf-sp4-usbout { font: 400 12px/18px ui-monospace, SFMono-Regular, Menlo, monospace;
    color: var(--kf-yellow); margin: 12px 0 0; min-height: 18px; }
.kf-sp4-side { gap: 8px; }
@media (max-width: 760px) { .kf-sp4-engines__stats { grid-template-columns: minmax(0,1fr); } }
@media (max-width: 960px) {
    .kf-sp4-pers__row { grid-template-columns: 46px minmax(0,1fr) 58px; }
    .kf-sp4-pers__tag { display: none; }
}
.kf-sp4-mark { display: grid; gap: 14px; justify-items: start; }
/* the spectrum rule — generated from the real CIE locus in JS */
.kf-sp4-mark__bar { display: block; width: 340px; height: 7px; border-radius: 2px;
    background: #444; background-size: 200% 100%; animation: kf-sp4-bar 9s linear infinite; }
@keyframes kf-sp4-bar { to { background-position: 200% 0; } }
.kf-sp4-mark__word { font-family: var(--kf-display); font-weight: 700; text-transform: uppercase;
    font-size: 54px; line-height: 58px; letter-spacing: 2px; color: #fff; }
.kf-sp4-mark__word b { color: var(--kf-yellow); }
@media (max-width: 640px) {
    .kf-sp4-mark__word { font-size: 36px; line-height: 40px; }
    .kf-sp4-mark__bar { width: 240px; }
}
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* --- hero: the spectral bar ------------------------------------------------- */
/* `.kf-sp4-hero { display:flex }` beats a plain `hidden` attribute, so keep the guard: anything
   that hides the hero does it with an attribute, and flex would otherwise win. */
.kf-sp4-hero[hidden] { display: none !important; }
/* The bar was background-size:200% with an animated position, so only half the spectrum was ever
   visible. The OS 3.0 art shows the FULL ramp — keep that, and move a soft specular sweep across
   it instead of moving the spectrum itself. */
.kf-sp4-mark__bar { background-size: 100% 100%; animation: none; position: relative; overflow: hidden; }
.kf-sp4-mark__bar::after { content: ''; position: absolute; inset: 0;
    background: linear-gradient(90deg, transparent 0%, rgba(255,255,255,.75) 50%, transparent 100%);
    transform: translateX(-100%); animation: kf-sp4-sheen 6s ease-in-out 1.2s infinite; }
@keyframes kf-sp4-sheen { 0% { transform: translateX(-100%); } 55%,100% { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) { .kf-sp4-mark__bar::after { animation: none; opacity: 0; } }

/* --- hero ground: the CIE gamut as atmosphere ------------------------------- */
/* A 420px canvas blurred up to fill the band — cheap to render, and the blur is what turns a
   diagram into light. It bleeds off the right edge so it reads as a field, not a chart. */
.kf-sp4-field { position: absolute; inset: 0; overflow: hidden; background: #000; }
.kf-sp4-field__cv { position: absolute; top: 50%; right: -14%; width: 1180px; height: 1180px;
    transform: translateY(-50%) rotate(-8deg); transform-origin: center;
    filter: blur(86px) saturate(1.55) brightness(1.08); opacity: .92;
    animation: kf-sp4-drift 36s ease-in-out infinite alternate; }
@keyframes kf-sp4-drift {
    from { transform: translateY(-52%) rotate(-10deg) scale(1); }
    to   { transform: translateY(-46%) rotate(-4deg)  scale(1.1); } }
/* copy side stays legible: a left-weighted wash, not a flat grey over the whole frame */
.kf-sp4-field::after { content: ''; position: absolute; inset: 0;
    background: linear-gradient(100deg, rgba(0,0,0,.95) 0%, rgba(0,0,0,.86) 34%,
                rgba(0,0,0,.52) 58%, rgba(0,0,0,.30) 100%),
                radial-gradient(120% 100% at 0% 50%, rgba(0,0,0,.55) 0%, transparent 60%); }
.kf-sp4-hero { background: #000; }
.kf-sp4-hero__glow { mix-blend-mode: screen; opacity: .85; }
@media (prefers-reduced-motion: reduce) { .kf-sp4-field__cv { animation: none; } }
@media (max-width: 960px) {
    .kf-sp4-field__cv { right: -40%; width: 820px; height: 820px; filter: blur(70px) saturate(1.5); }
    .kf-sp4-field::after { background: linear-gradient(180deg, rgba(0,0,0,.80) 0%, rgba(0,0,0,.90) 70%); } }

/* the ticker needs to read against colour now, so give it weight */
.kf-sp4-ticker__ramp i { width: 30px; height: 30px; box-shadow: 0 0 0 1px rgba(255,255,255,.22) inset; }
.kf-sp4-ticker { backdrop-filter: blur(2px); }
.kf-sp4-cieout__sw {
    width: 46px; height: 46px; border-radius: 50%; grid-row: span 2;
    box-shadow: 0 0 0 1px rgba(255,255,255,.28), 0 0 26px rgba(255,255,255,.22);
}
.kf-sp4-cieout__hint {
    grid-column: 2; margin: 2px 0 0;
    font-family: var(--kf-body); font-size: 11px; line-height: 15px; color: rgba(255,255,255,.55);
}
@media (max-width: 900px) {
    .kf-sp4-cieout__sw { width: 34px; height: 34px; }
    .kf-sp4-cieout__hint { display: none; }
}

/* --- hero A lockup --------------------------------------------------------------------------
 * Replaces the grey "logo" placeholder AND the duplicate h1 that sat under it. Same component as
 * the WebGL hero (spectral rule generated from the CIE locus, sheen sweeping across it), sized for
 * a hero where it is the headline rather than a mark beside a huge numeral.
 */
.kf-sp4-mark--hero { gap: 18px; margin-bottom: 26px; }
.kf-sp4-mark--hero .kf-sp4-mark__bar { width: 300px; height: 6px; }
.kf-sp4-mark--hero .kf-sp4-mark__word {
    font-size: 56px; line-height: 60px; letter-spacing: 1.6px; margin: 0;
}
.kf-sp4-mark__ver { color: #fff; }
@media (max-width: 960px) {
    .kf-sp4-mark--hero .kf-sp4-mark__word { font-size: 38px; line-height: 42px; }
    .kf-sp4-mark--hero .kf-sp4-mark__bar { width: 220px; height: 5px; }
}

/* --- hero A: the live readout becomes a real instrument --------------------------------------
 * The row already said "LIVE"; now it is. Drag it for CCT, tap a preset, or pick straight off the
 * gamut field behind — which IS the chromaticity diagram, so a coordinate there is a real one.
 */
.kf-sp4-ticker { user-select: none; }
/* 26px gaps put the row ~10px over the container and wrapped the presets onto a second line, which
   read as an overflow. Tighten the gaps and the track so the whole instrument sits on one line. */
.kf-sp4-ticker { gap: 12px 18px; }
.kf-sp4-cct__track { width: 150px; }
.kf-sp4-ticker__presets { gap: 5px; }
.kf-sp4-ticker__presets button { padding: 8px 11px; }
.kf-sp4-ticker__presets { display: inline-flex; gap: 6px; margin-left: 4px; }
.kf-sp4-ticker__presets button {
    font-family: var(--kf-body); font-size: 11px; line-height: 1; font-weight: 700;
    letter-spacing: .6px; color: rgba(255,255,255,.72); cursor: pointer;
    padding: 8px 12px; border-radius: 30px; background: rgba(255,255,255,.08);
    border: 1px solid rgba(255,255,255,.18);
}
.kf-sp4-ticker__presets button:hover { color: #fff; background: rgba(255,255,255,.16); }
.kf-sp4-ticker__presets button.is-active {
    color: #000; background: var(--kf-yellow); border-color: var(--kf-yellow);
}
/* the drift must hold still while someone is picking, or the crosshair slides off their point */
.kf-sp4-hero.is-picking .kf-sp4-field__cv { animation-play-state: paused; }
.kf-sp4-hero.is-picking { cursor: crosshair; }
/* The readout and the slider were both reporting the live colour while the diagram showed nothing
   moving, so the locus looked like static decoration. This dot rides it: the auto-walk drives it,
   the CCT slider drives it, and it steps aside the moment the pointer enters (the crosshair takes
   over) or a colour is committed (the pin does). Quieter than the pin on purpose. */
.kf-sp4-walk {
    position: absolute; width: 14px; height: 14px; margin: -7px 0 0 -7px; border-radius: 50%;
    background: #fff; box-shadow: 0 0 0 1px rgba(0,0,0,.55), 0 0 16px rgba(255,255,255,.75);
    pointer-events: none; z-index: 4;
    transition: opacity .25s ease-out;
}
.kf-sp4-walk[hidden] { display: none !important; }

.kf-sp4-xhair {
    position: absolute; z-index: 3; width: 22px; height: 22px; margin: -11px 0 0 -11px;
    border: 2px solid #fff; border-radius: 50%; pointer-events: none;
    box-shadow: 0 0 0 1px rgba(0,0,0,.55), 0 0 14px rgba(255,255,255,.55);
    transition: opacity .12s linear;
}
/* outside the spectral locus there is no colour to report, so say so rather than clamping silently */
.kf-sp4-xhair.is-off { opacity: .25; border-style: dashed; }

/* The PIN marks the committed selection and is placed only on commit, so it stays where the click
   happened while the crosshair carries on following the cursor. */
.kf-sp4-pin {
    position: absolute; z-index: 4; width: 26px; height: 26px; margin: -13px 0 0 -13px;
    border: 2px solid var(--kf-yellow); border-radius: 50%; pointer-events: none;
    box-shadow: 0 0 0 1px rgba(0,0,0,.65), 0 0 18px rgba(254,210,8,.65);
}
.kf-sp4-pin::after {
    content: ''; position: absolute; inset: 7px; border-radius: 50%; background: var(--kf-yellow);
}
.kf-sp4-ticker__presets [data-sp4-auto] { margin-left: 4px; }

/* --- let hover reach the colour field ---------------------------------------------------------
 * .kf-sp4-hero__inner and its .kf-container span the FULL width of the band, so they sat invisibly
 * over the whole gamut field and swallowed every pointermove — picking only ever worked for
 * synthetic events dispatched straight at the section. Make the empty wrapper transparent to the
 * pointer and hand it back to the things that actually need it, so text stays selectable and the
 * buttons still click.
 */
.kf-sp4-hero .kf-sp4-hero__inner,
.kf-sp4-hero .kf-sp4-hero__inner > .kf-container { pointer-events: none; }
/* NOTE: .kf-sp4-ticker is deliberately absent. It used to need re-enabling here because it lived
   inside this wrapper; now it sits in the readout bar, and listing it overrode that bar's
   pointer-events:none at higher specificity, which put the hover-swallowing back. */
.kf-sp4-hero .kf-sp4-mark,
.kf-sp4-hero .kf-sp4-hero__b,
.kf-sp4-hero .kf-lede,
.kf-sp4-hero .kf-sp4-actions { pointer-events: auto; }
/* On a wide hero the copy sits in the left half and there is plenty of open field beside it. At
   phone width the copy fills the band and was swallowing 61% of the picker -- a tap on the headline
   or the lede did nothing at all. Hand those three back to the field; .kf-sp4-actions keeps its
   pointer events so both buttons stay tappable. Reach goes from 39% to 80% of the hero. */
@media (max-width: 760px) {
    .kf-sp4-hero .kf-sp4-mark,
    .kf-sp4-hero .kf-sp4-hero__b,
    .kf-sp4-hero .kf-lede { pointer-events: none; }
}

/* --- gamut verdict ---------------------------------------------------------------------------
 * Appears only once a colour is committed, so it is the payoff for choosing rather than noise
 * while browsing. Sits under the live row, same left edge as the copy column.
 */
.kf-sp4-verdict {
    position: absolute; z-index: 6; width: 290px;
    display: grid; gap: 8px; padding: 14px 40px 14px 16px; pointer-events: auto;
    border-radius: var(--kf-card-radius, 4px);
    background: rgba(8,8,8,.86); backdrop-filter: blur(8px);
    border: 1px solid rgba(255,255,255,.18);
    box-shadow: 0 10px 34px rgba(0,0,0,.5);
    font-family: var(--kf-body); font-size: 13px; line-height: 18px;
}
/* A thin connector, not a filled nib. The card carries backdrop-filter and a pseudo-element does
   not, so a dark diamond painted over the raw background read noticeably darker than the card it was
   meant to belong to. A light 1px rule has no fill to mismatch. */
.kf-sp4-verdict::before {
    content: ''; position: absolute; top: 50%; left: -22px;
    width: 22px; height: 1px; background: rgba(255,255,255,.38);
}
.kf-sp4-verdict.is-flipped::before { left: auto; right: -22px; }
.kf-sp4-verdict > b { display: flex; align-items: center; gap: 9px; }
.kf-sp4-verdict__dot { flex: none; }
.kf-sp4-verdict[hidden] { display: none !important; }
/* a rounded RECT, not a circle: as a disc it read as a status LED, so a green swatch sitting beside
   "Outside fixture gamut" looked like a pass. It is a sample of the picked colour — status lives in
   the label's colour alone. */
.kf-sp4-verdict__dot {
    width: 26px; height: 16px; border-radius: 3px;
    box-shadow: 0 0 0 1px rgba(255,255,255,.35);
}
.kf-sp4-verdict b {
    font-family: var(--kf-display); font-weight: 700; text-transform: uppercase;
    letter-spacing: 1.2px; font-size: 12px; color: #fff;
}
.kf-sp4-verdict.is-ok b    { color: #7FD44B; }
.kf-sp4-verdict.is-out b   { color: var(--kf-yellow); }
.kf-sp4-verdict__why { color: rgba(255,255,255,.76); }
.kf-sp4-verdict__env {
    font-size: 11px; letter-spacing: .6px;
    color: rgba(255,255,255,.45); font-variant-numeric: tabular-nums;
}
.kf-sp4-verdict__snap {
    font-family: var(--kf-body); font-size: 12px; font-weight: 700; letter-spacing: .4px;
    color: #000; background: var(--kf-yellow); border: 0; border-radius: 30px;
    padding: 8px 16px; cursor: pointer;
}
.kf-sp4-verdict__snap[hidden] { display: none !important; }
.kf-sp4-verdict__snap:hover { filter: brightness(1.08); }
/* no room to float a callout on a phone — let it sit in the flow under the hero copy instead */
/* `position: static` put the card INSIDE the hero's flex row, so instead of dropping below the copy
   it sat beside it, 20px past the right edge of a phone screen and invisible. Keep it absolute and
   anchored to the pick -- which is the point of it -- and just cap the width so it still fits the
   narrowest screens; placeVerdict already clamps it inside the band. */
@media (max-width: 860px) {
    .kf-sp4-verdict { box-sizing: border-box; width: min(348px, calc(100vw - 32px)); }
    .kf-sp4-verdict::before { display: none; }
}

/* --- CTA artwork ------------------------------------------------------------------------------
 * The band is ink, the render is lit on black, so the two meet without a visible edge. The scrim is
 * a left-to-right gradient rather than a flat overlay: full ink under the copy, clear by the time
 * it reaches the hardware.
 */
.kf-sp4-sec--ink:has(.kf-sp4-cta__art) { position: relative; overflow: hidden; }
.kf-sp4-cta__art { position: absolute; inset: 0 0 0 auto; width: 62%; z-index: 0; }
/* Slide2c-2 is the same render as Slide2c-1 with the OS 3.1 paragraph left off the plate, so the
   artwork can be framed on the hardware instead of dodging type that contradicts the page. */
.kf-sp4-cta__art img { width: 100%; height: 100%; object-fit: cover; object-position: 62% 52%; }
.kf-sp4-cta__art::after { content: ''; position: absolute; inset: 0;
    background: linear-gradient(90deg, #000 0%, rgba(0,0,0,.92) 28%, rgba(0,0,0,.35) 62%, rgba(0,0,0,.15) 100%); }
.kf-sp4-sec--ink:has(.kf-sp4-cta__art) > .kf-container { position: relative; z-index: 1; }
@media (max-width: 860px) {
    /* at phone width the copy spans the band, so the art would sit under the text */
    .kf-sp4-cta__art { width: 100%; opacity: .5; }
    .kf-sp4-cta__art::after { background: linear-gradient(180deg, rgba(0,0,0,.9) 0%, rgba(0,0,0,.6) 100%); }
}

/* --- the achievable region, on the diagram ----------------------------------------------------
 * Sits above the blurred field but below the markers. Deliberately quiet: it is a guide, not the
 * subject — the hero still has to read as a hero before it reads as an instrument.
 */
.kf-sp4-gamut {
    position: absolute; inset: 0; width: 100%; height: 100%;
    pointer-events: none; z-index: 2; overflow: visible;
}
.kf-sp4-gamut [data-sp4-gamut-band] {
    fill: rgba(255,255,255,.07);
    stroke: rgba(255,255,255,.40); stroke-width: 1.25;
    stroke-dasharray: 5 5;
}
.kf-sp4-gamut [data-sp4-gamut-line] {
    fill: none; stroke: rgba(255,255,255,.62); stroke-width: 1.5;
}
/* once a colour is committed the band has done its job — fade it so it stops competing */
.kf-sp4-hero.is-picked .kf-sp4-gamut { opacity: .5; transition: opacity .2s linear; }

/* dismiss: hides the note, keeps the selection */
.kf-sp4-verdict__x {
    position: absolute; top: 8px; right: 8px; width: 26px; height: 26px;
    display: flex; align-items: center; justify-content: center;
    font: 400 18px/1 var(--kf-body); color: rgba(255,255,255,.55);
    background: none; border: 0; border-radius: 50%; cursor: pointer;
}
.kf-sp4-verdict__x:hover { color: #fff; background: rgba(255,255,255,.12); }

/* the pin is the selection, so clicking it is the obvious way to clear it */
.kf-sp4-pin { pointer-events: auto; cursor: pointer; padding: 0; background: none; }
.kf-sp4-pin:hover { box-shadow: 0 0 0 1px rgba(0,0,0,.65), 0 0 22px rgba(254,210,8,.9); }
.kf-sp4-pin[hidden] { display: none !important; }
/* set apart from the kelvin presets: it is an escape hatch, not another colour */
.kf-sp4-ticker__presets [data-sp4-auto] {
    margin-left: 10px; background: none; border-color: rgba(255,255,255,.28);
    color: rgba(255,255,255,.72);
}
.kf-sp4-ticker__presets [data-sp4-auto]:hover { background: rgba(255,255,255,.12); color: #fff; }
.kf-sp4-ticker__presets [data-sp4-auto][hidden] { display: none !important; }

/* --- CCT slider ------------------------------------------------------------------------------
 * An explicit control with a handle and a scale, replacing a drag that was spread invisibly across
 * the whole row. The track carries the Planckian locus, so what you drag along is what you get.
 */
.kf-sp4-cct { display: inline-flex; align-items: center; gap: 10px; }
.kf-sp4-cct__cap {
    font-family: var(--kf-body); font-size: 10px; font-weight: 500; letter-spacing: 1px;
    color: rgba(255,255,255,.5); font-style: normal; white-space: nowrap;
}
.kf-sp4-cct__track {
    position: relative; width: 190px; height: 10px; border-radius: 5px;
    cursor: ew-resize; touch-action: none;
    box-shadow: 0 0 0 1px rgba(255,255,255,.22) inset;
}
.kf-sp4-cct__track:focus-visible { outline: 2px solid var(--kf-yellow); outline-offset: 3px; }
.kf-sp4-cct__thumb {
    position: absolute; top: 50%; left: 50%; width: 18px; height: 18px; margin: -9px 0 0 -9px;
    border-radius: 50%; background: #fff; pointer-events: none;
    box-shadow: 0 0 0 2px rgba(0,0,0,.45), 0 2px 8px rgba(0,0,0,.5);
}
@media (max-width: 1100px) { .kf-sp4-cct__track { width: 130px; } }

/* Reserved, not removed: toggling display here re-wrapped the preset group and changed the hero's
   height, so the page jumped the moment a colour was picked. */
.kf-sp4-ticker__presets [data-sp4-auto].is-off { visibility: hidden; }
/* and pin the row's height so no future wrap can move the band either */
.kf-sp4-ticker { min-height: 46px; }
.kf-sp4-cct { margin-left: 4px; }
.kf-sp4-cct__cap { min-width: 42px; }
.kf-sp4-cct__cap:last-child { text-align: right; }

/* Hold the diagram still once a colour is committed. The field drifts on a 36s loop, so a pin left
   on it kept sliding and everything anchored to it moved with it. A chosen coordinate should stay
   where it was chosen; the drift is only there to keep an idle hero alive. */
.kf-sp4-hero.is-picked .kf-sp4-field__cv { animation-play-state: paused; }

/* --- hero A --------------------------------------------------------------------------------
 * The gradient keeps its original size: shrinking it read as weaker, and boxing it exposed the
 * sharp interior of an 86px blur. A taller band gives it room instead, and lifts the colour clear
 * of the readout row without making the mass smaller.
 */
.kf-sp4-hero { min-height: 760px; }
.kf-sp4-hero .kf-sp4-field { cursor: crosshair; }
.kf-sp4-hero .kf-sp4-field__cv { top: 46%; }

/* --- copy must not swallow hover over the gradient --------------------------------------------
 * .kf-sp4-mark and .kf-sp4-actions are full-width block/grid containers, so their INVISIBLE boxes
 * reached 1200px across and sat over the colour field. Hover landed on the heading instead of the
 * diagram, the field's listener never fired, and the crosshair froze where it was: the dead zone
 * and the "jumping" were this. Shrink the boxes to their content so only the actual text and
 * buttons take the pointer.
 */
.kf-sp4-hero .kf-sp4-mark--hero,
.kf-sp4-hero .kf-sp4-actions { width: max-content; max-width: 100%; }

/* --- hero A readout bar -----------------------------------------------------------------------
 * Full width, pinned to the bottom of the band, and INERT: the bar takes no pointer events at all,
 * so moving across it no longer interrupts tracking or strands the crosshair. Only the controls
 * themselves opt back in. The diagram above it is left completely clear.
 */
.kf-sp4-readout {
    position: absolute; left: 0; right: 0; bottom: 0; z-index: 5;
    pointer-events: none;
    padding-bottom: 26px;
    background: linear-gradient(180deg, transparent 0%, rgba(0,0,0,.42) 45%, rgba(0,0,0,.72) 100%);
}
.kf-sp4-readout .kf-sp4-ticker { margin-top: 0; }
/* the controls are the exception, and nothing else */
.kf-sp4-readout .kf-sp4-cct__track,
.kf-sp4-readout button { pointer-events: auto; }
/* the copy no longer has the readout under it, so it keeps its own bottom rhythm */
.kf-sp4-hero .kf-sp4-hero__inner { padding-bottom: 96px; }
/* The readout bar is absolute across the hero's bottom, so the copy above has to reserve room for
   it. As the screen narrows the bar re-wraps from one row to four -- 172px against the desktop 93px
   -- and the flat 96px above stopped covering it, putting the Release notes button underneath the
   bar. --sp4-readout is its measured height (set in JS on load, resize and font swap). This has to
   sit after the 96px rule: both are (0,2,0), so order is what decides it. */
@media (max-width: 960px) {
    .kf-sp4-hero .kf-sp4-hero__inner { padding-bottom: calc(var(--sp4-readout, 172px) + 28px); }
}

/* the spectral rule is part of the lockup, so it spans the whole wordmark rather than stopping at a
   fixed 300px halfway across it. The mark is width:max-content, so 100% here is the h1's own width. */
.kf-sp4-hero .kf-sp4-mark--hero .kf-sp4-mark__bar { width: 100%; }

/* no meaningful colour temperature: the handle has nowhere real to sit, so say so rather than
   parking it at an end as though the value were just extreme */
.kf-sp4-cct__thumb.is-off { background: transparent; box-shadow: 0 0 0 2px rgba(255,255,255,.6) inset, 0 0 0 2px rgba(0,0,0,.45); }

/* the readout is a band, not a column: let it run the full width of the hero rather than sitting
   inside the 1200px content container, so the hairline and the instrument span the whole section */
.kf-sp4-readout > .kf-container {
    width: auto; max-width: none; margin: 0; padding-left: 40px; padding-right: 40px;
}
@media (max-width: 860px) {
    .kf-sp4-readout > .kf-container { padding-left: 20px; padding-right: 20px; }
}

/* now that the bar runs the full width, the instrument centres in it rather than bunching against
   the left edge with a third of the band empty beside it */
.kf-sp4-readout .kf-sp4-ticker { justify-content: center; }

/* Centred layout plus variable-width values means the whole instrument slides left and right as the
   numbers change under the cursor. Reserve each value's width and use tabular figures so the row is
   dimensionally static whatever it is reporting ("n/a" and "5600 K" occupy the same box).
*/
.kf-sp4-ticker__kv b {
    display: inline-block; text-align: left; font-variant-numeric: tabular-nums;
}
/* fixed px, not ch: ch rounds sub-pixel and left a 2px wobble across the sweep */
.kf-sp4-ticker__kv b[data-sp4-hero="x"],
.kf-sp4-ticker__kv b[data-sp4-hero="y"]   { width: 52px; }
.kf-sp4-ticker__kv b[data-sp4-hero="cct"] { width: 58px; }
.kf-sp4-ticker__kv b[data-sp4-hero="duv"] { width: 62px; }

/* --- carried features grid --------------------------------------------------------------------
 * States all six outright so the section can be read without being operated. The ballast below is
 * then a demonstrator rather than the only route to the information.
 */
.kf-sp4-ops {
    display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px;
    list-style: none; margin: 32px 0 0; padding: 0;
}
.kf-sp4-ops > li { display: flex; }
.kf-sp4-op {
    display: grid; grid-template-columns: 30px 1fr; grid-template-rows: auto auto;
    gap: 4px 14px; align-items: start; width: 100%; text-align: left; cursor: pointer;
    padding: 18px 20px; border-radius: var(--kf-card-radius, 4px);
    background: rgba(255,255,255,.04); border: 1px solid rgba(255,255,255,.10);
    transition: background .15s linear, border-color .15s linear;
}
.kf-sp4-op:hover { background: rgba(255,255,255,.08); border-color: rgba(255,255,255,.22); }
.kf-sp4-op.is-active { background: rgba(254,210,8,.10); border-color: var(--kf-yellow); }
.kf-sp4-op__icon { grid-row: span 2; width: 30px; height: 30px; object-fit: contain; }
.kf-sp4-op__name {
    font-family: var(--kf-display); font-weight: 700; text-transform: uppercase;
    font-size: 13px; line-height: 17px; letter-spacing: .8px; color: #fff;
}
.kf-sp4-op.is-active .kf-sp4-op__name { color: var(--kf-yellow); }
.kf-sp4-op__desc {
    font-family: var(--kf-body); font-size: 12px; line-height: 17px; color: rgba(255,255,255,.66);
}
@media (max-width: 1100px) { .kf-sp4-ops { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 680px)  { .kf-sp4-ops { grid-template-columns: 1fr; } }

/* tray head is a label strip now, not a repeat of the feature card above it */
.kf-sp4-tray__head { display: flex; align-items: center; gap: 10px; }
.kf-sp4-tray__head b {
    font-family: var(--kf-display); font-weight: 700; text-transform: uppercase;
    font-size: 12px; letter-spacing: 1px; color: rgba(255,255,255,.78);
}
.kf-sp4-tray__icon { width: 22px; height: 22px; object-fit: contain; }



.kf-sp4-panel {
    position: relative; padding: 70px 34px 34px;
    background: linear-gradient(180deg, #2b2b2d 0%, #1b1b1d 100%);
    border: 1px solid rgba(255,255,255,.14);
    border-radius: 10px;
    box-shadow: inset 0 1px 0 rgba(255,255,255,.14), 0 18px 40px rgba(0,0,0,.5);
}
.kf-sp4-face {
    display: block; position: absolute; left: 0; right: 0; top: 0; height: 56px;
    border-bottom: 1px solid rgba(255,255,255,.10);
}
.kf-sp4-face__brand {
    position: absolute; left: 24px; top: 19px;
    font-family: var(--kf-body); font-size: 11px; letter-spacing: 2px; text-transform: uppercase;
    color: rgba(255,255,255,.45);
}
.kf-sp4-face__brand b { color: rgba(255,255,255,.8); font-weight: 700; }
/* an encoder, because Nudge is described as "encoders and screen-side buttons" */
.kf-sp4-face__knob {
    position: absolute; right: 24px; top: 10px; width: 36px; height: 36px; border-radius: 50%;
    background: radial-gradient(circle at 38% 32%, #4a4a4e 0%, #2a2a2d 60%, #171719 100%);
    box-shadow: inset 0 1px 0 rgba(255,255,255,.22), 0 2px 6px rgba(0,0,0,.6);
}
.kf-sp4-face__knob::after {
    content: ''; position: absolute; left: 50%; top: 5px; width: 2px; height: 11px;
    margin-left: -1px; border-radius: 1px; background: rgba(255,255,255,.55);
}
.kf-sp4-face__vents {
    position: absolute; right: 80px; top: 22px; width: 92px; height: 12px;
    background: repeating-linear-gradient(90deg, rgba(0,0,0,.45) 0 3px, transparent 3px 7px);
    border-radius: 2px;
}
/* the screen reads as a recessed LCD, not a div */
.kf-sp4-screen {
    box-shadow: inset 0 0 0 1px rgba(0,0,0,.9), inset 0 2px 10px rgba(0,0,0,.8),
                0 0 0 1px rgba(255,255,255,.08);
}
/* physical buttons rather than web pills */
.kf-sp4-side__btn,
.kf-sp4-cue {
    background: linear-gradient(180deg, #3a3a3d 0%, #232326 100%);
    border: 1px solid rgba(0,0,0,.65);
    box-shadow: inset 0 1px 0 rgba(255,255,255,.16), 0 2px 4px rgba(0,0,0,.5);
}
.kf-sp4-side__btn.is-on,
.kf-sp4-cue.is-active {
    background: linear-gradient(180deg, #ffe04e 0%, var(--kf-yellow) 100%);
    color: #000; border-color: rgba(0,0,0,.5);
}


/* --- local ops: the device, lit by what it is driving ----------------------------------------
 * The ballast sits in a wash of the recalled cue's colour, so pressing a cue changes the light the
 * device is in. That is the section's claim stated directly, with no invented luminaire and no flat
 * swatch pretending to be one. The six carried features sit beside it rather than above.
 */
.kf-sp4-stage { position: relative; }
.kf-sp4-stage__wash {
    position: absolute; inset: -14% -10%; z-index: 0; border-radius: 50%;
    filter: blur(70px); opacity: .5; transition: background-color .35s linear, opacity .35s linear;
}
.kf-sp4-stage .kf-sp4-panel { position: relative; z-index: 1; }
/* The picker's wash is tuned down against the ballast's: that stage is a dark photo-like scene
   where a strong glow reads as light in a room, while this one sits behind a panel on a dark band
   and the same strength washed the diagram out. */
.kf-sp4-stage--cie .kf-sp4-stage__wash { inset: -10% -12%; filter: blur(100px); opacity: .42; }
/* A 100px blur reaches well past the panel, which is the point -- but it was also reaching past the
   BAND, spilling colour onto the top of the next section. Clip each wash to the band it belongs to.
   Only sections that actually hold one are affected, so nothing else changes its overflow. */
.kf-sp4-sec:has(.kf-sp4-stage__wash) { overflow: hidden; }
/* the scrub wash sets its own opacity per frame from the intensity, so no fixed value here */
.kf-sp4-stage--scrub .kf-sp4-stage__wash { inset: -14% -14%; filter: blur(110px); }
.kf-sp4-stage .kf-sp4-fixnote { position: relative; z-index: 1; margin-top: 16px; }

/* the cards stack in the narrower column beside the device */
.kf-sp4-split > .kf-sp4-ops { grid-template-columns: 1fr; gap: 10px; margin: 0; align-content: start; }
.kf-sp4-split > .kf-sp4-ops .kf-sp4-op { padding: 14px 16px; }
@media (max-width: 980px) { .kf-sp4-split > .kf-sp4-ops { grid-template-columns: 1fr; } }

/* the device needs room for its screen and side keys; the cards take the rest */
.kf-sp4-split:has(> .kf-sp4-stage) { grid-template-columns: minmax(0, 1fr) 480px; align-items: center; }
@media (max-width: 1100px) { .kf-sp4-split:has(> .kf-sp4-stage) { grid-template-columns: 1fr; } }

/* the streaming monitor rendered on the ballast's own LCD */
.kf-sp4-screen__mon {
    margin: 0; font: 400 13px/22px ui-monospace, SFMono-Regular, Menlo, monospace;
    color: var(--kf-yellow); white-space: pre; overflow-x: auto;
}
.kf-sp4-screen__mon[hidden] { display: none !important; }

/* --- the screen does the menus --------------------------------------------------------------
 * Preset temperatures, USB actions and nudge steps used to appear as rows of web chips below the
 * device. None of those exist on a LiteDimmer: it has a screen, five side keys and an encoder.
 */
.kf-sp4-screen__list {
    list-style: none; margin: 0; padding: 0;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 14px; line-height: 26px;
    color: rgba(255,255,255,.62);
}
.kf-sp4-screen__list li { display: flex; justify-content: space-between; align-items: center; }
.kf-sp4-screen__list li.is-sel { color: var(--kf-yellow); }
.kf-sp4-screen__list li.is-sel i { font-style: normal; }
.kf-sp4-screen__title {
    font-family: var(--kf-body); font-size: 10px; letter-spacing: 1.6px; text-transform: uppercase;
    color: var(--kf-yellow); margin-bottom: 6px;
}
.kf-sp4-screen__foot {
    margin-top: 8px; font-family: var(--kf-body); font-size: 11px; line-height: 16px;
    color: rgba(255,255,255,.42);
}
.kf-sp4-screen__list[hidden] { display: none !important; }
/* the field the encoder is currently editing */
.kf-sp4-screen .is-sel { color: var(--kf-yellow); }
.kf-sp4-screen__big.is-sel { text-shadow: 0 0 18px rgba(254,210,8,.45); }

/* the encoder is a control, so it behaves like one */
.kf-sp4-face__knob { cursor: ew-resize; padding: 0; transition: transform .12s ease-out; touch-action: none; }
.kf-sp4-face__knob:focus-visible { outline: 2px solid var(--kf-yellow); outline-offset: 3px; }

/* =============================================================================================
 * FACEPLATE, after the real LiteDimmer AC/DC 400
 * Amber LCD; its bottom row is the label strip for the three soft keys directly beneath it; two
 * encoders and the POWER/DATA indicators to the right. Replaces an invented five-button column.
 * ============================================================================================= */
.kf-sp4-dev {
    position: relative;
    display: grid; grid-template-columns: 1fr 108px; grid-template-rows: auto auto;
    gap: 14px 22px; align-items: start;
    padding: 26px 26px 54px;
    background: linear-gradient(180deg, #3a3b3d 0%, #232426 58%, #19191b 100%);
    border: 1px solid rgba(0,0,0,.7); border-radius: 14px;
    box-shadow: inset 0 1px 0 rgba(255,255,255,.14), 0 20px 44px rgba(0,0,0,.55);
}
.kf-sp4-dev__screen {
    grid-column: 1; background: #0d0c08;
    border-radius: 4px; padding: 14px 16px;
    box-shadow: inset 0 0 0 2px #000, inset 0 3px 14px rgba(0,0,0,.9), 0 0 0 1px rgba(255,255,255,.07);
}
/* the LCD itself: amber on near-black, divided like the real screen */
.kf-lcd { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: #F5C518; }
.kf-lcd__r1, .kf-lcd__r3 {
    display: flex; justify-content: space-between; font-size: 12px; line-height: 20px;
}
.kf-lcd__r1 { border-bottom: 1px solid rgba(245,197,24,.3); padding-bottom: 4px; }
.kf-lcd__r3 { border-top: 1px solid rgba(245,197,24,.3); padding-top: 4px; margin-top: 4px; }
.kf-lcd__r2 { display: grid; grid-template-columns: 1fr 1fr; text-align: center; padding: 8px 0; }
.kf-lcd__r2 > div + div { border-left: 1px solid rgba(245,197,24,.3); }
.kf-lcd__r2 i { display: block; font-style: normal; font-size: 12px; line-height: 16px; }
/* 30px/36px is the design size and the clamp caps there, so every viewport wide enough to give
   7vw >= 30px (about 428px up, i.e. all of tablet and desktop) renders exactly as before. Below
   that the big readout scales with the screen so the two columns keep fitting the LCD instead of
   being cut off. 1.2 is the same ratio 36/30 had. */
.kf-lcd__r2 b { display: block; font-size: clamp(19px, 7vw, 30px); line-height: 1.2; font-weight: 700; }
.kf-lcd__keys {
    display: grid; grid-template-columns: repeat(3, 1fr); text-align: center;
    border-top: 1px solid rgba(245,197,24,.3); margin-top: 4px; padding-top: 4px;
    font-size: 12px; line-height: 20px; letter-spacing: .5px;
}
.kf-lcd__keys span.is-on { background: #F5C518; color: #0d0c08; }
/* the field an encoder is editing */
.kf-lcd b.is-sel { text-shadow: 0 0 12px rgba(245,197,24,.9); }

/* three soft keys, under the labels they belong to */
.kf-sp4-dev__keys { grid-column: 1; display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; padding: 0 6px; }
.kf-sp4-softkey {
    height: 26px; border-radius: 13px; cursor: pointer; color: rgba(255,255,255,.5); font-size: 11px;
    background: linear-gradient(180deg, #4a4b4e 0%, #2a2b2d 100%);
    border: 1px solid rgba(0,0,0,.7);
    box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 2px 4px rgba(0,0,0,.5);
}
.kf-sp4-softkey:active, .kf-sp4-softkey.is-on { background: linear-gradient(180deg, #ffe04e 0%, #F5C518 100%); color: #000; }

/* encoders and indicators */
.kf-sp4-dev__side { grid-column: 2; grid-row: 1 / span 2; display: grid; justify-items: center; gap: 16px; }
.kf-sp4-leds { display: grid; gap: 3px; justify-items: start; width: 100%; }
.kf-sp4-leds i {
    font-style: normal; font-family: var(--kf-body); font-size: 8px; letter-spacing: .8px;
    color: rgba(255,255,255,.4); display: flex; align-items: center; gap: 5px;
}
.kf-sp4-leds i::before {
    content: ''; width: 6px; height: 6px; border-radius: 1px; background: #2c2c2e;
}
.kf-sp4-leds i.is-on::before { background: #6ee07a; box-shadow: 0 0 6px #6ee07a; }
.kf-sp4-enc {
    width: 62px; height: 62px; border-radius: 50%; cursor: ew-resize; padding: 0;
    touch-action: none; transition: transform .1s linear;
    background: radial-gradient(circle at 38% 30%, #56575a 0%, #303134 55%, #141416 100%);
    border: 1px solid rgba(0,0,0,.8);
    box-shadow: inset 0 2px 0 rgba(255,255,255,.18), 0 4px 10px rgba(0,0,0,.6);
    position: relative;
}
.kf-sp4-enc::after {
    content: ''; position: absolute; left: 50%; top: 7px; width: 3px; height: 15px;
    margin-left: -1.5px; border-radius: 2px; background: rgba(255,255,255,.65);
}
.kf-sp4-enc:focus-visible { outline: 2px solid var(--kf-yellow); outline-offset: 4px; }
.kf-sp4-dev__badge {
    position: absolute; left: 50%; bottom: 14px; transform: translateX(-50%);
    font-family: var(--kf-display); font-weight: 700; text-transform: uppercase;
    font-size: 11px; letter-spacing: 2px; color: rgba(255,255,255,.34);
}

/* cues are a page control, not hardware: the device reaches them through its menus */
.kf-sp4-cuebar { display: grid; gap: 10px; margin-top: 26px; }
.kf-sp4-cuebar__label {
    font-family: var(--kf-body); font-size: 10px; letter-spacing: 1.6px; text-transform: uppercase;
    color: rgba(255,255,255,.45);
}

/* The device IS the panel: a card around it was a second box around a box, and the faceplate
   already carries its own edge, shadow and ground. Scoped to the ballast -- the CIE picker reuses
   the same stage for its wash but is a panel of controls, not a photographed object, so it keeps
   its card and the light sits BEHIND it rather than washing across it. */
.kf-sp4-stage:not(.kf-sp4-stage--cie):not(.kf-sp4r-card) .kf-sp4-panel {
    background: none; border: 0; box-shadow: none; padding: 0;
}
/* The flicker band opts back IN: it is a chart and two monitors on a dark band with no wash of its
   own, so without a card it has no ground at all. Specificity is raised a notch deliberately --
   at equal weight this pair would be decided by file order, which is how it has bitten before. */
.kf-sp4-sec .kf-sp4-stage.kf-sp4r-card .kf-sp4-panel {
    background: var(--sp4-panel); border: 1px solid var(--sp4-hair);
    border-radius: var(--kf-card-radius); padding: 26px 28px 28px;
}

/* the caption sits in the wash, so it needs its own ground to stay readable against any colour */
.kf-sp4-stage .kf-sp4-fixnote {
    display: inline-block; padding: 7px 12px; border-radius: var(--kf-card-radius, 4px);
    background: rgba(0,0,0,.55); backdrop-filter: blur(4px);
    color: rgba(255,255,255,.82);
}

/* The LCD is a fixed piece of glass: pin its height so no mode can resize the device. */
.kf-sp4-dev__screen { height: 190px; box-sizing: border-box; overflow: hidden; }
/* The faceplate is a hardware replica, so the LCD type should not shrink independently of it -- but
   at phone width the fixed 108px encoder column plus 26px padding squeezed the screen to 128px when
   its content needs ~184, and the readout was cut mid-word ("INTEN82%" -> "INTE"). Take the space
   back from the chrome instead: tighter padding and gap, narrower encoder column. The knobs stay
   62px, still comfortably past a 44px touch target. */
@media (max-width: 420px) {
    .kf-sp4-dev { grid-template-columns: 1fr 84px; gap: 12px; padding: 14px 14px 40px; }
    .kf-sp4-dev__side { gap: 12px; }
}
.kf-sp4-screen__list { height: 100%; display: flex; flex-direction: column; }
.kf-sp4-screen__more {
    display: flex; justify-content: space-between; align-items: center;
    font-size: 10px; line-height: 14px; color: rgba(245,197,24,.55);
}
.kf-sp4-screen__foot { margin-top: auto; }

/* Scroll anchors. `id` is a YOOtheme builder field, so one set in an element definition is wiped by
   the empty prop when the element merges; the anchor is its own node instead. Zero-height and at the
   top of its band, so offsetTop is the band's own. */
/* Anchor targets sit at the very top of their band, so a jump would put the heading behind the
   site header AND this page's nav. Hold both off the top. 65px is the nav; --sp4-chrome is the
   theme's sticky header, measured live (0 where there is none). */
.kf-sp4-anchor { display: block; height: 0; overflow: hidden;
    scroll-margin-top: calc(var(--sp4-chrome, 0px) + 65px); }

/* ===============================================================================================
 * REVIEW §1 -- two-view colour picker (firmware team's prototype, styled for the page).
 * The magnified view is the point of it: at 330px for the whole horseshoe a 1-step MacAdam
 * ellipse is roughly one pixel, so the locus, the tint scale and the steps are invisible there.
 * ============================================================================================ */
/* The band is a normal two-column section again -- copy left, instrument right, like every other
   band. The instrument column is wider than the usual 480px because the magnified plot is the
   thing being read; the copy column gives up the difference. */
/* Specificity raised on purpose: `.kf-sp4-split:has(> .kf-sp4-stage)` already pins stage-bearing
   splits to a 480px instrument column, and at (0,2,0) it beats a bare class. 480 is too narrow for
   a plot that has to be read, so this one states its own width and outweighs it. */
.kf-sp4-sec .kf-sp4-split.kf-sp4r-head {
    grid-template-columns: minmax(0, 1fr) 700px; gap: 60px;
    /* top-aligned, not centred: the instrument is nearly twice the height of the copy, and
       centring it left a screen-height hole above the heading before anything was readable. */
    align-items: start; }
.kf-sp4r-head .kf-sp4-bullets { margin-top: 26px; }
.kf-sp4r-stage { position: relative; }

/* The band wash. Wide and very soft, sitting behind both columns and clipped to the section by the
   `:has(.kf-sp4-stage__wash)` overflow rule, so it lights the room without bleeding into the band
   below. Everything else in the band is lifted above it. */
.kf-sp4-sec:has(> .kf-sp4r-bandwash) { position: relative; }
.kf-sp4r-bandwash { inset: -20% -6%; filter: blur(150px); opacity: .4; border-radius: 50%; }
.kf-sp4-sec:has(> .kf-sp4r-bandwash) > .kf-container { position: relative; z-index: 1; }
/* The panel card sits ABOVE the wash, or the glow washes the plot out. The base rule only lifts a
   .kf-sp4-panel that is a direct child of the stage; this one nests a level deeper. */
.kf-sp4r-stage > .kf-sp4-panel, .kf-sp4r-stage .kf-sp4-panel { position: relative; z-index: 1; }
.kf-sp4r-stage .kf-sp4-stage__wash { inset: -14% -12%; filter: blur(110px); }

/* The card holds the magnified plot alone now. The full gamut sits in the copy column opposite,
   where it has room to be read -- as a 128px corner thumbnail on a card already carrying controls,
   a readout and a verdict, it was decoration. */
.kf-sp4r-cie { display: grid; gap: 12px; margin-top: 18px; justify-items: start; }
/* Never past the 660px backing store. Upscaling a plot whose whole point is a 3-step MacAdam
   ellipse only softens the thing being measured -- which is what happens once the band stacks and
   the card gets the full container width. */
.kf-sp4r-cie__zoom { position: relative; width: 100%; max-width: 660px; }
.kf-sp4r-cie__cap, .kf-sp4r-ctl, .kf-sp4r-read, .kf-sp4r-verdict, .kf-sp4r-notes { width: 100%; }
.kf-sp4r-cie__cap { margin: 0; max-width: 60ch; color: rgba(255,255,255,.62); }
.kf-sp4r-cie__full { position: relative; }

/* The gamut in the copy column: square, capped so it never outgrows the text beside it, and given
   the vertical room the column was leaving empty under the bullets. */
.kf-sp4r-gamut { margin: 34px 0 0; max-width: 320px; }
.kf-sp4r-gamut .kf-sp4r-cie__full { aspect-ratio: 1; }
/* The copy column sits directly in the wash, which at a cool pick is pale. Default axis grey
   disappeared against it, so the caption carries its own weight. */
.kf-sp4r-gamut figcaption { margin-top: 10px; color: rgba(255,255,255,.72); }
/* Scoped to the two VIEWS, not to the card: the full-gamut view lives in the copy column now, and
   when these were `.kf-sp4r-cie canvas/svg` it fell out of scope there -- the overlay lost its
   absolute positioning and stacked under the canvas as a second 320px block. */
.kf-sp4r-cie__full canvas, .kf-sp4r-cie__zoom canvas {
    display: block; width: 100%; height: auto; border-radius: 4px;
    background: #0b0b0a; border: 1px solid var(--sp4-hair); }
.kf-sp4r-cie__full svg, .kf-sp4r-cie__zoom svg {
    position: absolute; inset: 0; width: 100%; height: auto; pointer-events: none; }
.kf-sp4r-cie__zoom canvas { aspect-ratio: 660 / 470; cursor: crosshair; }
.kf-sp4r-cie__full canvas { aspect-ratio: 1; cursor: crosshair; }
.kf-sp4r-cie__zoom canvas:focus-visible,
.kf-sp4r-cie__full canvas:focus-visible { outline: 2px solid var(--kf-yellow); outline-offset: 2px; }


/* Controls in labelled rows rather than one long wrapping line, so "MacAdam" and "Mode" stay with
   the control they name instead of drifting apart as the column narrows. */
.kf-sp4r-ctl { display: grid; gap: 10px; margin-top: 20px; padding-top: 18px;
    border-top: 1px solid var(--sp4-hair); }
.kf-sp4r-ctl__row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; }
.kf-sp4r-ctl__row > .kf-sp4-preview__label { flex: 0 0 64px; }
.kf-sp4r-ctl__row--sel { gap: 8px 10px; }
.kf-sp4r-ctl__row--sel > .kf-sp4-preview__label:nth-of-type(2) { flex: 0 0 auto; margin-left: 10px; }
.kf-sp4r-sel { background: rgba(255,255,255,.06); color: #fff; border: 1px solid var(--sp4-hair);
    border-radius: 17px; padding: 8px 30px 8px 14px; font: 700 10px/1 var(--kf-body);
    letter-spacing: .8px; text-transform: uppercase; min-height: 34px; cursor: pointer;
    -webkit-appearance: none; appearance: none;
    background-image: linear-gradient(45deg, transparent 50%, rgba(255,255,255,.55) 50%),
                      linear-gradient(135deg, rgba(255,255,255,.55) 50%, transparent 50%);
    background-position: calc(100% - 16px) 50%, calc(100% - 11px) 50%;
    background-size: 5px 5px, 5px 5px; background-repeat: no-repeat; }
.kf-sp4r-sel:focus-visible { outline: 2px solid var(--kf-yellow); outline-offset: 2px; }

/* Eight values. Four across reads as a instrument panel; eight across makes each one a sliver. */
.kf-sp4r-read { grid-template-columns: repeat(4, minmax(0, 1fr)); row-gap: 16px; margin-top: 20px;
    padding-top: 18px; border-top: 1px solid var(--sp4-hair); }

.kf-sp4r-verdict { margin-top: 16px; padding: 12px 16px; border-radius: var(--kf-card-radius);
    background: rgba(255,255,255,.05); border: 1px solid var(--sp4-hair);
    border-left: 3px solid var(--sp4-hair);
    font: 400 13px/19px var(--kf-body); color: var(--kf-on-dark, rgba(255,255,255,.8)); }
.kf-sp4r-verdict b { color: #fff; }
.kf-sp4r-verdict.ok { border-left-color: #7FD44B; }
.kf-sp4r-verdict.ok b { color: #7FD44B; }
.kf-sp4r-verdict.warn { border-left-color: var(--kf-yellow); }
.kf-sp4r-verdict.warn b { color: var(--kf-yellow); }

.kf-sp4r-notes { list-style: none; margin: 14px 0 0; padding: 0; display: grid; gap: 6px;
    font: 400 12px/18px var(--kf-body); color: var(--kf-mute); }
.kf-sp4r-notes li::before { content: '· '; }

@media (max-width: 1240px) {
    .kf-sp4-sec .kf-sp4-split.kf-sp4r-head { grid-template-columns: minmax(0, 1fr) minmax(0, 560px); gap: 40px; }
}
@media (max-width: 1040px) {
    .kf-sp4-sec .kf-sp4-split.kf-sp4r-head { grid-template-columns: minmax(0, 1fr); gap: 32px; }
}
/* Stacked, the gamut sits between the copy and the instrument, so it reads as the lead-in to the
   plot rather than a leftover at the bottom of the text. */
@media (max-width: 1040px) {
    .kf-sp4r-gamut { max-width: 260px; margin-top: 28px; }
}
@media (max-width: 620px) {
    .kf-sp4r-gamut { max-width: 100%; display: flex; align-items: center; gap: 16px; }
    .kf-sp4r-gamut .kf-sp4r-cie__full { flex: 0 0 132px; }
    .kf-sp4r-gamut figcaption { margin-top: 0; }
    .kf-sp4r-ctl__row > .kf-sp4-preview__label { flex: 0 0 100%; }
}
@media (max-width: 760px) {
    .kf-sp4r-read { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ===============================================================================================
 * REVIEW §5 -- front-panel simulator.
 * The faceplate is the firmware's own 128x64 OLED, upscaled 4x with nearest-neighbour so the
 * emulated pixels stay the emulated pixels: smoothing them would hide exactly the detail the
 * pixel-for-pixel verification against the firmware captures is there to prove.
 * ============================================================================================ */
.kf-sp4r-panel__head { display: flex; flex-wrap: wrap; align-items: center;
    justify-content: space-between; gap: 10px 18px; }
.kf-sp4r-panel__head .kf-sp4-panel__title { margin: 0; }
.kf-sp4r-panel__pick { display: inline-flex; align-items: center; gap: 10px; }

/* The OLED is 128x64 and the canvas is a 4x backing store: past 512 CSS pixels the emulated
   pixels just get bigger, and the faceplate stops reading as hardware. */
/* Device left, the six feature cards right, so the card the demo is performing is on screen
   alongside the device performing it. 420px is what the card column needs at one per row; the rest
   goes to the bezel, which needs 512 for the OLED plus 104 for the encoder stack. */
.kf-sp4r-split { grid-template-columns: minmax(0, 1fr) 420px; gap: 44px; align-items: start;
    margin-top: 28px; }
.kf-sp4r-split > .kf-sp4-ops { grid-template-columns: minmax(0, 1fr); gap: 10px; margin: 0;
    align-content: start; }
.kf-sp4r-split .kf-sp4-op { padding: 14px 16px; }

.kf-sp4r-panel { display: grid; gap: 20px; margin-top: 20px; }

/* With the card gone the device sits directly in the wash, so the bezel carries its own shadow to
   lift it off the colour rather than floating on it. */
.kf-sp4r-stage .kf-sp4r-bezel { box-shadow: 0 18px 50px rgba(0,0,0,.45); }
/* Only the uncarded stage needs this: on the carded bands the panel is already its own ground. */
.kf-sp4r-stage:not(.kf-sp4r-card):not(.kf-sp4-stage--cie) .kf-sp4-panel__hint,
.kf-sp4r-stage:not(.kf-sp4r-card):not(.kf-sp4-stage--cie) .kf-sp4-axis {
    color: rgba(255,255,255,.72); }
.kf-sp4r-stage:not(.kf-sp4r-card):not(.kf-sp4-stage--cie) .kf-sp4r-verdict {
    background: rgba(0,0,0,.42); backdrop-filter: blur(5px);
    border-color: rgba(255,255,255,.14); color: rgba(255,255,255,.88); }

.kf-sp4r-bezel { position: relative; display: grid; grid-template-columns: minmax(0, 524px) 104px;
    justify-content: start;
    gap: 16px 22px; padding: 20px 20px 42px; border-radius: 14px;
    background: linear-gradient(#2c2d30, #1b1c1e); border: 1px solid #3d3e42; }
.kf-sp4r-bezel .kf-sp4-dev__badge { bottom: 16px; }

.kf-sp4r-oled { display: block; width: 100%; max-width: 512px; height: auto;
    image-rendering: pixelated; background: #0a0906; border: 6px solid #0c0c0d; border-radius: 4px; }

/* Keys sit under the screen, narrower than it and evenly spaced, with the tick mark printed on
   the faceplate above each one — that is where it is on the hardware. The keys carry no label of
   their own because the OLED's bottom row is their label strip. */
.kf-sp4r-keys { display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: auto auto;
    gap: 2px 16px; width: 78%; margin: 2px auto 0 10px; }
.kf-sp4r-keys__mark { grid-row: 1; text-align: center; font: 400 13px/13px var(--kf-body);
    color: rgba(255,255,255,.34); letter-spacing: 1px; }
.kf-sp4r-key { grid-row: 2; height: 26px; border-radius: 13px; border: 1px solid #56575c;
    touch-action: none; background: linear-gradient(#46474b, #2d2e31); cursor: pointer; }
.kf-sp4r-key.is-down { background: #1f2022; transform: translateY(1px); }
.kf-sp4r-key:focus-visible { outline: 2px solid var(--kf-yellow); outline-offset: 3px; }

/* POWER and DATA sit at the very top of the right-hand column, above the top encoder, which is
   where they are on the device. The top encoder is the smaller of the two. */
.kf-sp4r-knobs { grid-column: 2; grid-row: 1 / span 2; display: flex; flex-direction: column;
    align-items: center; gap: 6px; }
.kf-sp4r-knobs .kf-sp4-leds { margin-bottom: 10px; }
.kf-sp4r-knob { position: relative; border-radius: 50%; cursor: grab; touch-action: none;
    border: 1px solid #56575c;
    background: radial-gradient(circle at 40% 35%, #4b4c51, #222326 70%); }
.kf-sp4r-knob--top { width: 62px; height: 62px; }
.kf-sp4r-knob--bottom { width: 82px; height: 82px; }
.kf-sp4r-knob::after { content: ''; position: absolute; left: 50%; top: 7px; width: 4px; height: 14px;
    margin-left: -2px; border-radius: 2px; background: #d9d9d9;
    transform-origin: 2px 24px; transform: rotate(var(--rot, 0deg)); }
.kf-sp4r-knob--bottom::after { height: 18px; transform-origin: 2px 34px; }
.kf-sp4r-knob:active { cursor: grabbing; }
.kf-sp4r-knob:focus-visible { outline: 2px solid var(--kf-yellow); outline-offset: 3px; }
.kf-sp4r-klabel { font: 500 11px/14px var(--kf-body); color: var(--kf-mute); margin-bottom: 4px; }

.kf-sp4r-pside { min-width: 0; }
/* "Screen: Home" -- where the device has got to, nothing more. */
.kf-sp4r-screen { display: inline-flex; align-items: baseline; gap: 10px; margin: 12px 0 0;
    padding: 8px 14px; border-radius: var(--kf-card-radius, 4px);
    background: rgba(0,0,0,.34); border: 1px solid var(--sp4-hair); }
.kf-sp4r-screen b { font-family: var(--kf-display); font-weight: 700; text-transform: uppercase;
    font-size: 13px; letter-spacing: .6px; color: #fff; }
.kf-sp4r-screen .kf-sp4-preview__label { flex: 0 0 auto; }

@media (max-width: 1180px) {
    .kf-sp4r-split { grid-template-columns: minmax(0, 1fr); gap: 30px; }
    .kf-sp4r-split > .kf-sp4-ops { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 760px) {
    .kf-sp4r-bezel { grid-template-columns: minmax(0, 1fr); padding: 16px 16px 40px; }
    /* Stacked, the knob column becomes a knob ROW, and a flex wrap leaves each label beside its
       knob rather than under it. Place the five children explicitly: indicators across the top,
       then a knob over its own label in each column. */
    .kf-sp4r-knobs { display: grid; grid-template-columns: repeat(2, auto); grid-row: auto;
        grid-column: 1; justify-content: center; align-items: center; gap: 4px 30px; }
    .kf-sp4r-knobs > :nth-child(1) { grid-area: 1 / 1 / 2 / -1; display: flex; gap: 20px;
        justify-content: center; width: auto; margin-bottom: 10px; }
    .kf-sp4r-knobs > :nth-child(2) { grid-area: 2 / 1; }
    .kf-sp4r-knobs > :nth-child(3) { grid-area: 3 / 1; }
    .kf-sp4r-knobs > :nth-child(4) { grid-area: 2 / 2; }
    .kf-sp4r-knobs > :nth-child(5) { grid-area: 3 / 2; }
    .kf-sp4r-split > .kf-sp4-ops { grid-template-columns: minmax(0, 1fr); }
}

/* ===============================================================================================
 * REVIEW §4 -- on camera. The claim sits beside the heading so it is read before the toy, and the
 * test conditions sit under the band, because the claim is only true with them attached.
 * ============================================================================================ */
.kf-sp4r-claim { font-family: var(--kf-display); font-weight: 700; font-size: 20px; line-height: 28px;
    color: #fff; margin: 0; padding-left: 20px; border-left: 3px solid var(--kf-yellow); }

.kf-sp4r-shutter { position: relative; }
.kf-sp4r-smarks { position: relative; height: 34px; margin-top: 6px; }
.kf-sp4r-smarks span { position: absolute; top: 0; transform: translateX(-50%); text-align: center;
    font: 500 10px/13px var(--kf-body); color: var(--kf-mute); white-space: nowrap; }
.kf-sp4r-smarks span::before { content: ''; display: block; width: 1px; height: 7px; margin: 0 auto 4px;
    background: rgba(255,255,255,.3); }
.kf-sp4r-smarks span b { display: block; color: rgba(255,255,255,.8); font-weight: 700; }

.kf-sp4r-sread { font-family: var(--kf-display); font-weight: 700; font-size: 18px; line-height: 24px;
    color: #fff; margin: 14px 0 0; }

.kf-sp4r-cams { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px;
    margin-top: 18px; }
.kf-sp4r-cam { margin: 0; min-width: 0; }
.kf-sp4r-cam canvas { display: block; width: 100%; height: auto; border-radius: 4px;
    border: 1px solid var(--sp4-hair); background: #0b0b0a; }
.kf-sp4r-cam figcaption { display: flex; flex-wrap: wrap; gap: 4px 10px; align-items: baseline;
    margin-top: 10px; font: 400 13px/18px var(--kf-body); color: var(--kf-mute); }
.kf-sp4r-cam figcaption b { font-family: var(--kf-display); font-weight: 700; text-transform: uppercase;
    font-size: 13px; letter-spacing: .6px; color: #fff; }

.kf-sp4r-foot { margin-top: 18px; }

@media (max-width: 760px) {
    .kf-sp4r-cams { grid-template-columns: minmax(0, 1fr); }
    .kf-sp4r-claim { font-size: 17px; line-height: 24px; padding-left: 14px; }
}

/* ===============================================================================================
 * REVIEW -- the supplied Spectrum OS 4.0 lockup (1585-004, 2026-10-15), in place of the type
 * treatment the hero had been standing in with.
 *
 * The lockup is a fixed-aspect raster: the dot wheel is a gradient the .ai carries as a raster too,
 * so there is no vector version to serve and no good reason to redraw a brand asset by hand. It is
 * served at 1200w for a ~460px slot, which is 2x on a retina screen.
 * ============================================================================================ */
.kf-sp4r-mark { gap: 0; }
.kf-sp4r-logo { margin: 0; line-height: 0; }
.kf-sp4r-logo img {
    display: block; width: 460px; max-width: 100%; height: auto;
    /* the export carries no clear space of its own, so the layout provides it */
    margin-left: -6px;
}
@media (max-width: 960px) { .kf-sp4r-logo img { width: 360px; } }
@media (max-width: 560px) { .kf-sp4r-logo img { width: 280px; } }
