ncei hero

This commit is contained in:
Felix Klement committed 2026-10-04 13:06:04 +02:00
1 parent 2b509386eb
commit 00edb9dfc2
3 files changed
+109 -20

No files matched your search

+106 -19
View File
@@ -5,14 +5,57 @@ const { lang } = Astro.props;
const ui = t(lang);
const s = ui.verifier.soon;
const n = s.notify;
/* The background: a relief of thin ridge lines, computed once at build time. Each line is a row
of smooth value noise, lifted most on the right where the page is quiet and nearly flat under
the text on the left. Rows are drawn back to front and filled with night, so a nearer ridge
hides the one behind it. Colour runs through the logo blues, deep at the back to cyan in front. */
const W = 1600, H = 1000, rows = 46, steps = 160;
const hash = (x: number, y: number) => {
const h = Math.sin(x * 127.1 + y * 311.7) * 43758.5453;
return h - Math.floor(h);
};
const smooth = (t: number) => t * t * (3 - 2 * t);
const value = (x: number, y: number) => {
const xi = Math.floor(x), yi = Math.floor(y), xf = smooth(x - xi), yf = smooth(y - yi);
const a = hash(xi, yi), b = hash(xi + 1, yi), c = hash(xi, yi + 1), d = hash(xi + 1, yi + 1);
return a + (b - a) * xf + (c - a) * yf + (a - b - c + d) * xf * yf;
};
const fbm = (x: number, y: number) => value(x, y) * 0.6 + value(x * 2.1 + 5, y * 2.1) * 0.28 + value(x * 4.3 + 9, y * 4.3) * 0.12;
const ridges = Array.from({ length: rows }, (_, r) => {
const depth = r / (rows - 1);
const base = H * 0.22 + depth * H * 0.78;
const pts = Array.from({ length: steps + 1 }, (_, i) => {
const x = (i / steps) * W;
const u = x / W;
const envelope = Math.exp(-((u - 0.7) ** 2) / 0.045) * (0.35 + 0.65 * Math.sin(depth * Math.PI));
const lift = Math.max(0, fbm(u * 5.5, r * 0.22) - 0.3) * 480 * envelope;
return `${x.toFixed(1)},${(base - lift).toFixed(1)}`;
});
return {
d: `M${pts.join(' L')} L${W},${H} L0,${H} Z`,
mix: Math.round(depth * 100),
alpha: (0.35 + depth * 0.5).toFixed(2),
};
});
---
<section class="shero night" aria-labelledby="shero-title">
<div class="shero__media" aria-hidden="true">
<svg class="shero__relief" viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="xMidYMax slice" xmlns="http://www.w3.org/2000/svg">
{ridges.map((r) => (
<path d={r.d} style={`--mix: ${r.mix}%; --alpha: ${r.alpha}`} />
))}
</svg>
<div class="shero__overlay"></div>
</div>
<div class="container shero__inner">
<!-- First in the document, last on screen: the product name, set across the full width. -->
<h1 id="shero-title" class="shero__name">{s.title[0].replace(/\.$/, '')}<span class="visually-hidden">. {s.title[1]}</span></h1>
<div class="shero__text">
<p class="eyebrow">{s.eyebrow}</p>
<h1 id="shero-title" class="display shero__title">
{s.title.map((line, i) => <span class:list={['line', { 'line--muted': i > 0 }]}>{line}</span>)}
</h1>
<p class="shero__soon" aria-hidden="true">{s.title[1]}</p>
<p class="shero__lead">{s.lead}</p>
</div>
@@ -52,31 +95,71 @@ const n = s.notify;
</section>
<style>
.line { display: block; }
.line--muted { color: var(--ink-muted); }
.shero {
position: relative;
/* Runs up under the floating header, as the home hero does, so the bar takes the night tone. */
margin-top: calc(-1 * var(--header-height));
overflow: hidden;
}
.shero__media { position: absolute; inset: 0; animation: shero-settle var(--dur-hero) var(--ease) both; transform-origin: 60% 50%; }
.shero__relief { width: 100%; height: 100%; display: block; }
.shero__relief path {
fill: var(--night);
stroke: color-mix(in oklab, var(--logo-deep), var(--logo-cyan) var(--mix));
stroke-opacity: var(--alpha);
stroke-width: 1;
vector-effect: non-scaling-stroke;
}
/* Darker on the left, where the type sits. */
.shero__overlay {
position: absolute;
inset: 0;
background: linear-gradient(to right, rgb(14 16 15 / 0.7), rgb(14 16 15 / 0) 60%);
}
.shero__inner {
position: relative;
display: grid;
gap: var(--space-8);
padding-block: var(--space-9) var(--space-7);
min-height: calc(100svh - var(--header-height));
padding-top: calc(var(--header-height) + var(--space-7));
min-height: 100svh;
align-content: end;
}
.shero__text { display: grid; gap: var(--space-5); justify-items: start; }
.shero__title { text-wrap: balance; }
.shero__text { display: grid; gap: var(--space-4); justify-items: start; }
.shero__soon {
font-size: var(--step-h2);
letter-spacing: var(--track-h2);
line-height: var(--leading-h2);
font-weight: var(--weight-medium);
}
.shero__lead { max-width: 26em; color: var(--ink-muted); }
/* Fade the headline in line by line: the page's one load moment. */
.shero__title .line, .shero__lead, .signup, .readout {
/* The name: one word across the container, its lower edge cut by the section. */
.shero__name {
order: 9;
font-size: var(--step-giant);
font-weight: var(--weight-medium);
letter-spacing: var(--track-display);
line-height: 0.8;
white-space: nowrap;
margin-bottom: -0.1em;
margin-left: -0.05em;
}
/* The one load moment: the image settles, then the type arrives line by line. */
@keyframes shero-settle { from { transform: scale(1.045); } to { transform: none; } }
.shero__soon, .shero__lead, .signup, .readout, .shero__name {
opacity: 0;
animation: shero-in var(--dur-slow) var(--ease) forwards;
}
.shero__title .line:nth-child(1) { animation-delay: 120ms; }
.shero__title .line:nth-child(2) { animation-delay: 280ms; }
.shero__lead { animation-delay: 440ms; }
.signup, .readout { animation-delay: 600ms; }
.shero__name { animation-duration: 900ms; animation-delay: 200ms; }
.shero__soon { animation-delay: 500ms; }
.shero__lead { animation-delay: 640ms; }
.signup, .readout { animation-delay: 780ms; }
@keyframes shero-in { to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
.shero__title .line, .shero__lead, .signup, .readout { animation: none; opacity: 1; }
.shero__media { animation: none; }
.shero__soon, .shero__lead, .signup, .readout, .shero__name { animation: none; opacity: 1; }
}
/* Sign-up: a framed instrument panel. */
@@ -85,6 +168,7 @@ const n = s.notify;
display: grid;
gap: var(--space-5);
padding: var(--space-6);
background: var(--glass-on-night);
align-content: start;
justify-items: start;
}
@@ -126,6 +210,7 @@ const n = s.notify;
letter-spacing: var(--track-caps);
text-transform: uppercase;
line-height: var(--leading-small);
background: var(--glass-on-night);
}
.readout__cell {
display: grid;
@@ -142,17 +227,19 @@ const n = s.notify;
.signup { padding: var(--space-5); }
}
/* Wide: headline across seven columns, sign-up across four beside it, the strip along the foot. */
/* Wide: the line and lead on the left, sign-up on the right, the strip, then the name. */
@media (min-width: 64rem) {
.shero__inner {
grid-template-columns: repeat(12, minmax(0, 1fr));
column-gap: var(--gap);
row-gap: var(--space-9);
row-gap: var(--space-6);
align-items: end;
}
.shero__text { grid-column: 1 / span 7; }
.signup { padding: var(--space-5) var(--space-6); gap: var(--space-4); }
.shero__text { grid-column: 1 / span 6; }
.signup { grid-column: 9 / span 4; }
.readout { grid-column: 1 / -1; grid-template-columns: 1fr 2fr 2fr 2fr; }
.shero__name { grid-column: 1 / -1; margin-top: var(--space-4); }
.readout__cell { grid-template-columns: 1fr; gap: var(--space-1); padding: var(--space-4) var(--space-5); }
.readout__cell + .readout__cell { border-top: 0; border-left: var(--hairline) solid var(--line-on-night); }
}