v4.0.6 0 Simplicity focus

This commit is contained in:
2026-07-04 10:00:16 +12:00
parent 8eb20813c8
commit 018024510b
31 changed files with 11902 additions and 744 deletions
@@ -114,7 +114,9 @@ describe('BookingSection', () => {
expect(payload.firstInteractionAt).toEqual(expect.any(Number));
expect(payload.sendClickedAt).toEqual(expect.any(Number));
expect(screen.getByRole('dialog', { name: /Booking confirmed/i })).toBeInTheDocument();
// The success modal is code-split (dynamic import) and only mounts after
// submission resolves, so wait for it rather than asserting synchronously.
expect(await screen.findByRole('dialog', { name: /Booking confirmed/i })).toBeInTheDocument();
expect(screen.getByRole('heading', { name: /on our radar/i })).toBeInTheDocument();
await fireEvent.click(screen.getByRole('button', { name: /Sounds great!/i }));
@@ -167,7 +169,8 @@ describe('BookingSection', () => {
journey: [window.location.pathname]
});
expect(screen.getByRole('dialog', { name: /Enquiry confirmed/i })).toBeInTheDocument();
// The success modal is code-split (dynamic import); wait for it to mount.
expect(await screen.findByRole('dialog', { name: /Enquiry confirmed/i })).toBeInTheDocument();
expect(screen.getByText(/Your message is with us!/i)).toBeInTheDocument();
});
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,347 @@
<script lang="ts">
/*
* Service-area coverage for the v5 service pages.
*
* Replaces the old fragile ServiceAreaMap (hand-placed SVG pins on fake
* geography, hidden entirely below 768px). This says the same thing more
* honestly: a central Auckland hub with coverage radiating out, and every
* suburb a real, tappable link to its local page. No pixel coordinates, so
* it is robust and identical on desktop and mobile.
*/
import { locationPages } from '$lib/content/locations';
import type { LocationPageContent, ServiceAreaStat } from '$lib/types';
export let eyebrow = 'Service area';
export let title = 'Where we walk across Auckland Central';
export let lead =
'Free door-to-door pickup and drop-off across Auckland Central and the suburbs around it. Tap your suburb for local parks, routes, and details.';
export let hubLabel = 'Auckland Central';
export let locations: LocationPageContent[] = locationPages;
export let stats: ServiceAreaStat[] | null = null;
$: suburbCount = locations.length;
$: resolvedStats =
stats ?? [
{ strong: `${suburbCount}`, label: 'suburbs covered' },
{ strong: 'Free', label: 'pickup & drop-off' },
{ strong: 'Door to door,', label: 'every walk' }
];
</script>
<section class="area" aria-labelledby="area-heading">
<div class="area-inner">
<div class="area-head">
<p class="area-eyebrow">{eyebrow}</p>
<h2 id="area-heading" class="area-title">{title}</h2>
<p class="area-lead">{lead}</p>
</div>
<div class="area-stats">
{#each resolvedStats as stat}
<span class="area-stat"><strong>{stat.strong}</strong> {stat.label}</span>
{/each}
</div>
<div class="area-field">
<svg
class="area-rings"
viewBox="0 0 100 64"
preserveAspectRatio="xMidYMin slice"
aria-hidden="true"
focusable="false"
>
<g fill="none" stroke="currentColor" stroke-width="0.3">
<circle cx="50" cy="2" r="14" />
<circle cx="50" cy="2" r="28" />
<circle cx="50" cy="2" r="44" />
<circle cx="50" cy="2" r="60" />
</g>
</svg>
<p class="area-hub">
<span class="area-hub-dot" aria-hidden="true"></span>
<span class="area-hub-text">
<strong>{hubLabel}</strong>
<span>The Goodwalk base. We come to you.</span>
</span>
</p>
<ul class="area-suburbs">
{#each locations as location}
<li>
<a class="area-suburb" href={`/locations/${location.slug}`}>{location.suburb}</a>
</li>
{/each}
</ul>
</div>
<p class="area-caption">
Every suburb links to its local page with parks, routes, and service details.
</p>
</div>
</section>
<style>
.area {
padding-block: clamp(48px, 6vw, 84px);
}
.area-inner {
max-width: 1120px;
margin: 0 auto;
padding-inline: var(--space-container-x);
}
/* ── Head: matches the v5 section system (centered eyebrow + H2) ── */
.area-head {
max-width: 40rem;
margin: 0 auto clamp(22px, 3vw, 32px);
text-align: center;
}
.area-eyebrow {
display: inline-flex;
align-items: center;
gap: 10px;
margin: 0 0 14px;
font-family: var(--font-body);
font-size: 12px;
font-weight: 700;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--gw-green);
}
.area-eyebrow::before {
content: '';
width: 24px;
height: 2px;
border-radius: 2px;
background: var(--yellow);
}
.area-title {
margin: 0;
font-family: var(--font-head);
font-size: clamp(28px, 3.4vw, 42px);
font-weight: 700;
line-height: 1.08;
letter-spacing: -0.03em;
color: var(--text-heading);
text-wrap: balance;
}
.area-lead {
margin: 14px auto 0;
max-width: 38rem;
color: var(--text-muted);
font-size: 17px;
line-height: 1.6;
}
/* ── Stat pills ── */
.area-stats {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 12px;
margin: 0 auto clamp(24px, 3vw, 36px);
}
.area-stat {
display: inline-flex;
align-items: center;
gap: 7px;
padding: 8px 16px;
border-radius: var(--radius-pill);
background: var(--surface-brand-soft);
color: var(--gw-green);
font-size: 14px;
font-weight: 600;
}
.area-stat strong {
font-family: var(--font-head);
font-weight: 700;
}
/* ── Coverage field ── */
.area-field {
position: relative;
isolation: isolate;
overflow: hidden;
border-radius: var(--radius-xl);
background: var(--surface-panel);
box-shadow:
inset 0 0 0 1px var(--border-soft-strong),
var(--shadow-card);
padding: clamp(28px, 4vw, 52px) clamp(18px, 3vw, 44px) clamp(30px, 4vw, 52px);
text-align: center;
}
/* Coverage radiating from the hub: concentric rings anchored top-centre,
plus a soft brand glow. Decorative, resolution-independent, no pins. */
.area-rings {
position: absolute;
inset: 0 0 auto 0;
width: 100%;
height: auto;
color: var(--gw-green);
opacity: 0.1;
z-index: 0;
pointer-events: none;
}
.area-field::before {
content: '';
position: absolute;
inset: 0;
z-index: 0;
pointer-events: none;
background: radial-gradient(
120% 88% at 50% -12%,
rgba(var(--brand-rgb), 0.1),
rgba(var(--brand-rgb), 0) 62%
);
}
/* ── Hub marker ── */
.area-hub {
position: relative;
z-index: 1;
display: inline-flex;
align-items: center;
gap: 12px;
margin: 0 auto clamp(22px, 3vw, 32px);
padding: 10px 18px 10px 14px;
border-radius: var(--radius-pill);
background: var(--gw-green);
color: var(--text-inverse);
box-shadow: 0 12px 26px rgba(33, 48, 33, 0.22);
}
.area-hub-dot {
position: relative;
flex: 0 0 auto;
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--yellow);
}
.area-hub-dot::after {
content: '';
position: absolute;
inset: -6px;
border-radius: 50%;
border: 2px solid var(--yellow);
opacity: 0.5;
animation: areaHubPulse 3s ease-out infinite;
}
.area-hub-text {
display: flex;
flex-direction: column;
align-items: flex-start;
text-align: left;
line-height: 1.1;
}
.area-hub-text strong {
font-family: var(--font-head);
font-size: 15px;
font-weight: 700;
letter-spacing: -0.01em;
}
.area-hub-text span {
margin-top: 2px;
font-size: 11.5px;
opacity: 0.82;
}
/* ── Suburb links: a centred, wrapping cloud. The functional core. ── */
.area-suburbs {
position: relative;
z-index: 1;
max-width: 46rem;
margin: 0 auto;
padding: 0;
list-style: none;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: clamp(8px, 1vw, 12px);
}
.area-suburb {
display: inline-flex;
align-items: center;
padding: 9px 16px;
border-radius: var(--radius-pill);
background: var(--surface-panel);
border: 1px solid var(--border-soft-strong);
color: var(--text-heading);
font-family: var(--font-head);
font-size: 14px;
font-weight: 600;
letter-spacing: -0.01em;
text-decoration: none;
box-shadow: 0 1px 2px rgba(17, 20, 24, 0.04);
transition:
transform 0.18s cubic-bezier(0.22, 1, 0.36, 1),
background 0.18s ease,
color 0.18s ease,
border-color 0.18s ease,
box-shadow 0.18s ease;
}
@media (hover: hover) {
.area-suburb:hover {
background: var(--gw-green);
border-color: var(--gw-green);
color: var(--text-inverse);
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(33, 48, 33, 0.18);
}
}
.area-suburb:focus-visible {
outline: 2px solid var(--gw-green);
outline-offset: 2px;
}
.area-suburb:active {
transform: translateY(0);
}
.area-caption {
margin: clamp(18px, 2.4vw, 24px) auto 0;
max-width: 40rem;
color: var(--text-subtle);
font-size: 13px;
line-height: 1.55;
text-align: center;
}
@keyframes areaHubPulse {
0% {
transform: scale(0.8);
opacity: 0.6;
}
70% {
transform: scale(1.5);
opacity: 0;
}
100% {
transform: scale(1.5);
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.area-hub-dot::after {
animation: none;
}
}
</style>