v4.0.6 0 Simplicity focus
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user