From 7508f6e084bd975afcbcefe4d53804db32727628 Mon Sep 17 00:00:00 2001 From: Felix Klement Date: Sun, 4 Oct 2026 22:43:25 +0200 Subject: [PATCH] asdfdas --- src/components/Header.astro | 17 ++++++++++++++--- src/components/home/Contact.astro | 2 +- src/components/home/Insights.astro | 7 ++++++- src/components/verifier/ComingSoon.astro | 3 +-- src/components/verifier/SoonHero.astro | 24 +++++++++++++++++++++--- src/layouts/Base.astro | 7 +++++-- src/pages/_Home.astro | 2 +- src/pages/_Verifier.astro | 2 +- src/styles/tokens.css | 5 +++++ 9 files changed, 55 insertions(+), 14 deletions(-) diff --git a/src/components/Header.astro b/src/components/Header.astro index 5e0f236..6e59927 100644 --- a/src/components/Header.astro +++ b/src/components/Header.astro @@ -2,8 +2,8 @@ import Wordmark from './Wordmark.astro'; import { localePath, t, type Lang } from '../i18n/ui'; -interface Props { lang: Lang } -const { lang } = Astro.props; +interface Props { lang: Lang; tone?: 'paper' | 'night' } +const { lang, tone = 'paper' } = Astro.props; const ui = t(lang); const home = localePath(lang, '/'); const items = [ @@ -16,7 +16,7 @@ const contactHref = `${home}#contact`; const path = Astro.url.pathname.replace(/\/$/, '') || '/'; const isCurrent = (href: string) => href.split('#')[0] === path && !href.includes('#'); --- -
+
@@ -102,6 +102,12 @@ const isCurrent = (href: string) => href.split('#')[0] === path && !href.include background: var(--glass-solid); transition: background-color var(--dur) var(--ease); } + /* Until the script has settled the tone on load, it changes without a fade: the tone is a fact + about where the page is, not a change the visitor made. */ + .header[data-tone-init], + .header[data-tone-init] *, + .header[data-tone-init] ::before, + .header[data-tone-init] ::after { transition: none !important; } @supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) { .header__bar::before, .header__nav[data-open]::before { @@ -242,6 +248,8 @@ const isCurrent = (href: string) => href.split('#')[0] === path && !href.include /* Tone: watch a one-pixel line through the middle of the bar and switch to night while a .night section crosses it. An IntersectionObserver, so nothing runs on scroll. */ const sections = document.querySelectorAll('.night'); + // Transitions come back once the first tone is drawn (two frames, so it has been painted). + const settle = () => requestAnimationFrame(() => requestAnimationFrame(() => header?.removeAttribute('data-tone-init'))); if (header && sections.length && 'IntersectionObserver' in window) { const under = new Set(); let observer: IntersectionObserver | undefined; @@ -254,6 +262,7 @@ const isCurrent = (href: string) => href.split('#')[0] === path && !href.include if (entry.isIntersecting) under.add(entry.target); else under.delete(entry.target); } header.dataset.tone = under.size ? 'night' : 'paper'; + if (header.hasAttribute('data-tone-init')) settle(); }, { rootMargin: `-${mid}px 0px -${window.innerHeight - mid - 1}px 0px` }); sections.forEach((section) => observer!.observe(section)); }; @@ -263,5 +272,7 @@ const isCurrent = (href: string) => href.split('#')[0] === path && !href.include window.clearTimeout(resize); resize = window.setTimeout(watch, 150); }); + } else { + settle(); } diff --git a/src/components/home/Contact.astro b/src/components/home/Contact.astro index 83e251c..82fd270 100644 --- a/src/components/home/Contact.astro +++ b/src/components/home/Contact.astro @@ -54,7 +54,7 @@ const f = ui.contact.form;
- +
{ display: grid; gap: var(--space-5); padding: var(--space-6); - background: var(--glass-on-night); + background: var(--panel-on-night); align-content: start; justify-items: start; } @@ -210,7 +210,7 @@ const ridges = Array.from({ length: rows }, (_, r) => { letter-spacing: var(--track-caps); text-transform: uppercase; line-height: var(--leading-small); - background: var(--glass-on-night); + background: var(--panel-on-night); } .readout__cell { display: grid; @@ -221,7 +221,23 @@ const ridges = Array.from({ length: rows }, (_, r) => { .readout__cell + .readout__cell { border-top: var(--hairline) solid var(--line-on-night); } .readout__cell--head, .readout dt { color: var(--ink-muted); } .readout dd { display: flex; align-items: center; gap: var(--space-2); } - .readout__live { width: 0.5rem; height: 0.5rem; border-radius: 50%; background: var(--signal); flex: none; } + .readout__live { position: relative; width: 0.5rem; height: 0.5rem; border-radius: 50%; background: var(--signal); flex: none; } + /* The live phase pulses: a ring in the signal tone grows out of the dot and fades. */ + .readout__live::after { + content: ''; + position: absolute; + inset: 0; + border-radius: 50%; + background: var(--signal); + animation: readout-pulse calc(6 * var(--dur-slow)) var(--ease) infinite; + } + @keyframes readout-pulse { + from { transform: scale(1); opacity: 0.7; } + 70%, to { transform: scale(3); opacity: 0; } + } + @media (prefers-reduced-motion: reduce) { + .readout__live::after { animation: none; opacity: 0; } + } @media (max-width: 30rem) { .signup { padding: var(--space-5); } @@ -242,5 +258,7 @@ const ridges = Array.from({ length: rows }, (_, r) => { .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); } + /* The strip's title sits in the middle of its cell, apart from the label/value cells beside it. */ + .readout__cell--head { place-content: center; text-align: center; } } diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro index 708e103..4466d44 100644 --- a/src/layouts/Base.astro +++ b/src/layouts/Base.astro @@ -11,10 +11,13 @@ interface Props { ogImage?: string; /** Pages that only make sense as the answer to an action, such as the contact form result. */ noindex?: boolean; + /** The surface the page opens on. 'night' for pages that start on a dark hero, so the header + is drawn in its night tone from the first frame instead of switching after load. */ + tone?: 'paper' | 'night'; } const lang: Lang = Astro.props.lang ?? langFromUrl(Astro.url); -const { title, description, noindex } = Astro.props; +const { title, description, noindex, tone = 'paper' } = Astro.props; const ui = t(lang); const fullTitle = title === ui.meta.home.title ? title : `${title}. ${ui.meta.siteName}`; const canonical = new URL(Astro.url.pathname, Astro.site); @@ -48,7 +51,7 @@ const altDe = new URL(switchLang(Astro.url, 'de'), Astro.site); -
+
diff --git a/src/pages/_Home.astro b/src/pages/_Home.astro index 9b466b9..e27bd59 100644 --- a/src/pages/_Home.astro +++ b/src/pages/_Home.astro @@ -15,7 +15,7 @@ interface Props { lang: Lang } const { lang } = Astro.props; const ui = t(lang); --- - + diff --git a/src/pages/_Verifier.astro b/src/pages/_Verifier.astro index 931d553..4c6c519 100644 --- a/src/pages/_Verifier.astro +++ b/src/pages/_Verifier.astro @@ -15,7 +15,7 @@ interface Props { lang: Lang } const { lang } = Astro.props; const ui = t(lang); --- - + {verifierComingSoon ? ( <> diff --git a/src/styles/tokens.css b/src/styles/tokens.css index 3592552..1021e97 100644 --- a/src/styles/tokens.css +++ b/src/styles/tokens.css @@ -41,6 +41,8 @@ --field-sky: #6AAEE8; /* Darkens the lower edge of a field so paper-coloured type on it holds 3:1 or better. */ --field-scrim: linear-gradient(to top, rgb(14 16 15 / 0.55), rgb(14 16 15 / 0) 65%); + /* The same for a field whose text sits centred on the left (the home page Insights cards). */ + --field-scrim-centre: radial-gradient(90% 85% at 20% 50%, rgb(14 16 15 / 0.72), rgb(14 16 15 / 0) 100%); /* Signal: the one status tone the brief allows. Used only for a live state (the coming-soon page's current phase), at most twice per page. Never for text, links or decoration. */ @@ -146,6 +148,9 @@ --glass-on-night: rgb(14 16 15 / 0.56); --glass-solid: rgb(233 229 223 / 0.96); --glass-solid-on-night: rgb(14 16 15 / 0.94); + /* Panels over a busy night background (the product page's sign-up and status strip): darker + than the menu glass, so the lines behind only just show through. */ + --panel-on-night: rgb(14 16 15 / 0.85); --glass-blur: 14px; /* Tints mixed from the text colour, so they work on either tone. */ --tint-hover: 6%;