v4
@@ -4,9 +4,9 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#213021" />
|
||||
<link rel="icon" href="/images/goodwalk-favicon-32.png" sizes="32x32" type="image/png" />
|
||||
<link rel="icon" href="/images/goodwalk-favicon-192.png" sizes="192x192" type="image/png" />
|
||||
<link rel="apple-touch-icon" href="/images/goodwalk-favicon-192.png" />
|
||||
<link rel="icon" href="/images/goodwalk-favicon-32.webp" sizes="32x32" type="image/png" />
|
||||
<link rel="icon" href="/images/goodwalk-favicon-192.webp" sizes="192x192" type="image/png" />
|
||||
<link rel="apple-touch-icon" href="/images/goodwalk-favicon-192.webp" />
|
||||
<style>
|
||||
.no-js-overlay {
|
||||
position: fixed;
|
||||
|
||||
@@ -6,13 +6,19 @@ type RevealOptions = {
|
||||
|
||||
const defaultOptions: Required<RevealOptions> = {
|
||||
delay: 0,
|
||||
distance: 24,
|
||||
distance: 16,
|
||||
threshold: 0.18
|
||||
};
|
||||
|
||||
export function reveal(node: HTMLElement, options: RevealOptions = {}) {
|
||||
const settings = { ...defaultOptions, ...options };
|
||||
const media = window.matchMedia('(prefers-reduced-motion: reduce)');
|
||||
const mobileMedia = window.matchMedia('(max-width: 768px)');
|
||||
const isMobile = mobileMedia.matches;
|
||||
const effectiveDelay = isMobile ? Math.min(settings.delay, 16) : settings.delay;
|
||||
const effectiveDistance = isMobile ? Math.min(settings.distance, 14) : settings.distance;
|
||||
const effectiveThreshold = isMobile ? Math.min(settings.threshold, 0.08) : settings.threshold;
|
||||
const effectiveRootMargin = isMobile ? '0px 0px 18% 0px' : '0px 0px -8% 0px';
|
||||
|
||||
if (media.matches) {
|
||||
node.classList.add('reveal-visible');
|
||||
@@ -21,57 +27,52 @@ export function reveal(node: HTMLElement, options: RevealOptions = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
node.style.setProperty('--reveal-delay', `${settings.delay}ms`);
|
||||
node.style.setProperty('--reveal-distance', `${settings.distance}px`);
|
||||
node.style.setProperty('--reveal-delay', `${effectiveDelay}ms`);
|
||||
node.style.setProperty('--reveal-distance', `${effectiveDistance}px`);
|
||||
node.classList.add('reveal-ready');
|
||||
|
||||
// If the element is already visible at all in the initial viewport,
|
||||
// reveal it immediately so the first section below the hero doesn't
|
||||
// appear blank on page load.
|
||||
const initialCheck = () => {
|
||||
let observer: IntersectionObserver | null = null;
|
||||
let cancelled = false;
|
||||
|
||||
// Defer the layout-reading initial check + observer setup to the next
|
||||
// frame. With 20+ `use:reveal` instances mounting in a row, calling
|
||||
// getBoundingClientRect() synchronously after a class mutation forces
|
||||
// a layout recalc per element — a textbook layout-thrash pattern and
|
||||
// the largest contributor to forced-reflow warnings on this page.
|
||||
requestAnimationFrame(() => {
|
||||
if (cancelled) return;
|
||||
|
||||
const rect = node.getBoundingClientRect();
|
||||
const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
|
||||
if (rect.top < viewportHeight && rect.bottom > 0) {
|
||||
const initialViewportReach = isMobile ? viewportHeight * 1.18 : viewportHeight;
|
||||
if (rect.top < initialViewportReach && rect.bottom > 0) {
|
||||
node.classList.add('reveal-visible');
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (initialCheck()) {
|
||||
return {
|
||||
destroy() {}
|
||||
};
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
for (const entry of entries) {
|
||||
if (entry.isIntersecting) {
|
||||
node.classList.add('reveal-visible');
|
||||
continue;
|
||||
}
|
||||
|
||||
const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
|
||||
const rect = entry.boundingClientRect;
|
||||
const fullyOutOfView = rect.bottom <= 0 || rect.top >= viewportHeight;
|
||||
|
||||
if (fullyOutOfView) {
|
||||
node.classList.remove('reveal-visible');
|
||||
observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
for (const entry of entries) {
|
||||
if (entry.isIntersecting) {
|
||||
node.classList.add('reveal-visible');
|
||||
observer?.unobserve(node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
threshold: effectiveThreshold,
|
||||
rootMargin: effectiveRootMargin
|
||||
}
|
||||
},
|
||||
{
|
||||
threshold: settings.threshold,
|
||||
rootMargin: '0px 0px -8% 0px'
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
observer.observe(node);
|
||||
observer.observe(node);
|
||||
});
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
observer.disconnect();
|
||||
cancelled = true;
|
||||
observer?.disconnect();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</div>
|
||||
</PageHeader>
|
||||
|
||||
<BookingSection {booking} {allowGeneralEnquiry} variant="card-stepper" />
|
||||
<BookingSection {booking} {allowGeneralEnquiry} variant="contact-modern" />
|
||||
<InfoSection {info} />
|
||||
</main>
|
||||
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
<script lang="ts">
|
||||
import { accordion } from '$lib/actions/accordion';
|
||||
import Icon from '$lib/components/Icon.svelte';
|
||||
import type { FaqItem } from '$lib/types';
|
||||
|
||||
export let title = 'FAQs';
|
||||
export let intro: string | undefined = undefined;
|
||||
export let faqs: FaqItem[];
|
||||
export let emitSchema = true;
|
||||
export let variant: 'panel' | 'plain' = 'panel';
|
||||
|
||||
$: schemaJson = JSON.stringify({
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'FAQPage',
|
||||
mainEntity: faqs.map((faq) => ({
|
||||
'@type': 'Question',
|
||||
name: faq.question,
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: faq.answer
|
||||
}
|
||||
}))
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
{#if emitSchema && faqs.length}
|
||||
{@html `<script type="application/ld+json">${schemaJson}<` + `/script>`}
|
||||
{/if}
|
||||
</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}
|
||||
<div use:accordion class="faq">
|
||||
{#each faqs as faq}
|
||||
<details>
|
||||
<summary>{faq.question}</summary>
|
||||
<p>{faq.answer}</p>
|
||||
</details>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.faq-section-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-right: 10px;
|
||||
border-radius: 12px;
|
||||
background: var(--gw-green);
|
||||
box-shadow: 0 10px 22px rgba(33, 48, 33, 0.16);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.faq-section-icon :global(.icon) {
|
||||
color: var(--yellow);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.faq-section-heading {
|
||||
margin: 0 0 14px;
|
||||
}
|
||||
|
||||
.faq-section-intro {
|
||||
margin: 0 0 18px;
|
||||
color: #5b6067;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.faq-section-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.faq-section-heading {
|
||||
text-align: left;
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
text-wrap: balance;
|
||||
font-size: clamp(22px, 5.6vw, 26px);
|
||||
line-height: 1.18;
|
||||
}
|
||||
|
||||
.faq summary,
|
||||
.faq details p {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -100,7 +100,9 @@
|
||||
</div>
|
||||
|
||||
<div class="footer-locations">
|
||||
<p class="footer-col-label">Areas we serve</p>
|
||||
<p class="footer-col-label">
|
||||
<a href="/locations">Areas we serve</a>
|
||||
</p>
|
||||
<ul class="footer-nav">
|
||||
{#each locationPages as loc}
|
||||
<li><a href="/locations/{loc.slug}">{loc.suburb}</a></li>
|
||||
|
||||
@@ -6,6 +6,12 @@
|
||||
|
||||
export let founderStory: FounderStoryContent;
|
||||
|
||||
const founderTrustNotes = [
|
||||
'The same friendly face at the door',
|
||||
'Little groups, never a crowded van',
|
||||
'Updates that help you relax while you are out'
|
||||
];
|
||||
|
||||
const founderStoryParagraphs = [
|
||||
'Goodwalk started with my own little dog and the kind of relationship I have always had with animals. Growing up in Italy with a German Shepherd, I saw early on how much joy, comfort, personality, and companionship dogs bring into a home. They are not just pets. They become part of your family and your daily life.',
|
||||
'When I moved to Auckland, I noticed a lot of dog walking felt rushed, overcrowded, or impersonal, especially for smaller dogs. So I built Goodwalk around the kind of care I would want for my own dog: familiar faces, safe and social little groups, lots of fun, and genuine relationships with every dog we walk.',
|
||||
@@ -19,154 +25,240 @@
|
||||
<section id="promise" use:reveal={{ delay: 20 }} class="reveal-block">
|
||||
<div class="founder-inner">
|
||||
<article class="founder-note">
|
||||
<span class="eyebrow founder-kicker">A note from Aless</span>
|
||||
|
||||
<h2 class="founder-heading">
|
||||
<span class="founder-heading-main">Hi, Welcome to Goodwalk.</span>
|
||||
</h2>
|
||||
|
||||
<div class="founder-body">
|
||||
{#each founderStoryParagraphs as paragraph}
|
||||
<p>{paragraph}</p>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="founder-signoff">
|
||||
<div class="founder-avatar">
|
||||
{#if founderStoryEnhanced}
|
||||
<enhanced:img
|
||||
class="founder-avatar-img"
|
||||
src={founderStoryEnhanced}
|
||||
alt={founderStory.imageAlt}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
{:else}
|
||||
<img
|
||||
class="founder-avatar-img"
|
||||
src={founderStory.imageUrl}
|
||||
alt={founderStory.imageAlt}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
{/if}
|
||||
<div class="founder-intro">
|
||||
<span class="eyebrow founder-kicker">A note from Aless</span>
|
||||
<span class="founder-greeting">Hi, I'm Aless.</span>
|
||||
</div>
|
||||
<div class="founder-signoff-text">
|
||||
<span class="founder-name">Aless</span>
|
||||
<span class="founder-role">Goodwalk founder</span>
|
||||
|
||||
<h2 class="founder-heading">
|
||||
<span class="founder-heading-main">{founderStory.title}</span>
|
||||
<span class="founder-heading-sub">Goodwalk is built around trust.</span>
|
||||
</h2>
|
||||
|
||||
<div class="founder-trust-strip" aria-label="What owners can expect from Goodwalk">
|
||||
<span class="founder-trust-label">What owners notice first</span>
|
||||
<ul class="founder-trust-list">
|
||||
{#each founderTrustNotes as note}
|
||||
<li>{note}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a class="founder-contact-note" href="mailto:info@goodwalk.co.nz" aria-label="Email Aless at Goodwalk">
|
||||
<span class="founder-contact-wave" aria-hidden="true">👋</span>
|
||||
<span>If you are unsure about anything, feel free to email me anytime.</span>
|
||||
</a>
|
||||
<div class="founder-body">
|
||||
{#each founderStoryParagraphs as paragraph}
|
||||
<p>{paragraph}</p>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<a href={founderStory.cta.href} class="btn btn-green btn-with-arrow founder-cta">
|
||||
{founderStory.cta.label}
|
||||
<Icon name="fas fa-arrow-right" />
|
||||
</a>
|
||||
<p class="founder-closing">
|
||||
Ready to <strong>{founderStory.emphasis}</strong>
|
||||
</p>
|
||||
|
||||
<div class="founder-actions">
|
||||
<a class="founder-contact-note" href="mailto:info@goodwalk.co.nz" aria-label="Email Aless at Goodwalk">
|
||||
<span class="founder-contact-wave" aria-hidden="true">👋</span>
|
||||
<span>If you are unsure about anything, feel free to email me anytime.</span>
|
||||
</a>
|
||||
|
||||
<a href={founderStory.cta.href} class="btn btn-green btn-with-arrow btn-hide-arrow-mobile founder-cta">
|
||||
{founderStory.cta.label}
|
||||
<Icon name="fas fa-arrow-right" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="founder-signoff">
|
||||
<div class="founder-signoff-copy">
|
||||
<p class="founder-signoff-name">Aless, founder of Goodwalk</p>
|
||||
<p class="founder-signoff-line">The same calm face at the door, the same trusted routine for your dog.</p>
|
||||
</div>
|
||||
|
||||
<div class="founder-media-card">
|
||||
{#if founderStoryEnhanced}
|
||||
<enhanced:img
|
||||
class="founder-portrait"
|
||||
src={founderStoryEnhanced}
|
||||
alt={founderStory.imageAlt}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
{:else}
|
||||
<img
|
||||
class="founder-portrait"
|
||||
src={founderStory.imageUrl}
|
||||
alt={founderStory.imageAlt}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
/* A quiet, letter-style sign-off — left-aligned, one clean panel, no ornament. */
|
||||
#promise {
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: 980px;
|
||||
}
|
||||
|
||||
.founder-inner {
|
||||
max-width: var(--max-w);
|
||||
max-width: 880px;
|
||||
margin: 0 auto;
|
||||
padding: 0 50px;
|
||||
}
|
||||
|
||||
.founder-media-card {
|
||||
overflow: hidden;
|
||||
width: min(100%, 168px);
|
||||
border-radius: 24px;
|
||||
background:
|
||||
linear-gradient(180deg, oklch(0.97 0.02 100) 0%, oklch(0.93 0.03 102) 100%);
|
||||
box-shadow: 0 16px 30px rgba(17, 20, 24, 0.08);
|
||||
}
|
||||
|
||||
.founder-portrait {
|
||||
display: block;
|
||||
width: 100%;
|
||||
aspect-ratio: 0.86;
|
||||
object-fit: cover;
|
||||
object-position: center 24%;
|
||||
}
|
||||
|
||||
.founder-note {
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
padding: 42px 52px 34px;
|
||||
background: #fff;
|
||||
border: 1px solid rgba(17, 20, 24, 0.08);
|
||||
border-radius: 28px;
|
||||
box-shadow: var(--shadow-panel-elevated);
|
||||
padding: clamp(32px, 4vw, 48px);
|
||||
background: var(--surface-panel);
|
||||
border: 1px solid var(--border-soft);
|
||||
border-radius: 34px;
|
||||
box-shadow: 0 24px 60px rgba(17, 20, 24, 0.06);
|
||||
}
|
||||
|
||||
.founder-intro {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.founder-kicker {
|
||||
display: block;
|
||||
letter-spacing: 0.14em;
|
||||
display: inline-block;
|
||||
color: var(--text-subtle);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.16em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.founder-greeting {
|
||||
color: oklch(0.29 0.02 118);
|
||||
font-family: var(--font-head);
|
||||
font-size: clamp(22px, 2.5vw, 28px);
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.03em;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.founder-heading {
|
||||
margin: 0 0 26px;
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin: 0 0 24px;
|
||||
}
|
||||
|
||||
.founder-heading-main {
|
||||
display: block;
|
||||
color: #0d1a0d;
|
||||
color: oklch(0.23 0.02 136);
|
||||
font-family: var(--font-head);
|
||||
font-size: clamp(32px, 3.6vw, 46px);
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.04em;
|
||||
line-height: 1;
|
||||
font-size: clamp(28px, 3.6vw, 42px);
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.035em;
|
||||
line-height: 1.02;
|
||||
}
|
||||
|
||||
.founder-heading-sub {
|
||||
display: block;
|
||||
max-width: 28ch;
|
||||
color: oklch(0.4 0.018 118);
|
||||
font-size: clamp(16px, 1.8vw, 20px);
|
||||
font-weight: 500;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.founder-trust-strip {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
margin: 0 0 28px;
|
||||
padding-top: 18px;
|
||||
border-top: 1px solid var(--border-soft);
|
||||
}
|
||||
|
||||
.founder-trust-label {
|
||||
color: var(--text-subtle);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.16em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.founder-trust-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 14px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.founder-trust-list li {
|
||||
position: relative;
|
||||
padding-left: 14px;
|
||||
color: oklch(0.35 0.017 118);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.founder-trust-list li::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0.65em;
|
||||
left: 0;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: color-mix(in srgb, var(--gw-green) 72%, white);
|
||||
}
|
||||
|
||||
.founder-body {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
gap: 18px;
|
||||
max-width: 67ch;
|
||||
}
|
||||
|
||||
.founder-body p {
|
||||
margin: 0;
|
||||
color: #4c5056;
|
||||
color: oklch(0.39 0.014 105);
|
||||
font-size: var(--body-copy-size);
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.founder-signoff {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
margin-top: 28px;
|
||||
padding-top: 24px;
|
||||
border-top: 1px solid rgba(17, 20, 24, 0.08);
|
||||
}
|
||||
|
||||
.founder-avatar {
|
||||
flex: 0 0 auto;
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
overflow: hidden;
|
||||
border-radius: 50%;
|
||||
background: #ede4d2;
|
||||
box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.07);
|
||||
}
|
||||
|
||||
.founder-avatar-img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center 20%;
|
||||
}
|
||||
|
||||
.founder-signoff-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.founder-name {
|
||||
color: #0d1a0d;
|
||||
.founder-closing {
|
||||
margin: 24px 0 0;
|
||||
color: oklch(0.24 0.018 136);
|
||||
font-family: var(--font-head);
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
font-size: clamp(18px, 2vw, 22px);
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.025em;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.founder-role {
|
||||
color: var(--gray);
|
||||
font-size: 13px;
|
||||
line-height: 1.3;
|
||||
.founder-closing strong {
|
||||
color: var(--gw-green);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.founder-cta {
|
||||
.founder-actions {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
justify-items: start;
|
||||
margin-top: 28px;
|
||||
}
|
||||
|
||||
@@ -174,17 +266,16 @@
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
padding: 12px 16px;
|
||||
padding: 0;
|
||||
border-radius: 18px;
|
||||
background: rgba(33, 48, 33, 0.05);
|
||||
box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.06);
|
||||
color: var(--gw-green);
|
||||
color: oklch(0.33 0.018 118);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
text-decoration: none;
|
||||
transition: background 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease;
|
||||
transition:
|
||||
color 0.18s ease,
|
||||
transform 0.18s ease;
|
||||
}
|
||||
|
||||
.founder-contact-wave {
|
||||
@@ -194,18 +285,60 @@
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.07);
|
||||
background: var(--surface-panel-muted);
|
||||
box-shadow: inset 0 0 0 1px var(--border-soft);
|
||||
font-size: 16px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.founder-cta {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.founder-signoff {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
justify-content: space-between;
|
||||
gap: 22px;
|
||||
margin-top: 34px;
|
||||
padding-top: 24px;
|
||||
border-top: 1px solid var(--border-soft);
|
||||
}
|
||||
|
||||
.founder-signoff-copy {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
max-width: 30ch;
|
||||
}
|
||||
|
||||
.founder-signoff-name {
|
||||
margin: 0;
|
||||
color: oklch(0.24 0.02 136);
|
||||
font-family: var(--font-head);
|
||||
font-size: clamp(20px, 2vw, 24px);
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.03em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.founder-signoff-line {
|
||||
margin: 0;
|
||||
color: var(--text-subtle);
|
||||
font-size: 14px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.founder-media-card:hover .founder-portrait {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.founder-portrait {
|
||||
transition: transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.founder-contact-note:hover {
|
||||
background: rgba(33, 48, 33, 0.08);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(17, 20, 24, 0.08),
|
||||
0 10px 22px rgba(17, 20, 24, 0.05);
|
||||
color: var(--gw-green);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
@@ -216,13 +349,12 @@
|
||||
}
|
||||
|
||||
.founder-note {
|
||||
padding: 26px 24px 24px;
|
||||
padding: 26px 22px 24px;
|
||||
border-radius: 24px;
|
||||
}
|
||||
|
||||
.founder-heading {
|
||||
margin: 0 0 22px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.founder-body p {
|
||||
@@ -230,6 +362,25 @@
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.founder-media-card {
|
||||
border-radius: 24px;
|
||||
width: 132px;
|
||||
}
|
||||
|
||||
.founder-trust-strip {
|
||||
margin-bottom: 24px;
|
||||
padding-top: 16px;
|
||||
}
|
||||
|
||||
.founder-trust-list {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.founder-trust-list li {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.founder-contact-note {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
@@ -242,5 +393,16 @@
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.founder-signoff {
|
||||
align-items: start;
|
||||
flex-direction: column;
|
||||
margin-top: 28px;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.founder-signoff-copy {
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -55,12 +55,13 @@ describe('Header', () => {
|
||||
|
||||
const menuToggle = container.querySelector('.hamburger') as HTMLButtonElement;
|
||||
const mobileMenuShell = container.querySelector('.mobile-menu-shell') as HTMLDivElement;
|
||||
const mobileMenuBackdrop = container.querySelector('.mobile-menu-backdrop') as HTMLButtonElement;
|
||||
|
||||
await fireEvent.click(menuToggle);
|
||||
expect(menuToggle).toHaveAttribute('aria-expanded', 'true');
|
||||
expect(mobileMenuShell.classList.contains('open')).toBe(true);
|
||||
|
||||
await fireEvent.click(mobileMenuShell);
|
||||
await fireEvent.click(mobileMenuBackdrop);
|
||||
expect(menuToggle).toHaveAttribute('aria-expanded', 'false');
|
||||
expect(mobileMenuShell.classList.contains('open')).toBe(false);
|
||||
});
|
||||
|
||||
@@ -10,8 +10,11 @@
|
||||
$: mobileLead = mobileTitle.includes(hero.highlight)
|
||||
? mobileTitle.slice(0, mobileTitle.lastIndexOf(hero.highlight))
|
||||
: mobileTitle;
|
||||
$: accessibleTitle = `${titleParts.lead}${titleParts.connector ? ` ${titleParts.connector}` : ''} ${hero.highlight}`.trim();
|
||||
$: proofItems = (hero.subtitleChips ?? []).slice(0, 3);
|
||||
|
||||
const trustStars = Array.from({ length: 5 });
|
||||
|
||||
function splitTitle(title: string) {
|
||||
const trimmed = title.trim();
|
||||
|
||||
@@ -43,12 +46,20 @@
|
||||
constrained by hero-inner's stacking context -->
|
||||
<div class="hero-img">
|
||||
<picture>
|
||||
{#if hero.desktopImageWebpUrl}
|
||||
<source media="(min-width: 769px)" srcset={hero.desktopImageWebpUrl} type="image/webp" />
|
||||
{/if}
|
||||
{#if hero.desktopImageUrl}
|
||||
<source media="(min-width: 769px)" srcset={hero.desktopImageUrl} />
|
||||
{/if}
|
||||
{#if hero.imageWebpUrl}
|
||||
<source srcset={hero.imageWebpUrl} type="image/webp" />
|
||||
{/if}
|
||||
<img
|
||||
src={hero.imageUrl}
|
||||
alt={hero.imageAlt}
|
||||
width={hero.imageWidth ?? undefined}
|
||||
height={hero.imageHeight ?? undefined}
|
||||
loading="eager"
|
||||
fetchpriority="high"
|
||||
/>
|
||||
@@ -62,7 +73,8 @@
|
||||
{/if}
|
||||
|
||||
<h1 class="hero-heading">
|
||||
<span class="hero-heading-desktop">
|
||||
<span class="visually-hidden">{accessibleTitle}</span>
|
||||
<span class="hero-heading-desktop" aria-hidden="true">
|
||||
<span class="hero-title-main">{titleParts.lead}</span>
|
||||
{#if titleParts.connector}
|
||||
<span class="hero-title-connector"> {titleParts.connector}</span>
|
||||
@@ -70,11 +82,15 @@
|
||||
<br />
|
||||
<span class="hero-title-highlight">{hero.highlight}</span>
|
||||
</span>
|
||||
<span class="hero-heading-mobile">
|
||||
<span class="hero-heading-mobile" aria-hidden="true">
|
||||
{mobileLead}<span class="hero-title-highlight">{hero.highlight}</span>
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
{#if hero.seoHeading}
|
||||
<h2 class="hero-seo-heading">{hero.seoHeading}</h2>
|
||||
{/if}
|
||||
|
||||
{#if hero.subtitle}
|
||||
<p class="hero-subtitle hero-subtitle-desktop">{hero.subtitle}</p>
|
||||
{/if}
|
||||
@@ -95,14 +111,21 @@
|
||||
rel={reviewCta.external ? 'noopener' : undefined}
|
||||
aria-label="Read our five-star Google reviews"
|
||||
>
|
||||
<img
|
||||
class="hero-trust-logo"
|
||||
src="/images/google-g-logo.svg"
|
||||
alt=""
|
||||
width="18"
|
||||
height="19"
|
||||
/>
|
||||
<span>{reviewCta.label}</span>
|
||||
<span class="hero-trust-mark" aria-hidden="true">
|
||||
<img
|
||||
class="hero-trust-logo"
|
||||
src="/images/google-g-logo.svg"
|
||||
alt=""
|
||||
width="16"
|
||||
height="17"
|
||||
/>
|
||||
</span>
|
||||
<span class="hero-trust-stars" aria-hidden="true">
|
||||
{#each trustStars as _, index}
|
||||
<Icon name="fas fa-star" className={`hero-trust-star hero-trust-star-${index + 1}`} />
|
||||
{/each}
|
||||
</span>
|
||||
<span class="hero-trust-label">{reviewCta.label}</span>
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -113,7 +136,7 @@
|
||||
href={hero.primaryCta.href}
|
||||
target={linkTarget(hero.primaryCta.external)}
|
||||
rel={linkRel(hero.primaryCta.external)}
|
||||
class="btn btn-yellow btn-with-arrow"
|
||||
class="btn btn-yellow btn-with-arrow btn-hide-arrow-mobile"
|
||||
>
|
||||
{hero.primaryCta.label}
|
||||
<Icon name="fas fa-arrow-right" />
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
<span class="hiw-num">0{index + 1}</span>
|
||||
</div>
|
||||
<div class="hiw-icon-wrap">
|
||||
<Icon name={step.icon} className="hiw-step-icon" />
|
||||
<Icon name={step.icon ?? 'fas fa-paw'} className="hiw-step-icon" />
|
||||
</div>
|
||||
<h3 class="hiw-title">{step.title}</h3>
|
||||
<p class="hiw-body">{step.body}</p>
|
||||
@@ -251,8 +251,8 @@
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 34px;
|
||||
padding: 0 14px;
|
||||
min-height: 44px;
|
||||
padding: 0 16px;
|
||||
border-radius: 999px;
|
||||
background: var(--gw-green);
|
||||
box-shadow:
|
||||
@@ -291,8 +291,8 @@
|
||||
}
|
||||
|
||||
.hiw-journey-pill {
|
||||
min-height: 32px;
|
||||
padding: 0 12px;
|
||||
min-height: 44px;
|
||||
padding: 0 14px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
@@ -347,10 +347,10 @@
|
||||
/* ── Reveal ── */
|
||||
:global(.reveal-ready.reveal-block) {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, var(--reveal-distance, 24px), 0);
|
||||
transform: translate3d(0, var(--reveal-distance, 16px), 0);
|
||||
transition:
|
||||
opacity 0.55s ease,
|
||||
transform 0.7s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
opacity 0.3s ease,
|
||||
transform 0.45s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
transition-delay: var(--reveal-delay, 0ms);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { accordion } from '$lib/actions/accordion';
|
||||
import Icon from '$lib/components/Icon.svelte';
|
||||
import FaqSection from '$lib/components/FaqSection.svelte';
|
||||
import { locationPages } from '$lib/content/locations';
|
||||
import type { InfoContent } from '$lib/types';
|
||||
|
||||
@@ -50,29 +50,23 @@
|
||||
</div>
|
||||
|
||||
<div class="info-block">
|
||||
<h2>
|
||||
<span class="info-heading-icon"><Icon name="fas fa-circle-question" /></span>
|
||||
{info.faqTitle}
|
||||
</h2>
|
||||
<div use:accordion class="faq">
|
||||
{#each info.faqs as faq}
|
||||
<details>
|
||||
<summary>{faq.question}</summary>
|
||||
<p>{faq.answer}</p>
|
||||
</details>
|
||||
{/each}
|
||||
</div>
|
||||
<FaqSection title={info.faqTitle} faqs={info.faqs} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
#info {
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: 1100px;
|
||||
}
|
||||
|
||||
.info-heading-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-right: 10px;
|
||||
border-radius: 12px;
|
||||
background: var(--gw-green);
|
||||
@@ -104,8 +98,8 @@
|
||||
.info-suburb-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 40px;
|
||||
padding: 8px 14px;
|
||||
min-height: 44px;
|
||||
padding: 10px 16px;
|
||||
border-radius: 999px;
|
||||
background: #fff;
|
||||
box-shadow:
|
||||
@@ -177,6 +171,12 @@
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.info-heading-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.info-hours-card p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@@ -205,8 +205,8 @@
|
||||
}
|
||||
|
||||
.info-heading-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-right: 8px;
|
||||
border-radius: 11px;
|
||||
}
|
||||
@@ -218,8 +218,8 @@
|
||||
}
|
||||
|
||||
.info-suburb-chip {
|
||||
min-height: 36px;
|
||||
padding: 8px 12px;
|
||||
min-height: 44px;
|
||||
padding: 10px 14px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,13 +21,15 @@
|
||||
</div>
|
||||
|
||||
<div class="instagram-dog-wrap" aria-hidden="true">
|
||||
<enhanced:img src="$lib/images/dog-cutout.png" alt="" class="instagram-dog" loading="lazy" decoding="async" />
|
||||
<enhanced:img src="$lib/images/goodwalk-instagram-dog-cutout.webp" alt="" class="instagram-dog" loading="lazy" decoding="async" />
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<style>
|
||||
#instagram {
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: 360px;
|
||||
overflow: hidden;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
@@ -5,45 +5,62 @@
|
||||
export let intro: IntroContent;
|
||||
|
||||
const stars = Array.from({ length: 5 });
|
||||
|
||||
const statement = intro.text.replace(/\.$/, '');
|
||||
const statementWords = statement.split(/(\s+)/);
|
||||
</script>
|
||||
|
||||
<div id="intro">
|
||||
<section id="intro" aria-label="Goodwalk at a glance">
|
||||
<div class="intro-inner">
|
||||
<div class="intro-trust-badge">
|
||||
<div class="intro-statement">
|
||||
<span class="intro-kicker" aria-hidden="true">
|
||||
<span class="intro-kicker-rule"></span>
|
||||
Goodwalk · Auckland
|
||||
</span>
|
||||
<h2 class="intro-headline">
|
||||
{#each statementWords as token, index}
|
||||
{#if /\s+/.test(token)}
|
||||
{token}
|
||||
{:else}
|
||||
<span class="intro-word" style="--word-i: {index};">{token}</span>
|
||||
{/if}
|
||||
{/each}
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<aside class="intro-trust" aria-label="Reviews">
|
||||
<a
|
||||
class="intro-trust-mark intro-trust-mark-link"
|
||||
class="intro-google"
|
||||
href={intro.reviewCta.href}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
aria-label="Read our Google reviews"
|
||||
>
|
||||
<img
|
||||
class="intro-google-logo"
|
||||
src="/images/google-g-logo.svg"
|
||||
alt=""
|
||||
width="28"
|
||||
height="29"
|
||||
/>
|
||||
</a>
|
||||
|
||||
<div class="intro-trust-copy">
|
||||
<p>
|
||||
<span class="intro-trust-copy-desktop">{intro.text}</span>
|
||||
<span class="intro-trust-copy-mobile">30+ 5-star Google reviews. Small & medium dog specialists in Auckland.</span>
|
||||
</p>
|
||||
|
||||
<div class="intro-trust-meta">
|
||||
<div class="intro-trust-stars" aria-label="5 star rating">
|
||||
<span class="intro-google-mark" aria-hidden="true">
|
||||
<img
|
||||
class="intro-google-logo"
|
||||
src="/images/google-g-logo.svg"
|
||||
alt=""
|
||||
width="22"
|
||||
height="23"
|
||||
/>
|
||||
</span>
|
||||
<span class="intro-google-copy">
|
||||
<span class="intro-stars" aria-label="5 star rating">
|
||||
{#each stars as _, index}
|
||||
<Icon name="fas fa-star" className={`intro-star intro-star-${index + 1}`} />
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<a class="intro-trust-cta" href={intro.reviewCta.href} target="_blank" rel="noopener">
|
||||
</span>
|
||||
<span class="intro-google-label">
|
||||
{intro.reviewCta.label}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
</span>
|
||||
</a>
|
||||
|
||||
<p class="intro-meta">
|
||||
<span class="intro-meta-dot" aria-hidden="true"></span>
|
||||
Auckland Central · Mon–Fri
|
||||
</p>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -194,8 +194,7 @@
|
||||
}
|
||||
|
||||
:global(.mobile-book-bar-cta .mobile-book-bar-arrow) {
|
||||
font-size: 12px;
|
||||
opacity: 0.75;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
|
||||
@@ -1,30 +1,51 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let onClose: () => void;
|
||||
export let ariaLabel: string | undefined = undefined;
|
||||
export let ariaLabelledBy: string | undefined = undefined;
|
||||
|
||||
let hasMounted = false;
|
||||
|
||||
function portalToBody(node: HTMLElement) {
|
||||
document.body.appendChild(node);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
node.remove();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
if (event.key === 'Escape') onClose();
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
hasMounted = true;
|
||||
});
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="modal-backdrop"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label={ariaLabel}
|
||||
aria-labelledby={ariaLabelledBy}
|
||||
on:click|self={onClose}
|
||||
on:keydown={handleKeydown}
|
||||
tabindex="-1"
|
||||
>
|
||||
<div class="modal-card">
|
||||
<button class="modal-close" type="button" aria-label="Close" on:click={onClose}>
|
||||
✕
|
||||
</button>
|
||||
<slot />
|
||||
{#if hasMounted}
|
||||
<div
|
||||
use:portalToBody
|
||||
class="modal-backdrop"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label={ariaLabel}
|
||||
aria-labelledby={ariaLabelledBy}
|
||||
on:click|self={onClose}
|
||||
on:keydown={handleKeydown}
|
||||
tabindex="-1"
|
||||
>
|
||||
<div class="modal-card">
|
||||
<button class="modal-close" type="button" aria-label="Close" on:click={onClose}>
|
||||
✕
|
||||
</button>
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.modal-backdrop {
|
||||
|
||||
@@ -10,50 +10,48 @@
|
||||
mobileOrder: number;
|
||||
};
|
||||
export let variant: 'pricing' | 'service' = 'service';
|
||||
|
||||
$: featured = plan.isPopular;
|
||||
const ctaLabel = 'Book a Meet & Greet';
|
||||
</script>
|
||||
|
||||
<article
|
||||
class="plan-card"
|
||||
class:plan-card--popular={plan.isPopular}
|
||||
class:plan-card--featured={featured}
|
||||
class:plan-card--supporting={!featured}
|
||||
class:plan-card--pricing={variant === 'pricing'}
|
||||
class:plan-card--service={variant === 'service'}
|
||||
style="--mobile-order:{plan.mobileOrder};"
|
||||
>
|
||||
{#if plan.isPopular}
|
||||
<span class="plan-card__ribbon">
|
||||
<Icon name="fas fa-star" className="plan-card__ribbon-icon" />
|
||||
Popular
|
||||
{#if featured}
|
||||
<span class="plan-card__ribbon" aria-label="Most chosen routine">
|
||||
<Icon name="fas fa-paw" className="plan-card__ribbon-icon" />
|
||||
Goodwalk favourite
|
||||
</span>
|
||||
{/if}
|
||||
<div class="plan-card__header">
|
||||
<div class="plan-card__eyebrow">
|
||||
<span class="plan-card__eyebrow-badge">
|
||||
<Icon name={variant === 'pricing' ? 'fas fa-paw' : 'fas fa-leaf'} className="plan-card__eyebrow-icon" />
|
||||
</span>
|
||||
<span>{plan.isPopular ? 'Goodwalk favourite' : variant === 'pricing' ? 'Flexible routine' : 'Tailored support'}</span>
|
||||
|
||||
<header class="plan-card__header">
|
||||
<h3 class="plan-card__title">{plan.title}</h3>
|
||||
|
||||
<div class="plan-card__price-block">
|
||||
<span class="plan-card__price">{plan.price}</span>
|
||||
<span class="plan-card__period">{plan.period}</span>
|
||||
</div>
|
||||
<h3>{plan.title}</h3>
|
||||
<div class="plan-card__price">{plan.price}</div>
|
||||
<p class="plan-card__period">{plan.period}</p>
|
||||
</div>
|
||||
<div class="plan-card__body">
|
||||
<p class="plan-card__feature-label">
|
||||
<Icon name="fas fa-circle-check" className="plan-card__feature-label-icon" />
|
||||
What's included
|
||||
</p>
|
||||
<ul class="plan-card__features">
|
||||
{#each plan.features as feature}
|
||||
<li>
|
||||
<Icon
|
||||
name={variant === 'pricing' ? 'fas fa-check' : 'fas fa-paw'}
|
||||
className="plan-card__feature-icon"
|
||||
/>
|
||||
<span>{feature}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
<a class="btn btn-yellow plan-card__cta" href="#newlead">Book a Meet & Greet</a>
|
||||
</header>
|
||||
|
||||
<ul class="plan-card__features">
|
||||
{#each plan.features as feature}
|
||||
<li>
|
||||
<Icon name="fas fa-check" className="plan-card__feature-icon" />
|
||||
<span>{feature}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
<a class="btn btn-yellow plan-card__cta" href="#newlead">
|
||||
{ctaLabel}
|
||||
<Icon name="fas fa-arrow-right" />
|
||||
</a>
|
||||
</article>
|
||||
|
||||
<style>
|
||||
@@ -62,310 +60,289 @@
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-radius: 28px;
|
||||
border: 1.5px solid rgba(17, 20, 24, 0.09);
|
||||
padding: 38px 26px 30px;
|
||||
overflow: visible;
|
||||
border-radius: 26px;
|
||||
padding: 30px 26px 28px;
|
||||
overflow: hidden;
|
||||
isolation: isolate;
|
||||
transition:
|
||||
transform 0.18s cubic-bezier(0.22, 1, 0.36, 1),
|
||||
box-shadow 0.22s ease,
|
||||
border-color 0.22s ease,
|
||||
filter 0.22s ease;
|
||||
transform 0.22s cubic-bezier(0.22, 1, 0.36, 1),
|
||||
box-shadow 0.28s ease,
|
||||
border-color 0.22s ease;
|
||||
}
|
||||
|
||||
.plan-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0 0 auto;
|
||||
height: 88px;
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.5), rgba(255, 255, 255, 0));
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.plan-card--popular {
|
||||
border: 2px solid var(--yellow);
|
||||
/* ── Supporting plans (white on cream page) ── */
|
||||
.plan-card--supporting {
|
||||
background: var(--surface-panel);
|
||||
border: 1px solid rgba(33, 48, 33, 0.10);
|
||||
color: var(--text-heading);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(242, 191, 47, 0.45),
|
||||
0 14px 34px rgba(17, 20, 24, 0.06);
|
||||
0 1px 0 rgba(255, 255, 255, 0.7) inset,
|
||||
0 10px 24px rgba(17, 20, 24, 0.08),
|
||||
0 2px 6px rgba(17, 20, 24, 0.05);
|
||||
}
|
||||
|
||||
/* ── Service variant ── */
|
||||
.plan-card--service {
|
||||
align-items: stretch;
|
||||
height: 100%;
|
||||
background: linear-gradient(180deg, rgba(251, 251, 251, 0.98) 0%, rgba(247, 248, 246, 1) 100%);
|
||||
/* ── Featured plan (green-drenched, brand-true) ── */
|
||||
.plan-card--featured {
|
||||
background-color: var(--gw-green);
|
||||
background-image: none;
|
||||
border: 1px solid rgba(255, 209, 0, 0.22);
|
||||
color: rgba(255, 248, 230, 0.96);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(17, 20, 24, 0.045),
|
||||
0 8px 40px rgba(0, 0, 0, 0.06);
|
||||
0 1px 0 rgba(255, 248, 230, 0.08) inset,
|
||||
0 22px 46px rgba(33, 48, 33, 0.30),
|
||||
0 4px 10px rgba(33, 48, 33, 0.18);
|
||||
padding-top: 44px;
|
||||
}
|
||||
|
||||
/* ── Pricing variant ── */
|
||||
.plan-card--pricing {
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
background: linear-gradient(180deg, rgba(251, 251, 251, 0.98) 0%, rgba(247, 248, 246, 1) 100%);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(17, 20, 24, 0.045),
|
||||
0 14px 34px rgba(17, 20, 24, 0.05);
|
||||
.plan-card--featured .plan-card__header,
|
||||
.plan-card--featured .plan-card__features {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.plan-card--pricing.plan-card--popular {
|
||||
background:
|
||||
radial-gradient(circle at top, rgba(255, 209, 0, 0.12), rgba(255, 209, 0, 0) 40%),
|
||||
linear-gradient(180deg, rgba(251, 251, 251, 0.98) 0%, rgba(247, 248, 246, 1) 100%);
|
||||
/* ── Pricing variant: featured can grow taller on desktop for hierarchy ── */
|
||||
.plan-card--pricing.plan-card--featured {
|
||||
padding: 48px 28px 32px;
|
||||
}
|
||||
|
||||
.plan-card__header,
|
||||
.plan-card__body {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* ── Ribbon ── */
|
||||
/* ── Ribbon (featured only) — hand-pinned stamp ── */
|
||||
.plan-card__ribbon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gap: 7px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -40%);
|
||||
padding: 6px 12px;
|
||||
top: 16px;
|
||||
left: 22px;
|
||||
padding: 5px 11px 5px 9px;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(135deg, var(--gw-green), var(--green-mid));
|
||||
color: #fff;
|
||||
background: var(--yellow);
|
||||
color: var(--gw-green);
|
||||
font-family: var(--font-head);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
box-shadow: 0 10px 20px rgba(17, 20, 24, 0.08);
|
||||
white-space: nowrap;
|
||||
transform: rotate(-3deg);
|
||||
transform-origin: 12px 50%;
|
||||
box-shadow:
|
||||
0 1px 0 rgba(33, 48, 33, 0.10),
|
||||
0 8px 18px rgba(255, 209, 0, 0.32);
|
||||
z-index: 2;
|
||||
transition: transform 0.4s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.plan-card--featured:hover .plan-card__ribbon {
|
||||
transform: rotate(-1deg) translateY(-1px);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.plan-card__ribbon,
|
||||
.plan-card--featured:hover .plan-card__ribbon {
|
||||
transform: none;
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
:global(.plan-card__ribbon-icon) {
|
||||
color: var(--yellow);
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.plan-card__eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 18px;
|
||||
color: var(--gw-green);
|
||||
font-family: var(--font-head);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
/* ── Header ── */
|
||||
.plan-card__header {
|
||||
/* Override the global `header { ... }` in layout.css (page chrome only).
|
||||
Without this, every card inherits a background band and box-shadow. */
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
z-index: 1;
|
||||
isolation: auto;
|
||||
overflow: visible;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.plan-card__eyebrow-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, var(--gw-green), var(--green-mid));
|
||||
color: #fff;
|
||||
box-shadow: 0 10px 22px rgba(33, 48, 33, 0.14);
|
||||
}
|
||||
|
||||
:global(.plan-card__eyebrow-icon) {
|
||||
color: var(--yellow);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* ── Heading ── */
|
||||
.plan-card h3 {
|
||||
.plan-card__title {
|
||||
margin: 0;
|
||||
font-family: var(--font-head);
|
||||
font-size: 22px;
|
||||
line-height: 1.2;
|
||||
color: #16181d;
|
||||
font-size: 19px;
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
letter-spacing: -0.005em;
|
||||
}
|
||||
|
||||
.plan-card--featured .plan-card__title {
|
||||
color: rgba(255, 248, 230, 0.96);
|
||||
}
|
||||
|
||||
.plan-card--supporting .plan-card__title {
|
||||
color: var(--text-heading);
|
||||
}
|
||||
|
||||
/* ── Price block ── */
|
||||
.plan-card__price-block {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 8px;
|
||||
row-gap: 4px;
|
||||
margin-top: 18px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* ── Price ── */
|
||||
.plan-card__price {
|
||||
font-family: var(--font-head);
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.plan-card--service .plan-card__price {
|
||||
margin-top: 20px;
|
||||
font-size: 44px;
|
||||
color: var(--gw-green);
|
||||
}
|
||||
|
||||
.plan-card--pricing .plan-card__price {
|
||||
margin-top: 22px;
|
||||
font-size: 52px;
|
||||
line-height: 0.95;
|
||||
letter-spacing: -0.05em;
|
||||
color: #16181d;
|
||||
}
|
||||
|
||||
/* ── Period ── */
|
||||
.plan-card__period {
|
||||
margin: 8px 0 0;
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.plan-card--service .plan-card__period {
|
||||
color: #5d6166;
|
||||
font-size: 50px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.06em;
|
||||
line-height: 1;
|
||||
letter-spacing: -0.035em;
|
||||
}
|
||||
|
||||
.plan-card--pricing .plan-card__period {
|
||||
color: #5e6167;
|
||||
letter-spacing: 0.08em;
|
||||
.plan-card--featured .plan-card__price {
|
||||
color: var(--yellow);
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.plan-card__body {
|
||||
margin-top: 24px;
|
||||
padding-top: 18px;
|
||||
border-top: 1px solid rgba(17, 20, 24, 0.07);
|
||||
.plan-card--supporting .plan-card__price {
|
||||
color: var(--text-heading);
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.plan-card__feature-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 0 0 14px;
|
||||
color: #59606d;
|
||||
font-family: var(--font-head);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
.plan-card__period {
|
||||
font-family: var(--font-body);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
:global(.plan-card__feature-label-icon) {
|
||||
color: var(--gw-green);
|
||||
font-size: 12px;
|
||||
.plan-card--featured .plan-card__period {
|
||||
color: rgba(255, 248, 230, 0.78);
|
||||
}
|
||||
|
||||
.plan-card--supporting .plan-card__period {
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* ── Features ── */
|
||||
.plan-card__features {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.plan-card--service .plan-card__features {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
flex: 1 1 auto;
|
||||
margin: 24px 0 0;
|
||||
padding: 20px 0 0;
|
||||
list-style: none;
|
||||
border-top: 1px solid currentColor;
|
||||
}
|
||||
|
||||
/* Service: bullet style */
|
||||
.plan-card--service .plan-card__features li {
|
||||
.plan-card--featured .plan-card__features {
|
||||
border-top-color: rgba(255, 248, 230, 0.24);
|
||||
}
|
||||
|
||||
.plan-card--supporting .plan-card__features {
|
||||
border-top-color: rgba(33, 48, 33, 0.10);
|
||||
}
|
||||
|
||||
.plan-card__features li {
|
||||
display: grid;
|
||||
grid-template-columns: 18px minmax(0, 1fr);
|
||||
grid-template-columns: 16px minmax(0, 1fr);
|
||||
gap: 10px;
|
||||
color: #34363a;
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
align-items: start;
|
||||
font-size: 14px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.plan-card--service .plan-card__features li + li {
|
||||
margin-top: 12px;
|
||||
.plan-card__features li + li {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
:global(.plan-card--service .plan-card__feature-icon) {
|
||||
margin-top: 3px;
|
||||
color: var(--yellow-soft);
|
||||
font-size: 12px;
|
||||
.plan-card--featured .plan-card__features li {
|
||||
color: rgba(255, 248, 230, 0.94);
|
||||
}
|
||||
|
||||
/* Pricing: divider style */
|
||||
.plan-card--pricing .plan-card__features {
|
||||
width: 100%;
|
||||
.plan-card--supporting .plan-card__features li {
|
||||
color: var(--text-heading-soft);
|
||||
}
|
||||
|
||||
.plan-card--pricing .plan-card__features li {
|
||||
display: grid;
|
||||
grid-template-columns: 18px minmax(0, 1fr);
|
||||
gap: 10px;
|
||||
padding: 15px 0;
|
||||
border-top: 1px solid rgba(17, 20, 24, 0.08);
|
||||
color: #34363a;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
text-align: left;
|
||||
:global(.plan-card__feature-icon) {
|
||||
margin-top: 4px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.plan-card--pricing .plan-card__features li:first-child {
|
||||
padding-top: 0;
|
||||
border-top: none;
|
||||
:global(.plan-card--featured .plan-card__feature-icon) {
|
||||
color: var(--yellow);
|
||||
}
|
||||
|
||||
:global(.plan-card--pricing .plan-card__feature-icon) {
|
||||
margin-top: 3px;
|
||||
:global(.plan-card--supporting .plan-card__feature-icon) {
|
||||
color: var(--gw-green);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* ── CTA ── */
|
||||
.plan-card__cta {
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
margin: 28px auto 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
margin-top: 26px;
|
||||
align-self: center;
|
||||
width: auto;
|
||||
min-width: 180px;
|
||||
padding-inline: 22px;
|
||||
font-family: var(--font-head);
|
||||
}
|
||||
|
||||
/* ── Hover ── */
|
||||
@media (hover: hover) {
|
||||
.plan-card--service:hover {
|
||||
.plan-card--supporting:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: rgba(17, 20, 24, 0.14);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(17, 20, 24, 0.055),
|
||||
0 10px 40px rgba(0, 0, 0, 0.08);
|
||||
filter: brightness(1.015);
|
||||
border-color: rgba(33, 48, 33, 0.28);
|
||||
}
|
||||
|
||||
.plan-card--pricing:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: rgba(17, 20, 24, 0.14);
|
||||
.plan-card--featured:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(17, 20, 24, 0.06),
|
||||
0 18px 38px rgba(17, 20, 24, 0.08);
|
||||
filter: brightness(1.012);
|
||||
inset 0 1px 0 rgba(255, 248, 230, 0.10),
|
||||
0 26px 52px rgba(33, 48, 33, 0.26);
|
||||
}
|
||||
|
||||
.plan-card--popular:hover {
|
||||
border-color: var(--yellow);
|
||||
}
|
||||
}
|
||||
|
||||
.plan-card:active {
|
||||
transform: translateY(-2px) scale(0.992);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
/* ── Pricing variant alignment overrides ── */
|
||||
.plan-card--pricing {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* ── Mobile ── */
|
||||
@media (max-width: 768px) {
|
||||
.plan-card {
|
||||
order: var(--mobile-order, 0);
|
||||
width: min(100%, 420px);
|
||||
width: min(100%, 440px);
|
||||
margin-inline: auto;
|
||||
padding: 36px 22px 28px;
|
||||
padding: 28px 22px 24px;
|
||||
}
|
||||
|
||||
.plan-card__body {
|
||||
margin-top: 22px;
|
||||
padding-top: 16px;
|
||||
.plan-card--featured {
|
||||
padding-top: 44px;
|
||||
}
|
||||
|
||||
.plan-card--pricing .plan-card__price {
|
||||
font-size: 46px;
|
||||
.plan-card--pricing.plan-card--featured {
|
||||
padding: 44px 22px 24px;
|
||||
}
|
||||
|
||||
.plan-card__price {
|
||||
font-size: 44px;
|
||||
}
|
||||
|
||||
.plan-card--supporting .plan-card__price {
|
||||
font-size: 42px;
|
||||
}
|
||||
|
||||
.plan-card__cta {
|
||||
display: none;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -4,13 +4,16 @@
|
||||
export let title: string;
|
||||
export let description: string;
|
||||
export let canonicalPath: string;
|
||||
export let image = '/images/auckland-dog-walking-happy-dog-hero.png';
|
||||
export let image = '/images/goodwalk-auckland-happy-dog-hero.webp';
|
||||
export let imageAlt = 'Goodwalk Auckland dog walking services';
|
||||
export let type = 'website';
|
||||
export let structuredData: Record<string, unknown>[] = [];
|
||||
export let noindex = false;
|
||||
export let preloadImage = false;
|
||||
export let preloadImageUrl = ''; // explicit URL to preload (defaults to the og:image)
|
||||
export let preloadImageType = ''; // mime type, e.g. "image/webp"
|
||||
export let preloadImageSrcset = ''; // optional responsive srcset
|
||||
export let preloadImageSizes = ''; // optional sizes attribute
|
||||
|
||||
const siteName = 'Goodwalk';
|
||||
const siteUrl = 'https://www.goodwalk.co.nz';
|
||||
@@ -52,7 +55,15 @@
|
||||
<meta name="geo.region" content="NZ-AUK" />
|
||||
<meta name="geo.placename" content="Auckland Central" />
|
||||
{#if preloadImage && resolvedPreloadUrl}
|
||||
<link rel="preload" as="image" href={resolvedPreloadUrl} fetchpriority="high" />
|
||||
<link
|
||||
rel="preload"
|
||||
as="image"
|
||||
href={resolvedPreloadUrl}
|
||||
type={preloadImageType || undefined}
|
||||
imagesrcset={preloadImageSrcset || undefined}
|
||||
imagesizes={preloadImageSizes || undefined}
|
||||
fetchpriority="high"
|
||||
/>
|
||||
{/if}
|
||||
<link rel="canonical" href={canonicalUrl} />
|
||||
<link rel="alternate" hreflang="en-NZ" href={canonicalUrl} />
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import PricingPlanCard from '$lib/components/PricingPlanCard.svelte';
|
||||
import ServiceHero from '$lib/components/ServiceHero.svelte';
|
||||
import TestimonialsSection from '$lib/components/TestimonialsSection.svelte';
|
||||
import FaqSection from '$lib/components/FaqSection.svelte';
|
||||
import { getEnhancedImage } from '$lib/enhanced-images';
|
||||
import { decoratePlans } from '$lib/utils/pricing';
|
||||
import type { ServicePageContent, SiteSharedContent } from '$lib/types';
|
||||
@@ -25,11 +26,17 @@
|
||||
})) ?? [];
|
||||
$: relatedServices = content.services.filter((s) => s.href && s.href !== currentPath);
|
||||
$: pricingPlans = decoratePlans(pageContent.pricing.plans);
|
||||
$: introLeadParagraph = pageContent.hero.paragraphs?.[0] ?? '';
|
||||
$: introBodyParagraphs = pageContent.hero.paragraphs?.slice(1) ?? [];
|
||||
$: benefitCards = pageContent.benefits.items.map((benefit, index) => ({
|
||||
...benefit,
|
||||
tintClass: `service-benefit-tint-${(index % 3) + 1}`,
|
||||
featured: index === 0
|
||||
}));
|
||||
$: useSimpleBenefitSwipe =
|
||||
currentPath === '/pack-walks' ||
|
||||
currentPath === '/dog-walking' ||
|
||||
currentPath === '/puppy-visits';
|
||||
$: showRelatedServices = relatedServices.length > 0 && currentPath !== '/pack-walks';
|
||||
|
||||
$: relatedCards = [
|
||||
@@ -217,7 +224,7 @@
|
||||
</div>
|
||||
|
||||
<div class="service-benefit-shell">
|
||||
<div bind:this={benefitScroller} class="service-benefit-grid">
|
||||
<div bind:this={benefitScroller} class:service-benefit-grid-simple-swipe={useSimpleBenefitSwipe} class="service-benefit-grid">
|
||||
{#each benefitCards as benefit, index}
|
||||
<article
|
||||
class:active={index === activeBenefitIndex}
|
||||
@@ -236,43 +243,81 @@
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="service-benefit-mobile-controls" aria-label="Benefit cards navigation">
|
||||
<button
|
||||
type="button"
|
||||
class="service-benefit-mobile-button"
|
||||
aria-label="Previous benefit"
|
||||
disabled={activeBenefitIndex === 0}
|
||||
on:click={() => scrollBenefits(-1)}
|
||||
>
|
||||
<Icon name="fas fa-chevron-left" />
|
||||
</button>
|
||||
<div class="service-benefit-mobile-pager" aria-label="Current benefit">
|
||||
{#each benefitCards as _, index}
|
||||
<button
|
||||
type="button"
|
||||
class:active={index === activeBenefitIndex}
|
||||
class="service-benefit-mobile-dot"
|
||||
aria-label={`Go to benefit ${index + 1}`}
|
||||
aria-pressed={index === activeBenefitIndex}
|
||||
on:click={() => scrollBenefitTo(index)}
|
||||
></button>
|
||||
{/each}
|
||||
{#if !useSimpleBenefitSwipe}
|
||||
<div class="service-benefit-mobile-controls" aria-label="Benefit cards navigation">
|
||||
<button
|
||||
type="button"
|
||||
class="service-benefit-mobile-button"
|
||||
aria-label="Previous benefit"
|
||||
disabled={activeBenefitIndex === 0}
|
||||
on:click={() => scrollBenefits(-1)}
|
||||
>
|
||||
<Icon name="fas fa-chevron-left" />
|
||||
</button>
|
||||
<div class="service-benefit-mobile-pager" aria-label="Current benefit">
|
||||
{#each benefitCards as _, index}
|
||||
<button
|
||||
type="button"
|
||||
class:active={index === activeBenefitIndex}
|
||||
class="service-benefit-mobile-dot"
|
||||
aria-label={`Go to benefit ${index + 1}`}
|
||||
aria-pressed={index === activeBenefitIndex}
|
||||
on:click={() => scrollBenefitTo(index)}
|
||||
></button>
|
||||
{/each}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="service-benefit-mobile-button"
|
||||
aria-label="Next benefit"
|
||||
disabled={activeBenefitIndex === benefitCards.length - 1}
|
||||
on:click={() => scrollBenefits(1)}
|
||||
>
|
||||
<Icon name="fas fa-chevron-right" />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="service-benefit-mobile-button"
|
||||
aria-label="Next benefit"
|
||||
disabled={activeBenefitIndex === benefitCards.length - 1}
|
||||
on:click={() => scrollBenefits(1)}
|
||||
>
|
||||
<Icon name="fas fa-chevron-right" />
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section use:reveal class="service-pricing reveal-block">
|
||||
{#if pageContent.hero.paragraphs?.length}
|
||||
<section use:reveal class="service-intro reveal-block">
|
||||
<div class="page-inner">
|
||||
<article class="service-intro-card">
|
||||
<div class="service-intro-header">
|
||||
<div class="service-intro-badge">
|
||||
<div class="service-intro-mark" aria-hidden="true">
|
||||
<Icon name="fas fa-paw" />
|
||||
</div>
|
||||
<p class="service-intro-eyebrow">
|
||||
{pageContent.hero.introEyebrow ?? 'The detail'}
|
||||
</p>
|
||||
</div>
|
||||
<h2 class="service-intro-heading">
|
||||
{pageContent.hero.introHeading ?? `More about ${pageContent.hero.eyebrow}`}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="service-intro-body">
|
||||
<p class="service-intro-lead">{introLeadParagraph}</p>
|
||||
{#if introBodyParagraphs.length}
|
||||
<div class="service-intro-prose">
|
||||
{#each introBodyParagraphs as paragraph}
|
||||
<p>{paragraph}</p>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<section
|
||||
use:reveal
|
||||
class:service-pricing-immediate-mobile={useSimpleBenefitSwipe}
|
||||
class="service-pricing reveal-block"
|
||||
>
|
||||
<div class="page-inner">
|
||||
<div class="service-section-heading">
|
||||
<h2>{pageContent.pricing.title}</h2>
|
||||
@@ -320,6 +365,20 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{#if pageContent.faq?.items?.length}
|
||||
<section use:reveal class="service-faq reveal-block">
|
||||
<div class="page-inner">
|
||||
<div class="service-faq-card">
|
||||
<FaqSection
|
||||
title={pageContent.faq.title ?? 'Frequently asked questions'}
|
||||
intro={pageContent.faq.intro}
|
||||
faqs={pageContent.faq.items}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
{#if showRelatedServices}
|
||||
<section use:reveal class="service-related reveal-block" aria-label="Other services">
|
||||
<div class="page-inner">
|
||||
@@ -366,6 +425,216 @@
|
||||
background: var(--off-white);
|
||||
}
|
||||
|
||||
.service-intro {
|
||||
padding: 18px 0 60px;
|
||||
}
|
||||
|
||||
.service-intro-card {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(240px, 0.9fr) minmax(0, 1.35fr);
|
||||
gap: 44px;
|
||||
max-width: 980px;
|
||||
margin: 0 auto;
|
||||
padding: 38px 40px 40px;
|
||||
border-radius: 30px;
|
||||
background:
|
||||
radial-gradient(circle at top right, rgba(255, 209, 0, 0.14), transparent 30%),
|
||||
linear-gradient(180deg, rgba(252, 250, 243, 0.98) 0%, rgba(244, 239, 228, 0.98) 100%);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(33, 48, 33, 0.08),
|
||||
0 24px 52px rgba(17, 20, 24, 0.07);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.service-intro-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 16px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.45);
|
||||
border-radius: 24px;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.service-intro-card::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0 auto auto 0;
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle, rgba(33, 48, 33, 0.08) 0%, transparent 72%);
|
||||
transform: translate(-28%, -36%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.service-intro-header,
|
||||
.service-intro-body {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.service-intro-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
justify-content: space-between;
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
.service-intro-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
width: fit-content;
|
||||
padding: 10px 16px 10px 10px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.62);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(33, 48, 33, 0.07),
|
||||
0 12px 24px rgba(17, 20, 24, 0.04);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.service-intro-mark {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 14px;
|
||||
background: linear-gradient(180deg, var(--gw-green) 0%, var(--green-mid) 100%);
|
||||
color: var(--yellow);
|
||||
font-size: 17px;
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(255, 255, 255, 0.06),
|
||||
0 10px 22px rgba(33, 48, 33, 0.18);
|
||||
}
|
||||
|
||||
.service-intro-eyebrow {
|
||||
margin: 0;
|
||||
color: var(--gw-green);
|
||||
font-family: var(--font-head);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
opacity: 0.84;
|
||||
}
|
||||
|
||||
.service-intro-heading {
|
||||
margin: 0;
|
||||
color: var(--gw-green);
|
||||
font-size: clamp(26px, 2.7vw, 34px);
|
||||
line-height: 1.08;
|
||||
letter-spacing: -0.03em;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.service-intro-body {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
.service-intro-prose p {
|
||||
margin: 0 0 15px;
|
||||
color: #454a50;
|
||||
font-size: 16px;
|
||||
line-height: 1.75;
|
||||
}
|
||||
|
||||
.service-intro-prose p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.service-intro-lead {
|
||||
margin: 0;
|
||||
padding: 18px 20px 18px 22px;
|
||||
border-radius: 22px;
|
||||
border-left: 4px solid var(--yellow-soft);
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.72) 0%, rgba(250, 247, 239, 0.92) 100%);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(33, 48, 33, 0.05),
|
||||
0 12px 22px rgba(17, 20, 24, 0.04);
|
||||
color: #1f242a;
|
||||
font-size: 18px;
|
||||
line-height: 1.7;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.service-intro {
|
||||
padding: 6px 0 42px;
|
||||
}
|
||||
|
||||
.service-intro-card {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 20px;
|
||||
padding: 26px 20px 24px;
|
||||
border-radius: 24px;
|
||||
}
|
||||
|
||||
.service-intro-card::before {
|
||||
inset: 12px;
|
||||
border-radius: 18px;
|
||||
}
|
||||
|
||||
.service-intro-header {
|
||||
gap: 16px;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.service-intro-badge {
|
||||
padding: 8px 14px 8px 8px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.service-intro-mark {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 12px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.service-intro-heading {
|
||||
font-size: clamp(24px, 7vw, 30px);
|
||||
line-height: 1.12;
|
||||
}
|
||||
|
||||
.service-intro-prose p {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.service-intro-lead {
|
||||
padding: 16px 16px 16px 18px;
|
||||
font-size: 16px;
|
||||
line-height: 1.65;
|
||||
}
|
||||
}
|
||||
|
||||
.service-faq {
|
||||
padding: 0 0 var(--space-section-featured-y);
|
||||
}
|
||||
|
||||
.service-faq-card {
|
||||
max-width: 880px;
|
||||
margin: 0 auto;
|
||||
padding: 38px 40px 32px;
|
||||
border-radius: 30px;
|
||||
background: #fff;
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(33, 48, 33, 0.06),
|
||||
0 18px 40px rgba(17, 20, 24, 0.06);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.service-faq-card {
|
||||
padding: 26px 20px 22px;
|
||||
border-radius: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.service-related {
|
||||
padding: 0 0 var(--space-section-featured-y);
|
||||
}
|
||||
@@ -852,7 +1121,7 @@
|
||||
}
|
||||
|
||||
.service-benefit-card-featured::after {
|
||||
background: linear-gradient(90deg, #ffd54a 0%, rgba(242, 191, 47, 0.72) 100%);
|
||||
display: none;
|
||||
}
|
||||
|
||||
.service-benefit-icon {
|
||||
@@ -862,35 +1131,31 @@
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
border-radius: 16px;
|
||||
background: linear-gradient(180deg, rgba(255, 250, 236, 0.96) 0%, rgba(242, 191, 47, 0.18) 100%);
|
||||
background: var(--gw-green);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(17, 20, 24, 0.04),
|
||||
0 8px 20px rgba(17, 20, 24, 0.08);
|
||||
color: var(--gw-green);
|
||||
font-size: 17px;
|
||||
inset 0 0 0 1px rgba(255, 255, 255, 0.06),
|
||||
0 10px 22px rgba(33, 48, 33, 0.22);
|
||||
color: var(--yellow);
|
||||
font-size: 19px;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 14px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.service-benefit-tint-1 .service-benefit-icon {
|
||||
background: linear-gradient(180deg, rgba(255, 250, 236, 0.96) 0%, rgba(242, 191, 47, 0.16) 100%);
|
||||
}
|
||||
|
||||
.service-benefit-tint-2 .service-benefit-icon {
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.95) 0%, rgba(229, 214, 194, 0.42) 100%);
|
||||
}
|
||||
|
||||
.service-benefit-tint-1 .service-benefit-icon,
|
||||
.service-benefit-tint-2 .service-benefit-icon,
|
||||
.service-benefit-tint-3 .service-benefit-icon {
|
||||
background: linear-gradient(180deg, rgba(250, 252, 248, 0.96) 0%, rgba(33, 48, 33, 0.08) 100%);
|
||||
background: var(--gw-green);
|
||||
color: var(--yellow);
|
||||
}
|
||||
|
||||
.service-benefit-card-featured .service-benefit-icon {
|
||||
background: linear-gradient(180deg, rgba(255, 248, 214, 0.96) 0%, rgba(255, 209, 0, 0.34) 100%);
|
||||
background: var(--yellow);
|
||||
color: var(--gw-green);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(255, 255, 255, 0.08),
|
||||
0 10px 24px rgba(0, 0, 0, 0.16);
|
||||
inset 0 0 0 1px rgba(255, 255, 255, 0.4),
|
||||
0 10px 24px rgba(0, 0, 0, 0.22);
|
||||
}
|
||||
|
||||
.service-benefit-card h3,
|
||||
@@ -1046,6 +1311,36 @@
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.service-benefit-grid-simple-swipe {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin: 0 -16px;
|
||||
padding: 0 16px 8px;
|
||||
overflow-x: auto;
|
||||
overscroll-behavior-x: contain;
|
||||
scroll-snap-type: x mandatory;
|
||||
scrollbar-width: none;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
touch-action: pan-x pinch-zoom;
|
||||
}
|
||||
|
||||
.service-benefit-grid-simple-swipe::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.service-benefit-grid-simple-swipe .service-benefit-card {
|
||||
flex: 0 0 78%;
|
||||
min-height: clamp(230px, 42svh, 320px);
|
||||
min-width: 0;
|
||||
scroll-snap-align: start;
|
||||
scroll-snap-stop: always;
|
||||
}
|
||||
|
||||
:global(.reveal-ready.reveal-block).service-pricing-immediate-mobile {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.service-highlight-layout {
|
||||
gap: 24px;
|
||||
@@ -1197,15 +1492,35 @@
|
||||
}
|
||||
|
||||
.service-highlight-collage {
|
||||
grid-template-columns: 1fr;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
max-width: 420px;
|
||||
max-width: none;
|
||||
margin: 0 -16px;
|
||||
overflow-x: auto;
|
||||
overscroll-behavior-x: contain;
|
||||
scroll-snap-type: x mandatory;
|
||||
padding: 0 16px 8px;
|
||||
scrollbar-width: none;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
touch-action: pan-x pinch-zoom;
|
||||
}
|
||||
|
||||
.service-highlight-collage::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.service-collage-card {
|
||||
flex: 0 0 78%;
|
||||
min-height: 168px;
|
||||
min-width: 0;
|
||||
border-radius: 22px;
|
||||
scroll-snap-align: start;
|
||||
scroll-snap-stop: always;
|
||||
}
|
||||
|
||||
.service-collage-card-1,
|
||||
.service-collage-card-2,
|
||||
.service-collage-card-3 {
|
||||
min-height: 220px;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
export let services: IconCard[];
|
||||
export let heading = 'Choose the walk style that suits your dog best.';
|
||||
export let intro =
|
||||
'Dogs are social creatures. The Tiny Gang gives them their own little friendship group — older dogs guide the younger ones, playful dogs burn energy together, and everyone comes home happy, tired, and fulfilled. All the fun of doggy daycare, without the huge groups or price tag.';
|
||||
'Dogs are social creatures. The Tiny Gang gives them their own little friendship group: older dogs guide the younger ones, playful dogs burn energy together, and everyone comes home happy, tired, and fulfilled. All the fun of doggy daycare, without the huge groups or price tag.';
|
||||
|
||||
const sharedPromises = [
|
||||
'Familiar walkers',
|
||||
@@ -22,7 +22,6 @@
|
||||
{
|
||||
eyebrow: string;
|
||||
featured?: boolean;
|
||||
featuredLabel?: string;
|
||||
imageUrl: string;
|
||||
imageAlt: string;
|
||||
lead: string;
|
||||
@@ -32,22 +31,21 @@
|
||||
'Tiny Gang Pack Walks': {
|
||||
eyebrow: 'Good Walk Signature',
|
||||
featured: true,
|
||||
featuredLabel: 'Most loved',
|
||||
imageUrl: '/images/auckland-pack-walk-small-dogs-group.jpg',
|
||||
imageUrl: '/images/goodwalk-tiny-gang-pack-walk-small-dogs-auckland.webp',
|
||||
imageAlt: 'Small dogs together on a Tiny Gang pack walk',
|
||||
lead: 'The Tiny Gang is built for dogs who love company, big adventures, and coming home happily worn out!',
|
||||
cues: ['4-8 dogs', 'Pickup & drop-off', 'Tiny Gang matching']
|
||||
},
|
||||
'1:1 Walks': {
|
||||
eyebrow: 'Tailored support',
|
||||
imageUrl: '/images/one-on-one-dog-portrait-1.jpg',
|
||||
imageUrl: '/images/goodwalk-brown-curly-dog-one-on-one-walk-auckland.webp',
|
||||
imageAlt: 'Dog enjoying a one-on-one walk',
|
||||
lead: 'For nervous dogs, senior dogs, and little personalities who do better with extra attention.',
|
||||
cues: ['Solo focus', 'Custom pace', 'Confidence building']
|
||||
},
|
||||
'Puppy Visits': {
|
||||
eyebrow: 'Building Blocks For The Tiny Gang',
|
||||
imageUrl: '/images/auckland-puppy-home-visit.jpg',
|
||||
imageUrl: '/images/goodwalk-puppy-visit-cavalier-king-charles-spaniel-auckland.webp',
|
||||
imageAlt: 'Puppy during a calm home visit',
|
||||
lead: 'Early puppy visits designed to build confidence, routine, and good habits before Tiny Gang adventures begin!',
|
||||
cues: ['Home visits', 'Routine support', 'Play & company']
|
||||
@@ -91,9 +89,6 @@
|
||||
{#if meta}
|
||||
<img src={meta.imageUrl} alt={meta.imageAlt} loading="lazy" decoding="async" />
|
||||
{/if}
|
||||
{#if meta?.featuredLabel}
|
||||
<span class="service-card-badge">{meta.featuredLabel}</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="service-card-body">
|
||||
@@ -116,7 +111,7 @@
|
||||
{/if}
|
||||
|
||||
<span class="service-card-cta">
|
||||
View service page
|
||||
More info
|
||||
<Icon name="fas fa-arrow-right" className="service-card-cta-arrow" />
|
||||
</span>
|
||||
</div>
|
||||
@@ -127,112 +122,52 @@
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.services-inner {
|
||||
max-width: min(1180px, calc(var(--max-w) - 40px));
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
grid-template-columns: minmax(0, 1fr) minmax(20rem, 0.85fr);
|
||||
align-items: start;
|
||||
column-gap: clamp(32px, 5vw, 72px);
|
||||
row-gap: 14px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.section-header .section-heading {
|
||||
max-width: 22ch;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.section-header .section-intro {
|
||||
margin: 0;
|
||||
padding-top: 14px;
|
||||
max-width: 44ch;
|
||||
justify-self: end;
|
||||
text-align: left;
|
||||
line-height: 1.72;
|
||||
color: var(--text-heading-soft, var(--text-muted));
|
||||
}
|
||||
|
||||
/* ── Section intro ── */
|
||||
.services-intro {
|
||||
max-width: 700px;
|
||||
}
|
||||
|
||||
/* ── Overview band ── */
|
||||
.services-overview {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.2fr) minmax(300px, 0.8fr);
|
||||
gap: 18px;
|
||||
align-items: stretch;
|
||||
margin-top: 28px;
|
||||
}
|
||||
|
||||
.services-overview-copy,
|
||||
.services-overview-panel {
|
||||
border-radius: 28px;
|
||||
background: linear-gradient(180deg, #fff 0%, #fbf8f2 100%);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(17, 20, 24, 0.06),
|
||||
0 12px 28px rgba(17, 20, 24, 0.05);
|
||||
}
|
||||
|
||||
.services-overview-copy {
|
||||
padding: 28px 30px;
|
||||
}
|
||||
|
||||
.services-overview-kicker,
|
||||
.services-overview-panel-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 30px;
|
||||
width: fit-content;
|
||||
padding: 0 11px;
|
||||
border-radius: 999px;
|
||||
background: rgba(33, 48, 33, 0.08);
|
||||
color: var(--gw-green);
|
||||
font-family: var(--font-head);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.services-overview-copy h3 {
|
||||
margin: 14px 0 10px;
|
||||
color: #0d1a0d;
|
||||
font-size: clamp(26px, 2.6vw, 34px);
|
||||
line-height: 1.02;
|
||||
letter-spacing: -0.04em;
|
||||
}
|
||||
|
||||
.services-overview-copy p,
|
||||
.services-overview-panel {
|
||||
color: #4c5056;
|
||||
font-size: 15px;
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.services-overview-copy p {
|
||||
max-width: 44ch;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.services-overview-panel {
|
||||
display: grid;
|
||||
align-content: center;
|
||||
gap: 16px;
|
||||
padding: 24px 24px 22px;
|
||||
background:
|
||||
radial-gradient(circle at top right, rgba(255, 209, 0, 0.22), transparent 36%),
|
||||
linear-gradient(180deg, #fff 0%, #f8f3e7 100%);
|
||||
}
|
||||
|
||||
.services-overview-pills {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.services-overview-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 36px;
|
||||
padding: 0 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.82);
|
||||
box-shadow: inset 0 0 0 1px rgba(33, 48, 33, 0.08);
|
||||
color: var(--gw-green);
|
||||
font-family: var(--font-head);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
max-width: 34ch;
|
||||
}
|
||||
|
||||
/* ── Service cards ── */
|
||||
.services-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 22px;
|
||||
margin-top: 30px;
|
||||
align-items: stretch;
|
||||
gap: 24px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.service-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
@@ -248,6 +183,8 @@
|
||||
border-color 0.28s ease;
|
||||
}
|
||||
|
||||
/* Featured emphasis lives in the chrome (border + glow + emblem shine),
|
||||
not the column span — keeps all three cards visually balanced. */
|
||||
.service-card-featured {
|
||||
border-color: rgba(242, 191, 47, 0.45);
|
||||
box-shadow:
|
||||
@@ -267,7 +204,7 @@
|
||||
transform: scale(1.06);
|
||||
}
|
||||
|
||||
.service-card:hover .service-card-cta-arrow {
|
||||
.service-card:hover :global(.service-card-cta-arrow) {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
@@ -297,21 +234,6 @@
|
||||
transition: transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.service-card-badge {
|
||||
position: absolute;
|
||||
top: 14px;
|
||||
right: 14px;
|
||||
padding: 6px 11px;
|
||||
border-radius: 999px;
|
||||
background: var(--gw-green);
|
||||
color: #fff6cf;
|
||||
font-family: var(--font-head);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.service-card-body {
|
||||
position: relative;
|
||||
display: flex;
|
||||
@@ -389,11 +311,15 @@
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Push cues+CTA cluster to the bottom so it lines up across all cards
|
||||
regardless of how long each card's lead paragraph runs. */
|
||||
.service-card-cues {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 7px;
|
||||
margin-top: 16px;
|
||||
margin-top: auto;
|
||||
padding-top: 18px;
|
||||
padding-bottom: 18px;
|
||||
}
|
||||
|
||||
.service-card-cue {
|
||||
@@ -414,7 +340,6 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: auto;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid rgba(17, 20, 24, 0.08);
|
||||
color: var(--gw-green);
|
||||
@@ -423,64 +348,56 @@
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.service-card-cta-arrow {
|
||||
:global(.service-card-cta-arrow) {
|
||||
font-size: 11px;
|
||||
transition: transform 0.2s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
/* ── Mobile ── */
|
||||
@media (max-width: 1024px) {
|
||||
.section-header {
|
||||
grid-template-columns: 1fr;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.section-header .section-heading,
|
||||
.section-header .section-intro {
|
||||
max-width: 32rem;
|
||||
justify-self: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.section-header .section-intro {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.services-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
/* Featured card takes the full width on tablet so it sits on its own
|
||||
row above the other two — keeps the emphasis without the asymmetric
|
||||
desktop grid. */
|
||||
.service-card-featured {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.services-intro {
|
||||
max-width: 34ch;
|
||||
}
|
||||
|
||||
.services-overview {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
margin-top: 22px;
|
||||
}
|
||||
|
||||
.services-overview-copy,
|
||||
.services-overview-panel {
|
||||
border-radius: 24px;
|
||||
}
|
||||
|
||||
.services-overview-copy {
|
||||
padding: 22px 18px;
|
||||
}
|
||||
|
||||
.services-overview-copy h3 {
|
||||
font-size: clamp(24px, 8vw, 31px);
|
||||
line-height: 1.06;
|
||||
}
|
||||
|
||||
.services-overview-copy p,
|
||||
.services-overview-panel {
|
||||
font-size: 14px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.services-overview-panel {
|
||||
padding: 18px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.services-overview-pills {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.services-overview-pill {
|
||||
min-height: 32px;
|
||||
padding: 0 12px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.services-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 16px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.service-card-featured {
|
||||
grid-column: auto;
|
||||
}
|
||||
|
||||
.service-card-body {
|
||||
padding: 40px 22px 24px;
|
||||
}
|
||||
@@ -524,10 +441,10 @@
|
||||
/* ── Reveal ── */
|
||||
:global(.reveal-ready.reveal-block) {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, var(--reveal-distance, 24px), 0);
|
||||
transform: translate3d(0, var(--reveal-distance, 16px), 0);
|
||||
transition:
|
||||
opacity 0.55s ease,
|
||||
transform 0.7s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
opacity 0.3s ease,
|
||||
transform 0.45s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
transition-delay: var(--reveal-delay, 0ms);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import confetti from 'canvas-confetti';
|
||||
import ModalShell from '$lib/components/ModalShell.svelte';
|
||||
|
||||
export let firstName: string;
|
||||
@@ -13,35 +12,45 @@
|
||||
$: isGeneralEnquiry = enquiryType === 'general';
|
||||
|
||||
onMount(() => {
|
||||
const duration = 3200;
|
||||
const end = Date.now() + duration;
|
||||
let cancelled = false;
|
||||
|
||||
const frame = () => {
|
||||
confetti({
|
||||
particleCount: 3,
|
||||
angle: 60,
|
||||
spread: 65,
|
||||
origin: { x: 0, y: 0.75 },
|
||||
colors: ['#FFD100', gwGreenHex, '#ffffff', '#7aaa7a', '#ffeaa0'],
|
||||
gravity: 0.9,
|
||||
scalar: 1.1,
|
||||
});
|
||||
confetti({
|
||||
particleCount: 3,
|
||||
angle: 120,
|
||||
spread: 65,
|
||||
origin: { x: 1, y: 0.75 },
|
||||
colors: ['#FFD100', gwGreenHex, '#ffffff', '#7aaa7a', '#ffeaa0'],
|
||||
gravity: 0.9,
|
||||
scalar: 1.1,
|
||||
});
|
||||
void import('canvas-confetti').then(({ default: confetti }) => {
|
||||
if (cancelled) return;
|
||||
|
||||
if (Date.now() < end) {
|
||||
requestAnimationFrame(frame);
|
||||
}
|
||||
const duration = 3200;
|
||||
const end = Date.now() + duration;
|
||||
|
||||
const frame = () => {
|
||||
confetti({
|
||||
particleCount: 3,
|
||||
angle: 60,
|
||||
spread: 65,
|
||||
origin: { x: 0, y: 0.75 },
|
||||
colors: ['#FFD100', gwGreenHex, '#ffffff', '#7aaa7a', '#ffeaa0'],
|
||||
gravity: 0.9,
|
||||
scalar: 1.1
|
||||
});
|
||||
confetti({
|
||||
particleCount: 3,
|
||||
angle: 120,
|
||||
spread: 65,
|
||||
origin: { x: 1, y: 0.75 },
|
||||
colors: ['#FFD100', gwGreenHex, '#ffffff', '#7aaa7a', '#ffeaa0'],
|
||||
gravity: 0.9,
|
||||
scalar: 1.1
|
||||
});
|
||||
|
||||
if (!cancelled && Date.now() < end) {
|
||||
requestAnimationFrame(frame);
|
||||
}
|
||||
};
|
||||
|
||||
frame();
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
|
||||
frame();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -7,60 +7,36 @@
|
||||
|
||||
export let content: SiteSharedContent;
|
||||
|
||||
$: testimonials = content.testimonials.filter((testimonial) => testimonial.imageUrl);
|
||||
$: featuredTestimonial = testimonials[0];
|
||||
$: supportingTestimonials = testimonials.slice(1);
|
||||
$: testimonialCards = testimonials.map((testimonial, index) => ({
|
||||
$: testimonials = content.testimonials;
|
||||
$: testimonialCards = testimonials.map((testimonial) => ({
|
||||
...testimonial,
|
||||
enhanced: getEnhancedImage(testimonial.imageUrl),
|
||||
accentClass: `testimonial-card-accent-${(index % 4) + 1}`
|
||||
enhanced: testimonial.imageUrl ? getEnhancedImage(testimonial.imageUrl) : undefined
|
||||
}));
|
||||
|
||||
const testimonialHighlights = [
|
||||
{
|
||||
quote: 'Amazing with my slightly hyper and anxious dog.',
|
||||
reviewer: 'Kate',
|
||||
detail: "Archie's mum"
|
||||
},
|
||||
{
|
||||
quote: 'Great with communication if anything needs to change.',
|
||||
reviewer: 'Kate',
|
||||
detail: "Archie's mum"
|
||||
},
|
||||
{
|
||||
quote: 'Basically doubled as a second mum to Monty.',
|
||||
reviewer: 'Estelle',
|
||||
detail: "Monty's mum"
|
||||
},
|
||||
{
|
||||
quote: 'Provided feedback and training recommendations.',
|
||||
reviewer: 'Estelle',
|
||||
detail: "Monty's mum"
|
||||
},
|
||||
{
|
||||
quote: 'Otis absolutely adores her.',
|
||||
reviewer: 'Ross',
|
||||
detail: "Otis's dad"
|
||||
},
|
||||
{
|
||||
quote: 'He always comes back happy and tired.',
|
||||
reviewer: 'Ross',
|
||||
detail: "Otis's dad"
|
||||
},
|
||||
{
|
||||
quote: 'She has cared for my pup since she was 10 weeks old.',
|
||||
reviewer: 'Nina',
|
||||
detail: "Wallace's mum"
|
||||
},
|
||||
{
|
||||
quote: 'My dog has a great time every walk.',
|
||||
reviewer: 'Nina',
|
||||
detail: "Wallace's mum"
|
||||
}
|
||||
];
|
||||
function dogNameFromDetail(detail: string) {
|
||||
const match = detail.match(/^([^'’]+)/);
|
||||
return match ? match[1].trim() : '';
|
||||
}
|
||||
|
||||
function reviewerRole(testimonial: TestimonialContent) {
|
||||
const detail = testimonial.detail.trim();
|
||||
const reviewer = testimonial.reviewer.trim();
|
||||
const detailHasRelation = /(mum|dad|father|owner)/i.test(detail);
|
||||
const reviewerHasRelation = /(mum|dad|father|owner)/i.test(reviewer);
|
||||
|
||||
if (detailHasRelation) return detail;
|
||||
if (reviewerHasRelation) return reviewer;
|
||||
return 'Goodwalk client';
|
||||
}
|
||||
|
||||
function authorLine(testimonial: TestimonialContent) {
|
||||
const reviewer = testimonial.reviewer.trim();
|
||||
const role = reviewerRole(testimonial);
|
||||
return role === reviewer ? reviewer : `${reviewer} - ${role}`;
|
||||
}
|
||||
|
||||
function reviewAlt(testimonial: TestimonialContent) {
|
||||
const detailLead = testimonial.detail.match(/^([^'’]+)/)?.[1]?.trim();
|
||||
const detailLead = dogNameFromDetail(testimonial.detail);
|
||||
return detailLead
|
||||
? `${detailLead}, a Goodwalk dog in Auckland`
|
||||
: `${testimonial.reviewer}'s dog with Goodwalk in Auckland`;
|
||||
@@ -71,168 +47,79 @@
|
||||
<PageHeader
|
||||
variant="green"
|
||||
eyebrow="Goodwalk reviews"
|
||||
title="What our clients say"
|
||||
subtitle="The same things come up again and again: calmer dogs, smoother routines, and owners who finally stop worrying."
|
||||
title="Client Testimonials"
|
||||
subtitle="Read why clients say their dogs love Aless, the Tiny Gang, and getting out with their mates."
|
||||
>
|
||||
<a
|
||||
class="testimonials-page-trust"
|
||||
href="https://g.page/r/CUsvrWPhkYrAEB0/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
aria-label="Read our Google reviews"
|
||||
class="btn btn-yellow btn-hide-arrow-mobile testimonials-page-header-cta"
|
||||
href="#newlead"
|
||||
aria-label="Book a Meet and Greet"
|
||||
>
|
||||
<img
|
||||
class="testimonials-page-trust-logo"
|
||||
src="/images/google-g-logo.svg"
|
||||
alt=""
|
||||
width="18"
|
||||
height="19"
|
||||
/>
|
||||
<span class="testimonials-page-trust-stars" aria-hidden="true">
|
||||
{#each Array(5) as _}
|
||||
<Icon name="fas fa-star" />
|
||||
{/each}
|
||||
</span>
|
||||
<span>30+ five-star Google reviews</span>
|
||||
<span>Book a Meet & Greet</span>
|
||||
<Icon name="fas fa-arrow-right" />
|
||||
</a>
|
||||
</PageHeader>
|
||||
|
||||
<section class="testimonials-page-intro">
|
||||
<div class="page-inner testimonials-page-intro-grid">
|
||||
<div class="testimonials-page-copy">
|
||||
<span class="eyebrow testimonials-page-kicker">Why owners stay</span>
|
||||
<h2>The dogs are happy. The handoff feels easy. The routine starts working.</h2>
|
||||
<p>
|
||||
That is what people are really reviewing. Not just the walk itself, but what it feels
|
||||
like to trust someone with your dog, your keys, and part of your week.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<aside class="testimonials-page-proof">
|
||||
<span class="eyebrow testimonials-page-proof-label">What people keep saying</span>
|
||||
<div class="testimonials-page-proof-pills">
|
||||
<span class="testimonials-page-proof-pill">Reliable communication</span>
|
||||
<span class="testimonials-page-proof-pill">Dogs genuinely adore Aless</span>
|
||||
<span class="testimonials-page-proof-pill">Calmer afternoons at home</span>
|
||||
<span class="testimonials-page-proof-pill">A routine that feels easy</span>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{#if featuredTestimonial}
|
||||
<section class="testimonials-page-featured-section">
|
||||
<div class="page-inner testimonials-page-featured-grid">
|
||||
<article class="testimonials-page-spotlight">
|
||||
<div class="testimonials-page-spotlight-media">
|
||||
{#if testimonialCards[0]?.enhanced}
|
||||
<enhanced:img
|
||||
src={testimonialCards[0].enhanced}
|
||||
alt={reviewAlt(featuredTestimonial)}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
{:else}
|
||||
<img
|
||||
src={featuredTestimonial.imageUrl}
|
||||
alt={reviewAlt(featuredTestimonial)}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="testimonials-page-spotlight-copy">
|
||||
<span class="eyebrow testimonials-page-card-kicker">Featured review</span>
|
||||
<blockquote>{featuredTestimonial.quote}</blockquote>
|
||||
<div class="testimonials-page-author">
|
||||
<span class="testimonials-page-author-name">{featuredTestimonial.reviewer}</span>
|
||||
<span class="testimonials-page-author-detail">{featuredTestimonial.detail}</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<div class="testimonials-page-stack">
|
||||
{#each supportingTestimonials as testimonial, index}
|
||||
<article class={`testimonials-page-mini-card testimonial-card-accent-${((index + 1) % 4) + 1}`}>
|
||||
<div class="testimonials-page-mini-card-copy">
|
||||
<span class="testimonials-page-mini-stars" aria-hidden="true">★★★★★</span>
|
||||
<blockquote>{testimonial.quote}</blockquote>
|
||||
<div class="testimonials-page-author">
|
||||
<span class="testimonials-page-author-name">{testimonial.reviewer}</span>
|
||||
<span class="testimonials-page-author-detail">{testimonial.detail}</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<section class="testimonials-page-highlights-section">
|
||||
<div class="page-inner">
|
||||
<div class="section-header testimonials-page-highlights-header">
|
||||
<span class="eyebrow">Small moments owners mention</span>
|
||||
<h2 class="section-heading">The same little details keep coming up.</h2>
|
||||
<p class="section-intro">
|
||||
These are the lines that show up again and again when people describe what Goodwalk
|
||||
feels like.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="testimonials-page-highlights-grid">
|
||||
{#each testimonialHighlights as highlight}
|
||||
<article class="testimonials-page-highlight-card">
|
||||
<div class="testimonials-page-highlight-top">
|
||||
<span class="testimonials-page-highlight-mark">“</span>
|
||||
<span class="testimonials-page-highlight-stars" aria-hidden="true">★★★★★</span>
|
||||
</div>
|
||||
<p>{highlight.quote}</p>
|
||||
<div class="testimonials-page-highlight-author">
|
||||
<span>{highlight.reviewer}</span>
|
||||
<span>{highlight.detail}</span>
|
||||
</div>
|
||||
</article>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="testimonials-page-grid-section">
|
||||
<div class="page-inner">
|
||||
<div class="section-header testimonials-page-grid-header">
|
||||
<span class="eyebrow">Full reviews</span>
|
||||
<h2 class="section-heading">A closer look at what clients say after walking with us.</h2>
|
||||
</div>
|
||||
|
||||
<div class="testimonials-page-grid">
|
||||
{#each testimonialCards as testimonial}
|
||||
<article class={`testimonials-page-card ${testimonial.accentClass}`}>
|
||||
<article class="testimonials-page-card">
|
||||
<div class="testimonials-page-card-media">
|
||||
{#if testimonial.enhanced}
|
||||
{#if testimonial.imageUrl && testimonial.enhanced}
|
||||
<enhanced:img
|
||||
src={testimonial.enhanced}
|
||||
alt={reviewAlt(testimonial)}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
{:else}
|
||||
{:else if testimonial.imageUrl}
|
||||
<img
|
||||
src={testimonial.imageUrl}
|
||||
alt={reviewAlt(testimonial)}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
{:else}
|
||||
<div class="testimonials-page-card-fallback" aria-hidden="true">
|
||||
<span class="testimonials-page-card-fallback-mark">
|
||||
<span class="testimonials-page-card-fallback-mark-ring">
|
||||
<Icon name="fas fa-paw" />
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="testimonials-page-card-meta">
|
||||
<span class="testimonials-page-card-dog">
|
||||
{dogNameFromDetail(testimonial.detail) || testimonial.reviewer}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="testimonials-page-card-copy">
|
||||
<span class="testimonials-page-quote-mark">"</span>
|
||||
<div class="testimonials-page-card-top">
|
||||
<span class="testimonials-page-card-quote" aria-hidden="true">“</span>
|
||||
<div class="testimonials-page-card-review-meta">
|
||||
{#if testimonial.type === 'Google'}
|
||||
<img
|
||||
class="testimonials-page-card-source-icon"
|
||||
src="/images/google-g-logo.svg"
|
||||
alt="Google review"
|
||||
width="14"
|
||||
height="15"
|
||||
/>
|
||||
{/if}
|
||||
<span class="testimonials-page-card-stars" aria-hidden="true">
|
||||
{#each Array(5) as _}
|
||||
<Icon name="fas fa-star" />
|
||||
{/each}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<blockquote>{testimonial.quote}</blockquote>
|
||||
<div class="testimonials-page-author">
|
||||
<span class="testimonials-page-author-name">{testimonial.reviewer}</span>
|
||||
<span class="testimonials-page-author-detail">{testimonial.detail}</span>
|
||||
<span class="testimonials-page-author-name">{authorLine(testimonial)}</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
@@ -265,269 +152,57 @@
|
||||
|
||||
<style>
|
||||
.testimonials-page {
|
||||
background: #fff;
|
||||
background: var(--off-white);
|
||||
}
|
||||
|
||||
.testimonials-page-trust {
|
||||
.testimonials-page-header-cta {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-top: 22px;
|
||||
padding: 10px 18px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.16);
|
||||
color: #fff;
|
||||
font-family: var(--font-body);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.testimonials-page-trust-stars {
|
||||
display: inline-flex;
|
||||
gap: 2px;
|
||||
color: var(--yellow);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.testimonials-page-trust-logo {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.testimonials-page-intro,
|
||||
.testimonials-page-featured-section,
|
||||
.testimonials-page-highlights-section,
|
||||
.testimonials-page-grid-section {
|
||||
padding: 64px 0;
|
||||
}
|
||||
|
||||
.testimonials-page-featured-section,
|
||||
.testimonials-page-grid-section {
|
||||
background: var(--off-white);
|
||||
}
|
||||
|
||||
.testimonials-page-intro-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.05fr) minmax(320px, 0.95fr);
|
||||
gap: 22px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.testimonials-page-copy,
|
||||
.testimonials-page-proof,
|
||||
.testimonials-page-spotlight,
|
||||
.testimonials-page-mini-card,
|
||||
.testimonials-page-highlight-card,
|
||||
.testimonials-page-card {
|
||||
border-radius: 30px;
|
||||
background: #fff;
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(17, 20, 24, 0.05),
|
||||
var(--shadow-panel-elevated);
|
||||
}
|
||||
|
||||
.testimonials-page-copy {
|
||||
padding: 34px 34px 32px;
|
||||
}
|
||||
|
||||
.testimonials-page-kicker,
|
||||
.testimonials-page-proof-label,
|
||||
.testimonials-page-card-kicker {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.testimonials-page-copy h2 {
|
||||
margin: 16px 0 12px;
|
||||
color: #102010;
|
||||
font-family: var(--font-head);
|
||||
font-size: clamp(28px, 3vw, 42px);
|
||||
line-height: 1.04;
|
||||
letter-spacing: -0.04em;
|
||||
}
|
||||
|
||||
.testimonials-page-copy p {
|
||||
max-width: 44ch;
|
||||
margin: 0;
|
||||
color: #4c5056;
|
||||
font-size: 17px;
|
||||
line-height: 1.68;
|
||||
}
|
||||
|
||||
.testimonials-page-proof {
|
||||
display: grid;
|
||||
align-content: center;
|
||||
gap: 16px;
|
||||
padding: 28px 26px;
|
||||
background:
|
||||
radial-gradient(circle at top right, rgba(255, 209, 0, 0.2), transparent 34%),
|
||||
linear-gradient(180deg, #fffdf8 0%, #f5efe0 100%);
|
||||
}
|
||||
|
||||
.testimonials-page-proof-pills {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.testimonials-page-proof-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 36px;
|
||||
padding: 0 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.84);
|
||||
box-shadow: inset 0 0 0 1px rgba(33, 48, 33, 0.08);
|
||||
color: var(--gw-green);
|
||||
font-family: var(--font-body);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.testimonials-page-featured-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.15fr) minmax(300px, 0.85fr);
|
||||
gap: 24px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.testimonials-page-spotlight {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(240px, 0.9fr) minmax(0, 1.1fr);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.testimonials-page-spotlight-media {
|
||||
min-height: 100%;
|
||||
background: #ede4d2;
|
||||
}
|
||||
|
||||
.testimonials-page-spotlight-media img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.testimonials-page-spotlight-copy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 18px;
|
||||
padding: 34px 32px;
|
||||
background: linear-gradient(180deg, rgba(255, 250, 236, 0.76), #fff);
|
||||
}
|
||||
|
||||
.testimonials-page-spotlight-copy blockquote {
|
||||
margin: 0;
|
||||
color: #202226;
|
||||
font-size: clamp(18px, 1.65vw, 23px);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.testimonials-page-stack {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.testimonials-page-mini-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.testimonials-page-mini-card-copy {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
padding: 24px 24px 22px;
|
||||
}
|
||||
|
||||
.testimonials-page-mini-stars {
|
||||
color: var(--yellow);
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
|
||||
.testimonials-page-mini-card blockquote {
|
||||
margin: 0;
|
||||
color: #2e3031;
|
||||
font-size: 15px;
|
||||
line-height: 1.68;
|
||||
}
|
||||
|
||||
.testimonials-page-highlights-header,
|
||||
.testimonials-page-grid-header {
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.testimonials-page-highlights-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.testimonials-page-highlight-card {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
padding: 22px 20px 20px;
|
||||
}
|
||||
|
||||
.testimonials-page-highlight-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.testimonials-page-highlight-mark {
|
||||
color: rgba(242, 191, 47, 0.6);
|
||||
font-family: Georgia, serif;
|
||||
font-size: 42px;
|
||||
line-height: 0.8;
|
||||
}
|
||||
|
||||
.testimonials-page-highlight-stars {
|
||||
color: var(--yellow);
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.12em;
|
||||
}
|
||||
|
||||
.testimonials-page-highlight-card p {
|
||||
margin: 0;
|
||||
color: #1f2421;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.testimonials-page-highlight-author {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
color: #687076;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.testimonials-page-highlight-author span:first-child {
|
||||
color: #102010;
|
||||
font-family: var(--font-head);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
padding: 28px 0 72px;
|
||||
}
|
||||
|
||||
.testimonials-page-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 24px;
|
||||
gap: 22px;
|
||||
align-items: stretch;
|
||||
max-width: 1240px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.testimonials-page-card {
|
||||
position: relative;
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
padding: 12px;
|
||||
border: 1px solid rgba(33, 48, 33, 0.09);
|
||||
border-radius: 28px;
|
||||
background: #fff;
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.78),
|
||||
0 16px 34px rgba(33, 48, 33, 0.08);
|
||||
}
|
||||
|
||||
.testimonials-page-card-media {
|
||||
position: relative;
|
||||
aspect-ratio: 4 / 3;
|
||||
background: #ede4d2;
|
||||
overflow: hidden;
|
||||
border-radius: 22px;
|
||||
background: rgba(33, 48, 33, 0.12);
|
||||
}
|
||||
|
||||
.testimonials-page-card-media img {
|
||||
@@ -537,74 +212,160 @@
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.testimonials-page-card-copy {
|
||||
padding: 28px 28px 30px;
|
||||
.testimonials-page-card-fallback {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
background: var(--gw-green);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.testimonials-page-quote-mark {
|
||||
display: block;
|
||||
margin-bottom: 12px;
|
||||
color: var(--yellow-soft);
|
||||
font-family: Georgia, serif;
|
||||
font-size: 54px;
|
||||
line-height: 0.6;
|
||||
.testimonials-page-card-fallback::before,
|
||||
.testimonials-page-card-fallback::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 209, 0, 0.08);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.testimonials-page-card-fallback::before {
|
||||
top: 18px;
|
||||
right: 18px;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
}
|
||||
|
||||
.testimonials-page-card-fallback::after {
|
||||
bottom: -20px;
|
||||
left: -10px;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.testimonials-page-card-fallback-mark {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.testimonials-page-card-fallback-mark {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.testimonials-page-card-fallback-mark-ring {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(180deg, rgba(255, 244, 204, 0.96), rgba(255, 209, 0, 0.82));
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(17, 20, 24, 0.06),
|
||||
0 12px 28px rgba(17, 20, 24, 0.18);
|
||||
}
|
||||
|
||||
.testimonials-page-card-fallback-mark-ring :global(.icon) {
|
||||
font-size: 28px;
|
||||
color: var(--gw-green);
|
||||
}
|
||||
|
||||
.testimonials-page-card-meta {
|
||||
position: absolute;
|
||||
right: 14px;
|
||||
bottom: 14px;
|
||||
left: 14px;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-start;
|
||||
padding: 14px 16px;
|
||||
border-radius: 18px;
|
||||
background: linear-gradient(180deg, rgba(18, 26, 18, 0.1) 0%, rgba(18, 26, 18, 0.78) 100%);
|
||||
}
|
||||
|
||||
.testimonials-page-card-dog {
|
||||
color: #fff;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.18);
|
||||
font-family: var(--font-head);
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
|
||||
.testimonials-page-card-copy {
|
||||
display: grid;
|
||||
align-content: start;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
gap: 14px;
|
||||
padding: 18px 8px 10px;
|
||||
}
|
||||
|
||||
.testimonials-page-card-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.testimonials-page-card-quote {
|
||||
color: rgba(33, 48, 33, 0.22);
|
||||
font-family: Georgia, 'Times New Roman', serif;
|
||||
font-size: 40px;
|
||||
line-height: 0.8;
|
||||
}
|
||||
|
||||
.testimonials-page-card-review-meta {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.testimonials-page-card-source-icon {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.testimonials-page-card-stars {
|
||||
display: inline-flex;
|
||||
gap: 4px;
|
||||
color: #f0b72f;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.testimonials-page-card blockquote {
|
||||
margin: 0;
|
||||
color: #2e3031;
|
||||
font-size: 16px;
|
||||
line-height: 1.68;
|
||||
align-self: start;
|
||||
color: #243024;
|
||||
font-size: 17px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.testimonials-page-author {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 18px;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.testimonials-page-author-name {
|
||||
color: #102010;
|
||||
color: var(--gw-green);
|
||||
font-family: var(--font-head);
|
||||
font-size: 14px;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.testimonials-page-author-detail {
|
||||
color: #6b7280;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.testimonials-page-author-detail::before {
|
||||
content: '—';
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.testimonial-card-accent-1 .testimonials-page-card-copy,
|
||||
.testimonial-card-accent-1 .testimonials-page-mini-card-copy {
|
||||
background: linear-gradient(180deg, rgba(255, 250, 236, 0.72), #fff);
|
||||
}
|
||||
|
||||
.testimonial-card-accent-2 .testimonials-page-card-copy,
|
||||
.testimonial-card-accent-2 .testimonials-page-mini-card-copy {
|
||||
background: linear-gradient(180deg, rgba(229, 214, 194, 0.28), #fff);
|
||||
}
|
||||
|
||||
.testimonial-card-accent-3 .testimonials-page-card-copy,
|
||||
.testimonial-card-accent-3 .testimonials-page-mini-card-copy {
|
||||
background: linear-gradient(180deg, rgba(33, 48, 33, 0.04), #fff);
|
||||
}
|
||||
|
||||
.testimonial-card-accent-4 .testimonials-page-card-copy,
|
||||
.testimonial-card-accent-4 .testimonials-page-mini-card-copy {
|
||||
background: linear-gradient(180deg, rgba(255, 209, 0, 0.12), #fff);
|
||||
}
|
||||
|
||||
.testimonials-page-google-cta-wrap {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 28px;
|
||||
margin-top: 36px;
|
||||
}
|
||||
|
||||
.testimonials-page-google-cta {
|
||||
@@ -614,11 +375,11 @@
|
||||
min-height: 50px;
|
||||
padding: 0 18px;
|
||||
border-radius: 999px;
|
||||
background: #fff;
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(17, 20, 24, 0.06),
|
||||
0 10px 24px rgba(17, 20, 24, 0.06);
|
||||
color: var(--gw-green);
|
||||
0 16px 30px rgba(67, 49, 21, 0.08);
|
||||
color: #1d281e;
|
||||
font-family: var(--font-head);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
@@ -626,95 +387,77 @@
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.testimonials-page-intro-grid,
|
||||
.testimonials-page-featured-grid,
|
||||
.testimonials-page-highlights-grid,
|
||||
.testimonials-page-grid,
|
||||
.testimonials-page-spotlight {
|
||||
.testimonials-page-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.testimonials-page-highlights-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
@media (min-width: 1400px) {
|
||||
.testimonials-page-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
max-width: 1500px;
|
||||
}
|
||||
}
|
||||
|
||||
.testimonials-page-spotlight-media {
|
||||
aspect-ratio: 4 / 3;
|
||||
@media (min-width: 1800px) {
|
||||
.testimonials-page-grid {
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
max-width: 1760px;
|
||||
gap: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.testimonials-page-intro,
|
||||
.testimonials-page-featured-section,
|
||||
.testimonials-page-highlights-section,
|
||||
.testimonials-page-grid-section {
|
||||
padding: 52px 0;
|
||||
padding: 20px 0 56px;
|
||||
}
|
||||
|
||||
.testimonials-page-trust {
|
||||
.testimonials-page-header-cta {
|
||||
gap: 10px;
|
||||
padding: 10px 14px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.testimonials-page-copy {
|
||||
padding: 24px 20px 22px;
|
||||
.testimonials-page-header-cta :global(.icon) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.testimonials-page-proof {
|
||||
padding: 22px 18px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.testimonials-page-copy h2 {
|
||||
font-size: clamp(26px, 8vw, 34px);
|
||||
}
|
||||
|
||||
.testimonials-page-copy p {
|
||||
font-size: 15px;
|
||||
line-height: 1.58;
|
||||
}
|
||||
|
||||
.testimonials-page-spotlight-copy,
|
||||
.testimonials-page-mini-card-copy,
|
||||
.testimonials-page-card-copy {
|
||||
padding: 22px 20px 24px;
|
||||
gap: 14px;
|
||||
padding: 18px 6px 8px;
|
||||
}
|
||||
|
||||
.testimonials-page-spotlight-copy blockquote,
|
||||
.testimonials-page-card blockquote,
|
||||
.testimonials-page-mini-card blockquote {
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.testimonials-page-highlights-grid,
|
||||
.testimonials-page-grid {
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.testimonials-page-highlights-grid {
|
||||
grid-template-columns: 1fr;
|
||||
.testimonials-page-card {
|
||||
border-radius: 26px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.testimonials-page-copy,
|
||||
.testimonials-page-proof,
|
||||
.testimonials-page-spotlight,
|
||||
.testimonials-page-mini-card,
|
||||
.testimonials-page-highlight-card,
|
||||
.testimonials-page-card {
|
||||
border-radius: 24px;
|
||||
.testimonials-page-card-media {
|
||||
border-radius: 18px;
|
||||
}
|
||||
|
||||
.testimonials-page-card-meta {
|
||||
right: 12px;
|
||||
bottom: 12px;
|
||||
left: 12px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.testimonials-page-card-dog {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.testimonials-page-card blockquote {
|
||||
font-size: 16px;
|
||||
line-height: 1.64;
|
||||
}
|
||||
|
||||
.testimonials-page-author {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.testimonials-page-author-detail::before {
|
||||
content: '';
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -20,30 +20,38 @@
|
||||
Kate: {
|
||||
reviewer: 'Kate',
|
||||
detail: "Archie's mum",
|
||||
type: 'Google',
|
||||
showInSlider: true,
|
||||
quote:
|
||||
'Love Aless! She is so amazing with my slightly hyper and anxious dog. She is great with communication if anything on either of our ends need to change. Archie love his walks, and I love the photos she posts of him.',
|
||||
imageUrl: '/images/archie-auckland-dog-walking-review.jpg'
|
||||
imageUrl: '/images/archie-goodwalk-dog-walking-review-auckland.webp'
|
||||
},
|
||||
Estelle: {
|
||||
reviewer: 'Estelle',
|
||||
detail: "Monty's mum",
|
||||
type: 'Google',
|
||||
showInSlider: true,
|
||||
quote:
|
||||
'GoodWalk was the best dog walking service for my little pooch ! Aless was very helpful - basically doubled as a second mum to Monty. She always provided feedback on his outings and assisted where possible with any additional training that she felt he could work on and made recommendations where necessary which i feel is what every dog mum wants and needs!',
|
||||
imageUrl: '/images/monty-auckland-dog-walking-review.jpg'
|
||||
imageUrl: '/images/monty-goodwalk-dog-walking-review-auckland.webp'
|
||||
},
|
||||
Ross: {
|
||||
reviewer: 'Ross',
|
||||
detail: "Otis's dad",
|
||||
type: 'Google',
|
||||
showInSlider: true,
|
||||
quote:
|
||||
'Truly the best dog walker in Auckland! I feel so lucky to have found Aless and my little terrier Otis absolutely adores her. He enjoys his regular weekly walks and always comes back happy & tired. Love the updates on social media so I can see how my dog is enjoying his day! Aless makes logistics so easy too. Highly highly recommend, there’s a reason she has 5 stars!',
|
||||
imageUrl: '/images/otis-auckland-dog-walking-review.jpg'
|
||||
imageUrl: '/images/otis-goodwalk-dog-walking-review-auckland.webp'
|
||||
},
|
||||
Nina: {
|
||||
reviewer: 'Nina',
|
||||
detail: "Wallace's mum",
|
||||
type: 'Google',
|
||||
showInSlider: true,
|
||||
quote:
|
||||
'Alessandra has been walking and spending time with my pup since she was 10 weeks old, coming over and doing puppy visits through to transitioning her to pack walks with her little doggo friends. I know Alassandra loves and cares for my dog as much as I do and my dog has a great time! Cant recommend enough',
|
||||
imageUrl: '/images/wallace-auckland-dog-walking-review.jpg'
|
||||
imageUrl: '/images/wallace-goodwalk-dog-walking-review-auckland.webp'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -57,6 +65,7 @@
|
||||
|
||||
$: slides = testimonials
|
||||
.map((testimonial) => wordpressTestimonials[testimonial.reviewer] ?? testimonial)
|
||||
.filter((testimonial) => testimonial.showInSlider)
|
||||
.filter((testimonial): testimonial is TestimonialSlide => Boolean(testimonial.imageUrl));
|
||||
|
||||
$: if (activeIndex >= slides.length) {
|
||||
@@ -158,7 +167,7 @@
|
||||
syncMobileStage('auto');
|
||||
|
||||
const interval = window.setInterval(() => {
|
||||
if (!paused && !prefersReducedMotion && inView && slides.length > 1) {
|
||||
if (!isMobileViewport() && !paused && !prefersReducedMotion && inView && slides.length > 1) {
|
||||
showNext();
|
||||
}
|
||||
}, 9000);
|
||||
@@ -174,10 +183,33 @@
|
||||
|
||||
<section id="testimonials" use:reveal={{ delay: 40 }} class="reveal-block">
|
||||
<div class="testimonials-inner">
|
||||
<span class="testimonials-eyebrow">{eyebrow}</span>
|
||||
<h2 class="section-heading">{heading}</h2>
|
||||
<div class="testimonials-intro">
|
||||
<p>{blurb}</p>
|
||||
<div class="testimonials-header">
|
||||
<div class="testimonials-header-main">
|
||||
<span class="testimonials-eyebrow">
|
||||
<Icon name="fas fa-star" className="testimonials-eyebrow-star" />
|
||||
{eyebrow}
|
||||
</span>
|
||||
<h2 class="section-heading">{heading}</h2>
|
||||
</div>
|
||||
|
||||
<div class="testimonials-header-side">
|
||||
<div class="testimonials-intro">
|
||||
<p>{blurb}</p>
|
||||
</div>
|
||||
|
||||
<div class="testimonials-cta-row">
|
||||
<a href={testimonialsHref} class="btn btn-yellow testimonials-cta testimonials-cta-primary">
|
||||
<Icon name="fas fa-comment-dots" />
|
||||
<span class="testimonials-cta-label-desktop">All testimonials</span>
|
||||
<span class="testimonials-cta-label-mobile">Testimonials</span>
|
||||
</a>
|
||||
<a href={googleReviewsHref} target="_blank" rel="noopener" class="btn btn-outline-green testimonials-cta testimonials-cta-secondary">
|
||||
<img class="testimonials-cta-logo" src="/images/google-g-logo.svg" alt="" width="16" height="17" />
|
||||
<span class="testimonials-cta-label-desktop">Google reviews</span>
|
||||
<span class="testimonials-cta-label-mobile">Google</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if slides.length}
|
||||
@@ -257,17 +289,6 @@
|
||||
<Icon name="fas fa-chevron-right" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<a class="testimonial-google" href={googleReviewsHref} target="_blank" rel="noopener">
|
||||
<img
|
||||
class="testimonial-google-logo"
|
||||
src="/images/google-g-logo.svg"
|
||||
alt=""
|
||||
width="18"
|
||||
height="19"
|
||||
/>
|
||||
<span>30+ five-star Google reviews</span>
|
||||
</a>
|
||||
</div>
|
||||
</article>
|
||||
{/each}
|
||||
@@ -283,43 +304,71 @@
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="testimonials-cta-row">
|
||||
<a href={testimonialsHref} class="testimonials-cta testimonials-cta-primary">
|
||||
<Icon name="fas fa-comment-dots" />
|
||||
<span class="testimonials-cta-label-desktop">All testimonials</span>
|
||||
<span class="testimonials-cta-label-mobile">Testimonials</span>
|
||||
</a>
|
||||
<a href={googleReviewsHref} target="_blank" rel="noopener" class="testimonials-cta testimonials-cta-secondary">
|
||||
<img class="testimonials-cta-logo" src="/images/google-g-logo.svg" alt="" width="16" height="17" />
|
||||
<span class="testimonials-cta-label-desktop">Google reviews</span>
|
||||
<span class="testimonials-cta-label-mobile">Google</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
#testimonials {
|
||||
content-visibility: auto;
|
||||
contain-intrinsic-size: 980px;
|
||||
}
|
||||
|
||||
.testimonials-inner {
|
||||
max-width: min(1220px, calc(var(--max-w) - 24px));
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--space-container-x);
|
||||
}
|
||||
|
||||
.testimonials-header {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 0.95fr) minmax(20rem, 0.85fr);
|
||||
align-items: end;
|
||||
gap: clamp(24px, 4vw, 56px);
|
||||
}
|
||||
|
||||
.testimonials-header-main,
|
||||
.testimonials-header-side {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.testimonials-header-main {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.testimonials-eyebrow {
|
||||
display: block;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
width: fit-content;
|
||||
margin: 0 auto 10px;
|
||||
padding: 7px 12px;
|
||||
margin: 0 auto 14px;
|
||||
padding: 7px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(33, 48, 33, 0.08);
|
||||
background: var(--yellow);
|
||||
color: var(--gw-green);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.05);
|
||||
box-shadow: inset 0 0 0 1px rgba(33, 48, 33, 0.18);
|
||||
}
|
||||
|
||||
.testimonials-eyebrow :global(.testimonials-eyebrow-star) {
|
||||
color: var(--gw-green);
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.testimonials-header .section-heading {
|
||||
text-align: center;
|
||||
max-width: 11ch;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.testimonials-intro {
|
||||
max-width: 760px;
|
||||
margin: 18px auto 0;
|
||||
text-align: center;
|
||||
max-width: 34ch;
|
||||
margin: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.testimonials-intro p {
|
||||
@@ -330,48 +379,35 @@
|
||||
}
|
||||
|
||||
.testimonials-cta-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, max-content);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin: 22px auto 0;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.testimonials-cta {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 9px;
|
||||
min-height: 42px;
|
||||
padding: 9px 16px;
|
||||
border-radius: 999px;
|
||||
flex-shrink: 0;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
transition:
|
||||
transform 0.16s cubic-bezier(0.22, 1, 0.36, 1),
|
||||
background 0.2s ease,
|
||||
box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.testimonials-cta-primary {
|
||||
background: var(--gw-green);
|
||||
color: #fff;
|
||||
box-shadow: 0 10px 24px rgba(33, 48, 33, 0.16);
|
||||
}
|
||||
|
||||
.testimonials-cta-secondary {
|
||||
background: rgba(33, 48, 33, 0.06);
|
||||
color: var(--gw-green);
|
||||
box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.06);
|
||||
}
|
||||
|
||||
.testimonials-cta-secondary:hover {
|
||||
color: var(--gw-green);
|
||||
}
|
||||
|
||||
.testimonials-cta-logo {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
@@ -381,16 +417,13 @@
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.testimonials-cta:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.testimonials-cta-primary:hover {
|
||||
box-shadow: 0 14px 30px rgba(33, 48, 33, 0.2);
|
||||
}
|
||||
|
||||
.testimonials-cta-secondary:hover {
|
||||
background: rgba(33, 48, 33, 0.09);
|
||||
color: var(--gw-green);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(17, 20, 24, 0.06),
|
||||
0 10px 22px rgba(17, 20, 24, 0.08);
|
||||
@@ -400,10 +433,38 @@
|
||||
|
||||
.testimonials-carousel {
|
||||
position: relative;
|
||||
margin-top: 48px;
|
||||
margin-top: 40px;
|
||||
padding: 0 38px;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.testimonials-header {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.testimonials-header-main {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.testimonials-eyebrow {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.testimonials-header .section-heading,
|
||||
.testimonials-intro {
|
||||
max-width: 34rem;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.testimonials-cta-row {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.testimonials-eyebrow {
|
||||
margin-bottom: 8px;
|
||||
@@ -422,7 +483,7 @@
|
||||
|
||||
.testimonials-cta-row {
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.testimonials-cta {
|
||||
@@ -463,10 +524,10 @@
|
||||
|
||||
:global(.reveal-ready.reveal-block) {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, var(--reveal-distance, 24px), 0);
|
||||
transform: translate3d(0, var(--reveal-distance, 16px), 0);
|
||||
transition:
|
||||
opacity 0.55s ease,
|
||||
transform 0.7s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
opacity 0.3s ease,
|
||||
transform 0.45s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
transition-delay: var(--reveal-delay, 0ms);
|
||||
}
|
||||
|
||||
@@ -581,33 +642,6 @@
|
||||
background: #e7e7e7;
|
||||
}
|
||||
|
||||
.testimonial-google {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-top: 28px;
|
||||
padding: 10px 20px;
|
||||
border-radius: 999px;
|
||||
background: #f8f8f8;
|
||||
color: #0a304e;
|
||||
font-family: var(--font-head);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
letter-spacing: 0.01em;
|
||||
box-shadow: 0 0 0 1px rgba(10, 48, 78, 0.06);
|
||||
}
|
||||
|
||||
.testimonial-google-logo {
|
||||
width: 18px;
|
||||
height: 19px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.testimonial-google:hover {
|
||||
background: #efe6d5;
|
||||
}
|
||||
|
||||
.testimonial-mobile-controls {
|
||||
display: none;
|
||||
}
|
||||
@@ -674,9 +708,10 @@
|
||||
padding-bottom: 0;
|
||||
overflow-x: auto;
|
||||
overscroll-behavior-x: contain;
|
||||
scroll-snap-type: x proximity;
|
||||
scroll-snap-type: x mandatory;
|
||||
scrollbar-width: none;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
touch-action: pan-x pinch-zoom;
|
||||
}
|
||||
|
||||
.testimonial-stage::-webkit-scrollbar {
|
||||
@@ -756,17 +791,6 @@
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.testimonial-google {
|
||||
margin-top: 16px;
|
||||
font-size: 16px;
|
||||
gap: 10px;
|
||||
padding: 10px 14px;
|
||||
}
|
||||
|
||||
.testimonial-google :global(.icon) {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.testimonial-arrow-left,
|
||||
.testimonial-arrow-right {
|
||||
display: none;
|
||||
|
||||
@@ -92,13 +92,17 @@ describe('TestimonialsSection', () => {
|
||||
{
|
||||
reviewer: 'Casey',
|
||||
detail: "Poppy's mum",
|
||||
type: 'Client',
|
||||
quote: 'Thoughtful updates and a very happy dog after every walk.',
|
||||
imageUrl: '/images/custom-casey-review.png'
|
||||
imageUrl: '/images/custom-casey-review.webp',
|
||||
showInSlider: true
|
||||
},
|
||||
{
|
||||
reviewer: 'Jordan',
|
||||
detail: "Scout's dad",
|
||||
quote: 'Should be hidden because there is no image.'
|
||||
type: 'Client',
|
||||
quote: 'Should be hidden because there is no image.',
|
||||
showInSlider: true
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -33,34 +33,34 @@
|
||||
];
|
||||
const clientPhotos = [
|
||||
{
|
||||
imageUrl: '/images/dog.png',
|
||||
imageUrl: '/images/goodwalk-client-dogs-mt-cecila-auckland.webp',
|
||||
alt: 'Two happy Goodwalk client dogs out on a walk in Auckland',
|
||||
name: 'Happy clients',
|
||||
detail: 'out with Goodwalk'
|
||||
name: 'Happy clients at Mt Cecila',
|
||||
detail: ''
|
||||
},
|
||||
{
|
||||
imageUrl: '/images/pack-walk-tiny-gang.png',
|
||||
imageUrl: '/images/goodwalk-tiny-gang-pack-walk-auckland.webp',
|
||||
alt: 'Goodwalk Tiny Gang dogs together on a walk in Auckland',
|
||||
name: 'Tiny Gang',
|
||||
detail: 'small group pack walks'
|
||||
name: 'The Tiny Gang on a pack walk',
|
||||
detail: ''
|
||||
},
|
||||
{
|
||||
imageUrl: '/images/tiny-gang-mt-albert-park.png',
|
||||
imageUrl: '/images/goodwalk-tiny-gang-mt-albert-park-auckland.webp',
|
||||
alt: 'Goodwalk dogs together at Mt Albert Park in Auckland',
|
||||
name: 'Mt Albert Park',
|
||||
detail: 'Goodwalk regulars'
|
||||
name: 'Distinguished crew at Mt Albert park',
|
||||
detail: ''
|
||||
},
|
||||
{
|
||||
imageUrl: '/images/otis-auckland-dog-walking-review.jpg',
|
||||
imageUrl: '/images/goodwalk-dogs-group-outing-auckland.webp',
|
||||
alt: 'Otis enjoying his Goodwalk routine in Auckland',
|
||||
name: 'Otis',
|
||||
detail: 'regular weekly walks'
|
||||
},
|
||||
{
|
||||
imageUrl: '/images/wallace-auckland-dog-walking-review.jpg',
|
||||
alt: 'Wallace during a Goodwalk puppy-to-pack journey',
|
||||
name: 'Wallace',
|
||||
detail: 'from puppy visits to pack walks'
|
||||
imageUrl: '/images/goodwalk-tiny-gang-finishing-walk-suv-auckland.webp',
|
||||
alt: 'Tiny Gang Pack finishing up after a walk',
|
||||
name: 'Tiny Gang heading home',
|
||||
detail: ''
|
||||
}
|
||||
];
|
||||
|
||||
@@ -114,9 +114,11 @@
|
||||
decoding="async"
|
||||
/>
|
||||
{/if}
|
||||
<figcaption class="values-photo-caption">
|
||||
<figcaption class:values-photo-caption-solo={!photo.detail} class="values-photo-caption">
|
||||
<span class="values-photo-name">{photo.name}</span>
|
||||
<span class="values-photo-detail">{photo.detail}</span>
|
||||
{#if photo.detail}
|
||||
<span class="values-photo-detail">{photo.detail}</span>
|
||||
{/if}
|
||||
</figcaption>
|
||||
</figure>
|
||||
{/each}
|
||||
@@ -184,20 +186,20 @@
|
||||
.values-inner {
|
||||
max-width: var(--max-w);
|
||||
margin: 0 auto;
|
||||
padding: 0 50px;
|
||||
padding: 0 var(--space-container-x);
|
||||
}
|
||||
|
||||
.values-inner .section-heading {
|
||||
color: #000;
|
||||
color: var(--text-heading);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.values-eyebrow {
|
||||
width: fit-content;
|
||||
padding: 6px 12px;
|
||||
border-radius: 999px;
|
||||
background: rgba(33, 48, 33, 0.06);
|
||||
box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.07);
|
||||
border-radius: var(--radius-pill);
|
||||
background: var(--surface-brand-soft);
|
||||
box-shadow: var(--shadow-inset-strong);
|
||||
}
|
||||
|
||||
.values-intro {
|
||||
@@ -220,11 +222,11 @@
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
border-radius: 28px;
|
||||
background: #ede4d2;
|
||||
border-radius: var(--radius-xl);
|
||||
background: var(--beige);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(17, 20, 24, 0.05),
|
||||
0 18px 34px rgba(17, 20, 24, 0.08);
|
||||
var(--shadow-inset-soft),
|
||||
var(--shadow-card);
|
||||
}
|
||||
|
||||
.values-photo-card-featured {
|
||||
@@ -255,11 +257,16 @@
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 18px;
|
||||
border-radius: var(--radius-md);
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.78), rgba(255, 255, 255, 0.92));
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(17, 20, 24, 0.05),
|
||||
0 12px 24px rgba(17, 20, 24, 0.08);
|
||||
var(--shadow-inset-soft),
|
||||
var(--shadow-card);
|
||||
}
|
||||
|
||||
.values-photo-caption-solo {
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.values-photo-name,
|
||||
@@ -268,14 +275,14 @@
|
||||
}
|
||||
|
||||
.values-photo-name {
|
||||
color: #102010;
|
||||
color: var(--text-heading);
|
||||
font-family: var(--font-head);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.values-photo-detail {
|
||||
color: #5a605f;
|
||||
color: var(--text-muted);
|
||||
font-size: 13px;
|
||||
line-height: 1.3;
|
||||
text-align: right;
|
||||
@@ -288,11 +295,11 @@
|
||||
margin-right: auto;
|
||||
display: grid;
|
||||
gap: 1px;
|
||||
background: rgba(17, 20, 24, 0.1);
|
||||
border: 1px solid rgba(17, 20, 24, 0.1);
|
||||
border-radius: 18px;
|
||||
background: rgba(var(--ink-rgb), 0.06);
|
||||
border: 1px solid rgba(var(--ink-rgb), 0.06);
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
box-shadow: 0 14px 34px rgba(17, 20, 24, 0.06);
|
||||
box-shadow: var(--shadow-panel-strong);
|
||||
}
|
||||
|
||||
/* ── Before / after contrast ── */
|
||||
@@ -304,8 +311,8 @@
|
||||
.values-contrast-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 38px 36px;
|
||||
background: #fff;
|
||||
padding: var(--space-8) var(--space-8);
|
||||
background: var(--surface-panel);
|
||||
}
|
||||
|
||||
.values-contrast-cell-good {
|
||||
@@ -324,9 +331,9 @@
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 5px 11px;
|
||||
border-radius: 999px;
|
||||
background: rgba(17, 20, 24, 0.05);
|
||||
color: var(--gray);
|
||||
border-radius: var(--radius-pill);
|
||||
background: rgba(var(--ink-rgb), 0.05);
|
||||
color: var(--text-muted);
|
||||
font-family: var(--font-head);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
@@ -336,14 +343,14 @@
|
||||
|
||||
.values-contrast-label-good {
|
||||
background: var(--yellow);
|
||||
color: #000;
|
||||
color: var(--gw-green);
|
||||
}
|
||||
|
||||
.values-contrast-num {
|
||||
font-family: var(--font-head);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: rgba(17, 20, 24, 0.22);
|
||||
color: rgba(var(--ink-rgb), 0.22);
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
@@ -354,12 +361,12 @@
|
||||
font-weight: 700;
|
||||
line-height: 1.22;
|
||||
letter-spacing: -0.02em;
|
||||
color: #0d1a0d;
|
||||
color: var(--text-heading);
|
||||
}
|
||||
|
||||
.values-contrast-body {
|
||||
margin: 0 0 20px;
|
||||
color: #4c5056;
|
||||
color: var(--text-muted);
|
||||
font-size: 15px;
|
||||
line-height: 1.65;
|
||||
}
|
||||
@@ -377,13 +384,13 @@
|
||||
gap: 12px;
|
||||
align-items: start;
|
||||
padding: 13px 0;
|
||||
color: #3f4348;
|
||||
color: var(--text-muted);
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.values-contrast-list li + li {
|
||||
border-top: 1px solid rgba(17, 20, 24, 0.08);
|
||||
border-top: 1px solid var(--border-muted);
|
||||
}
|
||||
|
||||
.values-contrast-bullet {
|
||||
@@ -397,7 +404,7 @@
|
||||
|
||||
.values-contrast-list :global(.values-contrast-glyph) {
|
||||
font-size: 10px;
|
||||
color: var(--gray);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.values-contrast-cell-good .values-contrast-bullet {
|
||||
@@ -413,10 +420,10 @@
|
||||
.values-contrast-footer {
|
||||
margin: auto 0 0;
|
||||
padding-top: 18px;
|
||||
border-top: 1px solid rgba(17, 20, 24, 0.08);
|
||||
border-top: 1px solid var(--border-muted);
|
||||
color: var(--gw-green);
|
||||
font-family: var(--font-head);
|
||||
font-size: 13px;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
line-height: 1.5;
|
||||
}
|
||||
@@ -428,23 +435,23 @@
|
||||
|
||||
/* Light text for the gw-green "With Goodwalk" cell */
|
||||
.values-contrast-cell-good h3 {
|
||||
color: #fff;
|
||||
color: var(--text-inverse);
|
||||
}
|
||||
|
||||
.values-contrast-cell-good .values-contrast-num {
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
color: rgba(var(--white-rgb), 0.4);
|
||||
}
|
||||
|
||||
.values-contrast-cell-good .values-contrast-body {
|
||||
color: rgba(255, 255, 255, 0.82);
|
||||
color: var(--text-inverse-muted);
|
||||
}
|
||||
|
||||
.values-contrast-cell-good .values-contrast-list li {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
color: rgba(var(--white-rgb), 0.9);
|
||||
}
|
||||
|
||||
.values-contrast-cell-good .values-contrast-list li + li {
|
||||
border-top-color: rgba(255, 255, 255, 0.14);
|
||||
border-top-color: var(--border-inverse-strong);
|
||||
}
|
||||
|
||||
/* ── Values points header ── */
|
||||
@@ -461,13 +468,13 @@
|
||||
font-weight: 700;
|
||||
line-height: 1.14;
|
||||
letter-spacing: -0.03em;
|
||||
color: #000;
|
||||
color: var(--text-heading);
|
||||
}
|
||||
|
||||
.values-points-intro {
|
||||
max-width: 560px;
|
||||
margin: 14px auto 0;
|
||||
color: #4c5056;
|
||||
color: var(--text-muted);
|
||||
font-size: var(--body-copy-size);
|
||||
line-height: 1.65;
|
||||
}
|
||||
@@ -482,14 +489,14 @@
|
||||
.values-point {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 32px 30px;
|
||||
background: #fff;
|
||||
padding: var(--space-7) var(--space-7);
|
||||
background: var(--surface-panel);
|
||||
transition: background 0.18s ease;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.values-point:hover {
|
||||
background: #fcfbf6;
|
||||
background: var(--surface-panel-warm);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,9 +507,9 @@
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-bottom: 18px;
|
||||
border-radius: 11px;
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--gw-green);
|
||||
box-shadow: 0 6px 16px rgba(33, 48, 33, 0.18);
|
||||
box-shadow: var(--shadow-badge);
|
||||
}
|
||||
|
||||
.values-point-icon :global(.values-point-glyph) {
|
||||
@@ -516,12 +523,12 @@
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
line-height: 1.25;
|
||||
color: #0d1a0d;
|
||||
color: var(--text-heading);
|
||||
}
|
||||
|
||||
.values-point p {
|
||||
margin: 0;
|
||||
color: #4c5056;
|
||||
color: var(--text-muted);
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
@@ -545,7 +552,8 @@
|
||||
|
||||
.values-eyebrow {
|
||||
padding: 6px 10px;
|
||||
font-size: 11px;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
|
||||
.values-photo-grid {
|
||||
@@ -555,31 +563,54 @@
|
||||
margin-top: 22px;
|
||||
}
|
||||
|
||||
.values-photo-card,
|
||||
.values-photo-card-featured {
|
||||
.values-photo-card {
|
||||
grid-row: auto;
|
||||
min-height: 178px;
|
||||
border-radius: 22px;
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
.values-photo-card-featured {
|
||||
grid-column: 1 / -1;
|
||||
grid-row: auto;
|
||||
min-height: 240px;
|
||||
border-radius: var(--radius-lg);
|
||||
}
|
||||
|
||||
.values-photo-caption {
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
display: grid;
|
||||
justify-content: start;
|
||||
align-items: start;
|
||||
gap: 3px;
|
||||
padding: 10px 11px;
|
||||
border-radius: 16px;
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.values-photo-caption-solo {
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.values-photo-name {
|
||||
font-size: 12px;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.values-photo-detail {
|
||||
font-size: 11px;
|
||||
line-height: 1.25;
|
||||
line-clamp: 2;
|
||||
text-align: left;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.values-bento {
|
||||
border-radius: 16px;
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.values-contrast {
|
||||
@@ -629,10 +660,10 @@
|
||||
/* ── Reveal ── */
|
||||
:global(.reveal-ready.reveal-block) {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, var(--reveal-distance, 24px), 0);
|
||||
transform: translate3d(0, var(--reveal-distance, 16px), 0);
|
||||
transition:
|
||||
opacity 0.55s ease,
|
||||
transform 0.7s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
opacity var(--motion-reveal-opacity, 0.3s ease),
|
||||
transform var(--motion-reveal-transform, 0.45s cubic-bezier(0.2, 0.8, 0.2, 1));
|
||||
transition-delay: var(--reveal-delay, 0ms);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ export const aboutPageContent: AboutPageContent = {
|
||||
"Goodwalk is built around Alessandra — who started this because she couldn't find a walker she actually trusted, and hasn't stopped showing up the same way since. She walks every dog herself, posts updates to Instagram so you can see exactly what your dog is up to, and has built relationships with some dogs from as young as ten weeks old. Thirty-plus five-star Google reviews later, the feedback keeps saying the same thing: the dogs adore her, and their owners finally stop worrying.",
|
||||
"We specialise in small and medium dogs because we understand them — not as a category, but as actual dogs with specific needs, specific quirks, and specific ways they fall apart in the wrong environment. The pace of a walk matters. The size of the group matters. The temperament of the other dogs matters. That's why we built a service around them, not just one that fits them in."
|
||||
],
|
||||
imageUrl: '/images/auckland-pack-walk-small-dogs-group.jpg',
|
||||
imageUrl: '/images/goodwalk-tiny-gang-pack-walk-small-dogs-auckland.webp',
|
||||
imageAlt: "Small dogs from Goodwalk's Tiny Gang pack walk sitting together in an Auckland park"
|
||||
},
|
||||
{
|
||||
@@ -20,7 +20,7 @@ export const aboutPageContent: AboutPageContent = {
|
||||
"Every walk you've seen across our pages — the Tiny Gang outings, the one-on-ones, the puppy visits — runs on the same principles. Calm handling, positive reinforcement, and a walker who already knows your dog. That's not a promise we make at signup. It's how every single walk actually goes.",
|
||||
"We keep packs small because we mean it when we say your dog gets real attention. We cover pickup and drop-off because your day shouldn't have to work around us. And every walker holds pet first aid certification and public liability insurance — because the dogs in our care aren't just bookings, they're the whole reason we do this."
|
||||
],
|
||||
imageUrl: '/images/auckland-dog-group-outing.jpg',
|
||||
imageUrl: '/images/goodwalk-dogs-group-outing-auckland.webp',
|
||||
imageAlt: 'Goodwalk dogs enjoying a group outing in Auckland',
|
||||
reverse: true,
|
||||
accent: 'gradient'
|
||||
@@ -32,7 +32,7 @@ export const aboutPageContent: AboutPageContent = {
|
||||
"Alessandra started Goodwalk because she couldn't find a walker she actually trusted with Maya. So she became one. Italian-born and Auckland Central-based, she leads every walk herself — not because she has to, but because handing that off was never something she was willing to do. The dogs she walks have her full attention. Their owners have her number.",
|
||||
"Maya is a Cavalier King Charles cross Shih Tzu, and the reason small dogs sit at the centre of everything Goodwalk does. She is opinionated, dramatic when it rains, and completely impossible to ignore on a walk. She is also the best argument we have for why small dogs deserve a service built specifically around them — not just accommodated by one."
|
||||
],
|
||||
imageUrl: '/images/founder-image-aless-goodwalk.jpg',
|
||||
imageUrl: '/images/alessandra-goodwalk-founder-auckland.webp',
|
||||
imageAlt: 'Alessandra, founder of Goodwalk Auckland',
|
||||
accent: 'founder'
|
||||
}
|
||||
|
||||
@@ -8,10 +8,12 @@ export const dogWalkingContent: ServicePageContent = {
|
||||
paragraphs: [
|
||||
'Goodwalk 1:1 Walks are for dogs who do better with more individual attention, a quieter setup, and a walk tailored to their own pace, confidence, and routine.',
|
||||
'They can be a great fit for larger dogs, dogs who are not suited to group walks, or owners who want a more personal approach with extra care and consistency.',
|
||||
'Common reasons owners choose 1:1 over pack walks: a dog who is reactive on lead, a senior dog who needs a slower pace, a dog recovering from injury or surgery, an anxious rescue still building confidence, or a dog whose play style does not suit a group. We are happy to talk through whether a 1:1 setup is genuinely the right fit during your free Meet & Greet, rather than upselling you into a service your dog will not benefit from.',
|
||||
'If your dog needs space, structure, and a walker who can focus fully on them, our one-on-one walks are designed for exactly that.',
|
||||
'Walk length is matched to your dog: 30 minutes for lower-energy or older dogs, 45 minutes for most routines, and 60 minutes for dogs who genuinely benefit from a longer outing. Door-to-door times include pickup and drop-off, and updates with photos arrive after every walk.',
|
||||
'We run 1:1 walks across Auckland Central — including Mt Eden, Ponsonby, Kingsland, Grey Lynn, Herne Bay, and surrounding suburbs — with free pickup and drop-off included.'
|
||||
],
|
||||
imageUrl: '/images/auckland-large-dog-one-on-one-walk.jpg',
|
||||
imageUrl: '/images/goodwalk-large-breed-dog-one-on-one-walk-auckland.webp',
|
||||
imageAlt: 'Large breed dog enjoying a Goodwalk one on one dog walk',
|
||||
chips: [
|
||||
{ icon: 'fas fa-dog', label: 'One-on-one walk' },
|
||||
@@ -23,7 +25,7 @@ export const dogWalkingContent: ServicePageContent = {
|
||||
highlight: {
|
||||
eyebrow: 'One dog. Full attention.',
|
||||
title: 'Built for dogs who need a more individual kind of walk',
|
||||
imageUrl: '/images/auckland-dogs-outdoor-pack.jpg',
|
||||
imageUrl: '/images/goodwalk-dogs-outdoor-pack-auckland.webp',
|
||||
imageAlt: 'Goodwalk dogs gathered together outdoors',
|
||||
points: [
|
||||
{
|
||||
@@ -44,15 +46,15 @@ export const dogWalkingContent: ServicePageContent = {
|
||||
],
|
||||
collageImages: [
|
||||
{
|
||||
imageUrl: '/images/one-on-one-dog-portrait-1.jpg',
|
||||
imageUrl: '/images/goodwalk-happy-black-dog-one-on-one-walk-auckland.webp',
|
||||
imageAlt: 'Happy black dog on a one-on-one Goodwalk walk in Auckland'
|
||||
},
|
||||
{
|
||||
imageUrl: '/images/one-on-one-dog-portrait-2.jpg',
|
||||
imageUrl: '/images/goodwalk-large-breed-dog-one-on-one-walk-auckland.webp',
|
||||
imageAlt: 'Older black dog enjoying a calm one-on-one Goodwalk walk in Auckland'
|
||||
},
|
||||
{
|
||||
imageUrl: '/images/one-on-one-dog-portrait-3.jpg',
|
||||
imageUrl: '/images/goodwalk-brown-curly-dog-one-on-one-walk-auckland.webp',
|
||||
imageAlt: 'Brown curly dog resting during a one-on-one Goodwalk walk in Auckland'
|
||||
}
|
||||
]
|
||||
@@ -119,6 +121,47 @@ export const dogWalkingContent: ServicePageContent = {
|
||||
}
|
||||
]
|
||||
},
|
||||
faq: {
|
||||
title: '1:1 Dog Walk FAQs',
|
||||
intro: 'The questions Auckland owners ask most before booking a one-on-one walker.',
|
||||
items: [
|
||||
{
|
||||
question: 'Which dogs is a 1:1 walk best for?',
|
||||
answer:
|
||||
'1:1 walks suit dogs who are nervous around other dogs, older, recovering from injury, reactive, or simply do better with full undivided attention. They are also the right starting point for dogs we don’t recommend for a pack walk after their assessment.'
|
||||
},
|
||||
{
|
||||
question: 'How long does a 1:1 walk last?',
|
||||
answer:
|
||||
'Walks are tailored to your dog: 30 minutes for lower-energy or senior dogs, 45 minutes for most routines, and 60 minutes for dogs who genuinely benefit from a longer outing. Door-to-door times include pickup and drop-off.'
|
||||
},
|
||||
{
|
||||
question: 'Is pickup and drop-off included?',
|
||||
answer:
|
||||
'Yes — free door-to-door pickup and drop-off is included across Auckland Central (Mt Eden, Ponsonby, Kingsland, Grey Lynn, Herne Bay and surrounding suburbs). Your dog stays in their own routine end-to-end.'
|
||||
},
|
||||
{
|
||||
question: 'Will my dog get the same walker every time?',
|
||||
answer:
|
||||
'Yes. Building a relationship with one trusted handler is the whole point of 1:1 walks. Your dog sees the same familiar face at the door every visit.'
|
||||
},
|
||||
{
|
||||
question: 'Will you send updates after the walk?',
|
||||
answer:
|
||||
'You’ll get photos and a short update after every walk so you can see exactly how your dog is doing — useful especially during the workday when you can’t check in yourself.'
|
||||
},
|
||||
{
|
||||
question: 'How do you handle dogs that are reactive or anxious?',
|
||||
answer:
|
||||
'We start with a free Meet & Greet at home and adjust pace, route, and handling to suit your dog. We use calm, positive reinforcement and avoid forcing interactions. If we don’t think we can give your dog a good walk, we’ll be straight with you.'
|
||||
},
|
||||
{
|
||||
question: 'Can I book a 1:1 walk as a casual one-off?',
|
||||
answer:
|
||||
'Casual 1:1 walks are available at a higher rate, but most owners get the best fit (and best price) from a regular weekly routine, which lets your dog settle into a predictable rhythm.'
|
||||
}
|
||||
]
|
||||
},
|
||||
testimonialsHeading: 'What our clients say',
|
||||
booking: {
|
||||
title: 'See if 1:1 walks are the right fit',
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { HomePageContent } from '$lib/types';
|
||||
|
||||
export const homepageContent: HomePageContent = {
|
||||
seo: {
|
||||
title: 'Home | Auckland Dog Walking | Goodwalk',
|
||||
title: 'Auckland Dog Walking | Pack Walks & Puppy Visits | Goodwalk',
|
||||
description:
|
||||
'At Goodwalk, we offer Tiny Gang pack walks and one on one dog walking services throughout Auckland. Give your dog his best life with Goodwalk!'
|
||||
},
|
||||
@@ -36,6 +36,7 @@ export const homepageContent: HomePageContent = {
|
||||
title: 'Come home to a',
|
||||
highlight: 'calm, happy dog',
|
||||
mobileTitle: 'Come home to a\ncalm, happy dog',
|
||||
seoHeading: 'Dog walking across Auckland Central',
|
||||
subtitle:
|
||||
'Reliable dog walking for busy Auckland owners who want happier dogs, calmer evenings, and a team they can trust.',
|
||||
primaryCta: { label: 'Book a Meet & Greet', href: '#newlead', variant: 'yellow' },
|
||||
@@ -44,8 +45,12 @@ export const homepageContent: HomePageContent = {
|
||||
href: '#how-it-works',
|
||||
variant: 'outline'
|
||||
},
|
||||
imageUrl: '/images/maya-mascot.png',
|
||||
imageAlt: 'Happy dog ready for a professional pack walk with Goodwalk Auckland dog walking service'
|
||||
imageUrl: '/images/smiling-happy-dog-maya.webp',
|
||||
imageAlt: 'Smiling happy dog Maya, mascot of Goodwalk Auckland dog walking service',
|
||||
imageWidth: 1536,
|
||||
imageHeight: 1024,
|
||||
imageWebpUrl: '/images/smiling-happy-dog-maya.webp',
|
||||
desktopImageWebpUrl: '/images/smiling-happy-dog-maya.webp'
|
||||
},
|
||||
intro: {
|
||||
text: 'Professional dog walking services across Auckland.',
|
||||
@@ -65,7 +70,7 @@ export const homepageContent: HomePageContent = {
|
||||
],
|
||||
emphasis: 'join the Tiny Gang?',
|
||||
cta: { label: 'Book a free Meet & Greet', href: '/contact-us', variant: 'green' },
|
||||
imageUrl: '/images/goodwalk-dog-walker-alessandra.png',
|
||||
imageUrl: '/images/alessandra-goodwalk-dog-walker-auckland.webp',
|
||||
imageAlt: 'Alessandra from Goodwalk with a dog in Auckland'
|
||||
},
|
||||
services: [
|
||||
@@ -168,29 +173,82 @@ export const homepageContent: HomePageContent = {
|
||||
quote:
|
||||
'Love Aless! She is so amazing with my slightly hyper and anxious dog. She is great with communication if anything on either of our ends need to change. Archie love his walks, and I love the photos she posts of him.',
|
||||
reviewer: 'Kate',
|
||||
detail: "Archie's mum",
|
||||
imageUrl: '/images/archie-auckland-dog-walking-review.jpg'
|
||||
detail: "Archie's Owner",
|
||||
type: 'Google',
|
||||
service: '1:1 Walk',
|
||||
showInSlider: true,
|
||||
imageUrl: '/images/archie-goodwalk-dog-walking-review-auckland.webp'
|
||||
},
|
||||
{
|
||||
quote:
|
||||
'GoodWalk was the best dog walking service for my little pooch ! Aless was very helpful - basically doubled as a second mum to Monty. She always provided feedback on his outings and assisted where possible with any additional training that she felt he could work on and made recommendations where necessary which i feel is what every dog mum wants and needs!',
|
||||
reviewer: 'Estelle',
|
||||
detail: "Monty's mum",
|
||||
imageUrl: '/images/monty-auckland-dog-walking-review.jpg'
|
||||
detail: "Monty's Owner",
|
||||
type: 'Google',
|
||||
service: '1:1 Walk',
|
||||
showInSlider: true,
|
||||
imageUrl: '/images/monty-goodwalk-dog-walking-review-auckland.webp'
|
||||
},
|
||||
{
|
||||
quote:
|
||||
'Truly the best dog walker in Auckland! I feel so lucky to have found Aless and my little terrier Otis absolutely adores her. He enjoys his regular weekly walks and always comes back happy & tired. Love the updates on social media so I can see how my dog is enjoying his day! Aless makes logistics so easy too. Highly highly recommend, there’s a reason she has 5 stars!',
|
||||
reviewer: 'Ross',
|
||||
detail: "Otis's dad",
|
||||
imageUrl: '/images/otis-auckland-dog-walking-review.jpg'
|
||||
detail: "Otis's Owner",
|
||||
type: 'Google',
|
||||
service: '1:1 Walk',
|
||||
showInSlider: true,
|
||||
imageUrl: '/images/otis-goodwalk-dog-walking-review-auckland.webp'
|
||||
},
|
||||
{
|
||||
quote:
|
||||
'Alessandra has been walking and spending time with my pup since she was 10 weeks old, coming over and doing puppy visits through to transitioning her to pack walks with her little doggo friends. I know Alassandra loves and cares for my dog as much as I do and my dog has a great time! Cant recommend enough',
|
||||
reviewer: 'Nina',
|
||||
detail: "Wallace's mum",
|
||||
imageUrl: '/images/wallace-auckland-dog-walking-review.jpg'
|
||||
detail: "Wallace's Owner",
|
||||
type: 'Google',
|
||||
service: 'Pack Walk',
|
||||
showInSlider: true,
|
||||
imageUrl: '/images/wallace-goodwalk-dog-walking-review-auckland.webp'
|
||||
},
|
||||
{
|
||||
quote:
|
||||
'Aless is awesome with our dog Floyd! He’s always super happy and worn out after his walks with her. She keeps us in the loop and clearly cares a lot about what she does. It’s such a relief knowing Floyd’s out having fun and in good hands. Totally recommend Goodwalk!',
|
||||
reviewer: 'Kylie',
|
||||
detail: "Floyd's Owner",
|
||||
type: 'Google',
|
||||
service: 'Pack walk'
|
||||
},
|
||||
{
|
||||
quote:
|
||||
'Aless has provided amazing care to my Freddie. He is prone to grass seeds in his paws which has resulted in two previous surgeries before he moved to Goodwalk. Aless is very careful to keep him off long grass in the grass seed season and we have had no further problems. Freddie loves his pack walks and I know he is in safe and expert hands!',
|
||||
reviewer: "Monique",
|
||||
detail: "Freddie's Owner",
|
||||
type: 'Google',
|
||||
service: 'Pack walk',
|
||||
imageUrl: '/images/freddy-goodwalk-pack-walk-review-auckland.webp'
|
||||
},
|
||||
{
|
||||
quote:
|
||||
"I'm so happy we found Aless. She has an amazing way with dogs. From the first time our dog met her, I knew that he would be in good hands. Our dog hadn't been enjoying dog daycare and he seems much happier now getting to go on fun adventures with Aless and the Tiny Gang over the last year. Aless posts videos and photos after each outing (which we eagerly await!) and all the dogs are so happy playing or doing their own thing sniffing around.",
|
||||
reviewer: "Rachel",
|
||||
detail: "Louis's Owner",
|
||||
type: 'Google',
|
||||
service: 'Pack walk'
|
||||
},
|
||||
{
|
||||
quote:
|
||||
'Aless is amazing with our boy Ted. He is confident and happy in her company and always comes home sated. To be quite honest Ted is better behaved since having his walks with Aless.',
|
||||
reviewer: 'Claire',
|
||||
detail: "Ted's Owner",
|
||||
type: 'Google',
|
||||
service: 'Pack walk'
|
||||
},
|
||||
{
|
||||
quote:
|
||||
'I highly recommend Good Walk to anyone looking for safe, stimulating dog walking services. My dog adores Aless and her dog friends, she is incredibly excited to go and then happy, contented and tired out on return. Aless provides an excellent service, she is friendly, knowledgeable and the communication is fantastic.',
|
||||
reviewer: 'Anna',
|
||||
detail: "Honey's Owner",
|
||||
type: 'Google',
|
||||
service: 'Pack walk'
|
||||
}
|
||||
],
|
||||
booking: {
|
||||
@@ -268,6 +326,7 @@ export const homepageContent: HomePageContent = {
|
||||
{ label: '1:1 Walks', href: '/dog-walking' },
|
||||
{ label: 'Puppy Visits', href: '/puppy-visits' },
|
||||
{ label: 'Our Pricing', href: '/our-pricing' },
|
||||
{ label: 'Testimonials', href: '/testimonials' },
|
||||
{ label: 'About Us', href: '/about' },
|
||||
{ label: 'Contact Us', href: '/contact-us' }
|
||||
],
|
||||
|
||||
@@ -11,12 +11,28 @@ const puppyVisitsService = sharedServices.find((service) => service.title === 'P
|
||||
export const ourPricingContent: PricingPageContent = {
|
||||
title: 'Our Pricing',
|
||||
subtitle: 'Choose the Goodwalk routine that fits your dog, your week, and the kind of support you need.',
|
||||
comparison: {
|
||||
title: 'Which service is right for my dog?',
|
||||
intro:
|
||||
'Three services, designed for three different stages and temperaments. Here is how to think about which one fits your dog best.',
|
||||
paragraphs: [
|
||||
'Tiny Gang Pack Walks are our specialty and the best value option for sociable small and medium dogs who do well in company. Most owners use them as a regular weekday routine — two to four walks a week, with the per-walk price dropping as frequency goes up. If your dog enjoys other dogs, has the temperament for shared outings, and would benefit from a consistent weekly rhythm, this is almost always the right starting point.',
|
||||
'1:1 Walks are the right fit when a group setting is not. Reactive dogs, senior dogs, dogs recovering from injury, anxious rescues still building confidence, larger dogs whose play style does not suit a pack, and dogs who simply prefer a quieter walk all do better one-on-one. The cost per walk is higher than Tiny Gang because the walk is fully tailored — pace, route, and duration shaped around your dog rather than a group schedule.',
|
||||
'Puppy Visits are for puppies under roughly 12 to 18 months who are not yet ready for full walks. Vets generally advise against long pavement walks while growth plates are still developing, so visits focus on toilet breaks, feeding, gentle play, and calm company at home. Many of our Tiny Gang dogs started here as puppies — visits build the early familiarity that makes the transition to pack walks much smoother later on.',
|
||||
'Still not sure? The free Meet & Greet is the best way to work it out. Aless will meet your dog at home, talk through routine and temperament, and give you a straight recommendation — including telling you when none of our services are the right fit.'
|
||||
]
|
||||
},
|
||||
sections: [
|
||||
{
|
||||
title: 'Tiny Gang Pack Walks',
|
||||
icon: packWalksService?.icon ?? 'fas fa-paw',
|
||||
blurb:
|
||||
'Our specialty for sociable small and medium dogs who thrive with calm structure, regular weekly outings, and the right dog company.',
|
||||
eyebrow: 'Good Walk signature',
|
||||
lede: 'For sociable small and medium dogs who do well in company.',
|
||||
meta: [
|
||||
{ label: 'Best for', value: 'Dogs who enjoy other dogs' },
|
||||
{ label: 'Typical routine', value: '2 to 4 walks a week' },
|
||||
{ label: 'Pricing note', value: 'Per-walk price drops as frequency goes up' }
|
||||
],
|
||||
detailCta: {
|
||||
label: 'View Tiny Gang Pack Walks',
|
||||
href: '/pack-walks',
|
||||
@@ -27,8 +43,13 @@ export const ourPricingContent: PricingPageContent = {
|
||||
{
|
||||
title: '1:1 Walks',
|
||||
icon: oneToOneService?.icon ?? 'fas fa-person-walking',
|
||||
blurb:
|
||||
'A more individual option for dogs who need extra attention, more space, or a walk shaped around their own pace and confidence.',
|
||||
eyebrow: 'Tailored support',
|
||||
lede: 'When a group setting is not the right fit.',
|
||||
meta: [
|
||||
{ label: 'Best for', value: 'Reactive, senior, recovering, anxious, or larger dogs' },
|
||||
{ label: 'How it differs', value: 'Pace, route, and duration shaped around your dog' },
|
||||
{ label: 'Pricing note', value: 'Higher per-walk than Tiny Gang because the walk is fully individual' }
|
||||
],
|
||||
detailCta: {
|
||||
label: 'View 1:1 Walks',
|
||||
href: '/dog-walking',
|
||||
@@ -39,8 +60,13 @@ export const ourPricingContent: PricingPageContent = {
|
||||
{
|
||||
title: 'Puppy Visits',
|
||||
icon: puppyVisitsService?.icon ?? 'fas fa-dog',
|
||||
blurb:
|
||||
'Home visits for young puppies who need company, toilet breaks, routine support, and a calmer start before they are ready for bigger adventures.',
|
||||
eyebrow: 'Building blocks for the Tiny Gang',
|
||||
lede: 'For puppies under 12 to 18 months, before they are ready for full walks.',
|
||||
meta: [
|
||||
{ label: 'Includes', value: 'Toilet breaks, feeding, gentle play, calm company at home' },
|
||||
{ label: 'Why visits, not walks', value: 'Vets advise against long pavement walks while growth plates develop' },
|
||||
{ label: 'Sets up', value: 'A smoother move to Tiny Gang later' }
|
||||
],
|
||||
detailCta: {
|
||||
label: 'View Puppy Visits',
|
||||
href: '/puppy-visits',
|
||||
|
||||
@@ -8,10 +8,12 @@ export const packWalksContent: ServicePageContent = {
|
||||
paragraphs: [
|
||||
'Goodwalk Tiny Gang Pack Walks are built for Auckland Central owners of small and medium dogs who want a reliable weekly routine, a well-exercised dog, and more peace of mind during the workday.',
|
||||
'Our Tiny Gang packs stay small, calm, and carefully matched, so sociable dogs can build confidence, enjoy safe group outings, and come home settled instead of overstimulated.',
|
||||
'Every dog joining the Tiny Gang starts with a free Meet & Greet at home, followed by a minimum of two assessment walks. This gives us time to learn how your dog handles new dogs, new environments, and being collected from your home, and gives your dog the chance to settle in without pressure. Only once we are confident the fit is right does your dog move into a regular weekly slot.',
|
||||
'Tiny Gang is best suited to sociable small and medium dogs who enjoy being around other dogs. If your dog would be better with a quieter, more individual setup, our 1:1 walks may be a better fit.',
|
||||
'A typical Tiny Gang outing runs about 60 to 75 minutes on the ground, with door-to-door times that include pickup, the walk itself, and drop-off. Walks happen across a rotation of central Auckland parks — Western Springs, Fowlds Park, Cornwall Park, Grey Lynn Park, the Oakley Creek walkway — chosen for the day based on weather, group make-up, and what each dog handles best.',
|
||||
'We run pack walks across Auckland Central — including Mt Eden, Kingsland, Ponsonby, Grey Lynn, Sandringham, Mt Albert, and surrounding suburbs — with free pickup and drop-off included in every booking.'
|
||||
],
|
||||
imageUrl: '/images/auckland-pack-walk-small-dogs-group.jpg',
|
||||
imageUrl: '/images/goodwalk-tiny-gang-pack-walk-small-dogs-auckland.webp',
|
||||
imageAlt: "Small dogs from Goodwalk's Tiny Gang pack walk sitting together in an Auckland park",
|
||||
chips: [
|
||||
{ icon: 'fas fa-users', label: 'Small groups · 4–8 dogs' },
|
||||
@@ -23,7 +25,7 @@ export const packWalksContent: ServicePageContent = {
|
||||
highlight: {
|
||||
eyebrow: 'What Tiny Gang is',
|
||||
title: 'A small-group walking routine for sociable dogs who love the right company',
|
||||
imageUrl: '/images/small-medium-dogs-pack-walk.jpg',
|
||||
imageUrl: '/images/goodwalk-small-medium-dogs-pack-walk-auckland.webp',
|
||||
imageAlt: 'Small and medium dogs together on a Goodwalk pack walk in Auckland',
|
||||
points: [
|
||||
{
|
||||
@@ -124,6 +126,47 @@ export const packWalksContent: ServicePageContent = {
|
||||
}
|
||||
]
|
||||
},
|
||||
faq: {
|
||||
title: 'Tiny Gang Pack Walk FAQs',
|
||||
intro: 'The questions Auckland owners ask most before joining the Tiny Gang.',
|
||||
items: [
|
||||
{
|
||||
question: 'How big are the pack walks?',
|
||||
answer:
|
||||
'Tiny Gang outings run with 4-8 dogs maximum, carefully matched on size, energy, and play style. We never run oversized packs — the small group size is the whole point.'
|
||||
},
|
||||
{
|
||||
question: 'What size and type of dog suits a Tiny Gang walk?',
|
||||
answer:
|
||||
'Tiny Gang is built for sociable small and medium dogs who genuinely enjoy other dogs. If your dog is reactive, anxious in groups, or much larger than the typical pack, a 1:1 walk is usually a better fit and we will tell you so honestly at the Meet & Greet.'
|
||||
},
|
||||
{
|
||||
question: 'How long is a Tiny Gang walk and where do you go?',
|
||||
answer:
|
||||
'Each outing is roughly 60-75 minutes on the ground, plus pickup and drop-off either side. We rotate across central Auckland parks like Western Springs, Fowlds Park, Cornwall Park, Grey Lynn Park, and the Oakley Creek walkway, choosing the spot based on weather and the dogs in that day’s group.'
|
||||
},
|
||||
{
|
||||
question: 'Is pickup and drop-off included?',
|
||||
answer:
|
||||
'Yes — free door-to-door pickup and drop-off is included across Auckland Central suburbs (Mt Eden, Kingsland, Ponsonby, Grey Lynn, Sandringham, Mt Albert and surrounds). You don’t need to drive your dog anywhere.'
|
||||
},
|
||||
{
|
||||
question: 'How does my dog join the Tiny Gang?',
|
||||
answer:
|
||||
'Every new dog starts with a free Meet & Greet at your home, then two assessment walks before joining a regular slot. This lets us see how your dog handles new dogs and being collected, and gives them time to settle in without pressure.'
|
||||
},
|
||||
{
|
||||
question: 'Can I book casual or one-off pack walks?',
|
||||
answer:
|
||||
'Casual pack walks are available at a higher rate ($65/walk) and only for dogs already known to fit our packs. The best value, and what suits dogs most, is a regular weekly routine.'
|
||||
},
|
||||
{
|
||||
question: 'What happens if it rains on a pack walk day?',
|
||||
answer:
|
||||
'We walk in most weather. If conditions are genuinely unsafe for dogs (extreme heat, thunderstorms, flooding), we contact you to reschedule or substitute with a shorter, calmer outing.'
|
||||
}
|
||||
]
|
||||
},
|
||||
testimonialsHeading: 'What our clients say',
|
||||
booking: {
|
||||
title: 'See if your dog fits our Tiny Gang',
|
||||
|
||||
@@ -8,10 +8,12 @@ export const puppyVisitsContent: ServicePageContent = {
|
||||
paragraphs: [
|
||||
'Goodwalk Puppy Visits are designed for busy owners who want their puppy cared for properly during the day, with toilet breaks, play, feeding, and calm one-on-one attention at home.',
|
||||
'They are also the first stage of the Goodwalk journey. For puppies who may later join our Tiny Gang Pack Walks, these visits help build familiarity, confidence, and the early routines that make that transition much smoother.',
|
||||
'A typical visit includes a toilet break in the garden or on a short on-lead walk, fresh water and a feed if scheduled, gentle play and enrichment to use up some puppy energy in the right way, and calm settling time before we leave so your puppy is more likely to rest after we go. You receive a short update with photos after each visit so you know how your puppy got on.',
|
||||
'Puppy Visits are the better choice over a full walk while your puppy is still growing. Vets generally recommend keeping structured exercise short and avoiding pavement-heavy walks until growth plates have matured — typically 12 to 18 months depending on breed. Our visits give your puppy company, mental stimulation, and toilet support without the risk of overworking developing joints.',
|
||||
'Instead of just getting through the day, your puppy gets a more thoughtful start, and you get more peace of mind while you are away.',
|
||||
'We offer puppy visits across Auckland Central including Mt Eden, Ponsonby, Grey Lynn, Kingsland, Sandringham, Herne Bay, and surrounding suburbs.'
|
||||
],
|
||||
imageUrl: '/images/auckland-puppy-home-visit.jpg',
|
||||
imageUrl: '/images/goodwalk-puppy-home-visit-auckland.webp',
|
||||
imageAlt: 'Puppy receiving a calm Goodwalk home visit in Auckland',
|
||||
chips: [
|
||||
{ icon: 'fas fa-house', label: 'In-home visit' },
|
||||
@@ -23,7 +25,7 @@ export const puppyVisitsContent: ServicePageContent = {
|
||||
highlight: {
|
||||
eyebrow: 'Start well. Grow well.',
|
||||
title: 'A home visit now can help set your puppy up for calmer routines and future Tiny Gang Pack Walks later on',
|
||||
imageUrl: '/images/auckland-puppy-visits-cavalier-king-charles-spaniel.jpg',
|
||||
imageUrl: '/images/goodwalk-puppy-visit-cavalier-king-charles-spaniel-auckland.webp',
|
||||
imageAlt: 'Young Cavalier King Charles Spaniel puppy resting at home before future Goodwalk Pack Walk training in Auckland',
|
||||
points: [
|
||||
{
|
||||
@@ -104,6 +106,47 @@ export const puppyVisitsContent: ServicePageContent = {
|
||||
}
|
||||
]
|
||||
},
|
||||
faq: {
|
||||
title: 'Puppy Visit FAQs',
|
||||
intro: 'The questions Auckland puppy owners ask most before booking a home visit.',
|
||||
items: [
|
||||
{
|
||||
question: 'How young can my puppy start with a visit?',
|
||||
answer:
|
||||
'We routinely visit puppies from around 8-10 weeks. Early visits focus on calm handling, gentle company, and easy routine support — not formal training — so they work even before your puppy is fully vaccinated.'
|
||||
},
|
||||
{
|
||||
question: 'What actually happens during a Puppy Visit?',
|
||||
answer:
|
||||
'A visit typically includes a toilet break, fresh water, calm play or settle time, gentle handling, and any feeding or routine support you’ve asked for. We’ll send a short update with photos after every visit.'
|
||||
},
|
||||
{
|
||||
question: 'How long is each visit?',
|
||||
answer:
|
||||
'Standard visits run around 30 minutes, which is the sweet spot for most young puppies. Longer visits are available if your puppy needs more time, but we keep the session-length appropriate to their age and stamina.'
|
||||
},
|
||||
{
|
||||
question: 'Do you take my puppy outside the house?',
|
||||
answer:
|
||||
'Most visits are entirely in-home until your puppy is fully vaccinated. After that, short, calm outings can be added. We never take young puppies on group walks — that comes much later, if and when they’re ready.'
|
||||
},
|
||||
{
|
||||
question: 'Will I get the same person each visit?',
|
||||
answer:
|
||||
'Yes — consistency matters most for puppies. You’ll have the same trusted handler so your puppy learns to feel safe with a familiar person showing up at the door.'
|
||||
},
|
||||
{
|
||||
question: 'Can Puppy Visits lead into Tiny Gang Pack Walks later?',
|
||||
answer:
|
||||
'That’s exactly what they’re designed for. By the time your puppy is old enough and the right temperament fit, we already know them well — so the transition into 1:1 walks or Tiny Gang Pack Walks is much smoother.'
|
||||
},
|
||||
{
|
||||
question: 'Do I need to be home for the visit?',
|
||||
answer:
|
||||
'No — most visits happen while owners are at work. We can sort key access, lockboxes, or smart-lock entry at your Meet & Greet so the visit happens reliably whether you’re home or not.'
|
||||
}
|
||||
]
|
||||
},
|
||||
testimonialsHeading: 'What our clients say',
|
||||
booking: {
|
||||
title: 'See if Puppy Visits are the right start',
|
||||
|
||||
@@ -11,7 +11,7 @@ const modules: Record<string, { default: Picture }> = import.meta.env.DEV
|
||||
|
||||
export function getEnhancedImage(src: string | undefined | null): Picture | null {
|
||||
if (!src) return null;
|
||||
// '/images/foo.png' -> './images/foo.png' (relative to src/lib/)
|
||||
// '/images/foo.webp' -> './images/foo.webp' (relative to src/lib/)
|
||||
const key = '.' + src;
|
||||
return modules[key]?.default ?? null;
|
||||
}
|
||||
|
||||
@@ -4,28 +4,29 @@ export interface ImageMetadata {
|
||||
}
|
||||
|
||||
const imageMetadata: Record<string, ImageMetadata> = {
|
||||
'/images/goodwalk-auckland-dog-walking-logo.png': { width: 241, height: 48 },
|
||||
'/images/goodwalk-auckland-dog-walking-logo-mobile.png': { width: 206, height: 41 },
|
||||
'/images/auckland-dog-walking-happy-dog-hero.png': { width: 500, height: 500 },
|
||||
'/images/auckland-dog-walking-happy-dogs-happy-humans.webp': { width: 1222, height: 1312 },
|
||||
'/images/archie-auckland-dog-walking-review.jpg': { width: 1122, height: 1402 },
|
||||
'/images/monty-auckland-dog-walking-review.jpg': { width: 1254, height: 1254 },
|
||||
'/images/otis-auckland-dog-walking-review.jpg': { width: 1254, height: 1254 },
|
||||
'/images/wallace-auckland-dog-walking-review.jpg': { width: 1254, height: 1254 },
|
||||
'/images/auckland-small-dog-pack-walk.jpg': { width: 640, height: 480 },
|
||||
'/images/auckland-pack-walk-small-dogs-group.jpg': { width: 1469, height: 1071 },
|
||||
'/images/small-medium-dogs-pack-walk.jpg': { width: 1240, height: 1269 },
|
||||
'/images/one-on-one-dog-portrait-1.jpg': { width: 1054, height: 1492 },
|
||||
'/images/one-on-one-dog-portrait-2.jpg': { width: 1091, height: 1441 },
|
||||
'/images/one-on-one-dog-portrait-3.jpg': { width: 1124, height: 1399 },
|
||||
'/images/tiny-gang-auckland-dog-pack.jpg': { width: 1024, height: 297 },
|
||||
'/images/auckland-large-dog-one-on-one-walk.jpg': { width: 1024, height: 970 },
|
||||
'/images/auckland-dogs-outdoor-pack.jpg': { width: 1024, height: 297 },
|
||||
'/images/auckland-puppy-home-visit.jpg': { width: 640, height: 427 },
|
||||
'/images/auckland-puppy-visits-cavalier-king-charles-spaniel.jpg': { width: 3327, height: 2217 },
|
||||
'/images/auckland-pack-walk-dog.jpg': { width: 480, height: 640 },
|
||||
'/images/auckland-dog-group-outing.jpg': { width: 640, height: 480 },
|
||||
'/images/founder-image-aless-goodwalk.jpg': { width: 1076, height: 1461 }
|
||||
'/images/goodwalk-auckland-dog-walking-logo.webp': { width: 241, height: 48 },
|
||||
'/images/goodwalk-auckland-dog-walking-logo-mobile.webp': { width: 206, height: 41 },
|
||||
'/images/goodwalk-auckland-happy-dog-hero.webp': { width: 500, height: 500 },
|
||||
'/images/smiling-happy-dog-maya.webp': { width: 1536, height: 1024 },
|
||||
'/images/goodwalk-auckland-happy-dogs-happy-humans.webp': { width: 1222, height: 1312 },
|
||||
'/images/archie-goodwalk-dog-walking-review-auckland.webp': { width: 1122, height: 1402 },
|
||||
'/images/monty-goodwalk-dog-walking-review-auckland.webp': { width: 1254, height: 1254 },
|
||||
'/images/otis-goodwalk-dog-walking-review-auckland.webp': { width: 1254, height: 1254 },
|
||||
'/images/wallace-goodwalk-dog-walking-review-auckland.webp': { width: 1254, height: 1254 },
|
||||
'/images/goodwalk-small-dog-pack-walk-auckland.webp': { width: 640, height: 480 },
|
||||
'/images/goodwalk-tiny-gang-pack-walk-small-dogs-auckland.webp': { width: 1469, height: 1071 },
|
||||
'/images/goodwalk-small-medium-dogs-pack-walk-auckland.webp': { width: 1240, height: 1269 },
|
||||
'/images/goodwalk-happy-black-dog-one-on-one-walk-auckland.webp': { width: 1054, height: 1492 },
|
||||
'/images/goodwalk-large-breed-dog-one-on-one-walk-auckland.webp': { width: 1091, height: 1441 },
|
||||
'/images/goodwalk-brown-curly-dog-one-on-one-walk-auckland.webp': { width: 1124, height: 1399 },
|
||||
'/images/goodwalk-tiny-gang-dog-pack-auckland.webp': { width: 1024, height: 297 },
|
||||
'/images/goodwalk-large-dog-one-on-one-walk-auckland.webp': { width: 1024, height: 970 },
|
||||
'/images/goodwalk-dogs-outdoor-pack-auckland.webp': { width: 1024, height: 297 },
|
||||
'/images/goodwalk-puppy-home-visit-auckland.webp': { width: 640, height: 427 },
|
||||
'/images/goodwalk-puppy-visit-cavalier-king-charles-spaniel-auckland.webp': { width: 3327, height: 2217 },
|
||||
'/images/goodwalk-pack-walk-dog-auckland.webp': { width: 480, height: 640 },
|
||||
'/images/goodwalk-dog-group-outing-auckland.webp': { width: 640, height: 480 },
|
||||
'/images/alessandra-goodwalk-founder-auckland.webp': { width: 1076, height: 1461 }
|
||||
};
|
||||
|
||||
export function getImageMetadata(src: string | undefined | null): ImageMetadata | null {
|
||||
|
||||
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 173 KiB |
|
After Width: | Height: | Size: 171 KiB |
|
After Width: | Height: | Size: 243 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 564 KiB After Width: | Height: | Size: 564 KiB |
|
After Width: | Height: | Size: 296 KiB |
|
After Width: | Height: | Size: 229 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 248 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 259 KiB |
|
After Width: | Height: | Size: 351 KiB |
|
After Width: | Height: | Size: 169 KiB |
|
After Width: | Height: | Size: 149 KiB |
|
After Width: | Height: | Size: 247 KiB |
|
After Width: | Height: | Size: 264 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 177 KiB |
|
After Width: | Height: | Size: 308 KiB |
|
After Width: | Height: | Size: 174 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 260 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 235 KiB |
|
After Width: | Height: | Size: 386 KiB |
|
After Width: | Height: | Size: 327 KiB |
|
After Width: | Height: | Size: 277 KiB |
|
After Width: | Height: | Size: 236 KiB |
|
After Width: | Height: | Size: 398 KiB |
|
After Width: | Height: | Size: 365 KiB |
@@ -19,7 +19,7 @@ describe('seo helpers', () => {
|
||||
|
||||
expect(seo.title).toBe('Dog Walkers in Mt Eden | Goodwalk Auckland');
|
||||
expect(seo.canonicalPath).toBe('/locations/mt-eden');
|
||||
expect(seo.image).toBe('/images/auckland-pack-walk-small-dogs-group.jpg');
|
||||
expect(seo.image).toBe('/images/goodwalk-tiny-gang-pack-walk-small-dogs-auckland.webp');
|
||||
expect(seo.imageAlt).toBe('Goodwalk dog walkers in Mt Eden, Auckland');
|
||||
expect(seo.structuredData).toHaveLength(2);
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import { locationPages } from '$lib/content/locations';
|
||||
import type { LocationPageContent } from '$lib/types';
|
||||
|
||||
const siteUrl = 'https://www.goodwalk.co.nz';
|
||||
const defaultLocationImage = '/images/auckland-pack-walk-small-dogs-group.jpg';
|
||||
const defaultLocationImage = '/images/goodwalk-tiny-gang-pack-walk-small-dogs-auckland.webp';
|
||||
const defaultLocationImageAlt = 'Goodwalk Auckland dog walking services';
|
||||
|
||||
interface BreadcrumbItem {
|
||||
|
||||
@@ -88,10 +88,10 @@ textarea {
|
||||
/* use:reveal action — applied via Svelte action on section elements */
|
||||
.reveal-ready.reveal-block {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, var(--reveal-distance, 24px), 0);
|
||||
transform: translate3d(0, var(--reveal-distance, 16px), 0);
|
||||
transition:
|
||||
opacity 0.55s ease,
|
||||
transform 0.7s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
opacity 0.3s ease,
|
||||
transform 0.45s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
transition-delay: var(--reveal-delay, 0ms);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
padding: 13px 28px;
|
||||
white-space: nowrap;
|
||||
font-family: var(--font-head);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
@@ -35,7 +36,7 @@
|
||||
@media (hover: hover) {
|
||||
.btn:hover {
|
||||
transform: translateY(-2px) scale(1.012);
|
||||
box-shadow: 0 12px 24px rgba(17, 20, 24, 0.14);
|
||||
box-shadow: var(--shadow-float);
|
||||
}
|
||||
|
||||
.btn-with-arrow:hover .icon {
|
||||
@@ -45,47 +46,57 @@
|
||||
|
||||
.btn:active {
|
||||
transform: translateY(1px) scale(0.985);
|
||||
box-shadow: 0 4px 10px rgba(17, 20, 24, 0.12);
|
||||
box-shadow: var(--shadow-press);
|
||||
filter: saturate(1.03);
|
||||
}
|
||||
|
||||
.btn:focus-visible {
|
||||
outline: 2px solid var(--gw-green);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
.btn-green:focus-visible,
|
||||
.btn-outline:focus-visible {
|
||||
outline-color: var(--yellow);
|
||||
}
|
||||
|
||||
.btn-green {
|
||||
background: var(--gw-green);
|
||||
color: #fff;
|
||||
background: var(--surface-brand);
|
||||
color: var(--text-inverse);
|
||||
}
|
||||
|
||||
.btn-green:hover {
|
||||
background: #172217;
|
||||
background: var(--surface-brand-hover);
|
||||
}
|
||||
|
||||
.btn-yellow {
|
||||
background: var(--yellow);
|
||||
color: #000;
|
||||
background: var(--surface-accent);
|
||||
color: var(--gw-green);
|
||||
}
|
||||
|
||||
.btn-yellow:hover {
|
||||
background: #e6bb00;
|
||||
background: var(--surface-accent-strong);
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
color: #fff;
|
||||
border: 2px solid #fff;
|
||||
color: var(--text-inverse);
|
||||
border: 2px solid var(--text-inverse);
|
||||
}
|
||||
|
||||
.btn-outline:hover {
|
||||
background: #fff;
|
||||
color: var(--gw-green);
|
||||
background: var(--surface-page);
|
||||
color: var(--text-brand);
|
||||
}
|
||||
|
||||
.btn-outline-green {
|
||||
color: var(--gw-green);
|
||||
border-color: var(--gw-green);
|
||||
color: var(--text-brand);
|
||||
border-color: var(--text-brand);
|
||||
}
|
||||
|
||||
.btn-outline-green:hover {
|
||||
background: var(--gw-green);
|
||||
color: #fff;
|
||||
background: var(--surface-brand);
|
||||
color: var(--text-inverse);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@@ -98,4 +109,8 @@
|
||||
.btn-with-arrow {
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn-hide-arrow-mobile .icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
margin-bottom: 12px;
|
||||
padding: 6px 10px;
|
||||
border-radius: 999px;
|
||||
background: rgba(33, 48, 33, 0.08);
|
||||
box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.05);
|
||||
background: var(--surface-brand-muted);
|
||||
box-shadow: var(--shadow-inset-soft);
|
||||
}
|
||||
|
||||
/* Intentional exception: this is a conversion-focused hero title, not a
|
||||
@@ -31,7 +31,7 @@
|
||||
}
|
||||
|
||||
.booking-title-plain {
|
||||
color: #000;
|
||||
color: var(--text-strong);
|
||||
}
|
||||
|
||||
.booking-title-highlight {
|
||||
@@ -42,7 +42,7 @@
|
||||
.booking-intro {
|
||||
max-width: 720px;
|
||||
margin: -8px auto 0;
|
||||
color: #4c5056;
|
||||
color: var(--text-muted);
|
||||
font-size: 17px;
|
||||
line-height: 1.65;
|
||||
}
|
||||
@@ -59,14 +59,14 @@
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 40px;
|
||||
padding: 8px 14px;
|
||||
min-height: 44px;
|
||||
padding: 10px 16px;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #fbfaf6 100%);
|
||||
background: linear-gradient(180deg, var(--surface-page) 0%, var(--surface-panel-muted) 100%);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(17, 20, 24, 0.06),
|
||||
0 12px 26px rgba(17, 20, 24, 0.05);
|
||||
color: var(--gw-green);
|
||||
var(--shadow-inset-strong),
|
||||
var(--shadow-card);
|
||||
color: var(--text-brand);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
@@ -122,10 +122,10 @@
|
||||
margin-top: 24px;
|
||||
padding: 18px 22px;
|
||||
border-radius: 28px;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #f8f7f2 100%);
|
||||
background: linear-gradient(180deg, var(--surface-page) 0%, var(--surface-panel-soft) 100%);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(17, 20, 24, 0.05),
|
||||
0 14px 32px rgba(17, 20, 24, 0.05);
|
||||
var(--shadow-inset-soft),
|
||||
var(--shadow-panel-strong);
|
||||
}
|
||||
|
||||
.booking-step {
|
||||
@@ -134,7 +134,7 @@
|
||||
gap: 12px;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: #000;
|
||||
color: var(--text-strong);
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
transition: transform 0.14s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
@@ -145,7 +145,7 @@
|
||||
.booking-step-number {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border: 2px solid rgba(33, 48, 33, 0.18);
|
||||
border: 2px solid var(--border-brand-strong);
|
||||
border-radius: 50%;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -153,7 +153,7 @@
|
||||
font-family: var(--font-head);
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(180deg, #ffffff 0%, #f8f7f2 100%);
|
||||
background: linear-gradient(180deg, var(--surface-page) 0%, var(--surface-panel-soft) 100%);
|
||||
transition:
|
||||
background 0.2s,
|
||||
border-color 0.2s,
|
||||
@@ -162,15 +162,15 @@
|
||||
}
|
||||
|
||||
.booking-step.active .booking-step-number {
|
||||
background: rgba(33, 48, 33, 0.12);
|
||||
border-color: var(--gw-green);
|
||||
box-shadow: 0 10px 24px rgba(17, 20, 24, 0.08);
|
||||
background: var(--surface-brand-selected);
|
||||
border-color: var(--text-brand);
|
||||
box-shadow: 0 10px 24px rgba(var(--ink-rgb), 0.08);
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.booking-step:hover .booking-step-number {
|
||||
transform: translateY(-2px) scale(1.04);
|
||||
box-shadow: 0 10px 20px rgba(17, 20, 24, 0.1);
|
||||
box-shadow: 0 10px 20px rgba(var(--ink-rgb), 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@
|
||||
.booking-step-divider {
|
||||
width: 1px;
|
||||
height: 92px;
|
||||
background: #cfd2d6;
|
||||
background: var(--border-muted-strong);
|
||||
}
|
||||
|
||||
.booking-form {
|
||||
@@ -205,20 +205,20 @@
|
||||
|
||||
.booking-panel-banner {
|
||||
background:
|
||||
radial-gradient(circle at top right, rgba(255, 209, 0, 0.16), transparent 32%),
|
||||
linear-gradient(180deg, #ffffff 0%, #f5f1e8 100%);
|
||||
color: #34363a;
|
||||
radial-gradient(circle at top right, var(--surface-highlight-soft), transparent 32%),
|
||||
linear-gradient(180deg, var(--surface-page) 0%, var(--surface-panel-cream-strong) 100%);
|
||||
color: var(--text-heading-soft);
|
||||
border-radius: 28px 28px 0 0;
|
||||
padding: 20px 24px 22px;
|
||||
text-align: center;
|
||||
font-family: var(--font-body);
|
||||
font-size: 15px;
|
||||
line-height: 1.55;
|
||||
border: 1px solid rgba(17, 20, 24, 0.06);
|
||||
border: 1px solid var(--border-soft-strong);
|
||||
border-bottom: none;
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(255, 255, 255, 0.28),
|
||||
0 10px 24px rgba(17, 20, 24, 0.04);
|
||||
var(--shadow-inset-highlight),
|
||||
var(--shadow-panel);
|
||||
}
|
||||
|
||||
.booking-card-grid {
|
||||
@@ -243,12 +243,12 @@
|
||||
}
|
||||
|
||||
.booking-field-card {
|
||||
background: linear-gradient(180deg, #ffffff 0%, #fdfcf9 100%);
|
||||
background: linear-gradient(180deg, var(--surface-page) 0%, var(--surface-panel-warm) 100%);
|
||||
border-radius: 28px;
|
||||
padding: 28px 32px 26px;
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(17, 20, 24, 0.05),
|
||||
0 14px 32px rgba(17, 20, 24, 0.04);
|
||||
var(--shadow-inset-soft),
|
||||
0 14px 32px rgba(var(--ink-rgb), 0.04);
|
||||
}
|
||||
|
||||
.booking-field-card-group {
|
||||
@@ -296,32 +296,21 @@
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.02em;
|
||||
text-transform: uppercase;
|
||||
color: #4a4f55;
|
||||
color: var(--text-muted-strong);
|
||||
}
|
||||
|
||||
.booking-help-text {
|
||||
margin: 14px 0 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.booking-inline-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
color: #5f6369;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
color: var(--text-soft);
|
||||
}
|
||||
|
||||
.booking-inline-link {
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
color: var(--gw-green);
|
||||
color: var(--text-brand);
|
||||
font-family: var(--font-head);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
@@ -351,12 +340,12 @@
|
||||
min-height: 44px;
|
||||
padding: 10px 16px;
|
||||
border-radius: 18px;
|
||||
background: linear-gradient(180deg, rgba(33, 48, 33, 0.08), rgba(33, 48, 33, 0.05));
|
||||
color: var(--gw-green);
|
||||
background: linear-gradient(180deg, var(--surface-brand-muted), rgba(var(--brand-rgb), 0.05));
|
||||
color: var(--text-brand);
|
||||
font-family: var(--font-body);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.05);
|
||||
box-shadow: var(--shadow-inset-soft);
|
||||
}
|
||||
|
||||
.booking-required {
|
||||
@@ -366,14 +355,14 @@
|
||||
.booking-field-card input,
|
||||
.booking-field-card textarea {
|
||||
width: 100%;
|
||||
border: 2px solid rgba(33, 48, 33, 0.18);
|
||||
border: 2px solid var(--border-brand-strong);
|
||||
border-radius: 16px;
|
||||
background: #fffdfa;
|
||||
background: var(--surface-input);
|
||||
padding: 13px 18px;
|
||||
font-size: 16px;
|
||||
line-height: 1.2;
|
||||
color: #34363a;
|
||||
box-shadow: inset 0 1px 2px rgba(17, 20, 24, 0.03);
|
||||
color: var(--text-heading-soft);
|
||||
box-shadow: var(--shadow-input-inset);
|
||||
transition:
|
||||
background 0.2s,
|
||||
border-color 0.2s;
|
||||
@@ -381,15 +370,15 @@
|
||||
|
||||
.booking-field-card input:hover,
|
||||
.booking-field-card textarea:hover {
|
||||
border-color: rgba(33, 48, 33, 0.3);
|
||||
background: #fff;
|
||||
border-color: var(--border-brand-hover);
|
||||
background: var(--surface-page);
|
||||
}
|
||||
|
||||
.booking-field-card input:focus,
|
||||
.booking-field-card textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--gw-green);
|
||||
background: #f8f5ef;
|
||||
border-color: var(--text-brand);
|
||||
background: var(--surface-panel-warm-strong);
|
||||
}
|
||||
|
||||
.booking-field-card textarea {
|
||||
@@ -402,13 +391,12 @@
|
||||
grid-template-columns: 220px 1fr;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
background: #fff;
|
||||
background: var(--surface-page);
|
||||
border-radius: 28px;
|
||||
padding: 22px 28px;
|
||||
box-shadow: 0 10px 30px rgba(17, 20, 24, 0.04);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(17, 20, 24, 0.05),
|
||||
0 10px 30px rgba(17, 20, 24, 0.04);
|
||||
var(--shadow-inset-soft),
|
||||
var(--shadow-panel-soft);
|
||||
}
|
||||
|
||||
.booking-service-options {
|
||||
@@ -424,7 +412,7 @@
|
||||
font-family: var(--font-head);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #34363a;
|
||||
color: var(--text-heading-soft);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -437,7 +425,7 @@
|
||||
.booking-check-box {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border: 2px solid rgba(33, 48, 33, 0.28);
|
||||
border: 2px solid var(--border-brand-stronger);
|
||||
border-radius: 8px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -449,16 +437,16 @@
|
||||
}
|
||||
|
||||
.booking-check-option input:checked + .booking-check-box {
|
||||
background: rgba(33, 48, 33, 0.12);
|
||||
border-color: var(--gw-green);
|
||||
background: var(--surface-brand-selected);
|
||||
border-color: var(--text-brand);
|
||||
}
|
||||
|
||||
.booking-check-option input:checked + .booking-check-box::after {
|
||||
content: '';
|
||||
width: 10px;
|
||||
height: 16px;
|
||||
border-right: 3px solid var(--gw-green);
|
||||
border-bottom: 3px solid var(--gw-green);
|
||||
border-right: 3px solid var(--text-brand);
|
||||
border-bottom: 3px solid var(--text-brand);
|
||||
transform: rotate(40deg) translate(-1px, -2px);
|
||||
}
|
||||
|
||||
@@ -482,7 +470,7 @@
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
line-height: 1.45;
|
||||
color: #6a6d72;
|
||||
color: var(--text-softest);
|
||||
}
|
||||
|
||||
.booking-next-button,
|
||||
@@ -503,12 +491,12 @@
|
||||
|
||||
.booking-field-card input.input-invalid,
|
||||
.booking-field-card textarea.input-invalid {
|
||||
border-color: #c94040;
|
||||
border-color: var(--border-critical);
|
||||
}
|
||||
|
||||
.booking-field-card-invalid,
|
||||
.booking-field-stack-invalid {
|
||||
box-shadow: 0 10px 30px rgba(201, 64, 64, 0.08);
|
||||
box-shadow: var(--shadow-invalid);
|
||||
}
|
||||
|
||||
.field-error {
|
||||
@@ -516,7 +504,7 @@
|
||||
align-items: flex-start;
|
||||
gap: 7px;
|
||||
margin: 10px 0 0;
|
||||
color: #c94040;
|
||||
color: var(--text-critical);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
|
||||
@@ -3,35 +3,78 @@ header {
|
||||
z-index: 100;
|
||||
isolation: isolate;
|
||||
overflow: visible;
|
||||
background: linear-gradient(to bottom, #f7f7f5 0%, #ffffff 100%);
|
||||
box-shadow: 0 2px 16px rgba(17, 20, 24, 0.08);
|
||||
background: var(--surface-page-gradient);
|
||||
box-shadow: 0 2px 16px rgba(var(--ink-rgb), 0.08);
|
||||
}
|
||||
|
||||
.nav-ribbon {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
background: var(--yellow);
|
||||
overflow: hidden;
|
||||
background: linear-gradient(90deg, #f3c400 0%, #ffd84d 52%, #f1be00 100%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 10px 18px;
|
||||
padding: 13px 20px;
|
||||
box-shadow:
|
||||
inset 0 -1px 0 rgba(0, 0, 0, 0.06),
|
||||
inset 0 1px 0 var(--border-inverse-highlight);
|
||||
}
|
||||
|
||||
.nav-ribbon::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: -18%;
|
||||
width: 14%;
|
||||
background: linear-gradient(90deg, transparent 0%, var(--border-inverse-strong) 35%, rgba(var(--white-rgb), 0.45) 50%, var(--border-inverse-strong) 65%, transparent 100%);
|
||||
transform: skewX(-22deg);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.nav-ribbon-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
gap: 10px;
|
||||
font-family: var(--font-head);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.055em;
|
||||
text-transform: uppercase;
|
||||
color: var(--gw-green);
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
text-shadow: 0 1px 0 rgba(var(--white-rgb), 0.2);
|
||||
}
|
||||
|
||||
.nav-ribbon-item .icon {
|
||||
font-size: 13px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
@media (min-width: 769px) {
|
||||
.nav-ribbon::after {
|
||||
animation: nav-ribbon-shine 8s ease-in-out 2.8s infinite;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes nav-ribbon-shine {
|
||||
0%,
|
||||
55%,
|
||||
100% {
|
||||
left: -18%;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
61% {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
74% {
|
||||
left: 108%;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
@@ -129,12 +172,12 @@ nav {
|
||||
}
|
||||
|
||||
.nav-links > li > a:hover {
|
||||
background: rgba(33, 48, 33, 0.09);
|
||||
background: var(--surface-brand-hover-soft);
|
||||
color: var(--gw-green);
|
||||
}
|
||||
|
||||
.nav-links > li > a.nav-link-active {
|
||||
background: rgba(33, 48, 33, 0.11);
|
||||
background: rgba(var(--brand-rgb), 0.11);
|
||||
color: var(--gw-green);
|
||||
}
|
||||
|
||||
@@ -163,7 +206,7 @@ nav {
|
||||
}
|
||||
|
||||
.mega-menu-inner {
|
||||
background: #fff;
|
||||
background: var(--surface-panel);
|
||||
border-radius: 20px;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
@@ -171,7 +214,7 @@ nav {
|
||||
gap: 0;
|
||||
box-shadow:
|
||||
0 4px 6px rgba(0, 0, 0, 0.04),
|
||||
0 16px 40px rgba(0, 0, 0, 0.12);
|
||||
var(--shadow-xl);
|
||||
min-width: 400px;
|
||||
}
|
||||
|
||||
@@ -188,8 +231,8 @@ nav {
|
||||
margin-top: 12px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 12px;
|
||||
background: rgba(33, 48, 33, 0.04);
|
||||
color: var(--gw-green);
|
||||
background: rgba(var(--brand-rgb), 0.04);
|
||||
color: var(--text-brand);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
@@ -197,7 +240,7 @@ nav {
|
||||
}
|
||||
|
||||
.mega-menu-footer:hover {
|
||||
background: rgba(33, 48, 33, 0.09);
|
||||
background: var(--surface-brand-hover-soft);
|
||||
}
|
||||
|
||||
:global(.mega-menu-footer-arrow) {
|
||||
@@ -228,20 +271,20 @@ nav {
|
||||
}
|
||||
|
||||
.mega-service:hover {
|
||||
background: rgba(33, 48, 33, 0.05);
|
||||
background: rgba(var(--brand-rgb), 0.05);
|
||||
}
|
||||
|
||||
.mega-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background: var(--gw-green);
|
||||
background: var(--surface-brand);
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
color: #fff;
|
||||
box-shadow: 0 10px 22px rgba(33, 48, 33, 0.12);
|
||||
color: var(--yellow);
|
||||
box-shadow: var(--shadow-badge);
|
||||
transform: translateY(0) rotate(0deg) scale(1);
|
||||
transition:
|
||||
background 0.15s,
|
||||
@@ -250,7 +293,7 @@ nav {
|
||||
}
|
||||
|
||||
.mega-service:hover .mega-icon {
|
||||
background: #2d4230;
|
||||
background: var(--surface-brand-strong);
|
||||
}
|
||||
|
||||
@media (hover: hover) and (min-width: 769px) {
|
||||
@@ -272,12 +315,12 @@ nav {
|
||||
|
||||
.mega-service:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 12px 26px rgba(17, 20, 24, 0.08);
|
||||
box-shadow: var(--shadow-card-hover);
|
||||
}
|
||||
|
||||
.mega-service:hover .mega-icon {
|
||||
transform: translateY(-3px) rotate(-5deg) scale(1.04);
|
||||
box-shadow: 0 16px 28px rgba(33, 48, 33, 0.18);
|
||||
box-shadow: var(--shadow-badge-hover);
|
||||
}
|
||||
|
||||
.mega-service:nth-child(even):hover .mega-icon {
|
||||
@@ -300,7 +343,7 @@ nav {
|
||||
.mega-service-label {
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
color: #000;
|
||||
color: var(--text-strong);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -327,8 +370,8 @@ nav {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: #fff;
|
||||
background: var(--surface-overlay-soft);
|
||||
color: var(--text-inverse);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@@ -363,7 +406,7 @@ nav {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 4px;
|
||||
color: #fff;
|
||||
color: var(--text-inverse);
|
||||
font-size: 26px;
|
||||
transition:
|
||||
transform 0.14s cubic-bezier(0.22, 1, 0.36, 1),
|
||||
|
||||
@@ -1,24 +1,4 @@
|
||||
@media (max-width: 1024px) {
|
||||
nav,
|
||||
.mobile-menu {
|
||||
padding-left: var(--space-container-x-tablet);
|
||||
padding-right: var(--space-container-x-tablet);
|
||||
}
|
||||
|
||||
.services-inner,
|
||||
.values-inner,
|
||||
.testimonials-inner,
|
||||
.info-inner,
|
||||
.form-inner {
|
||||
padding-left: var(--space-container-x-tablet);
|
||||
padding-right: var(--space-container-x-tablet);
|
||||
}
|
||||
|
||||
footer {
|
||||
padding-left: var(--space-container-x-tablet);
|
||||
padding-right: var(--space-container-x-tablet);
|
||||
}
|
||||
|
||||
.hero-text h1 {
|
||||
font-size: 40px;
|
||||
}
|
||||
@@ -66,8 +46,8 @@
|
||||
}
|
||||
|
||||
header {
|
||||
background: #fff;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
||||
background: var(--surface-page);
|
||||
border-bottom: 1px solid var(--border-muted);
|
||||
}
|
||||
|
||||
nav {
|
||||
@@ -91,12 +71,12 @@
|
||||
display: inline-flex;
|
||||
justify-self: center;
|
||||
align-self: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
min-height: 40px;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
min-height: 44px;
|
||||
padding: 0;
|
||||
background: rgba(33, 48, 33, 0.1);
|
||||
color: var(--gw-green);
|
||||
background: rgba(var(--brand-rgb), 0.1);
|
||||
color: var(--text-brand);
|
||||
}
|
||||
|
||||
.mobile-phone .icon {
|
||||
@@ -125,13 +105,19 @@
|
||||
}
|
||||
|
||||
.instagram-icon {
|
||||
color: #0a304e;
|
||||
color: rgb(var(--navy-rgb));
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.hamburger {
|
||||
display: flex;
|
||||
color: #2e3031;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
min-height: 44px;
|
||||
padding: 0;
|
||||
color: var(--text);
|
||||
grid-column: 4;
|
||||
justify-self: end;
|
||||
}
|
||||
@@ -143,13 +129,13 @@
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
padding: 10px 12px 14px;
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0.985), rgba(248, 248, 245, 0.98));
|
||||
border: 1px solid rgba(33, 48, 33, 0.08);
|
||||
background: linear-gradient(180deg, rgba(var(--white-rgb), 0.985), rgba(248, 248, 245, 0.98));
|
||||
border: 1px solid var(--border-brand-soft);
|
||||
border-top: 0;
|
||||
border-radius: 0 0 24px 24px;
|
||||
box-shadow:
|
||||
0 18px 32px rgba(17, 20, 24, 0.12),
|
||||
0 6px 14px rgba(17, 20, 24, 0.05);
|
||||
var(--shadow-menu),
|
||||
var(--shadow-menu-soft);
|
||||
opacity: 0;
|
||||
transform: translateY(-10px) scale(0.992);
|
||||
transition:
|
||||
@@ -164,7 +150,7 @@
|
||||
inset: var(--mobile-menu-top, 0px) 0 0;
|
||||
z-index: 120;
|
||||
padding: 0 0 max(20px, env(safe-area-inset-bottom));
|
||||
background: rgba(17, 20, 24, 0.04);
|
||||
background: var(--surface-scrim);
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
@@ -207,13 +193,13 @@
|
||||
min-height: 48px;
|
||||
padding: 9px 12px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(33, 48, 33, 0.06);
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
color: var(--gw-green);
|
||||
border: 1px solid rgba(var(--brand-rgb), 0.06);
|
||||
background: var(--surface-disabled);
|
||||
color: var(--text-brand);
|
||||
text-decoration: none;
|
||||
opacity: 0;
|
||||
animation: mobileMenuItemIn 220ms ease-out forwards;
|
||||
box-shadow: 0 2px 10px rgba(17, 20, 24, 0.03);
|
||||
box-shadow: var(--shadow-sm);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
@@ -232,10 +218,10 @@
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 11px;
|
||||
background: var(--gw-green);
|
||||
color: var(--yellow);
|
||||
background: var(--surface-brand);
|
||||
color: var(--text-accent);
|
||||
font-size: 13px;
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.04);
|
||||
box-shadow: inset 0 0 0 1px rgba(var(--white-rgb), 0.04);
|
||||
}
|
||||
|
||||
.mobile-menu-link-label {
|
||||
@@ -243,20 +229,20 @@
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
color: var(--gw-green);
|
||||
color: var(--text-brand);
|
||||
min-width: 0;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
:global(.mobile-menu-link-arrow) {
|
||||
font-size: 12px;
|
||||
color: rgba(33, 48, 33, 0.42);
|
||||
color: var(--text-brand-quiet);
|
||||
}
|
||||
|
||||
.mobile-menu-links a.mobile-link-active {
|
||||
background: linear-gradient(180deg, rgba(255, 209, 0, 0.2), rgba(255, 209, 0, 0.1));
|
||||
border-color: rgba(255, 209, 0, 0.28);
|
||||
box-shadow: 0 6px 16px rgba(255, 209, 0, 0.08);
|
||||
background: linear-gradient(180deg, var(--surface-accent-selected), var(--surface-accent-muted));
|
||||
border-color: var(--border-accent-muted);
|
||||
box-shadow: var(--shadow-highlight);
|
||||
}
|
||||
|
||||
body.mobile-menu-open {
|
||||
@@ -290,9 +276,9 @@
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent 22%,
|
||||
rgba(33, 48, 33, 0.45) 48%,
|
||||
rgba(33, 48, 33, 0.88) 65%,
|
||||
#213021 78%
|
||||
rgba(var(--brand-rgb), 0.45) 48%,
|
||||
rgba(var(--brand-rgb), 0.88) 65%,
|
||||
var(--surface-brand) 78%
|
||||
);
|
||||
}
|
||||
|
||||
@@ -320,7 +306,7 @@
|
||||
max-width: none;
|
||||
margin: 0;
|
||||
object-fit: cover;
|
||||
object-position: 43% center;
|
||||
object-position: 37% center;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
@@ -333,7 +319,7 @@
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
margin: 0;
|
||||
padding: 0 26px 36px;
|
||||
padding: 0 var(--space-container-x-mobile) 36px;
|
||||
}
|
||||
|
||||
.hero-text {
|
||||
@@ -347,9 +333,9 @@
|
||||
|
||||
.hero-kicker {
|
||||
margin: 0 0 8px;
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.13em;
|
||||
color: rgba(255, 255, 255, 0.48);
|
||||
font-size: 11px;
|
||||
letter-spacing: var(--tracking-eyebrow);
|
||||
color: rgba(var(--white-rgb), 0.72);
|
||||
}
|
||||
|
||||
.hero-text h1,
|
||||
@@ -365,7 +351,7 @@
|
||||
|
||||
.hero-text h1 .hero-heading-mobile {
|
||||
display: block;
|
||||
color: #fff;
|
||||
color: var(--text-inverse);
|
||||
font-family: var(--font-head);
|
||||
font-size: 40px;
|
||||
font-weight: 800;
|
||||
@@ -390,16 +376,34 @@
|
||||
|
||||
.hero-chip,
|
||||
.hero-trust-chip {
|
||||
min-height: 34px;
|
||||
padding: 8px 12px;
|
||||
min-height: 44px;
|
||||
padding: 10px 14px;
|
||||
font-size: 11px;
|
||||
background: rgba(255, 255, 255, 0.09);
|
||||
border-color: rgba(255, 255, 255, 0.14);
|
||||
background: rgba(var(--white-rgb), 0.09);
|
||||
border-color: var(--border-inverse-strong);
|
||||
}
|
||||
|
||||
.hero-trust-chip {
|
||||
gap: 8px;
|
||||
padding: 6px 14px 6px 6px;
|
||||
}
|
||||
|
||||
.hero-trust-mark {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.hero-trust-logo {
|
||||
width: 12px;
|
||||
height: 13px;
|
||||
width: 13px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.hero-trust-stars {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.hero-trust-label {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.hero-buttons {
|
||||
@@ -429,51 +433,55 @@
|
||||
}
|
||||
|
||||
.hero-buttons .btn-yellow:active {
|
||||
background: #e6bb00;
|
||||
background: var(--surface-accent-strong);
|
||||
}
|
||||
|
||||
#intro {
|
||||
padding: 20px 24px;
|
||||
padding: 44px var(--space-container-x-mobile) 40px;
|
||||
}
|
||||
|
||||
.intro-trust-badge {
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
padding: 12px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(33, 48, 33, 0.06);
|
||||
box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.06);
|
||||
.intro-inner {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 28px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.intro-trust-mark {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
font-size: 18px;
|
||||
box-shadow: none;
|
||||
.intro-kicker {
|
||||
gap: 10px;
|
||||
margin-bottom: 14px;
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.16em;
|
||||
}
|
||||
|
||||
.intro-kicker-rule {
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.intro-headline {
|
||||
font-size: clamp(26px, 7.2vw, 34px);
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.intro-trust {
|
||||
align-items: flex-start;
|
||||
padding-left: 0;
|
||||
padding-top: 22px;
|
||||
border-left: none;
|
||||
border-top: 1px solid rgba(var(--white-rgb), 0.12);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.intro-google {
|
||||
padding: 8px 14px 8px 8px;
|
||||
}
|
||||
|
||||
.intro-google-mark {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.intro-google-logo {
|
||||
width: 18px;
|
||||
}
|
||||
|
||||
#intro p {
|
||||
font-size: 13px;
|
||||
line-height: 1.3;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.intro-trust-copy-desktop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.intro-trust-copy-mobile {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.intro-trust-meta {
|
||||
display: none;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.testimonials-grid,
|
||||
@@ -511,7 +519,8 @@
|
||||
}
|
||||
|
||||
.booking-trust-chip {
|
||||
padding: 8px 12px;
|
||||
min-height: 44px;
|
||||
padding: 10px 14px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@@ -611,12 +620,6 @@
|
||||
gap: 12px 18px;
|
||||
}
|
||||
|
||||
.booking-inline-switch {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.booking-check-option {
|
||||
font-size: 15px;
|
||||
}
|
||||
@@ -719,9 +722,9 @@
|
||||
}
|
||||
|
||||
.mobile-phone {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
min-height: 38px;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
.hero-text h1,
|
||||
@@ -735,26 +738,6 @@
|
||||
line-height: 1.12;
|
||||
}
|
||||
|
||||
.hero-buttons {
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.hero-buttons .btn-yellow {
|
||||
flex: 1 1 0;
|
||||
width: 0;
|
||||
margin-right: 0 !important;
|
||||
padding: 13px 9px;
|
||||
font-size: 12px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.hero-secondary-link {
|
||||
width: auto;
|
||||
flex: 0 0 auto;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 769px) {
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
}
|
||||
|
||||
#hero {
|
||||
background: var(--gw-green);
|
||||
color: #fff;
|
||||
background: var(--surface-brand);
|
||||
color: var(--text-inverse);
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -36,10 +36,8 @@
|
||||
height: auto;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent 18%,
|
||||
rgba(33, 48, 33, 0.26) 48%,
|
||||
rgba(33, 48, 33, 0.78) 72%,
|
||||
#213021 86%
|
||||
transparent 30%,
|
||||
var(--surface-brand) 95%
|
||||
);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
@@ -71,8 +69,8 @@
|
||||
margin: 0 0 14px;
|
||||
padding: 7px 12px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 209, 0, 0.12);
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 209, 0, 0.2);
|
||||
background: var(--surface-accent-soft);
|
||||
box-shadow: inset 0 0 0 1px var(--border-accent-soft);
|
||||
font-family: var(--font-body);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
@@ -84,12 +82,29 @@
|
||||
.hero-subtitle {
|
||||
margin: -6px 0 22px;
|
||||
max-width: 560px;
|
||||
color: rgba(255, 255, 255, 0.86);
|
||||
color: var(--text-inverse-soft);
|
||||
font-size: var(--body-lead-size);
|
||||
line-height: 1.55;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero-seo-heading {
|
||||
margin: -10px 0 14px;
|
||||
color: var(--yellow);
|
||||
font-family: var(--font-head);
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.01em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.hero-seo-heading {
|
||||
margin: -4px 0 10px;
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -104,12 +119,12 @@
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 38px;
|
||||
padding: 8px 14px;
|
||||
min-height: 44px;
|
||||
padding: 10px 16px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.11);
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
color: #fff;
|
||||
background: var(--surface-overlay-strong);
|
||||
border: 1px solid var(--border-inverse-muted);
|
||||
color: var(--text-inverse);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
@@ -121,23 +136,69 @@
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.hero-trust-logo {
|
||||
width: 18px;
|
||||
height: 19px;
|
||||
.hero-trust-chip {
|
||||
gap: 10px;
|
||||
padding: 8px 16px 8px 8px;
|
||||
background: rgba(var(--white-rgb), 0.05);
|
||||
border-color: rgba(var(--white-rgb), 0.14);
|
||||
text-decoration: none;
|
||||
transition:
|
||||
background 0.22s ease,
|
||||
border-color 0.22s ease,
|
||||
transform 0.22s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.hero-trust-mark {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
background: var(--surface-page);
|
||||
box-shadow: var(--shadow-card);
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.hero-trust-chip {
|
||||
text-decoration: none;
|
||||
transition: background 0.2s ease, opacity 0.2s ease;
|
||||
.hero-trust-logo {
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.hero-trust-stars {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
color: var(--yellow);
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
:global(.hero-trust-stars .icon) {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.hero-trust-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.005em;
|
||||
color: rgba(var(--white-rgb), 0.92);
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.hero-trust-chip:hover {
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
background: rgba(var(--white-rgb), 0.09);
|
||||
border-color: rgba(var(--white-rgb), 0.24);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
.hero-trust-chip:focus-visible {
|
||||
outline: 2px solid var(--yellow);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
.hero-buttons {
|
||||
display: flex;
|
||||
gap: 18px;
|
||||
@@ -155,7 +216,9 @@
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: rgba(255, 255, 255, 0.86);
|
||||
min-height: 44px;
|
||||
padding: 0 4px;
|
||||
color: var(--text-inverse-soft);
|
||||
font-family: var(--font-head);
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
@@ -165,7 +228,7 @@
|
||||
|
||||
@media (hover: hover) {
|
||||
.hero-secondary-link:hover {
|
||||
color: #fff;
|
||||
color: var(--text-inverse);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,166 +256,315 @@
|
||||
margin: 0;
|
||||
object-fit: cover;
|
||||
object-position: center -10%;
|
||||
transform: none;
|
||||
transform: scale(1.06);
|
||||
opacity: 0;
|
||||
animation: heroImageEnter 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||
}
|
||||
|
||||
#intro {
|
||||
background: #fff;
|
||||
padding: 24px 50px;
|
||||
text-align: left;
|
||||
.hero-text > * {
|
||||
opacity: 0;
|
||||
transform: translateY(14px);
|
||||
animation: heroRise 0.45s cubic-bezier(0.22, 1, 0.36, 1) forwards;
|
||||
}
|
||||
|
||||
.intro-trust-badge {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
gap: 18px;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 18px 22px;
|
||||
border-radius: 26px;
|
||||
background:
|
||||
radial-gradient(circle at top right, rgba(255, 209, 0, 0.14), transparent 30%),
|
||||
linear-gradient(180deg, #ffffff 0%, #fbf8f2 100%);
|
||||
box-shadow:
|
||||
0 1px 0 rgba(17, 20, 24, 0.04),
|
||||
0 18px 40px rgba(17, 20, 24, 0.08);
|
||||
}
|
||||
.hero-text > :nth-child(1) { animation-delay: 120ms; }
|
||||
.hero-text > :nth-child(2) { animation-delay: 180ms; }
|
||||
.hero-text > :nth-child(3) { animation-delay: 240ms; }
|
||||
.hero-text > :nth-child(4) { animation-delay: 300ms; }
|
||||
.hero-text > :nth-child(5) { animation-delay: 360ms; }
|
||||
.hero-text > :nth-child(6) { animation-delay: 420ms; }
|
||||
.hero-text > :nth-child(7) { animation-delay: 480ms; }
|
||||
|
||||
.intro-trust-mark {
|
||||
display: inline-flex;
|
||||
.hero-title-highlight {
|
||||
position: relative;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
color: #e00706;
|
||||
font-size: 24px;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 0 0 1px rgba(14, 27, 41, 0.08);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.intro-trust-mark-link {
|
||||
text-decoration: none;
|
||||
isolation: isolate;
|
||||
transition:
|
||||
transform 0.22s cubic-bezier(0.22, 1, 0.36, 1),
|
||||
box-shadow 0.22s ease;
|
||||
}
|
||||
|
||||
.intro-trust-mark-link::after {
|
||||
.hero-title-highlight::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: -30% 35%;
|
||||
background: linear-gradient(120deg, transparent 0%, rgba(255, 255, 255, 0.2) 30%, rgba(255, 255, 255, 0.92) 50%, rgba(255, 255, 255, 0.2) 70%, transparent 100%);
|
||||
transform: translateX(-220%) rotate(18deg);
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: -0.06em;
|
||||
height: 2px;
|
||||
background: var(--yellow);
|
||||
transform: scaleX(0);
|
||||
transform-origin: left center;
|
||||
animation: heroUnderlineDraw 0.4s cubic-bezier(0.22, 1, 0.36, 1) forwards;
|
||||
animation-delay: 350ms;
|
||||
opacity: 0.85;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
:global(.hero-trust-stars .hero-trust-star) {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
animation: introGoogleShine 4.8s ease-in-out infinite;
|
||||
transform: scale(0.6);
|
||||
animation: heroStarPop 0.45s cubic-bezier(0.22, 1, 0.36, 1) forwards;
|
||||
}
|
||||
|
||||
.intro-trust-mark-link:hover,
|
||||
.intro-trust-mark-link:focus-visible {
|
||||
transform: translateY(-1px) scale(1.04);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(14, 27, 41, 0.08),
|
||||
0 10px 24px rgba(17, 20, 24, 0.12);
|
||||
}
|
||||
:global(.hero-trust-star-1) { animation-delay: 820ms; }
|
||||
:global(.hero-trust-star-2) { animation-delay: 880ms; }
|
||||
:global(.hero-trust-star-3) { animation-delay: 940ms; }
|
||||
:global(.hero-trust-star-4) { animation-delay: 1000ms; }
|
||||
:global(.hero-trust-star-5) { animation-delay: 1060ms; }
|
||||
|
||||
.intro-trust-mark-link:focus-visible {
|
||||
outline: 2px solid rgba(10, 48, 78, 0.28);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
.intro-google-logo {
|
||||
display: block;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 28px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.intro-trust-copy {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.intro-trust-copy-mobile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#intro p {
|
||||
font-size: var(--body-lead-size);
|
||||
font-weight: 700;
|
||||
line-height: 1.3;
|
||||
max-width: none;
|
||||
margin: 0;
|
||||
color: #1f2421;
|
||||
}
|
||||
|
||||
.intro-trust-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 18px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.intro-trust-stars {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: var(--yellow);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.intro-trust-cta {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(33, 48, 33, 0.06);
|
||||
color: var(--gw-green);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
box-shadow: inset 0 0 0 1px rgba(14, 27, 41, 0.08);
|
||||
}
|
||||
|
||||
.intro-trust-cta .icon {
|
||||
color: #e00706;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.intro-trust-cta:focus-visible {
|
||||
outline: 2px solid rgba(10, 48, 78, 0.28);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
@keyframes introGoogleShine {
|
||||
0%,
|
||||
64%,
|
||||
100% {
|
||||
transform: translateX(-220%) rotate(18deg);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
72% {
|
||||
@keyframes heroImageEnter {
|
||||
to {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
84% {
|
||||
transform: translateX(220%) rotate(18deg);
|
||||
opacity: 0;
|
||||
@keyframes heroRise {
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes heroUnderlineDraw {
|
||||
to {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes heroStarPop {
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.intro-trust-mark-link,
|
||||
.intro-trust-mark-link::after {
|
||||
.hero-img img,
|
||||
.hero-text > *,
|
||||
.hero-title-highlight::after {
|
||||
animation: none;
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.hero-title-highlight::after {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
|
||||
:global(.hero-trust-stars .hero-trust-star) {
|
||||
animation: none;
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
#intro {
|
||||
position: relative;
|
||||
background: var(--gw-green);
|
||||
color: var(--text-inverse);
|
||||
padding: clamp(48px, 7vw, 88px) var(--space-container-x);
|
||||
overflow: hidden;
|
||||
isolation: isolate;
|
||||
}
|
||||
|
||||
#intro::before,
|
||||
#intro::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
#intro::before {
|
||||
inset: 0;
|
||||
background:
|
||||
radial-gradient(60% 80% at 85% 20%, rgba(var(--accent-rgb), 0.10), transparent 60%),
|
||||
radial-gradient(50% 70% at 8% 100%, rgba(var(--white-rgb), 0.05), transparent 60%);
|
||||
}
|
||||
|
||||
#intro::after {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent 0%, rgba(var(--white-rgb), 0.16) 18%, rgba(var(--white-rgb), 0.16) 82%, transparent 100%);
|
||||
}
|
||||
|
||||
.intro-inner {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
max-width: var(--max-w);
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: end;
|
||||
gap: clamp(32px, 5vw, 80px);
|
||||
}
|
||||
|
||||
.intro-statement {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.intro-kicker {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
margin-bottom: clamp(16px, 2.4vw, 26px);
|
||||
color: rgba(var(--white-rgb), 0.62);
|
||||
font-family: var(--font-body);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.intro-kicker-rule {
|
||||
display: inline-block;
|
||||
width: 36px;
|
||||
height: 1px;
|
||||
background: var(--yellow);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.intro-headline {
|
||||
font-family: var(--font-head);
|
||||
font-weight: var(--weight-display);
|
||||
font-size: var(--text-display);
|
||||
line-height: 1.08;
|
||||
letter-spacing: var(--tracking-display);
|
||||
color: var(--text-inverse);
|
||||
margin: 0;
|
||||
max-width: 18ch;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.intro-word {
|
||||
display: inline-block;
|
||||
opacity: 0;
|
||||
transform: translateY(14px);
|
||||
animation: introWordRise 0.9s cubic-bezier(0.22, 1, 0.36, 1) forwards;
|
||||
animation-delay: calc(80ms + var(--word-i) * 55ms);
|
||||
}
|
||||
|
||||
.intro-word:last-child {
|
||||
color: var(--yellow);
|
||||
}
|
||||
|
||||
.intro-trust {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 14px;
|
||||
padding-left: clamp(24px, 4vw, 56px);
|
||||
border-left: 1px solid rgba(var(--white-rgb), 0.12);
|
||||
}
|
||||
|
||||
.intro-google {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 10px 14px 10px 10px;
|
||||
border-radius: 999px;
|
||||
background: rgba(var(--white-rgb), 0.04);
|
||||
box-shadow: inset 0 0 0 1px rgba(var(--white-rgb), 0.10);
|
||||
color: var(--text-inverse);
|
||||
text-decoration: none;
|
||||
transition:
|
||||
background 0.25s ease,
|
||||
box-shadow 0.25s ease,
|
||||
transform 0.25s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.intro-google:hover,
|
||||
.intro-google:focus-visible {
|
||||
background: rgba(var(--white-rgb), 0.08);
|
||||
box-shadow: inset 0 0 0 1px rgba(var(--white-rgb), 0.22);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.intro-google:focus-visible {
|
||||
outline: 2px solid var(--yellow);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
.intro-google-mark {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 50%;
|
||||
background: var(--surface-page);
|
||||
box-shadow: var(--shadow-card);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.intro-google-logo {
|
||||
display: block;
|
||||
width: 18px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.intro-google-copy {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 2px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.intro-stars {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
color: var(--yellow);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.intro-google-label {
|
||||
font-family: var(--font-body);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: rgba(var(--white-rgb), 0.92);
|
||||
letter-spacing: 0.005em;
|
||||
}
|
||||
|
||||
.intro-meta {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin: 0;
|
||||
font-family: var(--font-body);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(var(--white-rgb), 0.58);
|
||||
}
|
||||
|
||||
.intro-meta-dot {
|
||||
display: inline-block;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 50%;
|
||||
background: var(--yellow);
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
@keyframes introWordRise {
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.intro-word {
|
||||
animation: none;
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.intro-google {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
@@ -366,12 +578,12 @@
|
||||
#testimonials,
|
||||
#info,
|
||||
#newlead {
|
||||
background: #fff;
|
||||
background: var(--surface-page);
|
||||
}
|
||||
|
||||
footer {
|
||||
background: var(--gw-green);
|
||||
color: #fff;
|
||||
background: var(--surface-brand);
|
||||
color: var(--text-inverse);
|
||||
}
|
||||
|
||||
#testimonials .section-heading {
|
||||
@@ -383,16 +595,16 @@ footer {
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 60px 50px 32px;
|
||||
padding: var(--space-11) var(--space-container-x) var(--space-7);
|
||||
}
|
||||
|
||||
.testimonial-card {
|
||||
background: linear-gradient(180deg, #ffffff 0%, var(--off-white) 100%);
|
||||
border-radius: 28px;
|
||||
padding: 36px 32px;
|
||||
background: var(--surface-panel);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-7);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(17, 20, 24, 0.06),
|
||||
0 4px 24px rgba(0, 0, 0, 0.06);
|
||||
var(--shadow-inset-strong),
|
||||
var(--shadow-md);
|
||||
transition:
|
||||
transform 0.18s cubic-bezier(0.22, 1, 0.36, 1),
|
||||
box-shadow 0.22s ease;
|
||||
@@ -402,8 +614,8 @@ footer {
|
||||
.testimonial-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(17, 20, 24, 0.06),
|
||||
0 8px 40px rgba(0, 0, 0, 0.08);
|
||||
var(--shadow-inset-strong),
|
||||
var(--shadow-lg);
|
||||
filter: brightness(1.01);
|
||||
}
|
||||
}
|
||||
@@ -438,16 +650,16 @@ footer {
|
||||
}
|
||||
|
||||
.faq details {
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(33, 48, 33, 0.1);
|
||||
background: #fff;
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--border-brand-muted);
|
||||
background: var(--surface-page);
|
||||
padding: 0;
|
||||
margin-bottom: 10px;
|
||||
transition: box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.faq details[open] {
|
||||
box-shadow: 0 8px 24px rgba(17, 20, 24, 0.06);
|
||||
box-shadow: var(--shadow-lg);
|
||||
}
|
||||
|
||||
.faq summary {
|
||||
@@ -459,8 +671,8 @@ footer {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 18px 22px;
|
||||
border-radius: 16px;
|
||||
padding: var(--space-5) var(--space-6);
|
||||
border-radius: var(--radius-lg);
|
||||
transition: background 0.18s ease;
|
||||
}
|
||||
|
||||
@@ -469,17 +681,17 @@ footer {
|
||||
}
|
||||
|
||||
.faq summary:focus-visible {
|
||||
outline: 2px solid rgba(10, 48, 78, 0.28);
|
||||
outline: 2px solid var(--border-focus);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
.faq summary:hover {
|
||||
background: rgba(33, 48, 33, 0.03);
|
||||
background: var(--surface-brand-faint);
|
||||
}
|
||||
|
||||
.faq details[open] summary {
|
||||
border-bottom: 1px solid rgba(33, 48, 33, 0.08);
|
||||
border-radius: 16px 16px 0 0;
|
||||
border-bottom: 1px solid var(--border-brand-soft);
|
||||
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
||||
}
|
||||
|
||||
.faq summary::after {
|
||||
@@ -488,19 +700,19 @@ footer {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
background: rgba(33, 48, 33, 0.06);
|
||||
background: var(--surface-brand-soft);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
color: var(--gw-green);
|
||||
color: var(--text-brand);
|
||||
transition: background 0.18s ease, transform 0.22s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.faq details[open] summary::after {
|
||||
content: '−';
|
||||
background: rgba(33, 48, 33, 0.1);
|
||||
background: rgba(var(--brand-rgb), 0.1);
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
@@ -515,19 +727,19 @@ footer {
|
||||
#instagram {
|
||||
background: var(--yellow);
|
||||
text-align: center;
|
||||
padding: 60px 50px;
|
||||
padding: var(--space-section-support-y) var(--space-container-x);
|
||||
}
|
||||
|
||||
#instagram h2 {
|
||||
color: var(--gw-green);
|
||||
}
|
||||
|
||||
.instagram-blurb {
|
||||
margin: -8px 0 24px;
|
||||
font-size: var(--body-copy-size);
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
color: rgba(var(--brand-rgb), 0.72);
|
||||
}
|
||||
|
||||
#instagram .btn {
|
||||
background: var(--gw-green);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.footer-logo {
|
||||
display: block;
|
||||
@@ -539,7 +751,7 @@ footer {
|
||||
.footer-brand p {
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
opacity: 0.72;
|
||||
opacity: 0.85;
|
||||
margin-bottom: 18px;
|
||||
white-space: pre-line;
|
||||
max-width: 30ch;
|
||||
@@ -557,10 +769,10 @@ footer {
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding-left: 4px;
|
||||
color: rgba(255, 255, 255, 0.82);
|
||||
color: var(--text-inverse-muted);
|
||||
font-family: var(--font-head);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
font-weight: var(--weight-heading);
|
||||
letter-spacing: 0.02em;
|
||||
line-height: 1.2;
|
||||
}
|
||||
@@ -576,8 +788,8 @@ footer {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 16px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
|
||||
background: var(--surface-overlay-soft);
|
||||
box-shadow: var(--shadow-inset-inverse);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -590,7 +802,7 @@ footer {
|
||||
|
||||
.social-links a:hover {
|
||||
background: var(--yellow);
|
||||
color: #000;
|
||||
color: var(--text-strong);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
@@ -603,11 +815,11 @@ footer {
|
||||
.footer-col-label {
|
||||
margin: 0 0 14px;
|
||||
font-family: var(--font-head);
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
font-size: 12px;
|
||||
font-weight: var(--weight-heading);
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
opacity: 0.5;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.footer-nav {
|
||||
@@ -631,7 +843,7 @@ footer {
|
||||
padding: 8px 0;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
opacity: 0.72;
|
||||
opacity: 0.85;
|
||||
transition: opacity 0.18s;
|
||||
}
|
||||
|
||||
@@ -644,7 +856,7 @@ footer {
|
||||
gap: 8px;
|
||||
margin-top: 18px;
|
||||
padding-top: 18px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-top: 1px solid rgba(var(--white-rgb), 0.1);
|
||||
max-width: 24rem;
|
||||
}
|
||||
|
||||
@@ -652,11 +864,11 @@ footer {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 42px;
|
||||
min-height: 44px;
|
||||
padding: 0 2px;
|
||||
font-size: 13px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
opacity: 0.7;
|
||||
opacity: 0.85;
|
||||
transition: opacity 0.18s;
|
||||
}
|
||||
|
||||
@@ -676,10 +888,10 @@ footer {
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
gap: 12px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-top: 1px solid rgba(var(--white-rgb), 0.1);
|
||||
padding-top: 28px;
|
||||
font-size: 13px;
|
||||
opacity: 0.6;
|
||||
font-size: 12px;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.footer-bottom > span {
|
||||
@@ -713,7 +925,7 @@ footer {
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
opacity: 0.5;
|
||||
opacity: 0.55;
|
||||
cursor: pointer;
|
||||
flex: 0 0 auto;
|
||||
transition: opacity 0.2s;
|
||||
@@ -732,22 +944,22 @@ footer {
|
||||
.ph-inner {
|
||||
max-width: var(--max-w);
|
||||
margin: 0 auto;
|
||||
padding: 0 50px;
|
||||
padding: 0 var(--space-container-x);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Green variant */
|
||||
.page-header--green {
|
||||
background: var(--gw-green);
|
||||
color: #fff;
|
||||
background: var(--surface-brand);
|
||||
color: var(--text-inverse);
|
||||
}
|
||||
|
||||
.page-header--green .eyebrow {
|
||||
display: inline-block;
|
||||
padding: 7px 12px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 209, 0, 0.12);
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 209, 0, 0.2);
|
||||
background: var(--surface-accent-soft);
|
||||
box-shadow: inset 0 0 0 1px var(--border-accent-soft);
|
||||
color: var(--yellow);
|
||||
}
|
||||
|
||||
@@ -777,11 +989,11 @@ footer {
|
||||
}
|
||||
|
||||
.page-header--green .ph-title {
|
||||
color: #fff;
|
||||
color: var(--text-inverse);
|
||||
}
|
||||
|
||||
.page-header--white .ph-title {
|
||||
color: #000;
|
||||
color: var(--text-strong);
|
||||
}
|
||||
|
||||
/* Subtitle */
|
||||
@@ -793,7 +1005,7 @@ footer {
|
||||
}
|
||||
|
||||
.page-header--green .ph-subtitle {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
color: var(--text-inverse-gentle);
|
||||
}
|
||||
|
||||
.page-header--white .ph-subtitle {
|
||||
@@ -807,10 +1019,10 @@ footer {
|
||||
/* Media column */
|
||||
.ph-media {
|
||||
aspect-ratio: 4 / 3;
|
||||
border-radius: 28px;
|
||||
border-radius: var(--radius-lg);
|
||||
overflow: hidden;
|
||||
background: #f4efe7;
|
||||
box-shadow: 0 16px 40px rgba(17, 20, 24, 0.08);
|
||||
background: var(--surface-panel-beige);
|
||||
box-shadow: var(--shadow-2xl);
|
||||
}
|
||||
|
||||
.ph-media img {
|
||||
@@ -843,15 +1055,15 @@ footer {
|
||||
}
|
||||
|
||||
.page-header--green .ph-chip {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
color: #fff;
|
||||
background: var(--surface-overlay);
|
||||
border: 1px solid var(--border-inverse-strong);
|
||||
color: var(--text-inverse);
|
||||
}
|
||||
|
||||
.page-header--white .ph-chip {
|
||||
background: rgba(33, 48, 33, 0.07);
|
||||
border: 1px solid rgba(33, 48, 33, 0.1);
|
||||
color: var(--gw-green);
|
||||
background: rgba(var(--brand-rgb), 0.07);
|
||||
border: 1px solid var(--border-brand-muted);
|
||||
color: var(--text-brand);
|
||||
}
|
||||
|
||||
.ph-chip--link {
|
||||
@@ -860,11 +1072,11 @@ footer {
|
||||
}
|
||||
|
||||
.page-header--green .ph-chip--link:hover {
|
||||
background: rgba(255, 255, 255, 0.18);
|
||||
background: rgba(var(--white-rgb), 0.18);
|
||||
}
|
||||
|
||||
.page-header--white .ph-chip--link:hover {
|
||||
background: rgba(33, 48, 33, 0.12);
|
||||
background: var(--surface-brand-selected);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@@ -909,6 +1121,7 @@ footer {
|
||||
|
||||
.ph-chip {
|
||||
font-size: 13px;
|
||||
padding: 8px 14px;
|
||||
min-height: 44px;
|
||||
padding: 10px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,17 +50,17 @@
|
||||
h2,
|
||||
h3 {
|
||||
font-family: var(--font-head);
|
||||
letter-spacing: -0.02em;
|
||||
letter-spacing: var(--tracking-heading);
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: 700;
|
||||
font-weight: var(--weight-heading);
|
||||
line-height: 1.06;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-weight: 700;
|
||||
font-weight: var(--weight-heading);
|
||||
line-height: 1.16;
|
||||
}
|
||||
|
||||
@@ -68,29 +68,29 @@ h3 {
|
||||
.info-block h2,
|
||||
#instagram h2 {
|
||||
font-family: var(--font-head);
|
||||
font-weight: 700;
|
||||
font-weight: var(--weight-heading);
|
||||
line-height: 1.06;
|
||||
letter-spacing: -0.025em;
|
||||
letter-spacing: var(--tracking-heading);
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
font-size: var(--heading-section-size);
|
||||
font-weight: 800;
|
||||
font-size: var(--text-h1);
|
||||
font-weight: var(--weight-display);
|
||||
line-height: 1.03;
|
||||
letter-spacing: -0.035em;
|
||||
color: #000;
|
||||
letter-spacing: var(--tracking-display);
|
||||
color: var(--text-heading);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.hero-text h1 {
|
||||
font-family: var(--font-head);
|
||||
font-size: clamp(34px, 4vw, 56px);
|
||||
font-weight: 800;
|
||||
font-size: var(--text-display);
|
||||
font-weight: var(--weight-display);
|
||||
line-height: 1.03;
|
||||
letter-spacing: -0.045em;
|
||||
letter-spacing: var(--tracking-display);
|
||||
margin-bottom: 28px;
|
||||
color: #fff;
|
||||
color: var(--text-inverse);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -100,19 +100,33 @@ h3 {
|
||||
.eyebrow {
|
||||
font-family: var(--font-body);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.09em;
|
||||
font-weight: var(--weight-heading);
|
||||
letter-spacing: var(--tracking-eyebrow);
|
||||
text-transform: uppercase;
|
||||
color: var(--gw-green);
|
||||
margin: 0 0 14px;
|
||||
}
|
||||
|
||||
.eyebrow--inverse {
|
||||
color: var(--text-inverse-muted);
|
||||
}
|
||||
|
||||
.eyebrow--accent {
|
||||
color: var(--yellow);
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
max-width: var(--measure-wide);
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.section-header .eyebrow {
|
||||
display: inline-block;
|
||||
justify-self: center;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.section-header .section-heading {
|
||||
@@ -125,7 +139,7 @@ h3 {
|
||||
|
||||
.hero-title-main,
|
||||
.hero-title-connector {
|
||||
color: #fff;
|
||||
color: var(--text-inverse);
|
||||
}
|
||||
|
||||
.hero-title-highlight {
|
||||
@@ -140,9 +154,9 @@ h3 {
|
||||
}
|
||||
|
||||
.section-intro {
|
||||
max-width: 720px;
|
||||
max-width: var(--measure-section);
|
||||
margin: 16px auto 0;
|
||||
color: #4c5056;
|
||||
color: var(--text-muted);
|
||||
font-size: var(--body-lead-size);
|
||||
line-height: 1.65;
|
||||
}
|
||||
@@ -163,7 +177,7 @@ h3 {
|
||||
* - Instagram uses a compact CTA heading inside a tighter panel.
|
||||
*/
|
||||
.info-block h2 {
|
||||
font-size: clamp(28px, 2.4vw, 32px);
|
||||
font-size: var(--text-h2);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@@ -174,11 +188,15 @@ h3 {
|
||||
}
|
||||
|
||||
#instagram h2 {
|
||||
font-size: clamp(30px, 3vw, 36px);
|
||||
font-size: var(--text-h2);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.section-header {
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
display: block;
|
||||
width: fit-content;
|
||||
|
||||
@@ -32,36 +32,180 @@
|
||||
--text: #2e3031;
|
||||
--shadow-panel-elevated: 0 22px 52px rgba(17, 20, 24, 0.1);
|
||||
|
||||
/* Primitive channels */
|
||||
--ink-rgb: 17, 20, 24;
|
||||
--brand-rgb: 33, 48, 33;
|
||||
--accent-rgb: 255, 209, 0;
|
||||
--white-rgb: 255, 255, 255;
|
||||
--danger-rgb: 201, 64, 64;
|
||||
--navy-rgb: 10, 48, 78;
|
||||
--yelp-rgb: 224, 7, 6;
|
||||
|
||||
/* Semantic text */
|
||||
--text-strong: #000;
|
||||
--text-heading: #1f2421;
|
||||
--text-heading-soft: #34363a;
|
||||
--text-muted: #4c5056;
|
||||
--text-muted-strong: #4a4f55;
|
||||
--text-subtle: #5f6369;
|
||||
--text-soft: #666;
|
||||
--text-softest: #6a6d72;
|
||||
--text-inverse: #fff;
|
||||
--text-inverse-soft: rgba(var(--white-rgb), 0.86);
|
||||
--text-inverse-muted: rgba(var(--white-rgb), 0.82);
|
||||
--text-inverse-gentle: rgba(var(--white-rgb), 0.8);
|
||||
--text-brand: var(--gw-green);
|
||||
--text-brand-quiet: rgba(var(--brand-rgb), 0.42);
|
||||
--text-accent: var(--yellow);
|
||||
--text-critical: rgb(var(--danger-rgb));
|
||||
--text-yelp: rgb(var(--yelp-rgb));
|
||||
|
||||
/* Semantic surfaces */
|
||||
--surface-page: #fff;
|
||||
--surface-page-gradient: linear-gradient(to bottom, #f7f7f5 0%, #ffffff 100%);
|
||||
--surface-panel: #fff;
|
||||
--surface-panel-soft: var(--off-white);
|
||||
--surface-panel-muted: #fbfaf6;
|
||||
--surface-panel-warm: #fdfcf9;
|
||||
--surface-panel-warm-strong: #f8f5ef;
|
||||
--surface-panel-cream: #fbf8f2;
|
||||
--surface-panel-cream-strong: #f5f1e8;
|
||||
--surface-panel-beige: #f4efe7;
|
||||
--surface-input: #fffdfa;
|
||||
--surface-disabled: rgba(var(--white-rgb), 0.78);
|
||||
--surface-brand: var(--gw-green);
|
||||
--surface-brand-hover: #172217;
|
||||
--surface-brand-strong: var(--green-mid);
|
||||
--surface-brand-soft: rgba(var(--brand-rgb), 0.06);
|
||||
--surface-brand-muted: rgba(var(--brand-rgb), 0.08);
|
||||
--surface-brand-selected: rgba(var(--brand-rgb), 0.12);
|
||||
--surface-brand-hover-soft: rgba(var(--brand-rgb), 0.09);
|
||||
--surface-brand-faint: rgba(var(--brand-rgb), 0.03);
|
||||
--surface-accent: var(--yellow);
|
||||
--surface-accent-strong: #e6bb00;
|
||||
--surface-accent-soft: rgba(var(--accent-rgb), 0.12);
|
||||
--surface-accent-muted: rgba(var(--accent-rgb), 0.1);
|
||||
--surface-accent-selected: rgba(var(--accent-rgb), 0.2);
|
||||
--surface-highlight-soft: rgba(var(--accent-rgb), 0.16);
|
||||
--surface-overlay: rgba(var(--white-rgb), 0.1);
|
||||
--surface-overlay-soft: rgba(var(--white-rgb), 0.08);
|
||||
--surface-overlay-hover: rgba(var(--white-rgb), 0.16);
|
||||
--surface-overlay-strong: rgba(var(--white-rgb), 0.11);
|
||||
--surface-scrim: rgba(var(--ink-rgb), 0.04);
|
||||
|
||||
/* Semantic borders */
|
||||
--border-soft: rgba(var(--ink-rgb), 0.05);
|
||||
--border-soft-strong: rgba(var(--ink-rgb), 0.06);
|
||||
--border-muted: rgba(0, 0, 0, 0.08);
|
||||
--border-muted-strong: #cfd2d6;
|
||||
--border-brand-soft: rgba(var(--brand-rgb), 0.08);
|
||||
--border-brand-muted: rgba(var(--brand-rgb), 0.1);
|
||||
--border-brand-strong: rgba(var(--brand-rgb), 0.18);
|
||||
--border-brand-stronger: rgba(var(--brand-rgb), 0.28);
|
||||
--border-brand-hover: rgba(var(--brand-rgb), 0.3);
|
||||
--border-accent-soft: rgba(var(--accent-rgb), 0.2);
|
||||
--border-accent-muted: rgba(var(--accent-rgb), 0.28);
|
||||
--border-inverse-soft: rgba(var(--white-rgb), 0.08);
|
||||
--border-inverse-muted: rgba(var(--white-rgb), 0.12);
|
||||
--border-inverse-strong: rgba(var(--white-rgb), 0.14);
|
||||
--border-inverse-highlight: rgba(var(--white-rgb), 0.32);
|
||||
--border-focus: rgba(var(--navy-rgb), 0.28);
|
||||
--border-yelp-soft: rgba(14, 27, 41, 0.08);
|
||||
--border-critical: rgb(var(--danger-rgb));
|
||||
|
||||
/* Elevation */
|
||||
--shadow-xs: 0 1px 0 rgba(var(--ink-rgb), 0.04);
|
||||
--shadow-sm: 0 2px 10px rgba(var(--ink-rgb), 0.03);
|
||||
--shadow-md: 0 4px 24px rgba(0, 0, 0, 0.06);
|
||||
--shadow-lg: 0 8px 24px rgba(var(--ink-rgb), 0.06);
|
||||
--shadow-xl: 0 16px 40px rgba(0, 0, 0, 0.12);
|
||||
--shadow-2xl: 0 18px 40px rgba(var(--ink-rgb), 0.08);
|
||||
--shadow-float: 0 12px 24px rgba(var(--ink-rgb), 0.14);
|
||||
--shadow-press: 0 4px 10px rgba(var(--ink-rgb), 0.12);
|
||||
--shadow-panel: 0 10px 24px rgba(var(--ink-rgb), 0.04);
|
||||
--shadow-panel-strong: 0 14px 32px rgba(var(--ink-rgb), 0.05);
|
||||
--shadow-panel-soft: 0 10px 30px rgba(var(--ink-rgb), 0.04);
|
||||
--shadow-card: 0 12px 26px rgba(var(--ink-rgb), 0.05);
|
||||
--shadow-card-hover: 0 12px 26px rgba(var(--ink-rgb), 0.08);
|
||||
--shadow-badge: 0 10px 22px rgba(var(--brand-rgb), 0.12);
|
||||
--shadow-badge-hover: 0 16px 28px rgba(var(--brand-rgb), 0.18);
|
||||
--shadow-menu: 0 18px 32px rgba(var(--ink-rgb), 0.12);
|
||||
--shadow-menu-soft: 0 6px 14px rgba(var(--ink-rgb), 0.05);
|
||||
--shadow-invalid: 0 10px 30px rgba(var(--danger-rgb), 0.08);
|
||||
--shadow-highlight: 0 6px 16px rgba(var(--accent-rgb), 0.08);
|
||||
--shadow-inset-soft: inset 0 0 0 1px rgba(var(--ink-rgb), 0.05);
|
||||
--shadow-inset-strong: inset 0 0 0 1px rgba(var(--ink-rgb), 0.06);
|
||||
--shadow-inset-inverse: inset 0 0 0 1px rgba(var(--white-rgb), 0.08);
|
||||
--shadow-inset-highlight: inset 0 0 0 1px rgba(var(--white-rgb), 0.28);
|
||||
--shadow-input-inset: inset 0 1px 2px rgba(var(--ink-rgb), 0.03);
|
||||
|
||||
/* Radius scale */
|
||||
--radius-sm: 8px;
|
||||
--radius-md: 14px;
|
||||
--radius-lg: 20px;
|
||||
--radius-xl: 28px;
|
||||
--radius-pill: 999px;
|
||||
|
||||
/* Motion */
|
||||
--motion-fast: 0.18s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
--motion-base: 0.3s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
--motion-reveal-opacity: 0.3s ease;
|
||||
--motion-reveal-transform: 0.45s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
|
||||
/* Layout */
|
||||
--max-w: 1280px;
|
||||
--space-container-x: var(--space-9);
|
||||
--space-container-x-tablet: 30px;
|
||||
--space-container-x: clamp(var(--space-6), 4vw, var(--space-9));
|
||||
--space-container-x-mobile: var(--space-6);
|
||||
--measure-reading: 68ch;
|
||||
--measure-section: 46rem;
|
||||
--measure-wide: 74rem;
|
||||
/* Section vertical rhythm — two predictable tiers:
|
||||
feature (80) for key sections, standard (72) for the rest.
|
||||
All tiers collapse to a single value on mobile for an even flow. */
|
||||
--space-section-featured-y: var(--space-12);
|
||||
--space-section-support-y: var(--space-11);
|
||||
--space-section-form-y: var(--space-11);
|
||||
--space-section-page-y: var(--space-11);
|
||||
--space-section-featured-y: clamp(var(--space-12), 8vw, var(--space-14));
|
||||
--space-section-support-y: clamp(var(--space-11), 7vw, var(--space-13));
|
||||
--space-section-form-y: clamp(var(--space-11), 7vw, var(--space-14));
|
||||
--space-section-page-y: clamp(var(--space-11), 6vw, var(--space-13));
|
||||
--space-section-mobile-y: var(--space-8);
|
||||
--space-hero-inner-bottom: var(--space-7);
|
||||
--space-hero-inner-bottom: clamp(var(--space-7), 5vw, var(--space-10));
|
||||
|
||||
/* Typography */
|
||||
--font-body: 'Readex Pro', sans-serif;
|
||||
--font-head: 'Unbounded', sans-serif;
|
||||
--heading-section-size: clamp(30px, 4.6vw, 44px);
|
||||
|
||||
/* Type scale */
|
||||
--text-display: clamp(40px, 5vw, 56px);
|
||||
--text-h1: clamp(34px, 4vw, 44px);
|
||||
--text-h2: clamp(28px, 3vw, 36px);
|
||||
--text-h3: clamp(20px, 2vw, 24px);
|
||||
--text-body-lead: 17px;
|
||||
--text-body: 16px;
|
||||
--text-small: 13px;
|
||||
|
||||
/* Type weights */
|
||||
--weight-display: 800;
|
||||
--weight-heading: 700;
|
||||
--weight-emphasis: 600;
|
||||
--weight-body: 400;
|
||||
|
||||
/* Type tracking */
|
||||
--tracking-display: -0.035em;
|
||||
--tracking-heading: -0.02em;
|
||||
--tracking-eyebrow: 0.08em;
|
||||
|
||||
/* Legacy aliases — kept until consumers migrate */
|
||||
--heading-section-size: var(--text-h1);
|
||||
--heading-card-size: 20px;
|
||||
--heading-card-size-mobile: 18px;
|
||||
--body-lead-size: 17px;
|
||||
--body-lead-size: var(--text-body-lead);
|
||||
--body-lead-size-mobile: 15px;
|
||||
--body-copy-size: 16px;
|
||||
--body-copy-size: var(--text-body);
|
||||
--body-copy-size-mobile: 15px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
:root {
|
||||
--space-section-featured-y: var(--space-section-mobile-y);
|
||||
--space-section-featured-y: var(--space-10);
|
||||
--space-section-support-y: var(--space-section-mobile-y);
|
||||
--space-section-form-y: var(--space-section-mobile-y);
|
||||
--space-section-page-y: var(--space-section-mobile-y);
|
||||
|
||||
@@ -50,6 +50,11 @@ export interface HeroContent {
|
||||
imageUrl: string;
|
||||
desktopImageUrl?: string;
|
||||
imageAlt: string;
|
||||
imageWidth?: number;
|
||||
imageHeight?: number;
|
||||
imageWebpUrl?: string;
|
||||
desktopImageWebpUrl?: string;
|
||||
seoHeading?: string;
|
||||
}
|
||||
|
||||
export interface IntroContent {
|
||||
@@ -80,6 +85,9 @@ export interface TestimonialContent {
|
||||
quote: string;
|
||||
reviewer: string;
|
||||
detail: string;
|
||||
type: 'Google' | 'Client';
|
||||
service?: string;
|
||||
showInSlider?: boolean;
|
||||
imageUrl?: string;
|
||||
}
|
||||
|
||||
@@ -146,6 +154,8 @@ export interface ServicePageContent {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
paragraphs: string[];
|
||||
introEyebrow?: string;
|
||||
introHeading?: string;
|
||||
imageUrl: string;
|
||||
imageAlt: string;
|
||||
chips?: HeroChip[];
|
||||
@@ -171,21 +181,41 @@ export interface ServicePageContent {
|
||||
intro?: string;
|
||||
items: ServiceBenefit[];
|
||||
};
|
||||
faq?: {
|
||||
title?: string;
|
||||
intro?: string;
|
||||
items: FaqItem[];
|
||||
};
|
||||
testimonialsHeading: string;
|
||||
booking: BookingContent;
|
||||
}
|
||||
|
||||
export interface PricingPageSectionMeta {
|
||||
label: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface PricingPageSection {
|
||||
title: string;
|
||||
icon?: string;
|
||||
blurb?: string;
|
||||
eyebrow?: string;
|
||||
lede?: string;
|
||||
meta?: PricingPageSectionMeta[];
|
||||
detailCta?: CallToAction;
|
||||
plans: ServicePricingPlan[];
|
||||
}
|
||||
|
||||
export interface PricingPageComparison {
|
||||
title: string;
|
||||
intro?: string;
|
||||
paragraphs: string[];
|
||||
}
|
||||
|
||||
export interface PricingPageContent {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
comparison?: PricingPageComparison;
|
||||
sections: PricingPageSection[];
|
||||
testimonialsHeading: string;
|
||||
booking: BookingContent;
|
||||
|
||||
@@ -3,11 +3,14 @@ export function numericPrice(price: string): number {
|
||||
return Number.isFinite(value) ? value : Number.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
export function decoratePlans<T extends { price: string }>(plans: T[]) {
|
||||
const sorted = [...plans]
|
||||
.map((plan, index) => ({ plan, index, value: numericPrice(plan.price) }))
|
||||
.sort((a, b) => a.value - b.value || a.index - b.index);
|
||||
export function decoratePlans<T extends { price: string; period: string }>(plans: T[]) {
|
||||
const enriched = plans.map((plan, index) => ({
|
||||
plan,
|
||||
index,
|
||||
value: numericPrice(plan.price)
|
||||
}));
|
||||
|
||||
const sorted = [...enriched].sort((a, b) => a.value - b.value || a.index - b.index);
|
||||
const cheapestIndex = sorted[0]?.index ?? -1;
|
||||
const mobileOrder = new Map(sorted.map((entry, order) => [entry.index, order]));
|
||||
|
||||
|
||||
@@ -12,19 +12,12 @@
|
||||
import '@fontsource/readex-pro/latin-500.css';
|
||||
import '@fontsource/readex-pro/latin-600.css';
|
||||
import '@fontsource/readex-pro/latin-700.css';
|
||||
import '@fontsource/roboto/latin-400.css';
|
||||
import '@fontsource/roboto/latin-500.css';
|
||||
import '@fontsource/roboto/latin-700.css';
|
||||
import '@fontsource/noto-sans/latin-400.css';
|
||||
import '@fontsource/noto-sans/latin-500.css';
|
||||
import '@fontsource/noto-sans/latin-700.css';
|
||||
import unbounded700Woff2 from '@fontsource/unbounded/files/unbounded-latin-700-normal.woff2?url';
|
||||
import unbounded800Woff2 from '@fontsource/unbounded/files/unbounded-latin-800-normal.woff2?url';
|
||||
import '@fontsource/unbounded/latin-400.css';
|
||||
import '@fontsource/unbounded/latin-600.css';
|
||||
import '@fontsource/unbounded/latin-700.css';
|
||||
import '@fontsource/unbounded/latin-800.css';
|
||||
import '@fontsource/fredoka/latin-600.css';
|
||||
import '@fortawesome/fontawesome-free/css/fontawesome.min.css';
|
||||
import '@fortawesome/fontawesome-free/css/solid.min.css';
|
||||
import '@fortawesome/fontawesome-free/css/brands.min.css';
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
import HowItWorksSection from '$lib/components/HowItWorksSection.svelte';
|
||||
import InfoSection from '$lib/components/InfoSection.svelte';
|
||||
import InstagramSection from '$lib/components/InstagramSection.svelte';
|
||||
import IntroStrip from '$lib/components/IntroStrip.svelte';
|
||||
import BookingSection from '$lib/components/BookingSection.svelte';
|
||||
import FounderStorySection from '$lib/components/FounderStorySection.svelte';
|
||||
import ServicesSection from '$lib/components/ServicesSection.svelte';
|
||||
@@ -46,7 +45,7 @@
|
||||
description:
|
||||
'Professional dog walking services across Auckland Central, including pack walks, 1:1 walks, and puppy visits.',
|
||||
url: siteUrl,
|
||||
logo: `${siteUrl}/images/goodwalk-auckland-dog-walking-logo.png`,
|
||||
logo: `${siteUrl}/images/goodwalk-auckland-dog-walking-logo.webp`,
|
||||
image: absoluteUrl(data.content.hero.imageUrl),
|
||||
email: 'info@goodwalk.co.nz',
|
||||
telephone: '+64226421011',
|
||||
@@ -98,18 +97,6 @@
|
||||
},
|
||||
reviewBody: testimonial.quote
|
||||
}))
|
||||
},
|
||||
{
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'FAQPage',
|
||||
mainEntity: data.content.info.faqs.map((faq) => ({
|
||||
'@type': 'Question',
|
||||
name: faq.question,
|
||||
acceptedAnswer: {
|
||||
'@type': 'Answer',
|
||||
text: faq.answer
|
||||
}
|
||||
}))
|
||||
}
|
||||
]
|
||||
: [];
|
||||
@@ -127,11 +114,18 @@
|
||||
imageAlt={content.hero.imageAlt}
|
||||
structuredData={homepageStructuredData}
|
||||
preloadImage={true}
|
||||
preloadImageUrl={content.hero.imageWebpUrl ?? content.hero.imageUrl}
|
||||
preloadImageType={content.hero.imageWebpUrl ? 'image/webp' : ''}
|
||||
preloadImageSrcset={content.hero.imageWebpUrl && content.hero.desktopImageWebpUrl
|
||||
? `${content.hero.imageWebpUrl} 900w, ${content.hero.desktopImageWebpUrl} 1536w`
|
||||
: ''}
|
||||
preloadImageSizes={content.hero.imageWebpUrl && content.hero.desktopImageWebpUrl
|
||||
? '(min-width: 769px) 1536px, 100vw'
|
||||
: ''}
|
||||
/>
|
||||
|
||||
<Header navigation={content.navigation} />
|
||||
<HeroSection hero={content.hero} reviewCta={content.intro.reviewCta} />
|
||||
<IntroStrip intro={content.intro} />
|
||||
<ValuesSection values={content.values} />
|
||||
<ServicesSection services={content.services} />
|
||||
<HowItWorksSection content={content.howItWorks} />
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
export let data: PageData;
|
||||
|
||||
const siteUrl = 'https://www.goodwalk.co.nz';
|
||||
const defaultSeoImage = '/images/auckland-dog-walking-happy-dog-hero.png';
|
||||
const defaultSeoImage = '/images/goodwalk-auckland-happy-dog-hero.webp';
|
||||
const defaultSeoImageAlt = 'Goodwalk Auckland dog walking services';
|
||||
|
||||
function aggregateOfferSchema(plans: { price: string }[]) {
|
||||
@@ -209,7 +209,7 @@
|
||||
imageAlt={seoImageAlt}
|
||||
structuredData={pageStructuredData}
|
||||
preloadImage={preloadHeroImage}
|
||||
noindex={data.page.noindex ?? false}
|
||||
noindex={'noindex' in data.page ? data.page.noindex === true : false}
|
||||
/>
|
||||
|
||||
<Header navigation={data.content.navigation} />
|
||||
|
||||
@@ -16,7 +16,7 @@ describe('static slug route page', () => {
|
||||
['puppy-visits', puppyVisitsContent.hero.title],
|
||||
['our-pricing', ourPricingContent.subtitle],
|
||||
['about', aboutPageContent.sections[0].title],
|
||||
['testimonials', 'What our clients say'],
|
||||
['testimonials', 'Client Testimonials'],
|
||||
['contact-us', "Let's meet!"],
|
||||
['terms-and-conditions', '1. Application of Terms'],
|
||||
['privacy-policy', 'How we collect your information']
|
||||
@@ -62,13 +62,13 @@ describe('static slug route page', () => {
|
||||
}
|
||||
});
|
||||
|
||||
expect(screen.getByLabelText(/General enquiry/i)).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /General enquiry/i })).toBeInTheDocument();
|
||||
|
||||
rerender({
|
||||
data: createStaticRouteData('pack-walks')
|
||||
});
|
||||
|
||||
expect(screen.queryByLabelText(/General enquiry/i)).not.toBeInTheDocument();
|
||||
expect(screen.queryByRole('button', { name: /General enquiry/i })).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the shared FAQ section on the contact page', () => {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
const onboardingHosts = new Set(['onboarding.goodwalk.co.nz']);
|
||||
|
||||
export const load: PageServerLoad = async ({ url }) => {
|
||||
export const load = async ({ url }) => {
|
||||
const hostname = url.hostname.toLowerCase();
|
||||
const isOnboardingHost =
|
||||
onboardingHosts.has(hostname) || url.searchParams.get('preview') === 'contract';
|
||||
@@ -13,6 +12,6 @@ export const load: PageServerLoad = async ({ url }) => {
|
||||
}
|
||||
|
||||
return {
|
||||
isPreview: url.searchParams.get('preview') === 'contract',
|
||||
isPreview: url.searchParams.get('preview') === 'contract'
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,19 +1,14 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { homepageContent } from '$lib/content/homepage';
|
||||
|
||||
const { getHomepageContent, isHomepageHowItWorksEnabled } = vi.hoisted(() => ({
|
||||
getHomepageContent: vi.fn(),
|
||||
isHomepageHowItWorksEnabled: vi.fn()
|
||||
const { getHomepageContent } = vi.hoisted(() => ({
|
||||
getHomepageContent: vi.fn()
|
||||
}));
|
||||
|
||||
vi.mock('$lib/server/content', () => ({
|
||||
getHomepageContent
|
||||
}));
|
||||
|
||||
vi.mock('$lib/server/feature-flags', () => ({
|
||||
isHomepageHowItWorksEnabled
|
||||
}));
|
||||
|
||||
import { load } from './+page.server';
|
||||
|
||||
function createLoadEvent(url = 'https://www.goodwalk.co.nz/') {
|
||||
@@ -25,23 +20,19 @@ function createLoadEvent(url = 'https://www.goodwalk.co.nz/') {
|
||||
describe('home page server load', () => {
|
||||
it('returns homepage content', async () => {
|
||||
getHomepageContent.mockResolvedValue(homepageContent);
|
||||
isHomepageHowItWorksEnabled.mockReturnValue(false);
|
||||
|
||||
await expect(load(createLoadEvent())).resolves.toEqual({
|
||||
siteVariant: 'marketing',
|
||||
content: homepageContent,
|
||||
howItWorksEnabled: false
|
||||
content: homepageContent
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the how it works flag when enabled', async () => {
|
||||
it('returns the onboarding variant on the onboarding host', async () => {
|
||||
getHomepageContent.mockResolvedValue(homepageContent);
|
||||
isHomepageHowItWorksEnabled.mockReturnValue(true);
|
||||
|
||||
await expect(load(createLoadEvent())).resolves.toEqual({
|
||||
siteVariant: 'marketing',
|
||||
content: homepageContent,
|
||||
howItWorksEnabled: true
|
||||
await expect(load(createLoadEvent('https://onboarding.goodwalk.co.nz/'))).resolves.toEqual({
|
||||
siteVariant: 'onboarding',
|
||||
isPreview: false
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,7 +20,7 @@ describe('home page route', () => {
|
||||
const info = document.getElementById('info');
|
||||
const booking = document.getElementById('newlead');
|
||||
|
||||
expect(screen.getByText(homepageContent.hero.highlight)).toBeInTheDocument();
|
||||
expect(screen.getAllByText(homepageContent.hero.highlight).length).toBeGreaterThan(0);
|
||||
expect(screen.getByText(homepageContent.intro.text)).toBeInTheDocument();
|
||||
expect(screen.getByText('Calmer dogs. Clearer routines. Less worry.')).toBeInTheDocument();
|
||||
expect(screen.getByText(homepageContent.howItWorks.title)).toBeInTheDocument();
|
||||
@@ -36,6 +36,6 @@ describe('home page route', () => {
|
||||
expect(screen.queryByLabelText(/General enquiry/i)).not.toBeInTheDocument();
|
||||
expect(document.title).toBe(homepageContent.seo.title);
|
||||
expect(document.head.innerHTML).toContain('FAQPage');
|
||||
expect(document.head.innerHTML).toContain('https://www.goodwalk.co.nz/images/auckland-dog-walking-happy-dog-hero.png');
|
||||
expect(document.head.innerHTML).toContain('https://www.goodwalk.co.nz/images/goodwalk-auckland-happy-dog-hero.webp');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { getSharedPageContent } from '$lib/server/content';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async () => {
|
||||
return {
|
||||
content: await getSharedPageContent()
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,179 @@
|
||||
<script lang="ts">
|
||||
import Header from '$lib/components/Header.svelte';
|
||||
import Footer from '$lib/components/Footer.svelte';
|
||||
import SeoHead from '$lib/components/SeoHead.svelte';
|
||||
import { locationPages } from '$lib/content/locations';
|
||||
import { buildBreadcrumb } from '$lib/seo';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
const title = 'Auckland Dog Walking Locations | Goodwalk Service Areas';
|
||||
const description =
|
||||
'Goodwalk provides pack walks, 1:1 walks, and puppy visits across 17 Auckland Central suburbs. Find your suburb and see local parks, walking routes, and how we serve your area.';
|
||||
const canonicalPath = '/locations';
|
||||
|
||||
const structuredData = [
|
||||
buildBreadcrumb([
|
||||
{ name: 'Home', path: '/' },
|
||||
{ name: 'Locations', path: canonicalPath }
|
||||
])
|
||||
];
|
||||
</script>
|
||||
|
||||
<SeoHead {title} {description} {canonicalPath} {structuredData} />
|
||||
|
||||
<Header navigation={data.content.navigation} />
|
||||
|
||||
<main id="locations-hub">
|
||||
<section class="hub-hero">
|
||||
<div class="hub-inner">
|
||||
<p class="hub-kicker">Service Areas</p>
|
||||
<h1>Where Goodwalk walks dogs in Auckland</h1>
|
||||
<p class="hub-lead">
|
||||
We cover 17 suburbs across Auckland Central with pack walks, 1:1 walks, and
|
||||
puppy visits — including free pickup and drop-off. Choose your suburb below
|
||||
to see local parks and walking routes.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="hub-grid-section">
|
||||
<div class="hub-inner">
|
||||
<ul class="hub-grid">
|
||||
{#each locationPages as loc}
|
||||
<li>
|
||||
<a class="hub-card" href="/locations/{loc.slug}">
|
||||
<h2>{loc.suburb}</h2>
|
||||
<p>
|
||||
{loc.parks
|
||||
.slice(0, 3)
|
||||
.map((p) => p.name)
|
||||
.join(' · ')}
|
||||
</p>
|
||||
<span class="hub-card-cta">View {loc.suburb} →</span>
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="hub-cta">
|
||||
<div class="hub-inner">
|
||||
<h2>Live in a nearby suburb?</h2>
|
||||
<p>There's a good chance we can still help — get in touch.</p>
|
||||
<a class="btn btn-yellow" href="/contact-us">Book a Meet & Greet</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<Footer footer={data.content.footer} />
|
||||
|
||||
<style>
|
||||
#locations-hub {
|
||||
background: #f7f5f0;
|
||||
}
|
||||
|
||||
.hub-inner {
|
||||
max-width: 1180px;
|
||||
margin: 0 auto;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.hub-hero {
|
||||
padding: 72px 0 32px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hub-kicker {
|
||||
margin: 0 0 12px;
|
||||
color: var(--gw-green);
|
||||
font-family: var(--font-head);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.hub-hero h1 {
|
||||
margin: 0 0 18px;
|
||||
}
|
||||
|
||||
.hub-lead {
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
color: #4c5056;
|
||||
}
|
||||
|
||||
.hub-grid-section {
|
||||
padding: 32px 0 64px;
|
||||
}
|
||||
|
||||
.hub-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
|
||||
gap: 18px;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.hub-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
height: 100%;
|
||||
padding: 22px 22px 20px;
|
||||
border-radius: 20px;
|
||||
background: #fff;
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(17, 20, 24, 0.05),
|
||||
0 12px 28px rgba(17, 20, 24, 0.05);
|
||||
color: var(--gw-green);
|
||||
text-decoration: none;
|
||||
transition: transform 0.18s ease, box-shadow 0.18s ease;
|
||||
}
|
||||
|
||||
.hub-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(33, 48, 33, 0.18),
|
||||
0 18px 36px rgba(17, 20, 24, 0.09);
|
||||
}
|
||||
|
||||
.hub-card h2 {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.hub-card p {
|
||||
margin: 0;
|
||||
color: #5f6369;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.hub-card-cta {
|
||||
margin-top: auto;
|
||||
padding-top: 6px;
|
||||
color: var(--gw-green);
|
||||
font-family: var(--font-head);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.hub-cta {
|
||||
padding: 48px 0 80px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hub-cta h2 {
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.hub-cta p {
|
||||
margin: 0 0 20px;
|
||||
color: #4c5056;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,960 @@
|
||||
<!--
|
||||
/meet-greet-v2 — A/B test variant of the Meet & Greet booking flow.
|
||||
|
||||
Standalone "sticker stack" multi-step wizard. NOT linked from the main
|
||||
nav. Submits to the same /api/submit backend as the live contact form
|
||||
(BookingSection.svelte) using an identical payload shape, so the
|
||||
downstream handler treats it the same. Marked noindex so the variant
|
||||
does not appear in search results during the test.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
import Footer from '$lib/components/Footer.svelte';
|
||||
import Header from '$lib/components/Header.svelte';
|
||||
import Icon from '$lib/components/Icon.svelte';
|
||||
import { homepageContent } from '$lib/content/homepage';
|
||||
|
||||
type SuccessModalComponentType = typeof import('$lib/components/SuccessModal.svelte').default;
|
||||
type ErrorModalComponentType = typeof import('$lib/components/ErrorModal.svelte').default;
|
||||
|
||||
const navigation = homepageContent.navigation;
|
||||
const footerContent = homepageContent.footer;
|
||||
|
||||
const visitStartedStorageKey = 'goodwalk_visit_started_at';
|
||||
const journeyStorageKey = 'goodwalk_journey';
|
||||
const maxJourneyEntries = 8;
|
||||
|
||||
const serviceOptions = ['Tiny Gang Pack Walks', '1:1 Walks', 'Puppy Visits', 'Other Services'];
|
||||
const dogShortcuts = ['puppy', 'senior', 'rescue', 'shy'];
|
||||
|
||||
let step = 1;
|
||||
const totalSteps = 4;
|
||||
|
||||
let dogDetails = '';
|
||||
let selectedServices: string[] = [];
|
||||
let message = '';
|
||||
let fullName = '';
|
||||
let email = '';
|
||||
let phone = '';
|
||||
let location = '';
|
||||
|
||||
let website = ''; // honeypot
|
||||
let formStartedAt = 0;
|
||||
let visitStartedAt = 0;
|
||||
let pageEnteredAt = 0;
|
||||
let firstInteractionAt = 0;
|
||||
let sendClickedAt = 0;
|
||||
let stepChanges = 0;
|
||||
let journey: string[] = [];
|
||||
|
||||
let errors: Record<string, string> = {};
|
||||
let submitting = false;
|
||||
let submitted = false;
|
||||
let showErrorModal = false;
|
||||
let submitErrorDetail = '';
|
||||
|
||||
let SuccessModalComponent: SuccessModalComponentType | null = null;
|
||||
let ErrorModalComponent: ErrorModalComponentType | null = null;
|
||||
|
||||
$: dogFirstWord = dogDetails.trim().split(/[,\s]/)[0] || 'your dog';
|
||||
$: dogNameDisplay = dogFirstWord
|
||||
? dogFirstWord.charAt(0).toLocaleUpperCase() + dogFirstWord.slice(1)
|
||||
: 'your dog';
|
||||
|
||||
$: stepCopy = [
|
||||
{
|
||||
eyebrow: 'Question one',
|
||||
heading: "Who's the star?",
|
||||
helper: 'Tell us your dog in one line — name, age, breed. We will use this to point you toward the right walk.'
|
||||
},
|
||||
{
|
||||
eyebrow: 'Question two',
|
||||
heading: `What's ${dogNameDisplay} after?`,
|
||||
helper: 'Pick everything you are open to. We will recommend the best fit when we reply.'
|
||||
},
|
||||
{
|
||||
eyebrow: 'Question three',
|
||||
heading: 'Anything we should know?',
|
||||
helper: 'Health quirks, anxiety triggers, weekly schedule, dream outcome — anything that helps us prepare.'
|
||||
},
|
||||
{
|
||||
eyebrow: 'Question four',
|
||||
heading: 'How do we reach you?',
|
||||
helper: 'A real person replies within 24 hours, usually sooner.'
|
||||
}
|
||||
];
|
||||
|
||||
$: successPetName = dogNameDisplay;
|
||||
|
||||
$: if (submitted) {
|
||||
ensureSuccessModal();
|
||||
}
|
||||
$: if (showErrorModal) {
|
||||
ensureErrorModal();
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
const now = Date.now();
|
||||
formStartedAt = now;
|
||||
pageEnteredAt = now;
|
||||
visitStartedAt = readOrCreateVisitStartedAt(now);
|
||||
journey = updateJourneySnapshot(window.location.pathname, window.location.search);
|
||||
});
|
||||
|
||||
function readOrCreateVisitStartedAt(fallback: number) {
|
||||
try {
|
||||
const raw = window.sessionStorage.getItem(visitStartedStorageKey);
|
||||
const parsed = raw ? Number(raw) : NaN;
|
||||
if (Number.isFinite(parsed) && parsed > 0) return parsed;
|
||||
window.sessionStorage.setItem(visitStartedStorageKey, String(fallback));
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function updateJourneySnapshot(pathname: string, search: string) {
|
||||
const nextEntry = `${pathname}${search}`;
|
||||
try {
|
||||
const raw = window.sessionStorage.getItem(journeyStorageKey);
|
||||
const previous = raw ? (JSON.parse(raw) as string[]) : [];
|
||||
const cleaned = previous.filter((value) => typeof value === 'string' && value.trim());
|
||||
const deduped = cleaned[cleaned.length - 1] === nextEntry ? cleaned : [...cleaned, nextEntry];
|
||||
const nextJourney = deduped.slice(-maxJourneyEntries);
|
||||
window.sessionStorage.setItem(journeyStorageKey, JSON.stringify(nextJourney));
|
||||
return nextJourney;
|
||||
} catch {
|
||||
return [nextEntry];
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureSuccessModal() {
|
||||
if (SuccessModalComponent) return;
|
||||
SuccessModalComponent = (await import('$lib/components/SuccessModal.svelte')).default;
|
||||
}
|
||||
async function ensureErrorModal() {
|
||||
if (ErrorModalComponent) return;
|
||||
ErrorModalComponent = (await import('$lib/components/ErrorModal.svelte')).default;
|
||||
}
|
||||
|
||||
function noteInteraction() {
|
||||
if (!firstInteractionAt) firstInteractionAt = Date.now();
|
||||
}
|
||||
|
||||
function clearError(field: string) {
|
||||
if (errors[field]) errors = { ...errors, [field]: '' };
|
||||
}
|
||||
|
||||
function appendShortcut(token: string) {
|
||||
noteInteraction();
|
||||
const current = dogDetails.trim();
|
||||
if (current.toLowerCase().includes(token.toLowerCase())) return;
|
||||
dogDetails = current ? `${current.replace(/[,\s]+$/, '')}, ${token}` : token;
|
||||
clearError('dogDetails');
|
||||
}
|
||||
|
||||
function toggleService(service: string) {
|
||||
noteInteraction();
|
||||
if (selectedServices.includes(service)) {
|
||||
selectedServices = selectedServices.filter((s) => s !== service);
|
||||
} else {
|
||||
selectedServices = [...selectedServices, service];
|
||||
}
|
||||
clearError('services');
|
||||
}
|
||||
|
||||
function validateEmail(raw: string): string {
|
||||
const value = raw.trim();
|
||||
if (!value) return 'Please enter your email';
|
||||
if (!value.includes('@')) return 'Email is missing the @ sign';
|
||||
const re = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
|
||||
if (!re.test(value)) return "That email doesn't look quite right";
|
||||
return '';
|
||||
}
|
||||
|
||||
function validateStep(target: number): boolean {
|
||||
const next: Record<string, string> = {};
|
||||
if (target === 1 && !dogDetails.trim()) {
|
||||
next.dogDetails = "Tell us a little about your dog so we can match the right walk.";
|
||||
}
|
||||
if (target === 2 && selectedServices.length === 0) {
|
||||
next.services = 'Pick at least one service to continue.';
|
||||
}
|
||||
if (target === 4) {
|
||||
if (!fullName.trim()) next.fullName = 'Please enter your full name';
|
||||
const emailError = validateEmail(email);
|
||||
if (emailError) next.email = emailError;
|
||||
if (!phone.trim()) next.phone = 'Please enter your contact number';
|
||||
if (!location.trim()) next.location = 'Please enter your suburb';
|
||||
}
|
||||
errors = next;
|
||||
return Object.keys(next).length === 0;
|
||||
}
|
||||
|
||||
function goNext() {
|
||||
noteInteraction();
|
||||
if (!validateStep(step)) return;
|
||||
if (step < totalSteps) {
|
||||
step += 1;
|
||||
stepChanges += 1;
|
||||
}
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
noteInteraction();
|
||||
if (step > 1) {
|
||||
step -= 1;
|
||||
stepChanges += 1;
|
||||
errors = {};
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
noteInteraction();
|
||||
if (!validateStep(4)) return;
|
||||
|
||||
submitting = true;
|
||||
sendClickedAt = Date.now();
|
||||
submitErrorDetail = '';
|
||||
showErrorModal = false;
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/submit', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
enquiryType: 'booking',
|
||||
fullName,
|
||||
email,
|
||||
phone,
|
||||
petName: dogDetails,
|
||||
location,
|
||||
message,
|
||||
services: selectedServices,
|
||||
website,
|
||||
formStartedAt,
|
||||
visitStartedAt,
|
||||
pageEnteredAt,
|
||||
firstInteractionAt,
|
||||
sendClickedAt,
|
||||
stepChanges,
|
||||
journey,
|
||||
referrer: typeof document !== 'undefined' ? document.referrer : '',
|
||||
page: typeof window !== 'undefined' ? window.location.href : '',
|
||||
variant: 'meet-greet-v2'
|
||||
})
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const body = await res.json().catch(() => ({}));
|
||||
const detail =
|
||||
typeof body?.detail === 'string'
|
||||
? body.detail
|
||||
: body?.detail?.message ?? body?.message ?? `Server responded with ${res.status}`;
|
||||
throw new Error(detail);
|
||||
}
|
||||
|
||||
submitted = true;
|
||||
} catch (err: unknown) {
|
||||
submitErrorDetail = err instanceof Error ? err.message : String(err);
|
||||
showErrorModal = true;
|
||||
} finally {
|
||||
submitting = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Book your free Meet & Greet | Goodwalk</title>
|
||||
<meta name="robots" content="noindex, nofollow" />
|
||||
<meta name="description" content="Book a free Goodwalk Meet & Greet — short, guided application for Auckland dog owners." />
|
||||
</svelte:head>
|
||||
|
||||
<Header {navigation} />
|
||||
|
||||
<main class="mgv2-page">
|
||||
{#if submitted && SuccessModalComponent}
|
||||
<svelte:component
|
||||
this={SuccessModalComponent}
|
||||
firstName={fullName.split(' ')[0]}
|
||||
petName={successPetName}
|
||||
{email}
|
||||
enquiryType="booking"
|
||||
onClose={() => (submitted = false)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if showErrorModal && ErrorModalComponent}
|
||||
<svelte:component
|
||||
this={ErrorModalComponent}
|
||||
detail={submitErrorDetail}
|
||||
enquiryType="booking"
|
||||
onClose={() => (showErrorModal = false)}
|
||||
onRetry={() => (showErrorModal = false)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<section class="mgv2-shell">
|
||||
<header class="mgv2-intro">
|
||||
<span class="mgv2-kicker">Free Meet & Greet</span>
|
||||
<h1>Start with the right fit for your dog.</h1>
|
||||
<p>Four short questions. A real reply within 24 hours.</p>
|
||||
</header>
|
||||
|
||||
<div
|
||||
class="mgv2-progress"
|
||||
role="progressbar"
|
||||
aria-valuemin="1"
|
||||
aria-valuemax={totalSteps}
|
||||
aria-valuenow={step}
|
||||
>
|
||||
{#each Array.from({ length: totalSteps }) as _, index}
|
||||
<span class="mgv2-progress-dot" class:active={index + 1 === step}></span>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="mgv2-stack">
|
||||
<span class="mgv2-decoy mgv2-decoy-left" aria-hidden="true"></span>
|
||||
<span class="mgv2-decoy mgv2-decoy-right" aria-hidden="true"></span>
|
||||
|
||||
<article class="mgv2-card">
|
||||
<span class="mgv2-badge" aria-hidden="true">
|
||||
<Icon name="fas fa-paw" />
|
||||
</span>
|
||||
|
||||
<input
|
||||
bind:value={website}
|
||||
type="text"
|
||||
name="website"
|
||||
class="mgv2-honeypot"
|
||||
tabindex="-1"
|
||||
autocomplete="new-password"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
{#key step}
|
||||
<div class="mgv2-step" in:fade={{ duration: 200 }} out:fade={{ duration: 120 }}>
|
||||
<span class="mgv2-eyebrow">{stepCopy[step - 1].eyebrow}</span>
|
||||
<h2 class="mgv2-heading">{stepCopy[step - 1].heading}</h2>
|
||||
<p class="mgv2-helper">{stepCopy[step - 1].helper}</p>
|
||||
|
||||
{#if step === 1}
|
||||
<label class="mgv2-field" for="mgv2-dog">
|
||||
<span class="mgv2-label">Your dog, in a line</span>
|
||||
<input
|
||||
bind:value={dogDetails}
|
||||
on:input={() => clearError('dogDetails')}
|
||||
id="mgv2-dog"
|
||||
type="text"
|
||||
placeholder="Teddy, 3, schnoodle"
|
||||
class:mgv2-input-invalid={errors.dogDetails}
|
||||
autocomplete="off"
|
||||
/>
|
||||
{#if errors.dogDetails}
|
||||
<span class="mgv2-error">{errors.dogDetails}</span>
|
||||
{/if}
|
||||
</label>
|
||||
|
||||
<div class="mgv2-chips" aria-label="Quick add">
|
||||
{#each dogShortcuts as token}
|
||||
<button
|
||||
type="button"
|
||||
class="mgv2-chip"
|
||||
on:click={() => appendShortcut(token)}
|
||||
>+ {token}</button>
|
||||
{/each}
|
||||
</div>
|
||||
{:else if step === 2}
|
||||
<div
|
||||
class="mgv2-service-grid"
|
||||
class:mgv2-service-grid-invalid={errors.services}
|
||||
role="group"
|
||||
aria-label="Service interest"
|
||||
>
|
||||
{#each serviceOptions as service}
|
||||
{@const checked = selectedServices.includes(service)}
|
||||
<button
|
||||
type="button"
|
||||
class="mgv2-service"
|
||||
class:active={checked}
|
||||
aria-pressed={checked}
|
||||
on:click={() => toggleService(service)}
|
||||
>
|
||||
<span class="mgv2-service-check" aria-hidden="true">
|
||||
{#if checked}<Icon name="fas fa-check" />{/if}
|
||||
</span>
|
||||
<span class="mgv2-service-label">{service}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{#if errors.services}
|
||||
<span class="mgv2-error">{errors.services}</span>
|
||||
{/if}
|
||||
{:else if step === 3}
|
||||
<label class="mgv2-field" for="mgv2-message">
|
||||
<span class="mgv2-label">Notes for us</span>
|
||||
<textarea
|
||||
bind:value={message}
|
||||
id="mgv2-message"
|
||||
rows="5"
|
||||
placeholder="For example: nervous around bigger dogs, prefers shorter walks, recently rescued."
|
||||
></textarea>
|
||||
</label>
|
||||
{:else if step === 4}
|
||||
<div class="mgv2-grid-two">
|
||||
<label class="mgv2-field" for="mgv2-name">
|
||||
<span class="mgv2-label">Full name</span>
|
||||
<input
|
||||
bind:value={fullName}
|
||||
on:input={() => clearError('fullName')}
|
||||
id="mgv2-name"
|
||||
type="text"
|
||||
placeholder="Your full name"
|
||||
class:mgv2-input-invalid={errors.fullName}
|
||||
autocomplete="name"
|
||||
/>
|
||||
{#if errors.fullName}<span class="mgv2-error">{errors.fullName}</span>{/if}
|
||||
</label>
|
||||
|
||||
<label class="mgv2-field" for="mgv2-email">
|
||||
<span class="mgv2-label">Email</span>
|
||||
<input
|
||||
bind:value={email}
|
||||
on:input={() => clearError('email')}
|
||||
id="mgv2-email"
|
||||
type="email"
|
||||
placeholder="you@example.com"
|
||||
class:mgv2-input-invalid={errors.email}
|
||||
autocomplete="email"
|
||||
/>
|
||||
{#if errors.email}<span class="mgv2-error">{errors.email}</span>{/if}
|
||||
</label>
|
||||
|
||||
<label class="mgv2-field" for="mgv2-phone">
|
||||
<span class="mgv2-label">Phone</span>
|
||||
<input
|
||||
bind:value={phone}
|
||||
on:input={() => clearError('phone')}
|
||||
id="mgv2-phone"
|
||||
type="tel"
|
||||
placeholder="021 123 4567"
|
||||
class:mgv2-input-invalid={errors.phone}
|
||||
autocomplete="tel"
|
||||
/>
|
||||
{#if errors.phone}<span class="mgv2-error">{errors.phone}</span>{/if}
|
||||
</label>
|
||||
|
||||
<label class="mgv2-field" for="mgv2-location">
|
||||
<span class="mgv2-label">Suburb</span>
|
||||
<input
|
||||
bind:value={location}
|
||||
on:input={() => clearError('location')}
|
||||
id="mgv2-location"
|
||||
type="text"
|
||||
placeholder="For example, Grey Lynn"
|
||||
class:mgv2-input-invalid={errors.location}
|
||||
autocomplete="address-level2"
|
||||
/>
|
||||
{#if errors.location}<span class="mgv2-error">{errors.location}</span>{/if}
|
||||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="mgv2-actions">
|
||||
{#if step > 1}
|
||||
<button type="button" class="mgv2-back" on:click={goBack}>
|
||||
<Icon name="fas fa-arrow-left" />
|
||||
Back
|
||||
</button>
|
||||
{:else}
|
||||
<span class="mgv2-back-spacer" aria-hidden="true"></span>
|
||||
{/if}
|
||||
|
||||
{#if step < totalSteps}
|
||||
<button type="button" class="mgv2-next" on:click={goNext}>
|
||||
Next
|
||||
<Icon name="fas fa-arrow-right" />
|
||||
</button>
|
||||
{:else}
|
||||
<button
|
||||
type="button"
|
||||
class="mgv2-next"
|
||||
on:click={handleSubmit}
|
||||
disabled={submitting}
|
||||
>
|
||||
{submitting ? 'Sending…' : 'Send Meet & Greet request'}
|
||||
{#if !submitting}<Icon name="fas fa-arrow-right" />{/if}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/key}
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<p class="mgv2-footnote">
|
||||
No payment, no pressure. Reply from a real person within 24 hours.
|
||||
</p>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<Footer footer={footerContent} />
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--mgv2-yellow: #FFC700;
|
||||
--mgv2-cream: #FAF8F1;
|
||||
--mgv2-ink: #0b0b0b;
|
||||
--mgv2-line: rgba(11, 11, 11, 0.08);
|
||||
--mgv2-muted: rgba(11, 11, 11, 0.55);
|
||||
}
|
||||
|
||||
.mgv2-page {
|
||||
background: var(--mgv2-cream);
|
||||
min-height: 100vh;
|
||||
padding: 56px 24px 88px;
|
||||
overflow-x: clip;
|
||||
}
|
||||
|
||||
.mgv2-shell {
|
||||
width: 100%;
|
||||
max-width: 540px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mgv2-intro {
|
||||
margin: 0 auto 28px;
|
||||
max-width: 480px;
|
||||
}
|
||||
|
||||
.mgv2-kicker {
|
||||
display: inline-block;
|
||||
margin-bottom: 12px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 999px;
|
||||
background: rgba(11, 11, 11, 0.05);
|
||||
color: var(--mgv2-ink);
|
||||
font-family: var(--font-head);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.mgv2-intro h1 {
|
||||
margin: 0 0 8px;
|
||||
color: var(--mgv2-ink);
|
||||
font-family: var(--font-head);
|
||||
font-size: clamp(28px, 4vw, 38px);
|
||||
font-weight: 800;
|
||||
line-height: 1.08;
|
||||
letter-spacing: -0.03em;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.mgv2-intro p {
|
||||
margin: 0;
|
||||
color: var(--mgv2-muted);
|
||||
font-size: 15px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.mgv2-progress {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 28px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 999px;
|
||||
background: rgba(11, 11, 11, 0.04);
|
||||
}
|
||||
|
||||
.mgv2-progress-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 999px;
|
||||
background: rgba(11, 11, 11, 0.18);
|
||||
transition:
|
||||
width 0.22s cubic-bezier(0.22, 1, 0.36, 1),
|
||||
background 0.18s ease;
|
||||
}
|
||||
|
||||
.mgv2-progress-dot.active {
|
||||
width: 32px;
|
||||
background: var(--mgv2-yellow);
|
||||
}
|
||||
|
||||
.mgv2-stack {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.mgv2-decoy {
|
||||
position: absolute;
|
||||
inset: 14px -8px auto -8px;
|
||||
height: 100%;
|
||||
border-radius: 16px;
|
||||
background: #fff;
|
||||
border: 1px solid var(--mgv2-line);
|
||||
box-shadow: 0 18px 36px rgba(11, 11, 11, 0.08);
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.mgv2-decoy-left {
|
||||
transform: rotate(-3deg) translateY(-6px);
|
||||
}
|
||||
|
||||
.mgv2-decoy-right {
|
||||
transform: rotate(2deg) translateY(2px);
|
||||
}
|
||||
|
||||
.mgv2-card {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
padding: 36px 28px 28px;
|
||||
border-radius: 16px;
|
||||
background: #fff;
|
||||
border: 1px solid var(--mgv2-line);
|
||||
box-shadow:
|
||||
0 24px 60px rgba(11, 11, 11, 0.12),
|
||||
0 4px 14px rgba(11, 11, 11, 0.04);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.mgv2-badge {
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
right: 18px;
|
||||
z-index: 3;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 999px;
|
||||
background: var(--mgv2-yellow);
|
||||
color: var(--mgv2-ink);
|
||||
box-shadow:
|
||||
0 12px 24px rgba(255, 199, 0, 0.36),
|
||||
inset 0 0 0 2px rgba(11, 11, 11, 0.06);
|
||||
transform: rotate(8deg);
|
||||
}
|
||||
|
||||
.mgv2-badge :global(.icon) {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.mgv2-honeypot {
|
||||
position: absolute;
|
||||
left: -10000px;
|
||||
top: -10000px;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.mgv2-step {
|
||||
display: grid;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.mgv2-eyebrow {
|
||||
color: var(--mgv2-muted);
|
||||
font-family: var(--font-head);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.16em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.mgv2-heading {
|
||||
margin: -4px 0 0;
|
||||
color: var(--mgv2-ink);
|
||||
font-family: var(--font-head);
|
||||
font-size: 24px;
|
||||
font-weight: 800;
|
||||
line-height: 1.16;
|
||||
letter-spacing: -0.02em;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.mgv2-helper {
|
||||
margin: -4px 0 6px;
|
||||
color: var(--mgv2-muted);
|
||||
font-size: 14px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.mgv2-field {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.mgv2-label {
|
||||
color: var(--mgv2-ink);
|
||||
font-family: var(--font-head);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.mgv2-field input,
|
||||
.mgv2-field textarea {
|
||||
width: 100%;
|
||||
padding: 13px 14px;
|
||||
border: 1px solid var(--mgv2-line);
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
color: var(--mgv2-ink);
|
||||
font-family: var(--font-body);
|
||||
font-size: 16px;
|
||||
line-height: 1.35;
|
||||
transition: border-color 0.18s ease, box-shadow 0.18s ease;
|
||||
}
|
||||
|
||||
.mgv2-field textarea {
|
||||
resize: vertical;
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.mgv2-field input:focus,
|
||||
.mgv2-field textarea:focus {
|
||||
outline: none;
|
||||
border-color: rgba(11, 11, 11, 0.45);
|
||||
box-shadow: 0 0 0 4px rgba(255, 199, 0, 0.22);
|
||||
}
|
||||
|
||||
.mgv2-input-invalid,
|
||||
.mgv2-input-invalid:focus {
|
||||
border-color: rgba(192, 32, 38, 0.6);
|
||||
box-shadow: 0 0 0 4px rgba(192, 32, 38, 0.08);
|
||||
}
|
||||
|
||||
.mgv2-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.mgv2-chip {
|
||||
appearance: none;
|
||||
padding: 7px 12px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid var(--mgv2-line);
|
||||
background: #fff;
|
||||
color: var(--mgv2-ink);
|
||||
font-family: var(--font-head);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background 0.18s ease,
|
||||
border-color 0.18s ease,
|
||||
transform 0.16s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.mgv2-chip:hover {
|
||||
background: rgba(255, 199, 0, 0.16);
|
||||
border-color: rgba(255, 199, 0, 0.5);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.mgv2-service-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.mgv2-service {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 14px 14px;
|
||||
border-radius: 14px;
|
||||
border: 1px solid var(--mgv2-line);
|
||||
background: #fff;
|
||||
color: var(--mgv2-ink);
|
||||
font-family: var(--font-head);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.18s ease, background 0.18s ease;
|
||||
}
|
||||
|
||||
.mgv2-service-check {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 7px;
|
||||
border: 1.5px solid var(--mgv2-line);
|
||||
background: #fff;
|
||||
color: var(--mgv2-ink);
|
||||
font-size: 11px;
|
||||
transition: background 0.18s ease, border-color 0.18s ease;
|
||||
}
|
||||
|
||||
.mgv2-service.active {
|
||||
border-color: var(--mgv2-ink);
|
||||
background: rgba(255, 199, 0, 0.14);
|
||||
}
|
||||
|
||||
.mgv2-service.active .mgv2-service-check {
|
||||
background: var(--mgv2-yellow);
|
||||
border-color: var(--mgv2-yellow);
|
||||
}
|
||||
|
||||
.mgv2-service:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 4px rgba(255, 199, 0, 0.22);
|
||||
}
|
||||
|
||||
.mgv2-grid-two {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.mgv2-grid-two .mgv2-field:nth-child(3),
|
||||
.mgv2-grid-two .mgv2-field:nth-child(4) {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.mgv2-error {
|
||||
color: #b1262d;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.mgv2-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.mgv2-back,
|
||||
.mgv2-back-spacer {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-height: 44px;
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--mgv2-ink);
|
||||
font-family: var(--font-head);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
border-radius: 999px;
|
||||
transition: background 0.18s ease;
|
||||
}
|
||||
|
||||
.mgv2-back-spacer {
|
||||
visibility: hidden;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.mgv2-back:hover {
|
||||
background: rgba(11, 11, 11, 0.06);
|
||||
}
|
||||
|
||||
.mgv2-next {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 48px;
|
||||
padding: 10px 22px;
|
||||
border: none;
|
||||
border-radius: 999px;
|
||||
background: var(--mgv2-yellow);
|
||||
color: var(--mgv2-ink);
|
||||
font-family: var(--font-head);
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.01em;
|
||||
cursor: pointer;
|
||||
box-shadow:
|
||||
inset 0 -2px 0 rgba(11, 11, 11, 0.08),
|
||||
0 12px 24px rgba(255, 199, 0, 0.28);
|
||||
transition:
|
||||
transform 0.18s cubic-bezier(0.22, 1, 0.36, 1),
|
||||
box-shadow 0.18s ease,
|
||||
filter 0.18s ease;
|
||||
}
|
||||
|
||||
.mgv2-next:hover {
|
||||
transform: translateY(-1px);
|
||||
filter: brightness(1.02);
|
||||
}
|
||||
|
||||
.mgv2-next:disabled {
|
||||
opacity: 0.7;
|
||||
cursor: progress;
|
||||
}
|
||||
|
||||
.mgv2-footnote {
|
||||
margin: 32px auto 0;
|
||||
max-width: 360px;
|
||||
color: var(--mgv2-muted);
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.mgv2-page {
|
||||
padding: 36px 16px 64px;
|
||||
}
|
||||
|
||||
.mgv2-stack {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.mgv2-decoy {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mgv2-card {
|
||||
padding: 30px 20px 22px;
|
||||
}
|
||||
|
||||
.mgv2-badge {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
top: -16px;
|
||||
right: 14px;
|
||||
}
|
||||
|
||||
.mgv2-badge :global(.icon) {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.mgv2-heading {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.mgv2-service-grid,
|
||||
.mgv2-grid-two {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.mgv2-next {
|
||||
padding: 10px 18px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -18,8 +18,7 @@ const routes: SitemapRoute[] = [
|
||||
{ path: '/our-pricing', priority: '0.8', changefreq: 'monthly', lastmod: '2026-05-12' },
|
||||
{ path: '/about', priority: '0.7', changefreq: 'monthly', lastmod: '2026-05-12' },
|
||||
{ path: '/contact-us', priority: '0.7', changefreq: 'monthly', lastmod: '2026-05-12' },
|
||||
{ path: '/terms-and-conditions', priority: '0.3', changefreq: 'yearly', lastmod: '2026-05-12' },
|
||||
{ path: '/privacy-policy', priority: '0.3', changefreq: 'yearly', lastmod: '2026-05-12' }
|
||||
{ path: '/locations', priority: '0.8', changefreq: 'monthly', lastmod: '2026-05-12' }
|
||||
];
|
||||
|
||||
const locationRoutes: SitemapRoute[] = locationPages.map((loc) => ({
|
||||
|
||||
@@ -9,11 +9,11 @@ describe('sitemap endpoint', () => {
|
||||
expect(response.headers.get('content-type')).toBe('application/xml; charset=utf-8');
|
||||
expect(body).toContain('<loc>https://www.goodwalk.co.nz/</loc>');
|
||||
expect(body).toContain('<loc>https://www.goodwalk.co.nz/contact-us</loc>');
|
||||
expect(body).toContain('<loc>https://www.goodwalk.co.nz/privacy-policy</loc>');
|
||||
expect(body).toContain('<loc>https://www.goodwalk.co.nz/locations</loc>');
|
||||
expect(body).toContain('<lastmod>2026-05-12</lastmod>');
|
||||
expect(body).toContain('<loc>https://www.goodwalk.co.nz/locations/mt-eden</loc>');
|
||||
expect(body).toContain('<loc>https://www.goodwalk.co.nz/locations/kingsland</loc>');
|
||||
// 9 core pages + 17 location pages
|
||||
expect(body.match(/<url>/g)).toHaveLength(26);
|
||||
// 8 core pages + 17 location pages
|
||||
expect(body.match(/<url>/g)).toHaveLength(25);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
<div class="variant-split-layout">
|
||||
<article class="variant-split-story">
|
||||
<div class="variant-split-photo">
|
||||
<img src="/images/founder-image-aless-goodwalk.jpg" alt="Aless from Goodwalk" />
|
||||
<img src="/images/alessandra-goodwalk-founder-auckland.webp" alt="Aless from Goodwalk" />
|
||||
</div>
|
||||
<div class="variant-split-copy">
|
||||
<span class="variant-story-kicker">Book with confidence</span>
|
||||
|
||||