progress
This commit is contained in:
1 parent
49850fe485
commit
39bb0f480c
10 files changed
+240
-109
No files matched your search
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 6.6 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
+153
-44
@@ -17,89 +17,172 @@ const contactHref = `${home}#contact`;
|
|||||||
const path = Astro.url.pathname.replace(/\/$/, '') || '/';
|
const path = Astro.url.pathname.replace(/\/$/, '') || '/';
|
||||||
const isCurrent = (href: string) => href.split('#')[0] === path && !href.includes('#');
|
const isCurrent = (href: string) => href.split('#')[0] === path && !href.includes('#');
|
||||||
---
|
---
|
||||||
<header class="header" data-header>
|
<header class="header" data-header data-tone="paper">
|
||||||
<div class="container header__inner">
|
<div class="container header__shell">
|
||||||
<a class="header__home" href={home} aria-label={ui.nav.homeLabel}><Wordmark inline /></a>
|
<div class="header__bar hud-frame">
|
||||||
|
<a class="header__home" href={home} aria-label={ui.nav.homeLabel}>
|
||||||
|
<Wordmark inline class="header__logo header__logo--paper" />
|
||||||
|
<Wordmark inline light class="header__logo header__logo--night" />
|
||||||
|
</a>
|
||||||
|
|
||||||
<nav class="header__nav" aria-label="Primary" id="primary-nav">
|
<nav class="header__nav hud-frame" aria-label="Primary" id="primary-nav">
|
||||||
<ul class="header__list" role="list">
|
<ul class="header__list" role="list">
|
||||||
{items.map((item) => (
|
{items.map((item) => {
|
||||||
<li><a href={item.href} aria-current={isCurrent(item.href) ? 'page' : undefined}>{item.label}</a></li>
|
const current = isCurrent(item.href);
|
||||||
))}
|
return (
|
||||||
|
<li><a class:list={[{ 'hud-corners': current }]} href={item.href} aria-current={current ? 'page' : undefined}>{item.label}</a></li>
|
||||||
|
);
|
||||||
|
})}
|
||||||
<li class="header__contact-mobile"><a href={contactHref}>{ui.nav.contact}</a></li>
|
<li class="header__contact-mobile"><a href={contactHref}>{ui.nav.contact}</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<a class="button button--small header__contact" href={contactHref}>{ui.nav.contact}</a>
|
<a class="header__contact" href={contactHref}>{ui.nav.contact}</a>
|
||||||
|
|
||||||
<button class="header__toggle" type="button" aria-expanded="false" aria-controls="primary-nav" data-nav-toggle>
|
<button class="header__toggle" type="button" aria-expanded="false" aria-controls="primary-nav" data-nav-toggle>
|
||||||
<span data-label-open>{ui.nav.menu}</span>
|
<span data-label-open>{ui.nav.menu}</span>
|
||||||
<span data-label-close hidden>{ui.nav.close}</span>
|
<span data-label-close hidden>{ui.nav.close}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
/* The header is a transparent sticky strip; the bar floats inside it with room on every side
|
||||||
|
for the corner crosses. Only the bar takes pointer events, so the margin never blocks clicks. */
|
||||||
.header {
|
.header {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
background: var(--paper);
|
pointer-events: none;
|
||||||
border-bottom: var(--hairline) solid var(--line);
|
|
||||||
}
|
}
|
||||||
.header__inner {
|
.header__shell {
|
||||||
|
padding-block: var(--space-3);
|
||||||
|
}
|
||||||
|
.header__bar {
|
||||||
|
pointer-events: auto;
|
||||||
|
isolation: isolate;
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
min-height: calc(var(--header-height) - 2 * var(--space-3));
|
||||||
|
color: var(--ink-base);
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: var(--step-micro);
|
||||||
|
font-weight: var(--weight-medium);
|
||||||
|
line-height: 1;
|
||||||
|
letter-spacing: var(--track-caps);
|
||||||
|
text-transform: uppercase;
|
||||||
|
transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
|
||||||
|
}
|
||||||
|
/* The glass sits on a pseudo-element rather than the bar itself: a backdrop-filter on the bar
|
||||||
|
would make it a backdrop root, and the mobile panel inside it could then blur nothing. */
|
||||||
|
.header__bar::before,
|
||||||
|
.header__nav[data-open]::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: -1;
|
||||||
|
background: var(--glass-solid);
|
||||||
|
transition: background-color var(--dur) var(--ease);
|
||||||
|
}
|
||||||
|
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
|
||||||
|
.header__bar::before,
|
||||||
|
.header__nav[data-open]::before {
|
||||||
|
background: var(--glass);
|
||||||
|
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
|
||||||
|
backdrop-filter: blur(var(--glass-blur)) saturate(1.4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Over a night section the bar takes the night tone. */
|
||||||
|
.header[data-tone='night'] { --hud-line: var(--hud-line-on-night); }
|
||||||
|
.header[data-tone='night'] .header__bar { color: var(--ink-on-night); }
|
||||||
|
.header[data-tone='night'] .header__bar::before,
|
||||||
|
.header[data-tone='night'] .header__nav[data-open]::before { background: var(--glass-solid-on-night); }
|
||||||
|
@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
|
||||||
|
.header[data-tone='night'] .header__bar::before,
|
||||||
|
.header[data-tone='night'] .header__nav[data-open]::before { background: var(--glass-on-night); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.header__home {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
padding-inline: var(--space-4);
|
||||||
gap: var(--gap);
|
|
||||||
min-height: var(--header-height);
|
|
||||||
}
|
|
||||||
.header__home { text-decoration: none; }
|
|
||||||
.header__nav { margin-left: auto; }
|
|
||||||
.header__list {
|
|
||||||
display: flex;
|
|
||||||
gap: var(--space-6);
|
|
||||||
}
|
|
||||||
.header__list a {
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-weight: var(--weight-regular);
|
}
|
||||||
padding-block: var(--space-2);
|
.header__home :global(.header__logo--night),
|
||||||
border-bottom: var(--hairline) solid transparent;
|
.header[data-tone='night'] .header__home :global(.header__logo--paper) { display: none; }
|
||||||
transition: border-color var(--dur-fast) var(--ease);
|
.header[data-tone='night'] .header__home :global(.header__logo--night) { display: block; }
|
||||||
|
|
||||||
|
/* Cells: every item is a full-height cell, divided from its neighbour by one frame line. */
|
||||||
|
.header__nav { margin-left: auto; display: flex; }
|
||||||
|
/* On desktop the nav is just a run of cells inside the bar: no frame of its own. */
|
||||||
|
.header__nav.hud-frame { border: 0; }
|
||||||
|
.header__nav.hud-frame::after { content: none; }
|
||||||
|
.header__list { display: flex; }
|
||||||
|
.header__list li { display: flex; border-left: var(--line) solid var(--frame-color); }
|
||||||
|
.header__list a,
|
||||||
|
.header__contact,
|
||||||
|
.header__toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-inline: var(--space-4);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: background-color var(--dur-fast) var(--ease);
|
||||||
}
|
}
|
||||||
.header__list a:hover,
|
.header__list a:hover,
|
||||||
.header__list a[aria-current='page'] { border-bottom-color: currentColor; }
|
.header__toggle:hover { background: color-mix(in srgb, currentColor var(--tint-hover), transparent); }
|
||||||
.header__contact { margin-left: var(--space-6); }
|
.header__list a[aria-current='page'] { background: color-mix(in srgb, currentColor var(--tint-active), transparent); }
|
||||||
.header__contact-mobile { display: none; }
|
.header__contact {
|
||||||
|
border-left: var(--line) solid var(--frame-color);
|
||||||
|
background: var(--ink-base);
|
||||||
|
color: var(--paper);
|
||||||
|
}
|
||||||
|
.header__contact:hover { background: var(--ink-muted-base); }
|
||||||
|
.header[data-tone='night'] .header__contact { background: var(--paper); color: var(--night); }
|
||||||
|
.header[data-tone='night'] .header__contact:hover { background: var(--paper-deep); }
|
||||||
|
.header__list .header__contact-mobile { display: none; }
|
||||||
.header__toggle {
|
.header__toggle {
|
||||||
display: none;
|
display: none;
|
||||||
font-weight: var(--weight-medium);
|
border-left: var(--line) solid var(--frame-color);
|
||||||
padding: var(--space-2) 0;
|
text-transform: inherit;
|
||||||
|
letter-spacing: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus ring drawn inside the cell, so it never collides with the frame lines. */
|
||||||
|
.header__bar :focus-visible {
|
||||||
|
outline-offset: calc(-1 * var(--space-1));
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A panel left open across a resize to desktop must not paint a second glass layer. */
|
||||||
|
@media (min-width: 48rem) {
|
||||||
|
.header__nav[data-open]::before { content: none; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 47.99rem) {
|
@media (max-width: 47.99rem) {
|
||||||
.header__contact { display: none; }
|
.header__contact { display: none; }
|
||||||
.header__contact-mobile { display: block; }
|
.header__list .header__contact-mobile { display: flex; }
|
||||||
.header__toggle { display: inline-block; }
|
.header__toggle { display: flex; margin-left: auto; }
|
||||||
.header__nav {
|
.header__nav {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 100% 0 auto 0;
|
top: calc(100% + var(--space-3));
|
||||||
|
left: calc(-1 * var(--line));
|
||||||
|
right: calc(-1 * var(--line));
|
||||||
display: none;
|
display: none;
|
||||||
padding: var(--space-5) var(--gutter) var(--space-7);
|
|
||||||
background: var(--paper);
|
|
||||||
border-bottom: var(--hairline) solid var(--line);
|
|
||||||
}
|
}
|
||||||
|
/* As the mobile panel it is a full .hud-frame: border and corner crosses. */
|
||||||
|
.header__nav.hud-frame { border: var(--line) solid var(--frame-color); }
|
||||||
|
.header__nav.hud-frame::after { content: ''; }
|
||||||
.header__nav[data-open] { display: block; }
|
.header__nav[data-open] { display: block; }
|
||||||
.header__list { flex-direction: column; gap: 0; }
|
.header__list { flex-direction: column; }
|
||||||
.header__list li + li { border-top: var(--hairline) solid var(--line); }
|
.header__list li { border-left: 0; }
|
||||||
|
.header__list li + li { border-top: var(--line) solid var(--frame-color); }
|
||||||
.header__list a {
|
.header__list a {
|
||||||
display: block;
|
flex: 1;
|
||||||
padding-block: var(--space-4);
|
padding-block: var(--space-4);
|
||||||
font-size: var(--step-h3);
|
font-size: var(--step-small);
|
||||||
letter-spacing: var(--track-h3);
|
|
||||||
border-bottom: 0;
|
|
||||||
}
|
}
|
||||||
.header__list a[aria-current='page'] { font-weight: var(--weight-medium); }
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@@ -120,4 +203,30 @@ const isCurrent = (href: string) => href.split('#')[0] === path && !href.include
|
|||||||
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') set(false); });
|
document.addEventListener('keydown', (e) => { if (e.key === 'Escape') set(false); });
|
||||||
nav.addEventListener('click', (e) => { if ((e.target as HTMLElement).closest('a')) set(false); });
|
nav.addEventListener('click', (e) => { if ((e.target as HTMLElement).closest('a')) set(false); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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<HTMLElement>('.night');
|
||||||
|
if (header && sections.length && 'IntersectionObserver' in window) {
|
||||||
|
const under = new Set<Element>();
|
||||||
|
let observer: IntersectionObserver | undefined;
|
||||||
|
const watch = () => {
|
||||||
|
observer?.disconnect();
|
||||||
|
under.clear();
|
||||||
|
const mid = Math.round(header.offsetHeight / 2);
|
||||||
|
observer = new IntersectionObserver((entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.isIntersecting) under.add(entry.target); else under.delete(entry.target);
|
||||||
|
}
|
||||||
|
header.dataset.tone = under.size ? 'night' : 'paper';
|
||||||
|
}, { rootMargin: `-${mid}px 0px -${window.innerHeight - mid - 1}px 0px` });
|
||||||
|
sections.forEach((section) => observer!.observe(section));
|
||||||
|
};
|
||||||
|
watch();
|
||||||
|
let resize: number | undefined;
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
window.clearTimeout(resize);
|
||||||
|
resize = window.setTimeout(watch, 150);
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -4,13 +4,17 @@
|
|||||||
blinking in on every navigation; the header needs that, the footer does not. */
|
blinking in on every navigation; the header needs that, the footer does not. */
|
||||||
import logoInline from '../assets/logo.webp?inline';
|
import logoInline from '../assets/logo.webp?inline';
|
||||||
import logoUrl from '../assets/logo.webp?url';
|
import logoUrl from '../assets/logo.webp?url';
|
||||||
|
/* Light lettering for dark surfaces, derived from the supplied logo; the mark is unchanged. */
|
||||||
|
import logoLightInline from '../assets/logo-light.webp?inline';
|
||||||
|
import logoLightUrl from '../assets/logo-light.webp?url';
|
||||||
|
|
||||||
interface Props { class?: string; inline?: boolean }
|
interface Props { class?: string; inline?: boolean; light?: boolean }
|
||||||
const { class: className, inline = false } = Astro.props;
|
const { class: className, inline = false, light = false } = Astro.props;
|
||||||
|
const src = light ? (inline ? logoLightInline : logoLightUrl) : (inline ? logoInline : logoUrl);
|
||||||
---
|
---
|
||||||
<img
|
<img
|
||||||
class:list={['wordmark', className]}
|
class:list={['wordmark', className]}
|
||||||
src={inline ? logoInline : logoUrl}
|
src={src}
|
||||||
alt="CipherCell"
|
alt="CipherCell"
|
||||||
width="311"
|
width="311"
|
||||||
height="72"
|
height="72"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
import { Picture } from 'astro:assets';
|
import { Picture } from 'astro:assets';
|
||||||
import plant from '../../assets/images/plant-at-dusk.jpg';
|
import background from '../../assets/images/background.png';
|
||||||
import { t, type Lang } from '../../i18n/ui';
|
import { t, type Lang } from '../../i18n/ui';
|
||||||
|
|
||||||
interface Props { lang: Lang }
|
interface Props { lang: Lang }
|
||||||
@@ -9,33 +9,21 @@ const ui = t(lang);
|
|||||||
---
|
---
|
||||||
<section class="hero night" aria-labelledby="hero-title">
|
<section class="hero night" aria-labelledby="hero-title">
|
||||||
<div class="hero__media">
|
<div class="hero__media">
|
||||||
<!-- The still is the video's first frame, so it carries the hero until the video is ready
|
<!-- Abstract and decorative, so it carries no alt text. -->
|
||||||
and stays put for anyone who never gets one: no script, reduced motion, saved data. -->
|
|
||||||
<Picture
|
<Picture
|
||||||
src={plant}
|
src={background}
|
||||||
alt={ui.hero.imageAlt}
|
alt=""
|
||||||
|
quality={70}
|
||||||
formats={['avif']}
|
formats={['avif']}
|
||||||
fallbackFormat="webp"
|
fallbackFormat="webp"
|
||||||
widths={[640, 960, 1440, 1920, 2400, 3024]}
|
widths={[640, 960, 1440, 1920, 2400]}
|
||||||
sizes="100vw"
|
sizes="100vw"
|
||||||
loading="eager"
|
loading="eager"
|
||||||
fetchpriority="high"
|
fetchpriority="high"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
class="hero__img img-cover img-doc"
|
class="hero__img img-cover"
|
||||||
pictureAttributes={{ class: 'hero__picture' }}
|
pictureAttributes={{ class: 'hero__picture' }}
|
||||||
/>
|
/>
|
||||||
<video
|
|
||||||
class="hero__video img-cover img-doc"
|
|
||||||
muted
|
|
||||||
loop
|
|
||||||
playsinline
|
|
||||||
disablepictureinpicture
|
|
||||||
preload="none"
|
|
||||||
aria-hidden="true"
|
|
||||||
data-hero-video
|
|
||||||
data-mp4="/video/hero-plant.mp4"
|
|
||||||
data-webm="/video/hero-plant.webm"
|
|
||||||
></video>
|
|
||||||
<div class="hero__overlay"></div>
|
<div class="hero__overlay"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -53,8 +41,10 @@ const ui = t(lang);
|
|||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
/* Fills the screen under the header, and never less than the headline needs. */
|
/* Runs up under the floating header so the glass bar sits on the image, fills the screen,
|
||||||
min-height: max(28rem, calc(100svh - var(--header-height)));
|
and is never less than the headline needs. */
|
||||||
|
margin-top: calc(-1 * var(--header-height));
|
||||||
|
min-height: max(28rem, 100svh);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.hero__media { position: absolute; inset: 0; }
|
.hero__media { position: absolute; inset: 0; }
|
||||||
@@ -64,22 +54,10 @@ const ui = t(lang);
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
object-position: 60% 50%;
|
object-position: 50% 50%;
|
||||||
transform-origin: 50% 60%;
|
transform-origin: 50% 60%;
|
||||||
animation: hero-settle var(--dur-hero) var(--ease) both;
|
animation: hero-settle var(--dur-hero) var(--ease) both;
|
||||||
}
|
}
|
||||||
.hero__video {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
object-position: 60% 50%;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity var(--dur-slow) var(--ease);
|
|
||||||
}
|
|
||||||
/* Set once the video can actually play, so the still never cuts to black. */
|
|
||||||
.hero__video[data-ready] { opacity: 1; }
|
|
||||||
.hero__overlay {
|
.hero__overlay {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
@@ -109,32 +87,6 @@ const ui = t(lang);
|
|||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.hero__img, :global(.hero__img), .hero__line { animation: none; opacity: 1; transform: none; }
|
.hero__img, :global(.hero__img), .hero__line { animation: none; opacity: 1; transform: none; }
|
||||||
.hero__video { display: none; }
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
|
||||||
/* The sources are attached here rather than in the markup, so the file is only fetched for
|
|
||||||
someone who will actually see it move: not under reduced motion, not on saved data, and not
|
|
||||||
on a phone, where a 21:9 frame is cropped to a sliver and the bytes cost the most. */
|
|
||||||
const video = document.querySelector<HTMLVideoElement>('[data-hero-video]');
|
|
||||||
const wants = window.matchMedia('(min-width: 48rem)').matches
|
|
||||||
&& !window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
||||||
const connection = (navigator as Navigator & { connection?: { saveData?: boolean } }).connection;
|
|
||||||
if (video && wants && !connection?.saveData) {
|
|
||||||
const add = (src: string | null, type: string) => {
|
|
||||||
if (!src) return;
|
|
||||||
const source = document.createElement('source');
|
|
||||||
source.src = src;
|
|
||||||
source.type = type;
|
|
||||||
video.append(source);
|
|
||||||
};
|
|
||||||
add(video.dataset.webm ?? null, 'video/webm');
|
|
||||||
add(video.dataset.mp4 ?? null, 'video/mp4');
|
|
||||||
video.addEventListener('canplay', () => video.setAttribute('data-ready', ''), { once: true });
|
|
||||||
video.preload = 'auto';
|
|
||||||
video.load();
|
|
||||||
/* A blocked autoplay leaves the still in place, which is a fine hero on its own. */
|
|
||||||
video.play().catch(() => {});
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -51,7 +51,6 @@ const en = {
|
|||||||
},
|
},
|
||||||
hero: {
|
hero: {
|
||||||
lines: ['Security for the software', 'everything else runs on.'],
|
lines: ['Security for the software', 'everything else runs on.'],
|
||||||
imageAlt: 'An industrial plant at dusk: pipe racks, storage tanks and a lattice mast carrying 5G antennas under an overcast sky.',
|
|
||||||
},
|
},
|
||||||
manifesto: {
|
manifesto: {
|
||||||
lines: ['Security is built in,', 'not bolted on.'],
|
lines: ['Security is built in,', 'not bolted on.'],
|
||||||
@@ -269,7 +268,6 @@ const de: typeof en = {
|
|||||||
},
|
},
|
||||||
hero: {
|
hero: {
|
||||||
lines: ['Sicherheit für die Software,', 'auf der alles andere läuft.'],
|
lines: ['Sicherheit für die Software,', 'auf der alles andere läuft.'],
|
||||||
imageAlt: 'Eine Industrieanlage in der Dämmerung: Rohrbrücken, Tanks und ein Gittermast mit 5G-Antennen unter bedecktem Himmel.',
|
|
||||||
},
|
},
|
||||||
manifesto: {
|
manifesto: {
|
||||||
lines: ['Sicherheit wird eingebaut,', 'nicht angeschraubt.'],
|
lines: ['Sicherheit wird eingebaut,', 'nicht angeschraubt.'],
|
||||||
|
|||||||
@@ -18,6 +18,24 @@
|
|||||||
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Self-hosted JetBrains Mono, variable, latin + latin-ext. Menu bar only. */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'JetBrains Mono';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 100 900;
|
||||||
|
font-display: swap;
|
||||||
|
src: url('/fonts/jetbrains-mono-latin-wght-normal.woff2') format('woff2-variations');
|
||||||
|
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: 100 900;
|
||||||
|
font-display: swap;
|
||||||
|
src: url('/fonts/jetbrains-mono-latin-ext-wght-normal.woff2') format('woff2-variations');
|
||||||
|
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||||
|
}
|
||||||
|
|
||||||
/* Reset */
|
/* Reset */
|
||||||
*, *::before, *::after { box-sizing: border-box; }
|
*, *::before, *::after { box-sizing: border-box; }
|
||||||
* { margin: 0; }
|
* { margin: 0; }
|
||||||
@@ -92,6 +110,37 @@ p { overflow-wrap: break-word; }
|
|||||||
font-weight: var(--weight-regular);
|
font-weight: var(--weight-regular);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* HUD frame: a 1px border with a small plus-shaped cross centred on each outer corner.
|
||||||
|
.hud-frame draws border and crosses; .hud-corners draws only the crosses, for an element whose
|
||||||
|
edges already sit on lines (the current menu item). Both read --line, --arm and --frame-color,
|
||||||
|
so a parent can retune them. Note: --line is redefined here as a width, not the colour token. */
|
||||||
|
.hud-frame,
|
||||||
|
.hud-corners {
|
||||||
|
--line: var(--hud-line-width);
|
||||||
|
--arm: var(--hud-arm);
|
||||||
|
--frame-color: var(--hud-line);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.hud-frame { border: var(--line) solid var(--frame-color); }
|
||||||
|
.hud-frame::after,
|
||||||
|
.hud-corners::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: calc(-1 * (var(--arm) + var(--line)));
|
||||||
|
pointer-events: none;
|
||||||
|
--len: calc(2 * var(--arm) + var(--line));
|
||||||
|
--g: linear-gradient(var(--frame-color), var(--frame-color)) no-repeat;
|
||||||
|
background:
|
||||||
|
var(--g) left 0 top var(--arm) / var(--len) var(--line),
|
||||||
|
var(--g) left var(--arm) top 0 / var(--line) var(--len),
|
||||||
|
var(--g) right 0 top var(--arm) / var(--len) var(--line),
|
||||||
|
var(--g) right var(--arm) top 0 / var(--line) var(--len),
|
||||||
|
var(--g) left 0 bottom var(--arm) / var(--len) var(--line),
|
||||||
|
var(--g) left var(--arm) bottom 0 / var(--line) var(--len),
|
||||||
|
var(--g) right 0 bottom var(--arm) / var(--len) var(--line),
|
||||||
|
var(--g) right var(--arm) bottom 0 / var(--line) var(--len);
|
||||||
|
}
|
||||||
|
|
||||||
/* Primitives */
|
/* Primitives */
|
||||||
.container {
|
.container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
|
|
||||||
/* Type */
|
/* Type */
|
||||||
--font-sans: 'Inter Tight', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
--font-sans: 'Inter Tight', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
/* Menu bar only: the tactical HUD labels. */
|
||||||
|
--font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
|
||||||
|
|
||||||
--step-display: clamp(2.75rem, 1.2rem + 6.2vw, 7rem);
|
--step-display: clamp(2.75rem, 1.2rem + 6.2vw, 7rem);
|
||||||
--step-h2: clamp(1.9rem, 1.1rem + 2.6vw, 3.25rem);
|
--step-h2: clamp(1.9rem, 1.1rem + 2.6vw, 3.25rem);
|
||||||
@@ -33,6 +35,7 @@
|
|||||||
--step-title: clamp(1.6rem, 1.2rem + 1.5vw, 2.5rem);
|
--step-title: clamp(1.6rem, 1.2rem + 1.5vw, 2.5rem);
|
||||||
--step-body: clamp(1rem, 0.96rem + 0.2vw, 1.125rem);
|
--step-body: clamp(1rem, 0.96rem + 0.2vw, 1.125rem);
|
||||||
--step-small: 0.875rem;
|
--step-small: 0.875rem;
|
||||||
|
--step-micro: 0.75rem;
|
||||||
|
|
||||||
--weight-regular: 400;
|
--weight-regular: 400;
|
||||||
--weight-medium: 500;
|
--weight-medium: 500;
|
||||||
@@ -40,6 +43,7 @@
|
|||||||
--track-display: -0.03em;
|
--track-display: -0.03em;
|
||||||
--track-h2: -0.02em;
|
--track-h2: -0.02em;
|
||||||
--track-h3: -0.01em;
|
--track-h3: -0.01em;
|
||||||
|
--track-caps: 0.08em;
|
||||||
|
|
||||||
--leading-display: 1.02;
|
--leading-display: 1.02;
|
||||||
--leading-h2: 1.08;
|
--leading-h2: 1.08;
|
||||||
@@ -81,6 +85,21 @@
|
|||||||
/* Chrome */
|
/* Chrome */
|
||||||
--header-height: 4.5rem;
|
--header-height: 4.5rem;
|
||||||
|
|
||||||
|
/* HUD frame (menu bar): one line width and colour for every line, borders and crosses alike. */
|
||||||
|
--hud-line-width: 1px;
|
||||||
|
--hud-arm: 7px;
|
||||||
|
--hud-line: rgb(26 26 24 / 0.3);
|
||||||
|
--hud-line-on-night: rgb(233 229 223 / 0.3);
|
||||||
|
/* Frosted glass behind the bar. One blur, kept small so it stays cheap to composite. */
|
||||||
|
--glass: rgb(233 229 223 / 0.76);
|
||||||
|
--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);
|
||||||
|
--glass-blur: 14px;
|
||||||
|
/* Tints mixed from the text colour, so they work on either tone. */
|
||||||
|
--tint-hover: 6%;
|
||||||
|
--tint-active: 9%;
|
||||||
|
|
||||||
/* Focus */
|
/* Focus */
|
||||||
--focus-width: 2px;
|
--focus-width: 2px;
|
||||||
--focus-offset: 3px;
|
--focus-offset: 3px;
|
||||||
|
|||||||
Reference in new issue
Block a user