/* io · IO Custom 68. Стили шаблона kestrel-65 с правками под бренд.
   Правки шаблона помечены «шаблон: …», всё новое — в конце файла. */
@font-face{font-family:'Montserrat';font-style:normal;font-weight:100 900;font-display:swap;
src:url(../fonts/montserrat-cyrillic.woff2) format('woff2');
unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}
@font-face{font-family:'Montserrat';font-style:normal;font-weight:100 900;font-display:swap;
src:url(../fonts/montserrat-latin.woff2) format('woff2');
unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
/* знак ₽ лежит в latin-ext — вырезан в отдельные маленькие файлы */
@font-face{font-family:'Montserrat';font-style:normal;font-weight:100 900;font-display:swap;
src:url(../fonts/montserrat-rub.woff2) format('woff2');unicode-range:U+20BD}
@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:400 600;font-display:swap;
src:url(../fonts/jetbrains-mono-cyrillic.woff2) format('woff2');
unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}
@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:400 600;font-display:swap;
src:url(../fonts/jetbrains-mono-latin.woff2) format('woff2');
unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}
@font-face{font-family:'JetBrains Mono';font-style:normal;font-weight:400 600;font-display:swap;
src:url(../fonts/jetbrains-mono-rub.woff2) format('woff2');unicode-range:U+20BD}
:root{
--nav:62px; --footH:60px;
--bg:#070809; --paper:#e8e6e1; --ink:#f4f3f0; --ink-2:#8f9299; --ink-3:#4e535a;
--line:#191c20; --accent:#40c8ec; --hot:#fe3ca8;
--mono:'JetBrains Mono',ui-monospace,Consolas,monospace;
--disp:'Montserrat',-apple-system,'Segoe UI',sans-serif;
--pad:clamp(22px,4.4vw,74px);
--rail:clamp(48px,5vw,86px);
}
*{margin:0;padding:0;box-sizing:border-box}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}
html,body{overflow-x:clip}
body{background:var(--bg);color:var(--ink);font-family:var(--disp);
-webkit-font-smoothing:antialiased;font-synthesis:none}
/* ── the stack ──────────────────────────────────────────────────────────────
-5  the per-chapter field
-4  the column grid
-3  the hero's hex floor · background visual only, it takes no input
-2  the display word · the product occludes it, which is the whole point
-1  nothing
0  the WebGPU canvas, transparent where there is no geometry
1  the 2D effects canvas
2  the page                                                              */
/* A hairline column grid · a print device, and the
reason the page reads as a specimen sheet rather than a landing page. They
are fixed, so the content moves against a frame that does not. */
#bg{position:fixed;inset:0;width:100vw;height:100vh;z-index:-5;display:block}
/* the hero floor · sits over the field and UNDER the display word, so the
name still reads on top of it */
#hex{position:fixed;inset:0;width:100vw;height:100vh;z-index:-3;display:block;
opacity:0;pointer-events:none}
#grid{position:fixed;inset:0;z-index:-4;pointer-events:none;
background:repeating-linear-gradient(90deg,
rgba(255,255,255,.030) 0 1px, transparent 1px calc(100% / 12));
background-position:var(--rail) 0;background-size:calc(100vw - var(--rail)) 100%;
background-repeat:no-repeat;
mask-image:linear-gradient(180deg,transparent,#000 12%,#000 88%,transparent)}
#behind{position:fixed;inset:0;z-index:-2;pointer-events:none;overflow:hidden}
/* The word sits BEHIND the render · the product occludes it, so it can be set
at poster scale without ever covering the thing it names. Kept low contrast
and given a vertical falloff so it reads as ground, not as a headline. */
#behind b{position:absolute;left:var(--rail);top:var(--wy,50%);
transform:translateY(-50%);
display:block;font-weight:800;letter-spacing:.055em;line-height:.80;
/* the vh term keeps it a plate rather than a wall on a short window, which
is also what lets it sit clear of the copy */
font-size:calc(min(268px, 16.8vw, 30vh) * var(--wsc,1));white-space:nowrap;
color:transparent;width:max-content;
--g:linear-gradient(180deg,#7a8896 0%,#3d4650 52%,#161b21 100%);
/* --wl is how loud the name is · full while the product is still building
itself, settled back into the ground once it is finished */
--wl:.62;filter:brightness(var(--wl));
transition:opacity .5s,letter-spacing 1.6s cubic-bezier(.16,1,.3,1)}
#behind b.on{letter-spacing:-.03em}
/* one span per letter · a display word that only fades is a static plate.
Each letter lifts out of a blur on its own beat, so the name ASSEMBLES,
and it does it again every time the chapter changes it. */
#behind b span{display:inline-block;
background-image:var(--g);-webkit-background-clip:text;background-clip:text;
color:transparent;opacity:0;transform:translateY(.20em) scaleY(1.14);
filter:blur(18px);
transition:opacity .95s ease var(--d,0s),
transform 1.25s cubic-bezier(.16,1,.3,1) var(--d,0s),
filter 1.05s ease var(--d,0s)}
#behind b.on span{opacity:1;transform:none;filter:none}
#cv{position:fixed;inset:0;width:100vw;height:100vh;z-index:0;display:block}
#fx{position:fixed;inset:0;width:100vw;height:100vh;z-index:1;pointer-events:none}
/* ── the rail · index, progress and nav in one hairline ─────────────────── */
.rail{position:fixed;left:0;top:0;bottom:0;width:var(--rail);z-index:20;
border-right:1px solid var(--line);display:flex;flex-direction:column;
align-items:center;justify-content:space-between;padding:20px 0 22px;
background:linear-gradient(90deg,rgba(7,8,9,.92),rgba(7,8,9,.42))}
.mark{writing-mode:vertical-rl;font-family:var(--mono);font-size:10px;letter-spacing:.34em;
text-transform:uppercase;color:var(--ink)}
.idx{display:flex;flex-direction:column;gap:0;flex:1;justify-content:center;width:100%}
.idx button{position:relative;height:38px;width:100%;font-family:var(--mono);font-size:11px;
letter-spacing:.14em;color:var(--ink-3);transition:color .3s}
.idx button::before{content:'';position:absolute;left:0;top:50%;width:0;height:1px;
background:var(--ink);transition:width .4s cubic-bezier(.2,.7,.3,1)}
.idx button:hover{color:var(--ink-2)}
.idx button[aria-current="true"]{color:var(--ink)}
.idx button[aria-current="true"]::before{width:13px}
.idx button:focus-visible{outline:1px solid var(--accent);outline-offset:-3px}
.railfoot{font-family:var(--mono);font-size:9px;letter-spacing:.2em;color:var(--ink-3);
writing-mode:vertical-rl}
main{position:relative;z-index:2;padding-left:var(--rail)}
section{position:relative;padding:0 var(--pad)}
/* the header is fixed and opaque · a section that centres its content in the
FULL viewport puts the top of that content underneath it */
.chapter{min-height:100vh;display:flex;align-items:center;padding-top:var(--nav)}
.tall{height:340vh}
.tall .stick{position:sticky;top:0;height:100vh;display:flex;align-items:center}
/* a chapter whose product needs the whole frame puts its copy at the foot */
/* ── where the copy sits, per chapter ──────────────────────────────────────
Type in the same corner ten times reads as a template. Each chapter places
its block against where the product actually is, the way a poster would. */
.tall .stick.foot{align-items:flex-end;padding-bottom:56px}
.stick.pTop{align-items:flex-start;padding-top:calc(var(--nav) + 38px)}
.stick.pBot{align-items:flex-end;padding-bottom:64px}
.stick.pRight{justify-content:flex-end;text-align:right}
.stick.pRight .col{margin-left:auto}
.stick.pRight .groups{direction:rtl}
.stick.pRight .groups button{direction:ltr;text-align:right}
.stick.pRight .curve,.stick.pRight .wave{margin-left:auto}
.stick.pRight .angleRead{justify-content:flex-end}
/* The decay trace is an INSTRUMENT, not part of the paragraph. Left in the
copy flow it travelled with the type and landed on the open case; pinned to
the clear ground under the cutaway it can be read against nothing. */
#sound .stick{position:sticky}
#sound .curveMeta{justify-content:flex-end;gap:34px}
#sound .wave{position:absolute;right:clamp(28px,4.4vw,80px);
bottom:clamp(96px,15vh,190px);width:min(520px,40vw);height:150px;margin:0}
#sound .curveMeta{position:absolute;right:clamp(28px,4.4vw,80px);
bottom:clamp(52px,9vh,132px);width:min(520px,40vw);margin:0}
/* A chapter whose whole point is READING the product cannot put its copy on
top of the product. The band lays the copy across the top and leaves the
lower two thirds of the frame to the board. */
.tall .stick.band{align-items:flex-start;padding-top:calc(var(--nav) + 36px)}
.stick.band>div{width:100%}
.bandRow{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
gap:clamp(24px,5vw,90px);align-items:start}
.bandRow h2{max-width:18ch;margin-top:0}
.bandRow .body{margin-top:0}
.groupsRow{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:var(--line);
border:1px solid var(--line);margin-top:26px}
.groupsRow button{background:var(--bg);padding:14px 16px;text-align:left;
transition:background .35s,color .35s;color:var(--ink-3)}
.groupsRow button[aria-current="true"]{background:rgba(64,200,236,.13);color:var(--ink)}
@media (max-width:900px){
/* ── a phone gets smaller TYPE, not a smaller product ────────────────────
Backing the camera off to make room was solving the wrong half: the
product is the subject and it keeps its size. Everything set in type
comes down instead. */
.heroTL h1{font-size:clamp(19px,5.4vw,27px);line-height:1.38;margin-top:16px}
.heroTL .tag{font-size:9.5px}
.spexL{margin-top:20px}
.spexL li,.spexR li{font-size:9.5px;letter-spacing:.14em;line-height:1.9}
/* one spec column on a phone · the second block had nowhere to go but onto
the keycaps, and everything it carried is either in the left column or in
the foot strip */
.spexR{display:none}
.col h2,.bandRow h2{font-size:clamp(21px,5.6vw,30px);line-height:1.16}
.body{font-size:13px;line-height:1.5}
.tag{font-size:9.5px}
.layers{margin-top:16px}
.layers button{padding:7px 8px;grid-template-columns:20px 1fr auto;gap:9px}
.layers b{font-size:12px}
.layers em,.layers span{font-size:9px}
.groupsRow button,.groups button{padding:9px 10px}
.groupsRow b,.groups b{font-size:13px}
.strip{height:30px;font-size:9px}
.strip span{padding:0 14px}
/* the switch chapter carries the most copy of any · headline, paragraph, a
force graph and three readouts. On a phone that is the whole screen, so
the graph gives up half its height */
#switch .curve{height:120px}
#switch .body{font-size:12px;line-height:1.45}
.curveMeta{font-size:9px;gap:10px}
}
@media (max-width:900px){
/* the band is a band only while there is width for one · stacked, it eats
the frame and the board climbs into the copy. This has to come AFTER the
base rules: same specificity, so source order decides. */
.bandRow{grid-template-columns:1fr;gap:14px}
.bandRow h2{max-width:none}
.groupsRow{grid-template-columns:repeat(2,1fr)}
}
.groupsRow b{display:block;font-size:15px;font-weight:500}
.groupsRow span{font-family:var(--mono);font-size:11.5px;letter-spacing:.1em}
.stick.pCentre{justify-content:center;text-align:center}
.stick.pCentre .col{margin:0 auto}
.stick.foot>div{width:100%}
.footRow{display:grid;grid-template-columns:minmax(0,50ch) auto;gap:clamp(24px,5vw,80px);
align-items:end}
.stick>div,.chapter>div{position:relative;opacity:var(--fade,1);transition:opacity .3s linear}
/* No scrim element. Legibility is handled in the POST PASS · the render dims
and drains inside a soft rounded region wherever the copy sits, so the
separation is part of the picture instead of a box laid over it. A container
behind the type is the lazy version and always reads as a container. */
/* every text block wipes in · the page is a motion piece, so the type moves
like the product does instead of sitting still while it performs */
.rv{clip-path:inset(0 0 calc((1 - var(--rv,1)) * 100%) 0);
transform:translateY(calc((1 - var(--rv,1)) * 16px));will-change:clip-path,transform}
@media (prefers-reduced-motion:reduce){.rv{clip-path:none;transform:none}}
/* ── the header ──────────────────────────────────────────────────────────
Small at rest · a wordmark, one control and the price. It opens as a real
object: the panel hinges down from the top edge on the X axis inside a
perspective, and each row hinges after it. The trigger is the same idea at
micro scale · a two-faced block that rolls from Menu to Close.
It NEVER hides or shrinks on scroll · a fixed bar stays fixed. */
#nav{position:fixed;left:var(--rail);right:0;top:0;height:var(--nav);z-index:30;
display:flex;align-items:center;gap:18px;padding:0 var(--pad) 0 clamp(16px,2vw,34px);
border-bottom:1px solid var(--line);
background:linear-gradient(180deg,rgba(7,8,9,.94),rgba(7,8,9,.72));
backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px)}
.brand{display:flex;align-items:baseline;gap:8px;text-decoration:none;color:var(--ink);
margin-right:auto}
.brand{align-items:center;gap:12px}
.brand img{display:block;height:27px;width:auto}
.brand span{font-family:var(--mono);font-size:10.5px;letter-spacing:.2em;text-transform:uppercase;color:var(--ink-2);
padding-left:12px;border-left:1px solid var(--line);line-height:18px}
.navT{height:32px;padding:0 15px;border:1px solid var(--line);border-radius:2px;
font-family:var(--mono);font-size:10.5px;letter-spacing:.2em;text-transform:uppercase;
color:var(--ink-2);transition:border-color .3s,color .3s;
perspective:320px}
.navT:hover{border-color:var(--ink-3);color:var(--ink)}
.navT:focus-visible{outline:1px solid var(--accent);outline-offset:2px}
.navFlip{display:block;position:relative;height:12px;width:66px;
transform-style:preserve-3d;transition:transform .62s cubic-bezier(.2,.8,.25,1)}
.navFlip i{position:absolute;inset:0;display:grid;place-items:center;font-style:normal;
backface-visibility:hidden}
.navFlip i:nth-child(1){transform:translateZ(6px)}
.navFlip i:nth-child(2){transform:rotateX(-90deg) translateZ(6px)}
.navT[aria-expanded="true"] .navFlip{transform:rotateX(90deg)}
.navCta{display:flex;align-items:baseline;gap:9px;height:32px;padding:0 16px;
border-radius:2px;background:var(--paper);color:#07080a;text-decoration:none;
font-size:13.5px;font-weight:620;line-height:32px}
.navCta em{font-family:var(--mono);font-style:normal;font-size:11px;opacity:.62}
.navP{position:absolute;left:0;right:0;top:100%;
background:rgba(9,11,13,.97);border-bottom:1px solid var(--line);
backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);
transform-origin:top center;transform:rotateX(-92deg);opacity:0;
pointer-events:none;
transition:transform .58s cubic-bezier(.2,.85,.25,1),opacity .3s ease .04s;
display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background-color:var(--line);
padding:1px 0 0}
#nav{perspective:1600px}
#nav.open .navP{transform:none;opacity:1;pointer-events:auto}
.navP button{background:rgba(9,11,13,.98);padding:22px clamp(16px,1.6vw,26px);text-align:left;
display:block;transform-origin:top center;transform:rotateX(-60deg);opacity:0;
transition:transform .5s cubic-bezier(.2,.85,.25,1) var(--d,0s),
opacity .34s ease var(--d,0s),background-color .3s}
#nav.open .navP button{transform:none;opacity:1}
.navP button:hover{background:rgba(18,22,27,.98)}
.navP button:focus-visible{outline:1px solid var(--accent);outline-offset:-3px}
.navP em{display:block;font-family:var(--mono);font-size:10px;letter-spacing:.22em;
color:var(--ink-3);font-style:normal}
.navP b{display:block;margin-top:9px;font-size:17px;font-weight:560;letter-spacing:-.015em}
.navP span{display:block;margin-top:5px;font-size:12.5px;color:var(--ink-2)}
@media (prefers-reduced-motion:reduce){
.navP,.navP button,.navFlip{transition:none;transform:none}
.navP{opacity:0}#nav.open .navP{opacity:1}
}
@media (max-width:860px){
.navP{grid-template-columns:repeat(2,1fr)}
.navCta em{display:none}
}
/* ── the type system · data is mono, prose is tight, nothing is huge except
the word behind the product ─────────────────────────────────────────── */
.tag{font-family:var(--mono);font-size:11px;letter-spacing:.24em;text-transform:uppercase;
color:var(--ink-3);white-space:nowrap}
.tag em{font-style:normal;color:var(--accent)}
h2{font-size:clamp(28px,2.9vw,46px);font-weight:640;letter-spacing:-.022em;line-height:1.06;
max-width:16ch;margin-top:18px;text-wrap:balance}
.body{font-size:16px;line-height:1.6;color:#a2a6ad;max-width:40ch;margin-top:20px}
.col{max-width:min(560px,46vw)}
/* the layer list carries a value column on its right, so it reaches further
than a paragraph does · it gets its own width */
#build .col{max-width:min(470px,30vw)}
/* ── the hero arrives on LOAD ───────────────────────────────────────────
It was wired to the same scroll-driven wipe as every other chapter, which
meant the headline was clipped to zero height until you scrolled · and by
the time it had wiped in, the hero was leaving the frame. The first screen
cannot wait for a gesture that has not happened. One `.in` switch, and each
block has its own delay. */
.hero h1 .ln{display:block;overflow:hidden;padding-bottom:.06em;margin-bottom:-.06em}
.hero h1 .ln i{display:block;font-style:normal;transform:translateY(115%);
transition:transform 1.2s cubic-bezier(.16,1,.3,1) var(--d,0s)}
.hero.in h1 .ln i{transform:none}
.hero .tag,.hero .lead li,.hero .spexR li,.hero .strip,.hero .heroCta{opacity:0;
transform:translateY(16px);
transition:opacity .85s ease var(--d,0s),
transform 1.05s cubic-bezier(.16,1,.3,1) var(--d,0s)}
.hero.in .tag,.hero.in .lead li,.hero.in .spexR li,.hero.in .strip,.hero.in .heroCta{opacity:1;transform:none}
.hero .tag{--d:.06s}
.hero h1 .ln:nth-child(1) i{--d:.20s}
.hero h1 .ln:nth-child(2) i{--d:.33s}
.hero .lead li:nth-child(1){--d:.56s}
.hero .lead li:nth-child(2){--d:.62s}
.hero .lead li:nth-child(3){--d:.68s}
.hero .lead li:nth-child(4){--d:.74s}
.hero .lead li:nth-child(5){--d:.80s}
.hero .spexR li:nth-child(1){--d:.78s}
.hero .spexR li:nth-child(2){--d:.84s}
.hero .spexR li:nth-child(3){--d:.90s}
.hero .spexR li:nth-child(4){--d:.96s}
.hero .spexR li:nth-child(5){--d:1.02s}
.hero .heroCta{--d:.84s}
.hero .strip{--d:.96s}
/* the last chapter arrives the same way · it has no scroll of its own to be
revealed by, so it reveals when it comes into view */
#order .orderCol>*{opacity:0;transform:translateY(16px);
transition:opacity .8s ease var(--d,0s),
transform 1s cubic-bezier(.16,1,.3,1) var(--d,0s)}
#order.in .orderCol>*{opacity:1;transform:none}
#order .orderCol>*:nth-child(1){--d:.04s}
#order .orderCol>*:nth-child(2){--d:.14s}
#order .orderCol>*:nth-child(3){--d:.26s}
#order .orderCol>*:nth-child(4){--d:.38s}
#order .orderCol>*:nth-child(5){--d:.48s}
#order .orderCol>*:nth-child(6){--d:.56s}
@media (prefers-reduced-motion:reduce){
#order .orderCol>*{transition:none;transform:none;opacity:1}
}
@media (prefers-reduced-motion:reduce){
.hero h1 .ln i,.hero .tag,.hero .lead li,.hero .spexR li,.hero .strip,.hero .heroCta,
#behind b span{transition:none;transform:none;opacity:1;filter:none}
}
/* hero · corners only, nothing over the product */
/* The hero's copy is absolutely positioned inside a section that scrolls while
the camera is fixed, so on the way out the type · the foot strip especially ·
slides straight down across the keycaps. The whole section fades as it
leaves. It sits on the SECTION so it multiplies with each block's own
load-in opacity instead of fighting it. */
.hero{min-height:100vh;opacity:var(--fade,1)}
/* fit-content, not the default block width · a full-width box behind three
words of mono claims screen the product is standing in, and the overlap
gate is right to count it */
.heroTL{position:absolute;left:var(--pad);top:calc(var(--nav) + 22px);width:fit-content}
.heroTL h1,.heroTL .tag,.heroTL .spexL,.heroTL .spexL li{width:fit-content}
/* The hero is a spec plate: the headline is set in the same machine face as
every number on the page, in caps, tracked out. A tight display serif-ish
grotesque said "campaign"; this says "instrument", which is what the
product is. */
.heroTL h1{font-family:var(--mono);font-size:clamp(25px,3.35vw,53px);font-weight:500;
letter-spacing:.035em;line-height:1.34;text-transform:uppercase;margin-top:22px}
.heroTL h1 i{color:var(--ink)}
.spexL{list-style:none;margin-top:30px}
.spexL li{font-family:var(--mono);font-size:11px;letter-spacing:.17em;text-transform:uppercase;
color:#98a0aa;line-height:2.05}
.spexR{list-style:none;position:absolute;right:var(--pad);bottom:78px;text-align:right}
.spexR li{font-family:var(--mono);font-size:11px;letter-spacing:.17em;text-transform:uppercase;
color:#98a0aa;line-height:2.05}
.spexR b{color:#d3d8de;font-weight:400;margin-left:10px}
/* two more spec lines that only exist where the second column does not ·
the media query has to come AFTER this rule to beat it at equal specificity */
.narrowOnly{display:none}
@media (max-width:900px){ .narrowOnly{display:list-item} }
/* a data strip along the foot of the hero · tabular, machine-set, and the
only place on the page where the type runs edge to edge */
.strip{position:absolute;left:0;right:0;bottom:0;height:36px;display:flex;
align-items:center;gap:0;border-top:1px solid var(--line);overflow:hidden;
font-family:var(--mono);font-size:10.5px;letter-spacing:.2em;text-transform:uppercase;
color:#8c949e;white-space:nowrap}
.strip span{padding:0 22px;border-right:1px solid var(--line);flex:none}
.strip b{color:#d3d8de;font-weight:500}
.heroBL{bottom:62px}
/* 01 · the layer index, wiped by a liquid fill as each becomes current */
.layers{margin-top:26px;border-top:1px solid var(--line)}
.layers button{display:grid;grid-template-columns:26px 1fr auto;gap:14px;align-items:baseline;
width:100%;text-align:left;padding:13px 10px;border-bottom:1px solid var(--line);
position:relative;overflow:hidden;color:var(--ink-3);transition:color .35s}
.layers button::after{content:'';position:absolute;inset:0;background:
linear-gradient(90deg,rgba(64,200,236,.16),rgba(64,200,236,.02));
transform:scaleX(0);transform-origin:left;transition:transform .55s cubic-bezier(.2,.7,.3,1)}
.layers button.on{color:var(--ink)}
.layers button.on::after{transform:scaleX(1)}
.layers em{font-family:var(--mono);font-style:normal;font-size:11.5px;position:relative;z-index:1}
.layers b{font-size:15.5px;font-weight:500;position:relative;z-index:1}
.layers span{font-family:var(--mono);font-size:12px;color:var(--ink-3);position:relative;z-index:1}
/* 02 · the force curve reads from the effects canvas, this is just its frame */
.curve{margin-top:28px;width:min(520px,46vw);height:196px;position:relative}
.wave{margin-top:28px;width:min(540px,46vw);height:176px;position:relative}
.groups{margin-top:24px;display:grid;grid-template-columns:1fr 1fr;gap:1px;
background:var(--line);border:1px solid var(--line)}
.groups button{background:var(--bg);padding:13px 14px;text-align:left;
transition:background .35s,color .35s;color:var(--ink-3)}
.groups button[aria-current="true"]{background:rgba(64,200,236,.13);color:var(--ink)}
.groups b{display:block;font-size:15px;font-weight:500}
.groups span{font-family:var(--mono);font-size:11.5px;letter-spacing:.1em}
.angleRead{margin-top:26px;display:flex;align-items:baseline;gap:16px}
.angleRead b{font-family:var(--mono);font-size:62px;font-weight:400;letter-spacing:-.02em;
font-variant-numeric:tabular-nums}
.angleRead span{font-family:var(--mono);font-size:11.5px;letter-spacing:.2em;
text-transform:uppercase;color:var(--ink-3)}
@media (max-width:900px){
/* 62px of numerals plus a tracked-out label does not fit a phone on one
line · the label drops under the reading */
.angleRead{flex-wrap:wrap;gap:6px 16px}
.angleRead b{font-size:52px}
.angleRead span{width:100%}
}
.curveMeta{display:flex;justify-content:space-between;margin-top:14px;font-family:var(--mono);
font-size:11.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-3)}
.curveMeta b{color:var(--ink);font-weight:500;font-variant-numeric:tabular-nums}
/* 03 · a glass plate, because the copy sits over a live split */
.plate{}   /* the render gates itself now · no glass panel */
.splitKey{display:flex;gap:24px;margin-top:20px;font-family:var(--mono);font-size:9.5px;
letter-spacing:.16em;text-transform:uppercase;color:var(--ink-3)}
.splitKey b{color:var(--ink);font-weight:500}
/* 05 · the colourways */
.ways{display:flex;gap:9px;margin-top:24px}
/* The swatch that is ON THE KEYBOARD has to be unmistakable · a slightly wider
tile with a slightly brighter hairline was not readable at a glance while
the scroll walked the set. The one in play keeps its colour and its name;
the rest drain. */
.ways button{width:0;height:112px;flex:1 1 0;max-width:230px;min-width:64px;border-radius:2px;
border:1px solid rgba(255,255,255,.10);position:relative;overflow:hidden;
filter:saturate(.28) brightness(.52);
transition:flex-grow .55s cubic-bezier(.2,.7,.3,1),filter .45s ease,
border-color .45s ease,box-shadow .45s ease}
.ways button[aria-pressed="true"]{flex-grow:3;filter:none;
border-color:rgba(255,255,255,.9);
box-shadow:0 0 0 1px rgba(255,255,255,.55), 0 10px 30px rgba(0,0,0,.5)}
.ways button i{position:absolute;inset:0;display:block}
.ways button em{position:absolute;left:0;right:0;bottom:0;padding:7px 9px;font-style:normal;
font-family:var(--mono);font-size:10px;letter-spacing:.18em;text-transform:uppercase;
color:#fff;background:linear-gradient(180deg,rgba(0,0,0,0),rgba(0,0,0,.72));
opacity:0;transition:opacity .4s ease;text-align:left;white-space:nowrap}
.ways button[aria-pressed="true"] em{opacity:1}
.ways button:focus-visible{outline:1px solid var(--accent);outline-offset:2px}
/* the name now lives ON the live swatch · this stays as the announcement for
anyone not looking at it */
.wayName{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);
white-space:nowrap}
#behindWord{-webkit-background-clip:text;background-clip:text}
/* 06 · the order table */
.orderCol{width:min(52ch,46vw)}
#order{min-height:100vh}
/* 46vw is 179px on a phone · the spec table was being squeezed into a column
narrower than its own rows */
@media (max-width:900px){
.orderCol{width:100%}
/* the buy screen is the copy · a phone gives it the room and the product
takes what is left */
.spec{margin-top:20px}
.spec div{padding:12px 0}
.spec dd{font-size:14.5px}
.spec dt{font-size:10px}
.cta{padding:15px 22px}
}
.spec{margin-top:30px;border-top:1px solid var(--line)}
.spec div{display:flex;justify-content:space-between;gap:20px;padding:17px 0;
border-bottom:1px solid var(--line)}
.spec dt{font-family:var(--mono);font-size:11.5px;letter-spacing:.18em;text-transform:uppercase;
color:#828a94}
.spec dd{font-family:var(--mono);font-size:16.5px}
.cta{display:inline-block;text-decoration:none;margin-top:0;padding:17px 30px;border-radius:2px;background:var(--paper);color:#07080a;
font-weight:640;font-size:15.5px;letter-spacing:-.01em;position:relative;overflow:hidden}
.cta span{position:relative;z-index:1}
.cta::after{content:'';position:absolute;inset:0;background:var(--accent);
transform:translateY(101%);transition:transform .42s cubic-bezier(.2,.7,.3,1)}
.cta:hover::after{transform:translateY(0)}
.cta:active{transform:scale(.99)}
/* The last screen has to BE the last screen · with 110px of margin plus deep
padding the footer pushed the buy panel's headline up under the fixed header
the moment you reached the bottom of the document. The section gives back
exactly the footer's height instead. */
/* The footer does not lengthen the document · it rises from the bottom edge
once the last chapter is on screen, so the buy panel and the footer occupy
one viewport instead of one and a bit. */
footer{position:fixed;left:var(--rail);right:0;bottom:0;z-index:12;
border-top:1px solid var(--line);padding:21px var(--pad) 22px;
display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap;
font-family:var(--mono);font-size:10px;letter-spacing:.14em;text-transform:uppercase;
color:#8c949e;background:linear-gradient(180deg,rgba(7,8,9,.62),rgba(7,8,9,.96));
backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);
transform:translateY(101%);transition:transform .55s cubic-bezier(.2,.8,.25,1)}
body.atEnd footer{transform:none}
@media (prefers-reduced-motion:reduce){ footer{transition:none} }
.loading{position:fixed;inset:0;z-index:40;display:grid;place-items:center;background:var(--bg);
font-family:var(--mono);font-size:10px;letter-spacing:.3em;text-transform:uppercase;
color:var(--ink-3);transition:opacity .7s}
.loading.gone{opacity:0;pointer-events:none}
@media (max-width:860px){
.tall{height:280vh}
#behind b{font-size:clamp(52px,17vw,120px)}
.heroTL{top:88px}
.curve{width:min(340px,72vw)}
}
/* ── short windows ────────────────────────────────────────────────────────
LAST in the sheet on purpose: these override base rules at equal
specificity, and a media block placed above the rule it overrides simply
loses. That has now cost three separate fixes in this file. */
@media (max-width:900px){ :root{ --footH:92px } }   /* the footer wraps */
@media (max-height:780px){
.orderCol h2{font-size:clamp(30px,3.4vw,44px)}
.spec{margin-top:18px}
.spec div{padding:11px 0}
.spec dd{font-size:15px}
.spec dt{font-size:10.5px}
.cta{padding:14px 24px}
}

/* ══ Правки под io ════════════════════════════════════════════════════════ */
a{color:inherit}
:focus-visible{outline:1px solid var(--accent);outline-offset:3px}
.spec{margin-top:26px}
.spec dd{text-align:right}
.layers button,.groupsRow button{cursor:default}
/* шаблон: слово за клавиатурой набрано системным гротеском; у Montserrat буквы
   шире, а «РАСКЛАДКА» и «КЕЙКАПЫ» длиннее английских слов */
#behind b{font-weight:800;font-size:calc(min(232px, 14.4vw, 28vh) * var(--wsc,1))}

/* первый экран: кнопка видна сразу — в шаблоне её здесь не было вовсе */
.heroCta{display:flex;flex-wrap:wrap;gap:10px;margin-top:30px;--d:.84s}
.btn{display:inline-flex;align-items:center;height:46px;padding:0 24px;border-radius:2px;
background:var(--paper);color:#07080a;text-decoration:none;font-size:14.5px;font-weight:640;
position:relative;overflow:hidden;white-space:nowrap}
.btn span{position:relative;z-index:1}
.btn::after{content:'';position:absolute;inset:0;background:var(--accent);
transform:translateY(101%);transition:transform .42s cubic-bezier(.2,.7,.3,1)}
.btn:hover::after{transform:translateY(0)}
.btn.ghost{background:rgba(7,8,9,.55);color:var(--ink);border:1px solid #2a2f36;font-weight:560}
.btn.ghost::after{background:rgba(64,200,236,.16)}
.heroTL h1{letter-spacing:.03em}
.spexL li,.spexR li{color:#a3abb5}

/* глава 08: вместо градусов — ёмкость аккумулятора */
.angleRead b{font-weight:500}

/* расцветок две: плашки шире, название длинное */
.ways{justify-content:flex-end}
.ways button em{font-size:9.5px;letter-spacing:.12em}

/* покупка */
.kit{margin-top:18px;font-size:13.5px;line-height:1.55;color:#9aa0a8;max-width:52ch}
.buy{display:flex;flex-wrap:wrap;gap:10px;margin-top:22px}
.cta.alt{background:transparent;color:var(--ink);border:1px solid #333941}
.cta.alt::after{background:rgba(64,200,236,.18)}
.where{margin-top:14px;font-family:var(--mono);font-size:11.5px;letter-spacing:.06em;color:#8f97a1}
.where a{color:var(--ink);text-underline-offset:3px}
.where a:hover{color:var(--accent)}
footer .links{display:flex;flex-wrap:wrap;gap:6px 22px}
footer a{color:#b9c0c9;text-decoration:none}
footer a:hover{color:var(--accent)}
.shot{display:none}
.curveMeta{gap:14px}

@media (max-width:900px){
.heroCta{margin-top:18px;gap:8px}
.btn{height:42px;padding:0 16px;font-size:13px}
.brand span{display:none}
.brand img{height:24px}
.navT{padding:0 10px}
.navCta{padding:0 12px}
.kit{font-size:12.5px;margin-top:14px}
.buy{margin-top:16px}
.cta{font-size:14px}
.where{font-size:10.5px}
footer{font-size:9.5px;padding:12px var(--pad) 14px}
.curveMeta{flex-wrap:wrap;gap:4px 12px}
}
@media (max-height:780px){
.kit{margin-top:12px;font-size:12.5px}
.buy{margin-top:14px}
.where{margin-top:10px}
}

/* шаблон: «loading» закрывал весь экран, пока грузится модель и собираются
   шейдеры · теперь это подпись на месте клавиатуры, текст виден сразу */
.loading{inset:auto;left:calc(var(--rail) + 52%);top:60%;background:none;display:block;pointer-events:none;color:var(--ink-2)}
.loading::after{content:'';display:inline-block;width:1.6em;margin-left:.5em;height:1px;background:var(--accent);
vertical-align:middle;animation:ld 1.1s ease-in-out infinite alternate}
@keyframes ld{from{transform:scaleX(.2);opacity:.4}to{transform:scaleX(1);opacity:1}}
@media (prefers-reduced-motion:reduce){.loading::after{animation:none}}
@media (max-width:900px){.loading{left:calc(var(--rail) + var(--pad));top:66%}}
.hero .heroCta{--d:.46s}

/* шаблон: подвал закреплён поверх последнего экрана, а место под него не
   оставлено — на телефоне он закрывал кнопки покупки */
#order{padding-bottom:calc(var(--footH) + 14px)}
.cta.alt{background:rgba(7,8,9,.74)}
@media (max-width:900px){
/* шаблон: колонка главы «Сборка» — 30vw, на телефоне это 117px, заголовок и
   список обрезались */
#build .col{max-width:none}
.col{max-width:none}
.layers b{font-size:13px}
/* расцветки: текст и плашки друг под другом, а не в две тесные колонки */
.footRow{grid-template-columns:1fr;gap:16px}
.ways{justify-content:flex-start}
.ways button{height:60px;max-width:none}
/* звук: график на телефоне в 150px шириной ничего не показывает */
#sound .wave{display:none}
#sound .curveMeta{left:var(--pad);right:var(--pad);width:auto;bottom:26px;justify-content:flex-start;gap:6px 16px}
.spec div{padding:10px 0}
.spec dd{font-size:13.5px}
.orderCol h2{margin-top:0}
}

/* шаблон: полоса характеристик внизу первого экрана обрывалась краем экрана
   посреди слова («СТРОЕНИЕ EXTEN» на телефоне, «СВЯЗЬ 2.4G» на 1440) —
   у правого края она растворяется */
.strip{-webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 110px),transparent);
mask-image:linear-gradient(90deg,#000 calc(100% - 110px),transparent)}
/* шаблон: единица у числа «3000» была набрана цветом --ink-3 и на тёмном
   фоне почти не читалась */
.angleRead span{color:var(--ink-2)}
@media (max-width:900px){
/* шаблон: на телефоне моноширинные подписи стояли 9–10,5 px */
.strip{font-size:11px;letter-spacing:.16em;
-webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 72px),transparent);
mask-image:linear-gradient(90deg,#000 calc(100% - 72px),transparent)}
.splitKey{font-size:11px;letter-spacing:.1em;gap:4px 14px;flex-wrap:wrap}
.ways button em{font-size:11px;letter-spacing:.08em}
.where{font-size:11px}
.navT{font-size:11px}
.navP em{font-size:11px}
.railfoot{font-size:11px}
.loading{font-size:11px}
/* подвал в три строки стал выше: место под него на экране покупки — тоже */
footer{font-size:11px;letter-spacing:.1em}
:root{--footH:100px}
}
@media (max-width:900px) and (max-height:780px){
.spec dt{font-size:11px}
}

/* ══ Без сцены ═════════════════════════════════════════════════════════════
   Нет WebGPU, не загрузилась модель или выключен скрипт — страница остаётся
   обычной страницей: главы идут друг за другом, вместо живой клавиатуры кадры
   из той же сцены. В шаблоне на этом месте был экран «нужен Chrome 113+». */
:is(html.nojs body, body.nogpu) :is(#cv,#fx,#hex,#behind,.loading,.curve,.wave,.live,.ways,.splitKey){display:none}
:is(html.nojs body, body.nogpu) .tall{height:auto}
:is(html.nojs body, body.nogpu) .tall .stick{position:static;height:auto;display:grid;
grid-template-columns:minmax(0,5fr) minmax(0,7fr);gap:clamp(20px,4vw,64px);align-items:center;
padding:clamp(56px,10vh,120px) 0;text-align:left}
:is(html.nojs body, body.nogpu) .tall .stick>div{width:auto;margin:0}
:is(html.nojs body, body.nogpu) .col{max-width:none}
:is(html.nojs body, body.nogpu) #sound .curveMeta{position:static;width:auto;justify-content:flex-start;grid-column:1}
body.nogpu .tall .shot{grid-column:2;grid-row:1 / span 2}
:is(html.nojs body, body.nogpu) .bandRow{grid-template-columns:1fr;gap:14px}
:is(html.nojs body, body.nogpu) .footRow{grid-template-columns:1fr}
:is(html.nojs body, body.nogpu) .layers button{color:var(--ink)}
:is(html.nojs body, body.nogpu) .groupsRow{grid-template-columns:repeat(2,1fr)}
:is(html.nojs body, body.nogpu) .groupsRow button{color:var(--ink-2)}
:is(html.nojs body, body.nogpu) footer{position:static;transform:none;margin-left:var(--rail)}
:is(html.nojs body, body.nogpu) #order{min-height:0;padding-top:clamp(56px,10vh,120px);padding-bottom:64px}
body.nogpu .shot{display:block;width:100%;height:auto}
body.nogpu .hero .shot{position:absolute;right:0;bottom:36px;width:min(74vw,1100px);z-index:-1}
html.nojs .hero :is(h1 .ln i,.lead li,.spexR li,.strip,.heroCta){opacity:1;transform:none}
html.nojs #order .orderCol>*{opacity:1;transform:none}
@media (max-width:900px){
:is(html.nojs body, body.nogpu) .tall .stick{grid-template-columns:1fr;padding:44px 0}
body.nogpu .tall .shot{grid-column:1;grid-row:auto}
body.nogpu .hero{min-height:0;padding-bottom:36px}
body.nogpu .hero .heroTL{position:relative;left:auto;top:auto;padding-top:calc(var(--nav) + 22px)}
body.nogpu .hero .shot{position:static;width:100%;margin:18px 0 26px}
}
