asdf
This commit is contained in:
1 parent
a4c2ff3b32
commit
3cb5952e6d
4 files changed
+63
-19
No files matched your search
@@ -190,7 +190,11 @@ photography existed:
|
|||||||
| `engineer-glass.jpg` | Method section | https://www.magnific.com/app/creation/3zdScUIREY |
|
| `engineer-glass.jpg` | Method section | https://www.magnific.com/app/creation/3zdScUIREY |
|
||||||
| `sector-5g.jpg` | Sectors, tab 1 | https://www.magnific.com/app/creation/s7ZQP52l8e |
|
| `sector-5g.jpg` | Sectors, tab 1 | https://www.magnific.com/app/creation/s7ZQP52l8e |
|
||||||
| `sector-substation.jpg` | Sectors, tab 2 | https://www.magnific.com/app/creation/yi0r27vPW9 |
|
| `sector-substation.jpg` | Sectors, tab 2 | https://www.magnific.com/app/creation/yi0r27vPW9 |
|
||||||
| `sector-datacentre.jpg` | Sectors, tab 3 | https://www.magnific.com/app/creation/eIHAvetdqL |
|
| `sector-datacentre.jpg` | Sectors, tab 4 | https://www.magnific.com/app/creation/eIHAvetdqL |
|
||||||
|
|
||||||
|
`sector-ot.jpg` (Sectors, tab 3, operational technology) is a real photograph from the Freepik
|
||||||
|
stock catalogue via Magnific, "Workers in control room" (free licence, Canon EOS 5D Mark III, 2017),
|
||||||
|
downloaded on 7 October 2026 and scaled to 2304 px.
|
||||||
|
|
||||||
They are placeholders in the sense of the brief: swap them for the company's own documentary
|
They are placeholders in the sense of the brief: swap them for the company's own documentary
|
||||||
photography when it exists, keeping the same file names and the `img-doc` treatment. The grid
|
photography when it exists, keeping the same file names and the `img-doc` treatment. The grid
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 443 KiB |
@@ -2,12 +2,13 @@
|
|||||||
import { Picture } from 'astro:assets';
|
import { Picture } from 'astro:assets';
|
||||||
import fiveG from '../../assets/images/sector-5g.jpg';
|
import fiveG from '../../assets/images/sector-5g.jpg';
|
||||||
import substation from '../../assets/images/sector-substation.jpg';
|
import substation from '../../assets/images/sector-substation.jpg';
|
||||||
|
import controlRoom from '../../assets/images/sector-ot.jpg';
|
||||||
import datacentre from '../../assets/images/sector-datacentre.jpg';
|
import datacentre from '../../assets/images/sector-datacentre.jpg';
|
||||||
import { t, type Lang } from '../../i18n/ui';
|
import { 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 images = [fiveG, substation, datacentre];
|
const images = [fiveG, substation, controlRoom, datacentre];
|
||||||
const items = ui.sectors.items.map((item, i) => ({ ...item, image: images[i], id: `sector-${i + 1}` }));
|
const items = ui.sectors.items.map((item, i) => ({ ...item, image: images[i], id: `sector-${i + 1}` }));
|
||||||
---
|
---
|
||||||
<section class="section sectors" id="sectors" aria-labelledby="sectors-title">
|
<section class="section sectors" id="sectors" aria-labelledby="sectors-title">
|
||||||
@@ -36,8 +37,8 @@ const items = ui.sectors.items.map((item, i) => ({ ...item, image: images[i], id
|
|||||||
alt={item.imageAlt}
|
alt={item.imageAlt}
|
||||||
formats={['avif']}
|
formats={['avif']}
|
||||||
fallbackFormat="webp"
|
fallbackFormat="webp"
|
||||||
widths={[480, 768, 1024, 1440]}
|
widths={[480, 768, 1024, 1440, 1920]}
|
||||||
sizes="(min-width: 64rem) 50vw, 100vw"
|
sizes="(min-width: 64rem) 60vw, 100vw"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
class="sectors__img img-doc"
|
class="sectors__img img-doc"
|
||||||
@@ -56,8 +57,8 @@ const items = ui.sectors.items.map((item, i) => ({ ...item, image: images[i], id
|
|||||||
alt=""
|
alt=""
|
||||||
formats={['avif']}
|
formats={['avif']}
|
||||||
fallbackFormat="webp"
|
fallbackFormat="webp"
|
||||||
widths={[480, 768, 1024, 1440]}
|
widths={[480, 768, 1024, 1440, 1920]}
|
||||||
sizes="(min-width: 64rem) 50vw, 100vw"
|
sizes="(min-width: 64rem) 60vw, 100vw"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
decoding="async"
|
decoding="async"
|
||||||
class="sectors__img img-doc"
|
class="sectors__img img-doc"
|
||||||
@@ -81,11 +82,34 @@ const items = ui.sectors.items.map((item, i) => ({ ...item, image: images[i], id
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: var(--space-8);
|
gap: var(--space-8);
|
||||||
}
|
}
|
||||||
.sectors__tabs { margin-top: auto; }
|
/* The tabs are rows between hairlines. The selected row's rule turns ink: that is the marker. */
|
||||||
.sectors__tab + .sectors__tab { margin-top: var(--space-5); }
|
.sectors__tabs {
|
||||||
|
margin-top: auto;
|
||||||
|
border-bottom: var(--hairline) solid var(--line);
|
||||||
|
}
|
||||||
|
.sectors__tab {
|
||||||
|
position: relative;
|
||||||
|
border-top: var(--hairline) solid var(--line);
|
||||||
|
}
|
||||||
|
.sectors__tab::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: calc(-1 * var(--hairline));
|
||||||
|
height: var(--hairline);
|
||||||
|
background: var(--ink);
|
||||||
|
transform: scaleX(0);
|
||||||
|
transform-origin: left;
|
||||||
|
transition: transform var(--dur-slow) var(--ease);
|
||||||
|
}
|
||||||
|
.sectors__tab:has([aria-selected='true'])::before { transform: none; }
|
||||||
.sectors__heading { font-size: inherit; font-weight: inherit; }
|
.sectors__heading { font-size: inherit; font-weight: inherit; }
|
||||||
|
/* The whole row is the button, so the target is as wide as the column. */
|
||||||
.sectors__button {
|
.sectors__button {
|
||||||
display: block;
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
padding-block: var(--space-5) var(--space-3);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-size: var(--step-h3);
|
font-size: var(--step-h3);
|
||||||
line-height: var(--leading-h3);
|
line-height: var(--leading-h3);
|
||||||
@@ -96,7 +120,11 @@ const items = ui.sectors.items.map((item, i) => ({ ...item, image: images[i], id
|
|||||||
}
|
}
|
||||||
.sectors__button[aria-selected='false'] { color: var(--ink-muted); }
|
.sectors__button[aria-selected='false'] { color: var(--ink-muted); }
|
||||||
.sectors__button[aria-selected='false']:hover { color: var(--ink); }
|
.sectors__button[aria-selected='false']:hover { color: var(--ink); }
|
||||||
.sectors__panel-inner { display: grid; gap: var(--space-5); padding-top: var(--space-3); }
|
.sectors__panel-inner { display: grid; gap: var(--space-5); }
|
||||||
|
/* The button keeps a small step below its title, so an open body sits close under it and the
|
||||||
|
focus ring never crosses the text. The rest of the row padding is the panel's margin: below
|
||||||
|
the body when open, in place of it when closed, so every row is symmetric. */
|
||||||
|
.sectors__panel { margin-bottom: var(--space-5); }
|
||||||
.sectors__panel p { max-width: 28em; }
|
.sectors__panel p { max-width: 28em; }
|
||||||
:global(.sectors__picture) { display: block; width: 100%; }
|
:global(.sectors__picture) { display: block; width: 100%; }
|
||||||
.sectors__img, :global(.sectors__img) { width: 100%; height: auto; aspect-ratio: 4 / 3; object-fit: cover; }
|
.sectors__img, :global(.sectors__img) { width: 100%; height: auto; aspect-ratio: 4 / 3; object-fit: cover; }
|
||||||
@@ -110,17 +138,27 @@ const items = ui.sectors.items.map((item, i) => ({ ...item, image: images[i], id
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-rows: 0fr;
|
grid-template-rows: 0fr;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: grid-template-rows var(--dur-slow) var(--ease);
|
transition:
|
||||||
|
grid-template-rows var(--dur-slow) var(--ease),
|
||||||
|
margin var(--dur-slow) var(--ease);
|
||||||
}
|
}
|
||||||
:global(html.js) .sectors__panel-inner { min-height: 0; }
|
:global(html.js) .sectors__panel-inner { min-height: 0; }
|
||||||
:global(html.js) .sectors__panel[data-open] { grid-template-rows: 1fr; }
|
:global(html.js) .sectors__panel[data-open] { grid-template-rows: 1fr; }
|
||||||
|
:global(html.js) .sectors__panel:not([data-open]) { margin-bottom: calc(var(--space-5) - var(--space-3)); }
|
||||||
|
|
||||||
@media (min-width: 64rem) {
|
@media (min-width: 64rem) {
|
||||||
.sectors__text { grid-column: 1 / span 5; }
|
.sectors__text { grid-column: 1 / span 5; }
|
||||||
|
/* The image runs past the container to the right edge of the window: the page gutter, plus
|
||||||
|
half of whatever the window is wider than the container. The section clips, so a classic
|
||||||
|
scrollbar (counted in 100vw) cannot cause a horizontal scroll. */
|
||||||
|
.sectors { overflow-x: clip; }
|
||||||
.sectors__media {
|
.sectors__media {
|
||||||
grid-column: 7 / span 6;
|
grid-column: 7 / span 6;
|
||||||
display: grid;
|
display: grid;
|
||||||
|
margin-right: calc(-1 * (max(var(--gutter), var(--safe-right)) + max(0px, (100vw - var(--container)) / 2)));
|
||||||
}
|
}
|
||||||
|
/* Near square, but never taller than a screenful on very wide windows. */
|
||||||
|
.sectors__media :global(.sectors__img) { aspect-ratio: 5 / 4; max-height: 45rem; }
|
||||||
.sectors__media :global(.sectors__picture--stage) {
|
.sectors__media :global(.sectors__picture--stage) {
|
||||||
grid-area: 1 / 1;
|
grid-area: 1 / 1;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
@@ -132,7 +170,7 @@ const items = ui.sectors.items.map((item, i) => ({ ...item, image: images[i], id
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.sectors__panel, .sectors__media :global(.sectors__picture--stage) { transition: none; }
|
.sectors__panel, .sectors__tab::before, .sectors__media :global(.sectors__picture--stage) { transition: none; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
+10
-8
@@ -82,10 +82,11 @@ const en = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
sectors: {
|
sectors: {
|
||||||
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 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: 'Operational technology (OT)', body: 'Controllers, SCADA and the engineering software that configures them. Systems built to run for decades, often on protocols without authentication, and rarely patched.', imageAlt: 'Three operators in hard hats at their consoles in a plant control room, facing a wall-sized diagram of the process.' },
|
||||||
{ 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.' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -332,10 +333,10 @@ const en = {
|
|||||||
roles: {
|
roles: {
|
||||||
ceo: 'Founder and CEO',
|
ceo: 'Founder and CEO',
|
||||||
cto: 'Founder and CTO',
|
cto: 'Founder and CTO',
|
||||||
engineer: 'Software engineer',
|
engineer: 'Software Engineer',
|
||||||
bizdev: 'Business development',
|
bizdev: 'Business Development',
|
||||||
studentF: 'Working student, development',
|
studentF: 'Mathematical Engineer',
|
||||||
studentM: 'Working student, development',
|
studentM: 'Process Optimization & Automation',
|
||||||
},
|
},
|
||||||
research: {
|
research: {
|
||||||
title: ['Published', 'research.'],
|
title: ['Published', 'research.'],
|
||||||
@@ -427,10 +428,11 @@ const de: typeof en = {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
sectors: {
|
sectors: {
|
||||||
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 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: '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: 'Operational Technology (OT)', body: 'Steuerungen, SCADA und die Engineering-Software, mit der sie konfiguriert werden. Anlagen, die Jahrzehnte laufen, oft über Protokolle ohne Authentisierung, und selten Updates erhalten.', imageAlt: 'Drei Bediener mit Schutzhelm an ihren Pulten in der Leitwarte eines Werks, vor einem wandgroßen Schema der Anlage.' },
|
||||||
{ 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.' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -679,8 +681,8 @@ const de: typeof en = {
|
|||||||
cto: 'Gründer und CTO',
|
cto: 'Gründer und CTO',
|
||||||
engineer: 'Software Engineer',
|
engineer: 'Software Engineer',
|
||||||
bizdev: 'Business Development',
|
bizdev: 'Business Development',
|
||||||
studentF: 'Werkstudentin, Entwicklung',
|
studentF: 'Mathematical Engineer',
|
||||||
studentM: 'Werkstudent, Entwicklung',
|
studentM: 'Process Optimization & Automation',
|
||||||
},
|
},
|
||||||
research: {
|
research: {
|
||||||
title: ['Veröffentlichte', 'Forschung.'],
|
title: ['Veröffentlichte', 'Forschung.'],
|
||||||
|
|||||||
Reference in new issue
Block a user