v4.0.6 - Simplicity focus
This commit is contained in:
@@ -8,6 +8,9 @@
|
||||
export let faqs: FaqItem[];
|
||||
export let emitSchema = true;
|
||||
export let variant: 'panel' | 'plain' = 'panel';
|
||||
// When the host page supplies its own heading (e.g. to match a page-wide
|
||||
// section system), suppress the built-in one but keep the schema + list.
|
||||
export let showHeading = true;
|
||||
|
||||
$: schemaJson = JSON.stringify({
|
||||
'@context': 'https://schema.org',
|
||||
@@ -30,12 +33,14 @@
|
||||
</svelte:head>
|
||||
|
||||
<div class:faq-section-plain={variant === 'plain'} class="faq-section">
|
||||
<h2 class="faq-section-heading">
|
||||
<span class="faq-section-icon"><Icon name="fas fa-circle-question" /></span>
|
||||
{title}
|
||||
</h2>
|
||||
{#if intro}
|
||||
<p class="faq-section-intro">{intro}</p>
|
||||
{#if showHeading}
|
||||
<h2 class="faq-section-heading">
|
||||
<span class="faq-section-icon"><Icon name="fas fa-circle-question" /></span>
|
||||
{title}
|
||||
</h2>
|
||||
{#if intro}
|
||||
<p class="faq-section-intro">{intro}</p>
|
||||
{/if}
|
||||
{/if}
|
||||
<div use:accordion class="faq">
|
||||
{#each faqs as faq}
|
||||
|
||||
@@ -1,929 +0,0 @@
|
||||
<script lang="ts">
|
||||
type LabelAnchor = 'top' | 'bottom' | 'left' | 'right';
|
||||
type LabelTone = 'brand' | 'accent';
|
||||
type Breakpoint = 'desktop' | 'tablet' | 'mobile';
|
||||
|
||||
type Placement = {
|
||||
x: number;
|
||||
y: number;
|
||||
anchor: LabelAnchor;
|
||||
dx?: number;
|
||||
dy?: number;
|
||||
visible?: boolean;
|
||||
z?: number;
|
||||
};
|
||||
|
||||
type Pin = {
|
||||
slug: string;
|
||||
suburb: string;
|
||||
tone?: LabelTone;
|
||||
desktop: Placement;
|
||||
tablet?: Partial<Placement>;
|
||||
mobile?: Partial<Placement>;
|
||||
};
|
||||
|
||||
const mapWidth = 640;
|
||||
const mapViewY = 100;
|
||||
const mapHeight = 350;
|
||||
const centre = { x: 320, y: 238 };
|
||||
const city = { x: 344, y: 176 };
|
||||
|
||||
const pins: Pin[] = [
|
||||
{
|
||||
slug: 'pt-chevalier',
|
||||
suburb: 'Pt Chevalier',
|
||||
tone: 'accent',
|
||||
desktop: { x: 124, y: 228, anchor: 'left', dy: -6, z: 4 },
|
||||
tablet: { x: 120, y: 230, anchor: 'left', dy: -4 },
|
||||
mobile: { x: 114, y: 230, anchor: 'right', dx: 8, dy: -2, z: 6 }
|
||||
},
|
||||
{
|
||||
slug: 'herne-bay',
|
||||
suburb: 'Herne Bay',
|
||||
tone: 'accent',
|
||||
desktop: { x: 226, y: 118, anchor: 'top', dx: -10, z: 5 },
|
||||
tablet: { x: 224, y: 122, anchor: 'top', dx: -14, dy: -2 },
|
||||
mobile: { x: 214, y: 116, anchor: 'bottom', dx: -14, dy: 6, z: 6 }
|
||||
},
|
||||
{
|
||||
slug: 'freemans-bay',
|
||||
suburb: 'Freemans Bay',
|
||||
desktop: { x: 330, y: 124, anchor: 'top', dx: 14, dy: -4, z: 5 },
|
||||
tablet: { x: 326, y: 130, anchor: 'top', dx: 10, dy: -2 },
|
||||
mobile: { x: 322, y: 132, anchor: 'top', dx: 8, dy: -4, visible: false }
|
||||
},
|
||||
{
|
||||
slug: 'ponsonby',
|
||||
suburb: 'Ponsonby',
|
||||
desktop: { x: 250, y: 178, anchor: 'left', dx: -4, dy: -10, z: 5 },
|
||||
tablet: { x: 244, y: 184, anchor: 'left', dx: -6, dy: -10 },
|
||||
mobile: { x: 238, y: 172, anchor: 'top', dx: -6, dy: -10, z: 6 }
|
||||
},
|
||||
{
|
||||
slug: 'grey-lynn',
|
||||
suburb: 'Grey Lynn',
|
||||
desktop: { x: 214, y: 220, anchor: 'left', dx: -10, dy: -2, z: 5 },
|
||||
tablet: { x: 208, y: 222, anchor: 'left', dx: -10, dy: 2 },
|
||||
mobile: { x: 196, y: 222, anchor: 'left', dx: -6, dy: 0, z: 6 }
|
||||
},
|
||||
{
|
||||
slug: 'kingsland',
|
||||
suburb: 'Kingsland',
|
||||
desktop: { x: 248, y: 258, anchor: 'left', dx: -6, dy: -2 },
|
||||
tablet: { x: 236, y: 260, anchor: 'left', dx: -8, dy: -2 },
|
||||
mobile: { x: 232, y: 260, anchor: 'left', dx: -2, visible: false }
|
||||
},
|
||||
{
|
||||
slug: 'morningside',
|
||||
suburb: 'Morningside',
|
||||
desktop: { x: 196, y: 290, anchor: 'left', dx: -10, dy: 2 },
|
||||
tablet: { x: 186, y: 290, anchor: 'left', dx: -8, dy: 4 },
|
||||
mobile: { x: 184, y: 288, anchor: 'left', dx: -2, dy: 2, visible: false }
|
||||
},
|
||||
{
|
||||
slug: 'mt-albert',
|
||||
suburb: 'Mt Albert',
|
||||
tone: 'accent',
|
||||
desktop: { x: 148, y: 314, anchor: 'left', dx: -6, dy: 2, z: 4 },
|
||||
tablet: { x: 148, y: 314, anchor: 'left', dx: -8, dy: 4 },
|
||||
mobile: { x: 144, y: 312, anchor: 'left', dx: -2, dy: 0, z: 5 }
|
||||
},
|
||||
{
|
||||
slug: 'sandringham',
|
||||
suburb: 'Sandringham',
|
||||
desktop: { x: 244, y: 344, anchor: 'bottom', dx: -10, dy: 0, z: 4 },
|
||||
tablet: { x: 238, y: 340, anchor: 'bottom', dx: -14, dy: 2 },
|
||||
mobile: { x: 238, y: 340, anchor: 'bottom', dx: -14, dy: 2, visible: false }
|
||||
},
|
||||
{
|
||||
slug: 'mt-eden',
|
||||
suburb: 'Mt Eden',
|
||||
desktop: { x: 344, y: 280, anchor: 'right', dx: 6, dy: -8, z: 5 },
|
||||
tablet: { x: 334, y: 282, anchor: 'right', dx: 6, dy: -6, z: 5 },
|
||||
mobile: { x: 332, y: 280, anchor: 'right', dx: 4, dy: -8, z: 6 }
|
||||
},
|
||||
{
|
||||
slug: 'balmoral',
|
||||
suburb: 'Balmoral',
|
||||
desktop: { x: 314, y: 334, anchor: 'right', dx: 8, dy: 4, z: 4 },
|
||||
tablet: { x: 306, y: 338, anchor: 'bottom', dx: 0, dy: 6, z: 4 },
|
||||
mobile: { x: 304, y: 338, anchor: 'bottom', dx: 0, dy: 6, visible: false }
|
||||
},
|
||||
{
|
||||
slug: 'remuera',
|
||||
suburb: 'Remuera',
|
||||
tone: 'accent',
|
||||
desktop: { x: 470, y: 272, anchor: 'right', dx: 10, dy: -10, z: 4 },
|
||||
tablet: { x: 452, y: 280, anchor: 'right', dx: 8, dy: -8, z: 4 },
|
||||
mobile: { x: 438, y: 276, anchor: 'right', dx: 6, dy: -6, z: 5 }
|
||||
},
|
||||
{
|
||||
slug: 'greenlane',
|
||||
suburb: 'Greenlane',
|
||||
desktop: { x: 426, y: 348, anchor: 'right', dx: 10, dy: 6, z: 4 },
|
||||
tablet: { x: 410, y: 346, anchor: 'right', dx: 8, dy: 6, z: 4 },
|
||||
mobile: { x: 404, y: 340, anchor: 'right', dx: 4, dy: 6, z: 5 }
|
||||
},
|
||||
{
|
||||
slug: 'mt-roskill',
|
||||
suburb: 'Mt Roskill',
|
||||
desktop: { x: 182, y: 384, anchor: 'left', dx: -2, dy: 4, z: 3 },
|
||||
tablet: { x: 180, y: 384, anchor: 'left', dx: -2, dy: 4, z: 3 },
|
||||
mobile: { x: 178, y: 382, anchor: 'left', dx: 4, dy: 2, z: 5 }
|
||||
},
|
||||
{
|
||||
slug: 'three-kings',
|
||||
suburb: 'Three Kings',
|
||||
desktop: { x: 296, y: 390, anchor: 'bottom', dx: 8, dy: 0, z: 4 },
|
||||
tablet: { x: 292, y: 388, anchor: 'bottom', dx: 8, dy: 0, z: 4 },
|
||||
mobile: { x: 292, y: 390, anchor: 'bottom', dx: 6, dy: 0, z: 6 }
|
||||
},
|
||||
{
|
||||
slug: 'hillsborough',
|
||||
suburb: 'Hillsborough',
|
||||
tone: 'accent',
|
||||
desktop: { x: 216, y: 426, anchor: 'bottom', dx: -6, dy: 0, z: 4 },
|
||||
tablet: { x: 214, y: 422, anchor: 'bottom', dx: -8, dy: 0, z: 4 },
|
||||
mobile: { x: 218, y: 418, anchor: 'top', dx: -2, dy: -8, z: 6 }
|
||||
},
|
||||
{
|
||||
slug: 'onehunga',
|
||||
suburb: 'Onehunga',
|
||||
desktop: { x: 364, y: 426, anchor: 'bottom', dx: 8, dy: 0, z: 3 },
|
||||
tablet: { x: 354, y: 424, anchor: 'bottom', dx: 8, dy: 0, z: 3 },
|
||||
mobile: { x: 352, y: 420, anchor: 'top', dx: 10, dy: -8, visible: false }
|
||||
}
|
||||
];
|
||||
|
||||
function percentX(x: number) {
|
||||
return `${(x / mapWidth) * 100}%`;
|
||||
}
|
||||
|
||||
function percentY(y: number) {
|
||||
return `${((y - mapViewY) / mapHeight) * 100}%`;
|
||||
}
|
||||
|
||||
function resolvePlacement(pin: Pin, breakpoint: Breakpoint): Placement {
|
||||
const desktop = pin.desktop;
|
||||
const override =
|
||||
breakpoint === 'desktop' ? {} : breakpoint === 'tablet' ? pin.tablet ?? {} : pin.mobile ?? {};
|
||||
|
||||
return {
|
||||
...desktop,
|
||||
...override
|
||||
};
|
||||
}
|
||||
|
||||
function placementTokens(prefix: string, placement: Placement, gap: number, stem: number) {
|
||||
const dx = placement.dx ?? 0;
|
||||
const dy = placement.dy ?? 0;
|
||||
|
||||
if (placement.anchor === 'left') {
|
||||
return [
|
||||
`--${prefix}-direction: row-reverse`,
|
||||
`--${prefix}-transform: translate(calc(-100% - ${gap}px + ${dx}px), calc(-50% + ${dy}px))`,
|
||||
`--${prefix}-stem-w: ${stem}px`,
|
||||
`--${prefix}-stem-h: 1.5px`
|
||||
];
|
||||
}
|
||||
|
||||
if (placement.anchor === 'right') {
|
||||
return [
|
||||
`--${prefix}-direction: row`,
|
||||
`--${prefix}-transform: translate(calc(${gap}px + ${dx}px), calc(-50% + ${dy}px))`,
|
||||
`--${prefix}-stem-w: ${stem}px`,
|
||||
`--${prefix}-stem-h: 1.5px`
|
||||
];
|
||||
}
|
||||
|
||||
if (placement.anchor === 'top') {
|
||||
return [
|
||||
`--${prefix}-direction: column-reverse`,
|
||||
`--${prefix}-transform: translate(calc(-50% + ${dx}px), calc(-100% - ${gap}px + ${dy}px))`,
|
||||
`--${prefix}-stem-w: 1.5px`,
|
||||
`--${prefix}-stem-h: ${stem}px`
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
`--${prefix}-direction: column`,
|
||||
`--${prefix}-transform: translate(calc(-50% + ${dx}px), calc(${gap}px + ${dy}px))`,
|
||||
`--${prefix}-stem-w: 1.5px`,
|
||||
`--${prefix}-stem-h: ${stem}px`
|
||||
];
|
||||
}
|
||||
|
||||
function displayToken(placement: Placement | undefined) {
|
||||
return placement?.visible === false ? 'none' : 'inline-flex';
|
||||
}
|
||||
|
||||
function pinStyle(pin: Pin, index: number) {
|
||||
const desktop = resolvePlacement(pin, 'desktop');
|
||||
const tablet = resolvePlacement(pin, 'tablet');
|
||||
const mobile = resolvePlacement(pin, 'mobile');
|
||||
|
||||
return [
|
||||
`--desktop-left: ${percentX(desktop.x)}`,
|
||||
`--desktop-top: ${percentY(desktop.y)}`,
|
||||
`--desktop-z: ${desktop.z ?? 3}`,
|
||||
`--desktop-display: ${displayToken(desktop)}`,
|
||||
...placementTokens('desktop', desktop, 14, 16),
|
||||
`--tablet-left: ${percentX(tablet.x)}`,
|
||||
`--tablet-top: ${percentY(tablet.y)}`,
|
||||
`--tablet-z: ${tablet.z ?? desktop.z ?? 3}`,
|
||||
`--tablet-display: ${displayToken(tablet)}`,
|
||||
...placementTokens('tablet', tablet, 12, 14),
|
||||
`--mobile-left: ${percentX(mobile.x)}`,
|
||||
`--mobile-top: ${percentY(mobile.y)}`,
|
||||
`--mobile-z: ${mobile.z ?? tablet.z ?? desktop.z ?? 3}`,
|
||||
`--mobile-display: ${displayToken(mobile)}`,
|
||||
...placementTokens('mobile', mobile, 10, 12),
|
||||
`--pin-delay: ${((index * 0.11) % 1.6).toFixed(2)}s`
|
||||
].join('; ');
|
||||
}
|
||||
|
||||
function routePath(pin: Pin) {
|
||||
const target = pin.desktop;
|
||||
const controlX = (centre.x + target.x) / 2;
|
||||
const controlY = (centre.y + target.y) / 2 + (target.y >= centre.y ? 14 : -14);
|
||||
|
||||
return `M ${centre.x} ${centre.y} Q ${controlX} ${controlY} ${target.x} ${target.y}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<figure class="area-map" aria-labelledby="area-map-caption">
|
||||
<div class="area-map-shell">
|
||||
<div class="area-map-stage">
|
||||
<svg
|
||||
class="area-map-svg"
|
||||
viewBox={`0 ${mapViewY} ${mapWidth} ${mapHeight}`}
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
>
|
||||
<defs>
|
||||
<linearGradient id="area-map-bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="rgba(var(--white-rgb), 0.98)" />
|
||||
<stop offset="58%" stop-color="rgba(var(--white-rgb), 0.94)" />
|
||||
<stop offset="100%" stop-color="rgba(var(--accent-rgb), 0.1)" />
|
||||
</linearGradient>
|
||||
<radialGradient id="area-map-glow" cx="50%" cy="46%" r="54%">
|
||||
<stop offset="0%" stop-color="rgba(var(--accent-rgb), 0.14)" />
|
||||
<stop offset="48%" stop-color="rgba(var(--accent-rgb), 0.04)" />
|
||||
<stop offset="100%" stop-color="rgba(var(--accent-rgb), 0)" />
|
||||
</radialGradient>
|
||||
<radialGradient id="area-map-core-glow" cx="50%" cy="50%" r="58%">
|
||||
<stop offset="0%" stop-color="rgba(var(--brand-rgb), 0.12)" />
|
||||
<stop offset="70%" stop-color="rgba(var(--brand-rgb), 0.015)" />
|
||||
<stop offset="100%" stop-color="rgba(var(--brand-rgb), 0)" />
|
||||
</radialGradient>
|
||||
<linearGradient id="area-map-district-main" x1="18%" y1="12%" x2="82%" y2="88%">
|
||||
<stop offset="0%" stop-color="rgba(var(--brand-rgb), 0.16)" />
|
||||
<stop offset="100%" stop-color="rgba(var(--brand-rgb), 0.05)" />
|
||||
</linearGradient>
|
||||
<linearGradient id="area-map-district-soft" x1="10%" y1="20%" x2="95%" y2="85%">
|
||||
<stop offset="0%" stop-color="rgba(var(--accent-rgb), 0.1)" />
|
||||
<stop offset="100%" stop-color="rgba(var(--accent-rgb), 0.02)" />
|
||||
</linearGradient>
|
||||
<linearGradient id="area-map-route-flow" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" stop-color="rgba(var(--brand-rgb), 0)" />
|
||||
<stop offset="40%" stop-color="rgba(var(--brand-rgb), 0.04)" />
|
||||
<stop offset="56%" stop-color="rgba(var(--accent-rgb), 0.44)" />
|
||||
<stop offset="72%" stop-color="rgba(var(--brand-rgb), 0.08)" />
|
||||
<stop offset="100%" stop-color="rgba(var(--brand-rgb), 0)" />
|
||||
</linearGradient>
|
||||
<pattern id="area-map-grid" width="52" height="52" patternUnits="userSpaceOnUse">
|
||||
<path d="M 52 0 L 0 0 0 52" fill="none" stroke="rgba(var(--brand-rgb), 0.045)" stroke-width="1" />
|
||||
</pattern>
|
||||
<filter id="area-map-shadow" x="-20%" y="-20%" width="140%" height="140%">
|
||||
<feDropShadow dx="0" dy="10" stdDeviation="12" flood-color="rgba(var(--brand-rgb), 0.1)" />
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<rect x="0" y="0" width={mapWidth} height="500" rx="32" fill="url(#area-map-bg)" />
|
||||
<rect x="0" y="0" width={mapWidth} height="500" rx="32" fill="url(#area-map-grid)" />
|
||||
<ellipse cx="324" cy="234" rx="206" ry="154" fill="url(#area-map-glow)" />
|
||||
|
||||
<g class="area-map-waterways">
|
||||
<path
|
||||
d="M 26 108 C 96 74, 176 66, 244 84 C 302 100, 336 124, 396 122 C 470 120, 548 76, 620 90"
|
||||
class="area-map-waterway"
|
||||
/>
|
||||
<path
|
||||
d="M 78 454 C 140 430, 186 416, 240 420 C 292 424, 330 460, 392 462 C 472 466, 560 428, 616 384"
|
||||
class="area-map-waterway area-map-waterway-soft"
|
||||
/>
|
||||
</g>
|
||||
|
||||
<g class="area-map-districts" filter="url(#area-map-shadow)">
|
||||
<path
|
||||
d="M 94 206 C 132 162, 198 132, 274 126 C 334 122, 396 134, 446 160 C 500 188, 532 240, 530 300 C 528 362, 492 412, 430 434 C 342 466, 224 458, 150 412 C 92 376, 70 282, 94 206 Z"
|
||||
fill="url(#area-map-district-main)"
|
||||
/>
|
||||
<path
|
||||
d="M 126 214 C 168 180, 222 164, 284 164 C 348 164, 402 180, 442 214 C 474 242, 490 280, 486 320 C 482 366, 448 402, 394 420 C 316 446, 214 438, 154 394 C 112 360, 98 274, 126 214 Z"
|
||||
fill="rgba(var(--white-rgb), 0.32)"
|
||||
stroke="rgba(var(--brand-rgb), 0.08)"
|
||||
stroke-width="1"
|
||||
/>
|
||||
<path
|
||||
d="M 300 158 C 360 158, 420 176, 462 210 C 500 240, 522 278, 522 322 C 522 370, 500 402, 462 414 C 420 426, 362 412, 332 378 C 304 346, 302 312, 338 282 C 366 256, 384 230, 378 202 C 372 178, 346 162, 300 158 Z"
|
||||
fill="url(#area-map-district-soft)"
|
||||
/>
|
||||
<path
|
||||
d="M 156 248 C 188 236, 224 244, 248 268 C 268 288, 278 320, 266 348 C 252 382, 218 402, 180 402 C 148 400, 120 384, 104 356 C 88 324, 92 286, 116 262 C 128 250, 142 244, 156 248 Z"
|
||||
fill="rgba(var(--white-rgb), 0.16)"
|
||||
/>
|
||||
</g>
|
||||
|
||||
<g class="area-map-roads" aria-hidden="true">
|
||||
<path d="M 148 248 C 204 250, 244 266, 286 292 C 322 316, 360 354, 406 390" class="area-map-road" />
|
||||
<path d="M 240 126 C 270 160, 292 194, 316 238 C 336 278, 356 340, 364 430" class="area-map-road area-map-road-strong" />
|
||||
<path d="M 122 300 C 182 304, 244 300, 306 290 C 356 282, 418 264, 468 238" class="area-map-road area-map-road-soft" />
|
||||
</g>
|
||||
|
||||
<g class="area-map-routes">
|
||||
{#each pins as pin}
|
||||
<path d={routePath(pin)} class="area-map-route-base" pathLength="1" />
|
||||
<path d={routePath(pin)} class="area-map-route-flow" pathLength="1" />
|
||||
{/each}
|
||||
</g>
|
||||
|
||||
<g class="area-map-core">
|
||||
<circle cx={centre.x} cy={centre.y} r="64" class="area-map-core-aura" />
|
||||
<circle cx={centre.x} cy={centre.y} r="48" class="area-map-core-halo" />
|
||||
<circle cx={centre.x} cy={centre.y} r="24" class="area-map-core-pulse" />
|
||||
<circle cx={centre.x} cy={centre.y} r="15" class="area-map-core-ring" />
|
||||
<circle cx={centre.x} cy={centre.y} r="10" class="area-map-core-dot" />
|
||||
</g>
|
||||
|
||||
<g class="area-map-skyline" transform={`translate(${city.x - 46} ${city.y - 54}) scale(0.94)`}>
|
||||
<ellipse cx="42" cy="74" rx="34" ry="8" class="area-map-tower-shadow" />
|
||||
<path d="M 16 72 C 24 58, 29 41, 32 21 C 34 10, 36 6, 38.5 2.5 C 39.4 1.2, 40.2 0.5, 40.8 0 C 41.5 0.5, 42.3 1.2, 43.2 2.5 C 45.8 6, 47.8 10, 49.8 21 C 52.8 41, 57.8 58, 65.5 72 L 59 72 C 54.8 63.5, 51.8 49.5, 48.4 31 L 47.2 31 L 47.2 20.5 C 47.2 17.6, 46.3 15.7, 44.8 13.8 L 43.9 12.4 L 45 12.4 L 44 8.6 L 42.5 8.6 L 42.8 4.8 L 41.4 4.8 L 40.2 4.8 L 38.8 4.8 L 39.1 8.6 L 37.6 8.6 L 36.6 12.4 L 37.7 12.4 L 36.8 13.8 C 35.3 15.7, 34.4 17.6, 34.4 20.5 L 34.4 31 L 33.2 31 C 29.8 49.5, 26.8 63.5, 22.6 72 Z" class="area-map-tower-body" />
|
||||
<path d="M 34 21.5 C 34 16.5, 36.8 13.6, 40.8 13.6 C 44.8 13.6, 47.6 16.5, 47.6 21.5 C 47.6 25.3, 45.8 28.2, 43 29.5 L 43 34.2 C 45.8 34.9, 48 36.5, 49.6 39.3 L 31.8 39.3 C 33.5 36.5, 35.6 34.9, 38.6 34.2 L 38.6 29.5 C 35.8 28.2, 34 25.3, 34 21.5 Z" class="area-map-tower-observation" />
|
||||
<path d="M 30.8 39.3 L 50.6 39.3 L 52 42.9 L 29.4 42.9 Z" class="area-map-tower-band" />
|
||||
<path d="M 29.4 42.9 C 32.8 46.4, 36.1 47.8, 40.8 47.8 C 45.5 47.8, 48.8 46.4, 52.2 42.9 L 51.1 49.4 C 47.8 51.5, 45 52.2, 40.8 52.2 C 36.6 52.2, 33.8 51.5, 30.5 49.4 Z" class="area-map-tower-ring" />
|
||||
<path d="M 33.2 52.2 L 48.4 52.2 L 50 72 L 31.6 72 Z" class="area-map-tower-stem" />
|
||||
<path d="M 37.4 55.6 L 39.4 55.6 L 39.4 68.8 L 37.4 68.8 Z M 42.2 55.6 L 44.2 55.6 L 44.2 68.8 L 42.2 68.8 Z" class="area-map-tower-slit" />
|
||||
<circle cx="40.8" cy="10.4" r="1.9" class="area-map-tower-beacon" />
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<div class="area-map-overlay">
|
||||
<span
|
||||
class="area-map-label area-map-label-static area-map-label-city"
|
||||
style={`left:${percentX(city.x)}; top:${percentY(city.y)};`}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<span class="area-map-label-stem"></span>
|
||||
<span class="area-map-label-dot-wrap">
|
||||
<span class="area-map-label-dot area-map-label-dot-city"></span>
|
||||
</span>
|
||||
<span class="area-map-label-pill area-map-label-pill-city">City</span>
|
||||
</span>
|
||||
|
||||
{#each pins as pin, index}
|
||||
<a
|
||||
href={`/locations/${pin.slug}`}
|
||||
class={`area-map-label ${pin.tone === 'accent' ? 'area-map-label-accent' : ''}`}
|
||||
style={pinStyle(pin, index)}
|
||||
aria-label={`View ${pin.suburb} location page`}
|
||||
>
|
||||
<span class="area-map-label-stem" aria-hidden="true"></span>
|
||||
<span class="area-map-label-dot-wrap" aria-hidden="true">
|
||||
<span class="area-map-label-pulse"></span>
|
||||
<span class="area-map-label-dot"></span>
|
||||
</span>
|
||||
<span class="area-map-label-pill">
|
||||
<span class="area-map-label-text-full">{pin.suburb}</span>
|
||||
</span>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<figcaption id="area-map-caption" class="area-map-caption">
|
||||
Tap a suburb to open its local page with parks, routes, and service details.
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<style>
|
||||
.area-map {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.area-map {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.area-map-shell {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 0 clamp(12px, 1.8vw, 18px) clamp(14px, 2vw, 20px);
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.area-map-stage {
|
||||
position: relative;
|
||||
width: min(100%, 54rem);
|
||||
overflow: hidden;
|
||||
border-radius: clamp(26px, 2.8vw, 34px);
|
||||
background:
|
||||
radial-gradient(circle at 16% 12%, rgba(var(--accent-rgb), 0.1), transparent 30%),
|
||||
linear-gradient(180deg, rgba(var(--white-rgb), 0.24), rgba(var(--white-rgb), 0));
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(var(--brand-rgb), 0.08),
|
||||
0 18px 42px rgba(var(--ink-rgb), 0.08);
|
||||
}
|
||||
|
||||
.area-map-svg {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
aspect-ratio: 640 / 340;
|
||||
}
|
||||
|
||||
.area-map-overlay {
|
||||
position: absolute;
|
||||
inset: clamp(14px, 2vw, 20px) clamp(28px, 4vw, 48px);
|
||||
overflow: visible;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.area-map-waterway {
|
||||
fill: none;
|
||||
stroke: rgba(var(--white-rgb), 0.56);
|
||||
stroke-width: 10;
|
||||
stroke-linecap: round;
|
||||
opacity: 0.56;
|
||||
}
|
||||
|
||||
.area-map-waterway-soft {
|
||||
stroke-width: 7;
|
||||
opacity: 0.28;
|
||||
}
|
||||
|
||||
.area-map-road {
|
||||
fill: none;
|
||||
stroke: rgba(var(--brand-rgb), 0.1);
|
||||
stroke-width: 1.8;
|
||||
stroke-linecap: round;
|
||||
stroke-dasharray: 1 9;
|
||||
opacity: 0.66;
|
||||
}
|
||||
|
||||
.area-map-road-soft {
|
||||
opacity: 0.38;
|
||||
stroke-dasharray: 1 11;
|
||||
}
|
||||
|
||||
.area-map-road-strong {
|
||||
stroke: rgba(var(--brand-rgb), 0.15);
|
||||
stroke-width: 2.2;
|
||||
opacity: 0.72;
|
||||
}
|
||||
|
||||
.area-map-route-base {
|
||||
fill: none;
|
||||
stroke: rgba(var(--brand-rgb), 0.08);
|
||||
stroke-width: 1.2;
|
||||
stroke-linecap: round;
|
||||
opacity: 0.72;
|
||||
}
|
||||
|
||||
.area-map-route-flow {
|
||||
fill: none;
|
||||
stroke: url(#area-map-route-flow);
|
||||
stroke-width: 2.4;
|
||||
stroke-linecap: round;
|
||||
stroke-dasharray: 0.18 0.82;
|
||||
animation: areaRouteFlow 7.2s linear infinite;
|
||||
opacity: 0.76;
|
||||
}
|
||||
|
||||
.area-map-core-aura {
|
||||
fill: url(#area-map-core-glow);
|
||||
}
|
||||
|
||||
.area-map-core-halo {
|
||||
fill: rgba(var(--accent-rgb), 0.12);
|
||||
}
|
||||
|
||||
.area-map-core-pulse {
|
||||
fill: rgba(var(--brand-rgb), 0.1);
|
||||
transform-origin: 320px 238px;
|
||||
animation: areaCorePulse 4.8s ease-out infinite;
|
||||
}
|
||||
|
||||
.area-map-core-ring {
|
||||
fill: rgba(var(--white-rgb), 0.86);
|
||||
stroke: rgba(var(--brand-rgb), 0.16);
|
||||
stroke-width: 1.25;
|
||||
}
|
||||
|
||||
.area-map-core-dot {
|
||||
fill: var(--gw-green);
|
||||
stroke: rgba(var(--accent-rgb), 0.9);
|
||||
stroke-width: 2.2;
|
||||
}
|
||||
|
||||
.area-map-skyline {
|
||||
pointer-events: none;
|
||||
opacity: 0.62;
|
||||
filter: drop-shadow(0 6px 12px rgba(var(--ink-rgb), 0.12));
|
||||
}
|
||||
|
||||
.area-map-tower-shadow {
|
||||
fill: rgba(var(--ink-rgb), 0.08);
|
||||
}
|
||||
|
||||
.area-map-tower-body,
|
||||
.area-map-tower-observation,
|
||||
.area-map-tower-band,
|
||||
.area-map-tower-ring,
|
||||
.area-map-tower-stem,
|
||||
.area-map-tower-slit {
|
||||
animation: areaTowerFloat 8s ease-in-out infinite;
|
||||
transform-origin: 40.8px 74px;
|
||||
}
|
||||
|
||||
.area-map-tower-body {
|
||||
fill: rgba(var(--white-rgb), 0.92);
|
||||
stroke: rgba(var(--ink-rgb), 0.24);
|
||||
stroke-width: 1.05;
|
||||
}
|
||||
|
||||
.area-map-tower-observation {
|
||||
fill: rgba(var(--white-rgb), 0.96);
|
||||
stroke: rgba(var(--ink-rgb), 0.3);
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
.area-map-tower-band {
|
||||
fill: rgba(var(--ink-rgb), 0.72);
|
||||
}
|
||||
|
||||
.area-map-tower-ring {
|
||||
fill: rgba(var(--white-rgb), 0.98);
|
||||
stroke: rgba(var(--ink-rgb), 0.26);
|
||||
stroke-width: 0.9;
|
||||
}
|
||||
|
||||
.area-map-tower-stem {
|
||||
fill: rgba(var(--white-rgb), 0.84);
|
||||
stroke: rgba(var(--ink-rgb), 0.2);
|
||||
stroke-width: 0.85;
|
||||
}
|
||||
|
||||
.area-map-tower-slit {
|
||||
fill: rgba(var(--ink-rgb), 0.76);
|
||||
}
|
||||
|
||||
.area-map-tower-beacon {
|
||||
fill: var(--yellow);
|
||||
opacity: 0.7;
|
||||
animation: areaBeaconBlink 4.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.area-map-label {
|
||||
position: absolute;
|
||||
left: var(--desktop-left);
|
||||
top: var(--desktop-top);
|
||||
z-index: var(--desktop-z);
|
||||
display: var(--desktop-display);
|
||||
flex-direction: var(--desktop-direction);
|
||||
align-items: center;
|
||||
gap: clamp(6px, 0.85vw, 9px);
|
||||
transform: var(--desktop-transform);
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
pointer-events: auto;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.area-map-label-static {
|
||||
left: 53.75%;
|
||||
top: 32.38%;
|
||||
z-index: 2;
|
||||
transform: translate(-50%, calc(-100% - 12px));
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.area-map-label-stem {
|
||||
display: block;
|
||||
flex: 0 0 auto;
|
||||
width: var(--desktop-stem-w);
|
||||
height: var(--desktop-stem-h);
|
||||
border-radius: 999px;
|
||||
background: rgba(var(--brand-rgb), 0.18);
|
||||
transition: background var(--motion-fast);
|
||||
}
|
||||
|
||||
.area-map-label-dot-wrap {
|
||||
position: relative;
|
||||
flex: 0 0 auto;
|
||||
width: clamp(11px, 1.4vw, 14px);
|
||||
height: clamp(11px, 1.4vw, 14px);
|
||||
}
|
||||
|
||||
.area-map-label-dot {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
margin: auto;
|
||||
width: clamp(7px, 1vw, 9px);
|
||||
height: clamp(7px, 1vw, 9px);
|
||||
border-radius: 50%;
|
||||
background: var(--gw-green);
|
||||
border: 2px solid rgba(var(--white-rgb), 0.96);
|
||||
box-shadow: 0 0 0 5px rgba(var(--accent-rgb), 0.11);
|
||||
transition:
|
||||
transform var(--motion-fast),
|
||||
background var(--motion-fast),
|
||||
box-shadow var(--motion-fast);
|
||||
}
|
||||
|
||||
.area-map-label-pulse {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: 999px;
|
||||
background: rgba(var(--brand-rgb), 0.14);
|
||||
transform: scale(0.5);
|
||||
opacity: 0;
|
||||
animation: areaPinPulse 4.6s ease-out infinite;
|
||||
animation-delay: var(--pin-delay);
|
||||
}
|
||||
|
||||
.area-map-label-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: clamp(30px, 3vw, 34px);
|
||||
padding: clamp(6px, 0.95vw, 8px) clamp(10px, 1.35vw, 14px);
|
||||
border: 1px solid rgba(var(--brand-rgb), 0.12);
|
||||
border-radius: 999px;
|
||||
background: rgba(var(--white-rgb), 0.95);
|
||||
color: var(--text-brand);
|
||||
font-family: var(--font-head);
|
||||
font-size: clamp(0.61rem, 0.52rem + 0.24vw, 0.78rem);
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.015em;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(var(--white-rgb), 0.82),
|
||||
0 8px 18px rgba(var(--ink-rgb), 0.06);
|
||||
transition:
|
||||
transform var(--motion-fast),
|
||||
background var(--motion-fast),
|
||||
border-color var(--motion-fast),
|
||||
box-shadow var(--motion-fast),
|
||||
color var(--motion-fast);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.area-map-label-accent .area-map-label-pill {
|
||||
background: rgba(var(--accent-rgb), 0.12);
|
||||
border-color: rgba(var(--accent-rgb), 0.2);
|
||||
}
|
||||
|
||||
.area-map-label-pill-city {
|
||||
min-height: 28px;
|
||||
padding: 6px 10px;
|
||||
background: rgba(var(--brand-rgb), 0.1);
|
||||
border-color: rgba(var(--brand-rgb), 0.16);
|
||||
font-size: 0.66rem;
|
||||
box-shadow: 0 6px 14px rgba(var(--ink-rgb), 0.05);
|
||||
}
|
||||
|
||||
.area-map-label-dot-city {
|
||||
background: var(--yellow);
|
||||
box-shadow: 0 0 0 5px rgba(var(--brand-rgb), 0.1);
|
||||
}
|
||||
|
||||
.area-map-label:hover .area-map-label-pill,
|
||||
.area-map-label:focus-visible .area-map-label-pill {
|
||||
background: var(--gw-green);
|
||||
border-color: rgba(var(--brand-rgb), 0.18);
|
||||
color: var(--text-inverse);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 10px 22px rgba(var(--brand-rgb), 0.14);
|
||||
}
|
||||
|
||||
.area-map-label:hover .area-map-label-stem,
|
||||
.area-map-label:focus-visible .area-map-label-stem {
|
||||
background: rgba(var(--brand-rgb), 0.32);
|
||||
}
|
||||
|
||||
.area-map-label:hover .area-map-label-dot,
|
||||
.area-map-label:focus-visible .area-map-label-dot {
|
||||
background: var(--yellow);
|
||||
transform: scale(1.12);
|
||||
box-shadow: 0 0 0 6px rgba(var(--accent-rgb), 0.16);
|
||||
}
|
||||
|
||||
.area-map-caption {
|
||||
margin: 12px auto 0;
|
||||
max-width: 54rem;
|
||||
color: var(--text-subtle);
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@keyframes areaCorePulse {
|
||||
0% {
|
||||
transform: scale(0.8);
|
||||
opacity: 0.42;
|
||||
}
|
||||
72% {
|
||||
transform: scale(1.2);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: scale(1.2);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes areaPinPulse {
|
||||
0% {
|
||||
transform: scale(0.52);
|
||||
opacity: 0.32;
|
||||
}
|
||||
78% {
|
||||
transform: scale(2.25);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: scale(2.25);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes areaRouteFlow {
|
||||
from {
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
to {
|
||||
stroke-dashoffset: -1.3;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes areaTowerFloat {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-1.5px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes areaBeaconBlink {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.3;
|
||||
transform: scale(0.92);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.74;
|
||||
transform: scale(1.04);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1120px) {
|
||||
.area-map-shell {
|
||||
padding-block: clamp(14px, 2vw, 20px);
|
||||
}
|
||||
|
||||
.area-map-stage {
|
||||
width: min(100%, 48rem);
|
||||
}
|
||||
|
||||
.area-map-overlay {
|
||||
inset: 16px 18px 18px;
|
||||
}
|
||||
|
||||
.area-map-label {
|
||||
left: var(--tablet-left);
|
||||
top: var(--tablet-top);
|
||||
z-index: var(--tablet-z);
|
||||
display: var(--tablet-display);
|
||||
flex-direction: var(--tablet-direction);
|
||||
transform: var(--tablet-transform);
|
||||
}
|
||||
|
||||
.area-map-label-stem {
|
||||
width: var(--tablet-stem-w);
|
||||
height: var(--tablet-stem-h);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.area-map-stage {
|
||||
width: 100%;
|
||||
border-radius: 26px;
|
||||
}
|
||||
|
||||
.area-map-overlay {
|
||||
inset: 14px 16px 18px;
|
||||
}
|
||||
|
||||
.area-map-label-pill {
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(var(--white-rgb), 0.84),
|
||||
0 6px 14px rgba(var(--ink-rgb), 0.06);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.area-map-shell {
|
||||
padding-inline: 4px;
|
||||
padding-bottom: 14px;
|
||||
}
|
||||
|
||||
.area-map-stage {
|
||||
border-radius: 22px;
|
||||
}
|
||||
|
||||
.area-map-overlay {
|
||||
inset: 16px 14px 22px;
|
||||
}
|
||||
|
||||
.area-map-label {
|
||||
left: var(--mobile-left);
|
||||
top: var(--mobile-top);
|
||||
z-index: var(--mobile-z);
|
||||
display: var(--mobile-display);
|
||||
flex-direction: var(--mobile-direction);
|
||||
gap: 5px;
|
||||
transform: var(--mobile-transform);
|
||||
}
|
||||
|
||||
.area-map-label-stem {
|
||||
width: var(--mobile-stem-w);
|
||||
height: var(--mobile-stem-h);
|
||||
}
|
||||
|
||||
.area-map-label-pill {
|
||||
min-height: 28px;
|
||||
padding: 6px 9px;
|
||||
font-size: clamp(0.56rem, 0.52rem + 0.18vw, 0.64rem);
|
||||
}
|
||||
|
||||
.area-map-label-pill-city {
|
||||
min-height: 26px;
|
||||
padding-inline: 8px;
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
|
||||
.area-map-label-dot-wrap {
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
}
|
||||
|
||||
.area-map-label-dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
box-shadow: 0 0 0 4px rgba(var(--accent-rgb), 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 430px) {
|
||||
.area-map-shell {
|
||||
padding-inline: 0;
|
||||
}
|
||||
|
||||
.area-map-overlay {
|
||||
inset: 18px 12px 24px;
|
||||
}
|
||||
|
||||
.area-map-label-pill {
|
||||
min-height: 27px;
|
||||
padding: 5px 8px;
|
||||
font-size: 0.55rem;
|
||||
letter-spacing: -0.012em;
|
||||
}
|
||||
|
||||
.area-map-caption {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.area-map-route-flow,
|
||||
.area-map-core-pulse,
|
||||
.area-map-label-pulse,
|
||||
.area-map-tower-body,
|
||||
.area-map-tower-observation,
|
||||
.area-map-tower-band,
|
||||
.area-map-tower-ring,
|
||||
.area-map-tower-stem,
|
||||
.area-map-tower-slit,
|
||||
.area-map-tower-beacon {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user