Skip to content

Commit f4cb970

Browse files
committed
feat(site): the fifty-thousand-dollar pass — words, numbers, curtains, a rail
- the big headings arrive word by word, each from behind its own baseline on a long soft curve; the eyebrow's dash draws itself and the lede settles last. Split at runtime, so the German edition splits its own words - a spec line after the hero: 24 zones · 192 kHz · 8 output protocols · 0 cloud, counting up from zero as the band enters - real screenshots open like a window: clip-path curtain instead of a blur, ending past the box so the shadows survive - a chapter rail along the right edge — one quiet dot per chapter, built from the nav so it labels itself in both languages - all of it stands still under prefers-reduced-motion
1 parent 2bd765b commit f4cb970

4 files changed

Lines changed: 544 additions & 9 deletions

File tree

assets/site.css

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@
5252
--sans: 'Hanken Grotesk', -apple-system, BlinkMacSystemFont, system-ui, 'Segoe UI', Helvetica, Arial, sans-serif;
5353
--mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
5454
--ease: cubic-bezier(0.2, 0.8, 0.2, 1);
55+
/* The luxury curve: fast out of the gate, a long soft landing. For the big arrivals. */
56+
--ease-lux: cubic-bezier(0.16, 1, 0.3, 1);
5557

5658
--page: 1180px;
5759
--pad: clamp(20px, 5vw, 56px);
@@ -171,6 +173,83 @@ footer,
171173
box-shadow: 0 0 12px rgb(74 222 128 / 55%);
172174
}
173175

176+
/* --- the chapter rail: one quiet dot per chapter, right edge ------------------ */
177+
178+
/* Built by the script from the nav itself; never exists on small screens. */
179+
.rail {
180+
position: fixed;
181+
top: 50%;
182+
right: 16px;
183+
z-index: 30;
184+
display: grid;
185+
gap: 13px;
186+
transform: translateY(-50%);
187+
}
188+
189+
.rail a {
190+
position: relative;
191+
display: grid;
192+
place-items: center;
193+
width: 15px;
194+
height: 15px;
195+
}
196+
197+
.rail a i {
198+
width: 5px;
199+
height: 5px;
200+
border-radius: 50%;
201+
background: rgb(255 255 255 / 20%);
202+
transition:
203+
background 0.3s,
204+
box-shadow 0.3s,
205+
transform 0.3s var(--ease);
206+
}
207+
208+
.rail a:hover i {
209+
background: rgb(255 255 255 / 55%);
210+
transform: scale(1.4);
211+
}
212+
213+
.rail a.now i {
214+
background: var(--accent);
215+
box-shadow: 0 0 10px rgb(74 222 128 / 70%);
216+
transform: scale(1.4);
217+
}
218+
219+
.rail a span {
220+
position: absolute;
221+
right: 26px;
222+
top: 50%;
223+
padding: 4px 10px;
224+
border: 1px solid var(--line-soft);
225+
border-radius: 6px;
226+
background: rgb(10 12 16 / 92%);
227+
font-family: var(--mono);
228+
font-size: 9.5px;
229+
letter-spacing: 0.14em;
230+
text-transform: uppercase;
231+
white-space: nowrap;
232+
color: var(--muted);
233+
opacity: 0;
234+
transform: translate(6px, -50%);
235+
transition:
236+
opacity 0.25s var(--ease),
237+
transform 0.25s var(--ease);
238+
pointer-events: none;
239+
}
240+
241+
.rail a:hover span,
242+
.rail a:focus-visible span {
243+
opacity: 1;
244+
transform: translate(0, -50%);
245+
}
246+
247+
@media (max-width: 1099px) {
248+
.rail {
249+
display: none;
250+
}
251+
}
252+
174253
/* --- shared type --------------------------------------------------------- */
175254

