This commit is contained in:
Felix Klement committed 2026-10-04 20:38:57 +02:00
1 parent 5af2d78328
commit efc2ad7da4
7 files changed
+54 -68

No files matched your search

+2 -9
View File
@@ -6,7 +6,7 @@ import { localePath, t, type Lang } from '../../i18n/ui';
interface Props { lang: Lang } interface Props { lang: Lang }
const { lang } = Astro.props; const { lang } = Astro.props;
const ui = t(lang); const ui = t(lang);
const feed = (await getFeed()).slice(0, 2); const feed = (await getFeed()).slice(0, 3);
const m = ui.insights.mention; const m = ui.insights.mention;
--- ---
<section class="section insights" aria-labelledby="insights-title"> <section class="section insights" aria-labelledby="insights-title">
@@ -44,13 +44,6 @@ const m = ui.insights.mention;
)} )}
</li> </li>
))} ))}
<li class="insights__item">
<a class="insights__card insights__card--event" href={ui.insights.event.href} rel="noopener">
<Field seed={ui.insights.event.href} class="insights__panel"><span>{ui.insights.event.when}</span></Field>
<p class="small muted">{ui.insights.event.category}</p>
<h3 class="h3">{ui.insights.event.text}</h3>
</a>
</li>
</ul> </ul>
</div> </div>
</section> </section>
@@ -78,7 +71,7 @@ const m = ui.insights.mention;
.insights__card:hover h3 { text-decoration: underline; text-decoration-thickness: var(--hairline); text-underline-offset: 0.12em; } .insights__card:hover h3 { text-decoration: underline; text-decoration-thickness: var(--hairline); text-underline-offset: 0.12em; }
.insights__img, :global(.insights__img) { width: 100%; height: auto; aspect-ratio: 3 / 2; object-fit: cover; margin-bottom: var(--space-2); } .insights__img, :global(.insights__img) { width: 100%; height: auto; aspect-ratio: 3 / 2; object-fit: cover; margin-bottom: var(--space-2); }
.insights__card h3 { max-width: 18em; } .insights__card h3 { max-width: 18em; }
/* No picture: a colour field carries the source (or the event date) in large type. */ /* No picture: a colour field carries the source in large type. */
:global(.insights__panel) { :global(.insights__panel) {
aspect-ratio: 3 / 2; aspect-ratio: 3 / 2;
margin-bottom: var(--space-2); margin-bottom: var(--space-2);
+14 -48
View File
@@ -31,27 +31,14 @@ const logos = await Promise.all([
</ul> </ul>
<div class="partners"> <div class="partners">
<p class="small muted partners__label" id="partners-label">{ui.proof.partners}</p> <p class="small muted" id="partners-label">{ui.proof.partners}</p>
<ul class="partners__list" role="list" aria-labelledby="partners-label"> <ul class="partners__list" role="list" aria-labelledby="partners-label">
{logos.map((l) => ( {logos.map((l) => (
<li class="partners__item" data-partner> <li class="partners__item" data-partner>
<PartnerLogo name={l.name} src={l.src} url={l.url} h={l.h} /> <PartnerLogo name={l.name} src={l.src} url={l.url} h={l.h} />
</li> <p class="partners__text">
))} <span class="partners__name">{l.name}</span>
</ul> <span class="small partners__role">{l.role}</span>
</div>
{/* Variant B, for comparison: one column per partner across the full width, with its role.
Keep one of the two and delete the other. */}
<div class="partners-b">
<p class="small muted" id="partners-b-label">{ui.proof.partners}</p>
<ul class="partners-b__list" role="list" aria-labelledby="partners-b-label">
{logos.map((l) => (
<li class="partners-b__item" data-partner>
<PartnerLogo name={l.name} src={l.src} url={l.url} h={l.h} />
<p class="partners-b__text">
<span class="partners-b__name">{l.name}</span>
<span class="small partners-b__role">{l.role}</span>
</p> </p>
</li> </li>
))} ))}
@@ -74,23 +61,6 @@ const logos = await Promise.all([
.proof__name { font-weight: var(--weight-medium); } .proof__name { font-weight: var(--weight-medium); }
.proof__body { max-width: 30em; } .proof__body { max-width: 30em; }
.partners {
grid-column: 1 / -1;
margin-top: var(--space-9);
display: grid;
gap: var(--space-5);
}
.partners__list {
display: grid;
border-top: var(--hairline) solid var(--line);
}
.partners__item {
display: flex;
align-items: center;
min-height: var(--space-9);
padding: var(--space-5) var(--space-4);
border-bottom: var(--hairline) solid var(--line);
}
/* Hover answers the pointer with the logo's own ground: night, original colours. */ /* Hover answers the pointer with the logo's own ground: night, original colours. */
[data-partner] { transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease); } [data-partner] { transition: background-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease); }
@media (hover: hover) { @media (hover: hover) {
@@ -98,15 +68,14 @@ const logos = await Promise.all([
} }
@media (prefers-reduced-motion: reduce) { [data-partner] { transition: none; } } @media (prefers-reduced-motion: reduce) { [data-partner] { transition: none; } }
/* Variant B */ .partners {
.partners-b {
grid-column: 1 / -1; grid-column: 1 / -1;
margin-top: var(--space-9); margin-top: var(--space-9);
display: grid; display: grid;
gap: var(--space-5); gap: var(--space-5);
} }
.partners-b__list { display: grid; border-top: var(--hairline) solid var(--line); } .partners__list { display: grid; border-top: var(--hairline) solid var(--line); }
.partners-b__item { .partners__item {
display: grid; display: grid;
align-content: space-between; align-content: space-between;
gap: var(--space-7); gap: var(--space-7);
@@ -114,22 +83,19 @@ const logos = await Promise.all([
padding: var(--space-6) var(--space-5); padding: var(--space-6) var(--space-5);
border-bottom: var(--hairline) solid var(--line); border-bottom: var(--hairline) solid var(--line);
} }
.partners-b__text { display: grid; gap: var(--space-1); } .partners__text { display: grid; gap: var(--space-1); }
.partners-b__name { font-weight: var(--weight-medium); } .partners__name { font-weight: var(--weight-medium); }
.partners-b__role { color: var(--ink-muted); transition: color var(--dur-fast) var(--ease); } .partners__role { color: var(--ink-muted); transition: color var(--dur-fast) var(--ease); }
@media (hover: hover) { @media (hover: hover) {
.partners-b__item:hover .partners-b__role { color: var(--ink-muted-on-night); } .partners__item:hover .partners__role { color: var(--ink-muted-on-night); }
} }
@media (min-width: 48rem) { @media (min-width: 48rem) {
.proof__title { grid-column: 1 / span 6; } .proof__title { grid-column: 1 / span 6; }
.proof__list { grid-column: 7 / span 6; } .proof__list { grid-column: 7 / span 6; }
.proof__item { grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr); column-gap: var(--gap); } .proof__item { grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr); column-gap: var(--gap); }
.partners { grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--gap); align-items: start; } .partners__list { grid-template-columns: repeat(3, minmax(0, 1fr)); border-bottom: var(--hairline) solid var(--line); }
.partners__label { grid-column: 1 / span 6; padding-top: var(--space-5); } .partners__item { border-bottom: 0; }
.partners__list { grid-column: 7 / span 6; grid-template-columns: repeat(3, minmax(0, 1fr)); column-gap: var(--gap); } .partners__item + .partners__item { border-left: var(--hairline) solid var(--line); }
.partners-b__list { grid-template-columns: repeat(3, minmax(0, 1fr)); border-bottom: var(--hairline) solid var(--line); }
.partners-b__item { border-bottom: 0; }
.partners-b__item + .partners-b__item { border-left: var(--hairline) solid var(--line); }
} }
</style> </style>
+4 -6
View File
@@ -53,7 +53,7 @@ const en = {
lines: ['Security for the software', 'everything else runs on.'], lines: ['Security for the software', 'everything else runs on.'],
}, },
manifesto: { manifesto: {
lines: ['Security is built in,', 'not bolted on.'], lines: ['Security built in,', 'not bolted on.'],
p1: 'Infrastructure is software now. Private 5G networks, industrial control systems and cloud platforms are assembled from the code and components of many vendors. Every interface between them is an attack surface, and off-the-shelf tools were not built for that.', p1: 'Infrastructure is software now. Private 5G networks, industrial control systems and cloud platforms are assembled from the code and components of many vendors. Every interface between them is an attack surface, and off-the-shelf tools were not built for that.',
p2: 'CipherCell is a security engineering company for software. We model real adversaries, build zero trust into the architecture rather than around it, and prove that the code holds. From the first threat model to the running system.', p2: 'CipherCell is a security engineering company for software. We model real adversaries, build zero trust into the architecture rather than around it, and prove that the code holds. From the first threat model to the running system.',
cta: 'About CipherCell', cta: 'About CipherCell',
@@ -85,7 +85,7 @@ const en = {
title: 'Who we work with', title: 'Who we work with',
items: [ items: [
{ title: 'Private 5G and campus networks', body: 'Software-defined networks with open interfaces and a multi-vendor supply chain. Our origin, and still the hardest case.', imageAlt: 'A private 5G antenna and radio cabinet on the parapet of a factory roof, cranes in the distance.' }, { title: 'Private 5G and campus networks', body: 'Software-defined networks with open interfaces and a multi-vendor supply chain. Our origin, and still the hardest case.', imageAlt: 'A private 5G antenna and radio cabinet on the parapet of a factory roof, cranes in the distance.' },
{ title: 'Critical network infrastructure', body: 'Control and network software where a breach stops production, damages equipment or endangers people.', imageAlt: 'An electrical substation seen straight from above: transformers, switchgear and a service road.' }, { title: 'Critical infrastructure', body: 'Control and network software where a breach stops production, damages equipment or endangers people.', imageAlt: 'An electrical substation seen straight from above: transformers, switchgear and a service road.' },
{ title: 'Cloud-native systems', body: 'Containers, service meshes and CI/CD pipelines that carry infrastructure workloads. Security that runs as code, alongside yours.', imageAlt: 'A cold aisle between server racks in a data centre, a technician far in the distance.' }, { title: 'Cloud-native systems', body: 'Containers, service meshes and CI/CD pipelines that carry infrastructure workloads. Security that runs as code, alongside yours.', imageAlt: 'A cold aisle between server racks in a data centre, a technician far in the distance.' },
], ],
}, },
@@ -112,7 +112,6 @@ const en = {
all: 'All insights', all: 'All insights',
intro: 'Notes on software security in private 5G, critical networks and the systems that run them.', intro: 'Notes on software security in private 5G, critical networks and the systems that run them.',
readingTime: (min: number) => `${min} min read`, readingTime: (min: number) => `${min} min read`,
event: { category: 'Event', when: 'September 2026', text: 'Meet us at the xG‑ALOE Plugfest 2026.', link: 'Event details', href: 'https://xg-aloe.de/xg-aloe-plugfest-2026/' },
back: 'All insights', back: 'All insights',
by: 'By', by: 'By',
inEnglish: 'This essay is available in English.', inEnglish: 'This essay is available in English.',
@@ -348,7 +347,7 @@ const de: typeof en = {
lines: ['Sicherheit für die Software,', 'auf der alles andere läuft.'], lines: ['Sicherheit für die Software,', 'auf der alles andere läuft.'],
}, },
manifesto: { manifesto: {
lines: ['Sicherheit wird eingebaut,', 'nicht angeschraubt.'], lines: ['Sicherheit eingebaut,', 'nicht nachträglich angefügt.'],
p1: 'Infrastruktur ist heute Software. Private 5G-Netze, industrielle Steuerungssysteme und Cloud-Plattformen werden aus Code und Komponenten vieler Hersteller zusammengesetzt. Jede Schnittstelle dazwischen ist eine Angriffsfläche, und Standardwerkzeuge sind dafür nicht gebaut.', p1: 'Infrastruktur ist heute Software. Private 5G-Netze, industrielle Steuerungssysteme und Cloud-Plattformen werden aus Code und Komponenten vieler Hersteller zusammengesetzt. Jede Schnittstelle dazwischen ist eine Angriffsfläche, und Standardwerkzeuge sind dafür nicht gebaut.',
p2: 'CipherCell ist ein Security-Engineering-Unternehmen für Software. Wir modellieren reale Angreifer, verankern Zero Trust in der Architektur statt daneben und weisen nach, dass der Code standhält. Vom ersten Bedrohungsmodell bis zum laufenden System.', p2: 'CipherCell ist ein Security-Engineering-Unternehmen für Software. Wir modellieren reale Angreifer, verankern Zero Trust in der Architektur statt daneben und weisen nach, dass der Code standhält. Vom ersten Bedrohungsmodell bis zum laufenden System.',
cta: 'Über CipherCell', cta: 'Über CipherCell',
@@ -380,7 +379,7 @@ const de: typeof en = {
title: 'Für wen wir arbeiten', title: 'Für wen wir arbeiten',
items: [ items: [
{ title: 'Private 5G- und Campusnetze', body: 'Softwaredefinierte Netze mit offenen Schnittstellen und einer Lieferkette aus vielen Herstellern. Unser Ursprung, und noch immer der schwerste Fall.', imageAlt: 'Eine private 5G-Antenne mit Funkschrank auf der Attika eines Fabrikdachs, Kräne im Hintergrund.' }, { title: 'Private 5G- und Campusnetze', body: 'Softwaredefinierte Netze mit offenen Schnittstellen und einer Lieferkette aus vielen Herstellern. Unser Ursprung, und noch immer der schwerste Fall.', imageAlt: 'Eine private 5G-Antenne mit Funkschrank auf der Attika eines Fabrikdachs, Kräne im Hintergrund.' },
{ title: 'Kritische Netzinfrastruktur', body: 'Steuerungs- und Netzsoftware, bei der ein Einbruch die Produktion stoppt, Anlagen beschädigt oder Menschen gefährdet.', imageAlt: 'Ein Umspannwerk senkrecht von oben: Transformatoren, Schaltanlagen und eine Zufahrt.' }, { title: 'Kritische Infrastruktur', body: 'Steuerungs- und Netzsoftware, bei der ein Einbruch die Produktion stoppt, Anlagen beschädigt oder Menschen gefährdet.', imageAlt: 'Ein Umspannwerk senkrecht von oben: Transformatoren, Schaltanlagen und eine Zufahrt.' },
{ title: 'Cloud-native Systeme', body: 'Container, Service Meshes und CI/CD-Pipelines, die Infrastruktur tragen. Sicherheit als Code, neben Ihrem.', imageAlt: 'Ein Kaltgang zwischen Serverschränken in einem Rechenzentrum, ein Techniker weit hinten.' }, { title: 'Cloud-native Systeme', body: 'Container, Service Meshes und CI/CD-Pipelines, die Infrastruktur tragen. Sicherheit als Code, neben Ihrem.', imageAlt: 'Ein Kaltgang zwischen Serverschränken in einem Rechenzentrum, ein Techniker weit hinten.' },
], ],
}, },
@@ -407,7 +406,6 @@ const de: typeof en = {
all: 'Alle Einblicke', all: 'Alle Einblicke',
intro: 'Notizen zur Softwaresicherheit in privaten 5G-Netzen, kritischen Netzen und den Systemen, die sie betreiben.', intro: 'Notizen zur Softwaresicherheit in privaten 5G-Netzen, kritischen Netzen und den Systemen, die sie betreiben.',
readingTime: (min: number) => `${min} Min. Lesezeit`, readingTime: (min: number) => `${min} Min. Lesezeit`,
event: { category: 'Veranstaltung', when: 'September 2026', text: 'Treffen Sie uns beim xG‑ALOE Plugfest 2026.', link: 'Zur Veranstaltung', href: 'https://xg-aloe.de/xg-aloe-plugfest-2026/' },
back: 'Alle Einblicke', back: 'Alle Einblicke',
by: 'Von', by: 'Von',
inEnglish: 'Dieser Essay liegt auf Englisch vor.', inEnglish: 'Dieser Essay liegt auf Englisch vor.',
+3 -3
View File
@@ -22,13 +22,13 @@ const filters = [
<Base lang={lang} title={ui.meta.insights.title} description={ui.meta.insights.description}> <Base lang={lang} title={ui.meta.insights.title} description={ui.meta.insights.description}>
<section class="section section--intro"> <section class="section section--intro">
<div class="container grid"> <div class="container grid">
<h1 class="display index__title">{ui.insights.title}</h1> <h1 class="display index__title load-rise" style="--i: 0">{ui.insights.title}</h1>
<p class="index__lead">{ui.insights.intro}</p> <p class="index__lead load-rise" style="--i: 1">{ui.insights.intro}</p>
</div> </div>
</section> </section>
<section class="section--list" aria-label={ui.insights.title}> <section class="section--list" aria-label={ui.insights.title}>
<div class="container"> <div class="container load-rise" style="--i: 2">
{filters.length > 2 && ( {filters.length > 2 && (
<fieldset class="choice-group filters" data-filters> <fieldset class="choice-group filters" data-filters>
<legend class="choice-group__legend">{f.label}</legend> <legend class="choice-group__legend">{f.label}</legend>
+15 -2
View File
@@ -14,9 +14,9 @@ const ui = t(lang);
<section class="section section--intro team-intro"> <section class="section section--intro team-intro">
<div class="container grid"> <div class="container grid">
<h1 class="display team-intro__title"> <h1 class="display team-intro__title">
{ui.team.title.map((line) => <span class="line">{line}</span>)} {ui.team.title.map((line, i) => <span class="line load-rise" style={`--i: ${i}`}>{line}</span>)}
</h1> </h1>
<p class="team-intro__lead">{ui.team.intro}</p> <p class="team-intro__lead load-rise" style={`--i: ${ui.team.title.length}`}>{ui.team.intro}</p>
</div> </div>
</section> </section>
@@ -66,6 +66,19 @@ const ui = t(lang);
<style> <style>
.line { display: block; } .line { display: block; }
/* Load: the headline rises line by line and the lead follows (.load-rise, base.css);
the photo settles from a slight scale. Then stillness. */
.team-photo { overflow: hidden; }
.team-photo__img, :global(.team-photo__img) { animation: team-settle var(--dur-hero) var(--ease) both; }
@keyframes team-settle {
from { transform: scale(1.045); }
to { transform: none; }
}
@media (prefers-reduced-motion: reduce) {
.team-photo__img, :global(.team-photo__img) { animation: none; transform: none; }
}
.team-intro__title { grid-column: 1 / -1; } .team-intro__title { grid-column: 1 / -1; }
.team-intro__lead { grid-column: 1 / -1; max-width: var(--measure); color: var(--ink-muted); } .team-intro__lead { grid-column: 1 / -1; max-width: var(--measure); color: var(--ink-muted); }
@media (min-width: 48rem) { @media (min-width: 48rem) {
+13
View File
@@ -281,6 +281,18 @@ p { overflow-wrap: break-word; }
white-space: nowrap; border: 0; white-space: nowrap; border: 0;
} }
/* Page load: an element rises in after a delay of --i steps (the index in its sequence).
Used for the intro of inner pages, in the home hero's vocabulary. */
.load-rise {
opacity: 0;
animation: load-rise var(--dur-rise) var(--ease) forwards;
animation-delay: calc(var(--delay-rise) + var(--i, 0) * var(--stagger-rise));
}
@keyframes load-rise {
from { opacity: 0; transform: translateY(0.18em); }
to { opacity: 1; transform: none; }
}
/* Motion off. No exceptions. */ /* Motion off. No exceptions. */
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; } html { scroll-behavior: auto; }
@@ -290,4 +302,5 @@ p { overflow-wrap: break-word; }
transition-duration: 0.01ms !important; transition-duration: 0.01ms !important;
scroll-behavior: auto !important; scroll-behavior: auto !important;
} }
.load-rise { animation: none; opacity: 1; }
} }
+3
View File
@@ -125,6 +125,9 @@
--dur: 240ms; --dur: 240ms;
--dur-slow: 320ms; --dur-slow: 320ms;
--dur-hero: 1400ms; --dur-hero: 1400ms;
--dur-rise: 700ms; /* a headline line rising in on load */
--delay-rise: 240ms; /* before the first line */
--stagger-rise: 160ms; /* between lines */
/* Chrome */ /* Chrome */
--header-height: 4.5rem; --header-height: 4.5rem;