initial
This commit is contained in:
commit
4cda1ca41b
72 files changed
+8169
No files matched your search
@@ -0,0 +1,166 @@
|
||||
---
|
||||
import { Picture } from 'astro:assets';
|
||||
import fiveG from '../../assets/images/sector-5g.jpg';
|
||||
import substation from '../../assets/images/sector-substation.jpg';
|
||||
import datacentre from '../../assets/images/sector-datacentre.jpg';
|
||||
import { t, type Lang } from '../../i18n/ui';
|
||||
interface Props { lang: Lang }
|
||||
const { lang } = Astro.props;
|
||||
const ui = t(lang);
|
||||
const images = [fiveG, substation, datacentre];
|
||||
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">
|
||||
<div class="container sectors__grid" data-sectors>
|
||||
<div class="sectors__text">
|
||||
<h2 id="sectors-title" class="h2">{ui.sectors.title}</h2>
|
||||
<div class="sectors__tabs" role="tablist" aria-orientation="vertical" aria-labelledby="sectors-title">
|
||||
{items.map((item, i) => (
|
||||
<div class="sectors__tab" data-tab>
|
||||
<h3 class="sectors__heading">
|
||||
<button
|
||||
class="sectors__button"
|
||||
type="button"
|
||||
role="tab"
|
||||
id={`${item.id}-tab`}
|
||||
aria-controls={`${item.id}-panel`}
|
||||
aria-selected={i === 0 ? 'true' : 'false'}
|
||||
tabindex={i === 0 ? undefined : -1}
|
||||
>{item.title}</button>
|
||||
</h3>
|
||||
<div class="sectors__panel" role="tabpanel" id={`${item.id}-panel`} aria-labelledby={`${item.id}-tab`} tabindex="0">
|
||||
<div class="sectors__panel-inner">
|
||||
<p class="muted">{item.body}</p>
|
||||
<Picture
|
||||
src={item.image}
|
||||
alt={item.imageAlt}
|
||||
formats={['avif']}
|
||||
fallbackFormat="webp"
|
||||
widths={[480, 768, 1024, 1440]}
|
||||
sizes="(min-width: 64rem) 50vw, 100vw"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
class="sectors__img img-doc"
|
||||
pictureAttributes={{ class: 'sectors__picture' }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div class="sectors__media" aria-hidden="true">
|
||||
{items.map((item, i) => (
|
||||
<Picture
|
||||
src={item.image}
|
||||
alt=""
|
||||
formats={['avif']}
|
||||
fallbackFormat="webp"
|
||||
widths={[480, 768, 1024, 1440]}
|
||||
sizes="(min-width: 64rem) 50vw, 100vw"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
class="sectors__img img-doc"
|
||||
pictureAttributes={{ class: 'sectors__picture sectors__picture--stage', 'data-image': String(i), 'data-active': i === 0 ? '' : undefined }}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.sectors__grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(12, minmax(0, 1fr));
|
||||
column-gap: var(--gap);
|
||||
row-gap: var(--space-7);
|
||||
}
|
||||
.sectors__text {
|
||||
grid-column: 1 / -1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-8);
|
||||
}
|
||||
.sectors__tabs { margin-top: auto; }
|
||||
.sectors__tab + .sectors__tab { margin-top: var(--space-5); }
|
||||
.sectors__heading { font-size: inherit; font-weight: inherit; }
|
||||
.sectors__button {
|
||||
display: block;
|
||||
text-align: left;
|
||||
font-size: var(--step-h3);
|
||||
line-height: var(--leading-h3);
|
||||
letter-spacing: var(--track-h3);
|
||||
font-weight: var(--weight-medium);
|
||||
color: var(--ink);
|
||||
transition: color var(--dur-fast) var(--ease);
|
||||
}
|
||||
.sectors__button[aria-selected='false'] { color: var(--ink-muted); }
|
||||
.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 p { max-width: 28em; }
|
||||
:global(.sectors__picture) { display: block; width: 100%; }
|
||||
.sectors__img, :global(.sectors__img) { width: 100%; height: auto; aspect-ratio: 4 / 3; object-fit: cover; }
|
||||
|
||||
/* The stage image on the right exists for wide screens. On narrow screens each panel carries its own image. */
|
||||
.sectors__media { display: none; }
|
||||
|
||||
/* With scripting, one panel at a time; without, everything is readable. */
|
||||
:global(html.js) .sectors__panel {
|
||||
display: grid;
|
||||
grid-template-rows: 0fr;
|
||||
overflow: hidden;
|
||||
transition: grid-template-rows var(--dur-slow) var(--ease);
|
||||
}
|
||||
:global(html.js) .sectors__panel-inner { min-height: 0; }
|
||||
:global(html.js) .sectors__panel[data-open] { grid-template-rows: 1fr; }
|
||||
|
||||
@media (min-width: 64rem) {
|
||||
.sectors__text { grid-column: 1 / span 5; }
|
||||
.sectors__media {
|
||||
grid-column: 7 / span 6;
|
||||
display: grid;
|
||||
}
|
||||
.sectors__media :global(.sectors__picture--stage) {
|
||||
grid-area: 1 / 1;
|
||||
opacity: 0;
|
||||
transition: opacity var(--dur-slow) var(--ease);
|
||||
}
|
||||
.sectors__media :global(.sectors__picture--stage[data-active]) { opacity: 1; }
|
||||
/* Inside the panels the image is redundant on wide screens. */
|
||||
.sectors__panel :global(.sectors__picture) { display: none; }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.sectors__panel, .sectors__media :global(.sectors__picture--stage) { transition: none; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const root = document.querySelector<HTMLElement>('[data-sectors]');
|
||||
if (root) {
|
||||
const tabs = Array.from(root.querySelectorAll<HTMLButtonElement>('[role="tab"]'));
|
||||
const panels = tabs.map((t) => document.getElementById(t.getAttribute('aria-controls')!)!);
|
||||
const images = Array.from(root.querySelectorAll<HTMLElement>('[data-image]'));
|
||||
const select = (index: number, focus = false) => {
|
||||
tabs.forEach((tab, i) => {
|
||||
const on = i === index;
|
||||
tab.setAttribute('aria-selected', String(on));
|
||||
if (on) tab.removeAttribute('tabindex'); else tab.setAttribute('tabindex', '-1');
|
||||
if (on) panels[i].setAttribute('data-open', ''); else panels[i].removeAttribute('data-open');
|
||||
if (on) images[i]?.setAttribute('data-active', ''); else images[i]?.removeAttribute('data-active');
|
||||
});
|
||||
if (focus) tabs[index].focus();
|
||||
};
|
||||
tabs.forEach((tab, i) => {
|
||||
tab.addEventListener('click', () => select(i));
|
||||
tab.addEventListener('keydown', (e) => {
|
||||
const n = tabs.length;
|
||||
if (e.key === 'ArrowDown' || e.key === 'ArrowRight') { e.preventDefault(); select((i + 1) % n, true); }
|
||||
if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') { e.preventDefault(); select((i - 1 + n) % n, true); }
|
||||
if (e.key === 'Home') { e.preventDefault(); select(0, true); }
|
||||
if (e.key === 'End') { e.preventDefault(); select(n - 1, true); }
|
||||
});
|
||||
});
|
||||
select(0);
|
||||
}
|
||||
</script>
|
||||
Reference in new issue
Block a user