176255
/*
@@ -338,6 +417,78 @@ section[data-num]::before {
338417
max-width: 760px;
339418
}
340419

420+
/* --- the spec line: four numbers that are the whole pitch --------------------- */
421+
422+
.stats-band {
423+
padding: 0;
424+
background: rgb(255 255 255 / 1.2%);
425+
}
426+
427+
.stats-in {
428+
display: grid;
429+
grid-template-columns: repeat(4, 1fr);
430+
padding-block: clamp(30px, 5vh, 52px);
431+
}
432+
433+
.stat {
434+
padding: 6px clamp(16px, 2.6vw, 38px);
435+
}
436+
437+
.stat + .stat {
438+
border-left: 1px solid var(--line-soft);
439+
}
440+
441+
/* The numbers are cut thin and from the same light as the headings. */
442+
.stat-n {
443+
display: flex;
444+
align-items: baseline;
445+
gap: 7px;
446+
font-size: clamp(38px, 5vw, 66px);
447+
font-weight: 250;
448+
letter-spacing: -0.03em;
449+
line-height: 1;
450+
font-variant-numeric: tabular-nums;
451+
background: linear-gradient(180deg, #fff 28%, #97a0ad 115%);
452+
-webkit-background-clip: text;
453+
background-clip: text;
454+
-webkit-text-fill-color: transparent;
455+
}
456+
457+
.stat-n b {
458+
font-weight: inherit;
459+
}
460+
461+
.stat-n i {
462+
font-style: normal;
463+
font-family: var(--mono);
464+
font-size: 0.3em;
465+
font-weight: 500;
466+
letter-spacing: 0.08em;
467+
color: var(--accent);
468+
-webkit-text-fill-color: var(--accent);
469+
}
470+
471+
.stat-k {
472+
margin-top: 12px;
473+
font-family: var(--mono);
474+
font-size: 10.5px;
475+
font-weight: 500;
476+
letter-spacing: 0.14em;
477+
text-transform: uppercase;
478+
color: var(--dim);
479+
}
480+
481+
@media (max-width: 860px) {
482+
.stats-in {
483+
grid-template-columns: 1fr 1fr;
484+
row-gap: 30px;
485+
}
486+
487+
.stat:nth-child(3) {
488+
border-left: 0;
489+
}
490+
}
491+
341492
.cols {
342493
display: grid;
343494
gap: clamp(16px, 2vw, 22px);
@@ -382,6 +533,115 @@ section[data-num]::before {
382533
filter: none;
383534
}
384535

536+
/*
537+
* The section heads conduct themselves instead of arriving as one block: the eyebrow's
538+
* dash draws itself, the headline comes up word by word from behind its own baseline,
539+
* and the lede settles last. The word spans only exist once the script has split them,
540+
* so a static page keeps its sentences whole.
541+
*/
542+
.js .section-head.rise,
543+
.js .closing .rise {
544+
opacity: 1;
545+
transform: none;
546+
filter: none;
547+
transition: none;
548+
}
549+
550+
.js .section-head.rise .eyebrow,
551+
.js .closing .rise .eyebrow {
552+
opacity: 0;
553+
transform: translateY(8px);
554+
transition:
555+
opacity 0.6s var(--ease),
556+
transform 0.6s var(--ease);
557+
}
558+
559+
.js .section-head.rise .eyebrow::before,
560+
.js .closing .rise .eyebrow::before {
561+
width: 0;
562+
transition: width 0.8s var(--ease) 0.25s;
563+
}
564+
565+
.js .section-head.rise .lede,
566+
.js .closing .rise .lede,
567+
.js .closing .rise .actions {
568+
opacity: 0;
569+
transform: translateY(16px);
570+
transition:
571+
opacity 0.8s var(--ease) 0.4s,
572+
transform 0.8s var(--ease) 0.4s;
573+
}
574+
575+
.js .closing .rise .actions {
576+
transition-delay: 0.55s;
577+
}
578+
579+
.js .section-head.rise.in .eyebrow,
580+
.js .closing .rise.in .eyebrow,
581+
.js .section-head.rise.in .lede,
582+
.js .closing .rise.in .lede,
583+
.js .closing .rise.in .actions {
584+
opacity: 1;
585+
transform: none;
586+
}
587+
588+
.js .section-head.rise.in .eyebrow::before,
589+
.js .closing .rise.in .eyebrow::before {
590+
width: 18px;
591+
}
592+
593+
/* Each word in its own mask. The gradient moves to the word, so the mask can clip it. */
594+
.h2.words {
595+
background: none;
596+
}
597+
598+
.h2.words .w {
599+
display: inline-block;
600+
overflow: hidden;
601+
vertical-align: top;
602+
padding-bottom: 0.12em;
603+
margin-bottom: -0.12em;
604+
}
605+
606+
.h2.words .w-in {
607+
display: inline-block;
608+
background: linear-gradient(180deg, #fff 30%, #aab2bf 120%);
609+
-webkit-background-clip: text;
610+
background-clip: text;
611+
-webkit-text-fill-color: transparent;
612+
transform: translateY(118%) rotate(2.5deg);
613+
transform-origin: 0 100%;
614+
transition: transform 0.9s var(--ease-lux);
615+
transition-delay: calc(0.08s + var(--wi, 0) * 0.05s);
616+
}
617+
618+
.js .section-head.rise.in .w-in,
619+
.js .closing .rise.in .w-in {
620+
transform: none;
621+
}
622+
623+
/*
624+
* Real pixels get a curtain instead of a blur: the screenshot stands cropped into its
625+
* frame and the window opens as you arrive. The final inset is negative on purpose —
626+
* the clip ends beyond the box, so drop shadows survive the reveal.
627+
*/
628+
.js .reveal {
629+
clip-path: inset(14% 11% 14% 11% round var(--rvr, 18px));
630+
transform: translateY(28px) scale(1.04);
631+
transition:
632+
clip-path 1.15s var(--ease-lux),
633+
transform 1.15s var(--ease-lux);
634+
}
635+
636+
.js .reveal.in {
637+
clip-path: inset(-160px -160px -160px -160px round var(--rvr, 18px));
638+
transform: none;
639+
}
640+
641+
.phone-wrap.reveal {
642+
--rvr: 44px;
643+
}
644+
385645
/* --- top bar ------------------------------------------------------------- */
386646

387647
.bar {
@@ -3019,6 +3279,27 @@ footer {
30193279
transition: none;
30203280
}
30213281

3282+
.js .section-head.rise .eyebrow,
3283+
.js .closing .rise .eyebrow,
3284+
.js .section-head.rise .lede,
3285+
.js .closing .rise .lede,
3286+
.js .closing .rise .actions {
3287+
opacity: 1;
3288+
transform: none;
3289+
transition: none;
3290+
}
3291+
3292+
.js .section-head.rise .eyebrow::before,
3293+
.js .closing .rise .eyebrow::before {
3294+
width: 18px;
3295+
}
3296+
3297+
.js .reveal {
3298+
clip-path: none;
3299+
transform: none;
3300+
transition: none;
3301+
}
3302+
30223303
/* The mark, the headline and the chain stand finished instead of performing. */
30233304
.mark-roof {
30243305
animation: none;

0 commit comments

Comments
 (0)