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

+1 -1
View File
@@ -26,7 +26,7 @@ npm run check # Astro + TypeScript check
| `src/components/home/GridObject.astro` | The wireframe objects on the stage and product cards (sphere, cube, plane, wave, steps, nested, stack). Geometry is computed at build time; single stroke in `--ink`. Each drawing is laid out by hand, so the `viewBox` is derived from the points as they are emitted, not assumed: a square centred on the drawing, padded for the stroke. A new kind can therefore never be clipped, and the card gives the drawing the flexible row so it sits centred between title and body. Each drawing is emitted in two groups, the lines that carry its idea and the grid around them, which is what the hover and the reveal act on. |
| `src/components/home/MethodDiagram.astro` | The four-stage loop, wide and tall compositions. Labels come from `ui.method.stages`. |
| `src/components/verifier/*` | The CipherVerifier product page, in page order: Hero (line burst), Intro (image band), HowItWorks (five group cards, one schematic each; on wide screens the heading is sticky and the cards stack under it as you scroll), Cards, Facts, Outro. |
| `src/components/verifier/SoonHero.astro`, `ComingSoon.astro` | The coming-soon version of `/cipherverifier`, shown while `verifierComingSoon` in `src/data/flags.ts` is true: headline with the alpha sign-up beside it, a status strip along the foot of the hero, release path, spec sheet and the first schematic. Copy in `ui.verifier.soon`. |
| `src/components/verifier/SoonHero.astro`, `ComingSoon.astro` | The coming-soon version of `/cipherverifier`, shown while `verifierComingSoon` in `src/data/flags.ts` is true: hero on a build-time SVG relief of ridge lines in the logo blues, under the floating header, "Coming soon." with the alpha sign-up beside it, a status strip, and the product name set across the full width and cut by the hero's lower edge, release path, spec sheet and the first schematic. Copy in `ui.verifier.soon`. |
| `src/components/verifier/diagrams.ts` | The five schematics as data: a hand-placed wide layout and an auto-generated tall spine per group. `Schematic.astro` renders either. Labels come from `ui.verifier.how.groups`; the tuple shape is in `types.ts`. Every element carries the `group` of the node it belongs to, which drives the hover focus and the draw-in order. |
| `scripts/*.mjs` | Review tooling: `shot.mjs` (full page), `scroll-shots.mjs` (viewport by viewport), `tabs-test.mjs` (keyboard test for the sectors tabs), `contact-sheet.mjs`. Need `npx astro preview --port 4322`. |
| `src/pages/_*.astro` | Shared page bodies. `index.astro` and `de/index.astro` render `_Home.astro` with a `lang` prop, and so on. |
+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); }
}
+2
View File
@@ -55,6 +55,8 @@
--step-display: clamp(2.75rem, 1.2rem + 6.2vw, 7rem);
/* Home hero headline: about 10% below display, so the image keeps more of the frame. */
--step-hero: clamp(2.5rem, 1.1rem + 5.6vw, 6.25rem);
/* The coming-soon wordmark: one word sized to span the container, cut by the hero's lower edge. */
--step-giant: min(16.4vw, 14.25rem);
--step-h2: clamp(1.9rem, 1.1rem + 2.6vw, 3.25rem);
--step-h3: clamp(1.25rem, 1.05rem + 0.7vw, 1.6rem);
/* Card and footer titles: between h2 and h3, for headings that name rather than claim. */