Design Language tweaks
This commit is contained in:
@@ -391,12 +391,21 @@
|
||||
padding: 60px 0;
|
||||
}
|
||||
|
||||
.about-eyebrow {
|
||||
display: block;
|
||||
width: fit-content;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.about-section-grid {
|
||||
gap: 28px;
|
||||
}
|
||||
|
||||
.about-copy h2 {
|
||||
font-size: 28px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.about-copy p {
|
||||
@@ -415,6 +424,7 @@
|
||||
.about-founder-copy h2 {
|
||||
font-size: 26px;
|
||||
line-height: 1.02;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.about-founder-heading-desktop {
|
||||
|
||||
@@ -21,10 +21,10 @@
|
||||
messagePlaceholder: string;
|
||||
}
|
||||
> = {
|
||||
'Pack Walks': {
|
||||
'Tiny Gang Pack Walks': {
|
||||
intro:
|
||||
'Tell us about your dog, your area, and how they feel around other dogs so we can see if Pack Walks are the right fit.',
|
||||
messageLabel: 'Pack Walks fit',
|
||||
'Tell us about your dog, your area, and how they feel around other dogs so we can see if Tiny Gang Pack Walks are the right fit.',
|
||||
messageLabel: 'Tiny Gang Pack Walks fit',
|
||||
messagePlaceholder:
|
||||
'How old is your dog, how do they feel in groups, and is there anything about confidence, recall, or social behaviour we should know?'
|
||||
},
|
||||
@@ -75,6 +75,7 @@
|
||||
let submitted = false;
|
||||
let showErrorModal = false;
|
||||
let submitErrorDetail = '';
|
||||
let showServicePicker = false;
|
||||
|
||||
function validateEmail(raw: string): string {
|
||||
const value = raw.trim();
|
||||
@@ -134,6 +135,7 @@
|
||||
? 'Tell us if this is feedback, a complaint, a business enquiry, or anything else we should know.'
|
||||
: activeServicePrompt?.messagePlaceholder || 'Describe your pet, any special needs, or anything we should know.';
|
||||
$: successPetName = petName.trim() || 'your dog';
|
||||
$: serviceChoiceLocked = !isGeneralEnquiry && selectedServices.length === 1 && !showServicePicker;
|
||||
|
||||
onMount(() => {
|
||||
const now = Date.now();
|
||||
@@ -241,6 +243,7 @@
|
||||
}
|
||||
|
||||
selectedServices = [requestedService];
|
||||
showServicePicker = false;
|
||||
|
||||
try {
|
||||
window.sessionStorage.removeItem(requestedServiceStorageKey);
|
||||
@@ -283,6 +286,7 @@
|
||||
petName = '';
|
||||
location = '';
|
||||
selectedServices = [];
|
||||
showServicePicker = false;
|
||||
}
|
||||
errors = {};
|
||||
}
|
||||
@@ -438,7 +442,7 @@
|
||||
{/if}
|
||||
|
||||
<div class="booking-header">
|
||||
<span class="booking-eyebrow">{bookingEyebrow}</span>
|
||||
<span class="eyebrow booking-eyebrow">{bookingEyebrow}</span>
|
||||
<h2 class="booking-title">
|
||||
<span class="booking-title-plain">{headingParts.plain}</span>{' '}
|
||||
<span class="booking-title-highlight">{headingParts.highlight}</span>
|
||||
@@ -510,38 +514,12 @@
|
||||
class:booking-card-grid-with-banner={Boolean(detailsStepIntro)}
|
||||
class="booking-card-grid booking-card-grid-dog"
|
||||
>
|
||||
{#if allowGeneralEnquiry}
|
||||
<div class="booking-field-card booking-field-card-full">
|
||||
<label>
|
||||
<Icon name="fas fa-comments" /> Enquiry type
|
||||
</label>
|
||||
<div class="booking-toggle-group" role="radiogroup" aria-label="Enquiry type">
|
||||
<label class="booking-toggle-option">
|
||||
<input
|
||||
type="radio"
|
||||
name="enquiryType"
|
||||
value="booking"
|
||||
checked={enquiryType === 'booking'}
|
||||
on:change={() => setEnquiryType('booking')}
|
||||
/>
|
||||
<span class="booking-toggle-indicator" aria-hidden="true"></span>
|
||||
<span>Book a Meet & Greet</span>
|
||||
</label>
|
||||
<label class="booking-toggle-option">
|
||||
<input
|
||||
type="radio"
|
||||
name="enquiryType"
|
||||
value="general"
|
||||
checked={enquiryType === 'general'}
|
||||
on:change={() => setEnquiryType('general')}
|
||||
/>
|
||||
<span class="booking-toggle-indicator" aria-hidden="true"></span>
|
||||
<span>General enquiry</span>
|
||||
</label>
|
||||
</div>
|
||||
<p class="booking-help-text">
|
||||
General enquiries cover feedback, complaints, business enquiries, and other non-booking messages.
|
||||
</p>
|
||||
{#if allowGeneralEnquiry && !isGeneralEnquiry}
|
||||
<div class="booking-inline-switch booking-field-card-full">
|
||||
<span>Need help with something else?</span>
|
||||
<button type="button" class="booking-inline-link" on:click={() => setEnquiryType('general')}>
|
||||
Send a general enquiry
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -594,6 +572,40 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if hasServices}
|
||||
<div class="booking-field-stack booking-field-stack-full">
|
||||
<div class="booking-selected-service-row">
|
||||
<span class="booking-service-label"><Icon name="fas fa-paw" /> Service</span>
|
||||
{#if serviceChoiceLocked}
|
||||
<button type="button" class="booking-inline-link" on:click={() => (showServicePicker = true)}>
|
||||
Change
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if serviceChoiceLocked}
|
||||
<div class="booking-selected-service-chip">{selectedServices[0]}</div>
|
||||
{:else}
|
||||
<div class="booking-service-options">
|
||||
{#each booking.serviceOptions as service}
|
||||
<label class="booking-check-option">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="services"
|
||||
value={service}
|
||||
checked={selectedServices.includes(service)}
|
||||
on:change={(event) =>
|
||||
toggleService(service, (event.currentTarget as HTMLInputElement).checked)}
|
||||
/>
|
||||
<span class="booking-check-box" aria-hidden="true"></span>
|
||||
<span>{service}</span>
|
||||
</label>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div
|
||||
class="booking-field-stack booking-field-stack-full"
|
||||
class:booking-field-stack-invalid={errors.message}
|
||||
@@ -617,33 +629,17 @@
|
||||
</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if hasServices}
|
||||
<div class="booking-field-stack booking-field-stack-full">
|
||||
<span class="booking-service-label"><Icon name="fas fa-paw" /> Services</span>
|
||||
<div class="booking-service-options">
|
||||
{#each booking.serviceOptions as service}
|
||||
<label class="booking-check-option">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="services"
|
||||
value={service}
|
||||
checked={selectedServices.includes(service)}
|
||||
on:change={(event) =>
|
||||
toggleService(service, (event.currentTarget as HTMLInputElement).checked)}
|
||||
/>
|
||||
<span class="booking-check-box" aria-hidden="true"></span>
|
||||
<span>{service}</span>
|
||||
</label>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if isGeneralEnquiry}
|
||||
<div class="booking-inline-switch booking-field-card-full">
|
||||
<span>Want to book a Meet & Greet instead?</span>
|
||||
<button type="button" class="booking-inline-link" on:click={() => setEnquiryType('booking')}>
|
||||
Switch back to booking
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="booking-field-card booking-field-card-full"
|
||||
class:booking-field-card-invalid={errors.message}
|
||||
@@ -673,7 +669,7 @@
|
||||
</div>
|
||||
|
||||
<div class="booking-actions booking-actions-next">
|
||||
<button type="button" class="btn btn-yellow booking-next-button" on:click={goToOwnerStep}>
|
||||
<button type="button" class="btn btn-yellow btn-with-arrow booking-next-button" on:click={goToOwnerStep}>
|
||||
Next: {ownerStepLabel.toLowerCase()}
|
||||
<Icon name="fas fa-arrow-right" />
|
||||
</button>
|
||||
@@ -784,7 +780,7 @@
|
||||
>
|
||||
Back
|
||||
</button>
|
||||
<button type="submit" class="btn btn-yellow booking-submit-button" disabled={submitting}>
|
||||
<button type="submit" class="btn btn-yellow btn-with-arrow booking-submit-button" disabled={submitting}>
|
||||
{#if submitting}
|
||||
Sending…
|
||||
{:else if isGeneralEnquiry}
|
||||
|
||||
@@ -16,7 +16,7 @@ async function fillOwnerStep() {
|
||||
}
|
||||
|
||||
async function fillDogStep() {
|
||||
await fireEvent.click(screen.getByLabelText('Pack Walks'));
|
||||
await fireEvent.click(screen.getByLabelText('Tiny Gang Pack Walks'));
|
||||
await fireEvent.click(screen.getByLabelText('Other Services'));
|
||||
await fireEvent.input(screen.getByLabelText(/Dog's Name/i), {
|
||||
target: { value: 'Maya' }
|
||||
@@ -24,7 +24,7 @@ async function fillDogStep() {
|
||||
await fireEvent.input(screen.getByLabelText(/Location/i), {
|
||||
target: { value: 'Kingsland' }
|
||||
});
|
||||
await fireEvent.input(screen.getByLabelText(/Pack Walks fit/i), {
|
||||
await fireEvent.input(screen.getByLabelText(/Tiny Gang Pack Walks fit/i), {
|
||||
target: { value: 'Loves small group walks.' }
|
||||
});
|
||||
}
|
||||
@@ -103,7 +103,7 @@ describe('BookingSection', () => {
|
||||
petName: 'Maya',
|
||||
location: 'Kingsland',
|
||||
message: 'Loves small group walks.',
|
||||
services: ['Pack Walks', 'Other Services'],
|
||||
services: ['Tiny Gang Pack Walks', 'Other Services'],
|
||||
website: '',
|
||||
referrer: 'https://www.google.com/',
|
||||
stepChanges: 1,
|
||||
@@ -139,7 +139,7 @@ describe('BookingSection', () => {
|
||||
|
||||
await fireEvent.click(screen.getByLabelText(/General enquiry/i));
|
||||
expect(screen.queryByLabelText(/Dog's Name/i)).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Pack Walks')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Tiny Gang Pack Walks')).not.toBeInTheDocument();
|
||||
|
||||
await fireEvent.click(container.querySelector('.booking-next-button')!);
|
||||
expect(screen.getByText('Please tell us how we can help')).toBeInTheDocument();
|
||||
|
||||
@@ -10,10 +10,11 @@
|
||||
const desktop = logoDesktop as Picture;
|
||||
|
||||
export let preview = false;
|
||||
$: onboardingPageHref = preview ? '/?preview=onboarding' : '/';
|
||||
|
||||
const ownerEmail = 'info@goodwalk.co.nz';
|
||||
const ownerPhone = '(022) 642 1011';
|
||||
const services = ['Pack Walks', '1:1 Walks', 'Puppy Visits'];
|
||||
const services = ['Tiny Gang Pack Walks', '1:1 Walks', 'Puppy Visits'];
|
||||
const visitStartedStorageKey = 'goodwalk_visit_started_at';
|
||||
const draftStorageKey = 'goodwalk_contract_draft';
|
||||
|
||||
@@ -422,7 +423,7 @@
|
||||
|
||||
<div class="journey-bar">
|
||||
<div class="contract-shell journey-bar-inner">
|
||||
<a href="/?preview=onboarding" class="journey-stage" class:journey-done={onboardingCompleted} class:journey-current={!onboardingCompleted}>
|
||||
<a href={onboardingPageHref} class="journey-stage" class:journey-done={onboardingCompleted} class:journey-current={!onboardingCompleted}>
|
||||
<span class="journey-stage-icon">
|
||||
{#if onboardingCompleted}
|
||||
<Icon name="fas fa-check" />
|
||||
@@ -465,7 +466,7 @@
|
||||
<div>
|
||||
<strong>You haven't completed your onboarding form yet.</strong>
|
||||
Aless will need this before your service can start.
|
||||
<a href="/?preview=onboarding">Complete it now →</a>
|
||||
<a href={onboardingPageHref}>Complete it now →</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,10 @@
|
||||
<span class="cta-card__eyebrow">{eyebrow}</span>
|
||||
<h2>{title}</h2>
|
||||
<p class="cta-card__desc">{description}</p>
|
||||
<a class="btn btn-yellow btn-mobile-center cta-card__btn" href={ctaHref}>{ctaLabel}</a>
|
||||
<a class="btn btn-yellow btn-mobile-center btn-with-arrow cta-card__btn" href={ctaHref}>
|
||||
{ctaLabel}
|
||||
<Icon name="fas fa-arrow-right" />
|
||||
</a>
|
||||
{#if email || phone}
|
||||
<div class="cta-card__links">
|
||||
{#if email}
|
||||
@@ -52,8 +55,9 @@
|
||||
margin-bottom: 14px;
|
||||
padding: 7px 12px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.14);
|
||||
color: #fff;
|
||||
background: rgba(255, 209, 0, 0.12);
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 209, 0, 0.2);
|
||||
color: var(--yellow);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.08em;
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
|
||||
const aboutLink: LinkItem = { label: 'About Us', href: '/about' };
|
||||
|
||||
function scrollToTop() {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
function withAboutLink(links: LinkItem[]) {
|
||||
if (links.some((link) => link.href === aboutLink.href || link.label === aboutLink.label)) {
|
||||
return links;
|
||||
@@ -105,6 +109,8 @@
|
||||
<a href="/terms-and-conditions">Terms & Conditions</a>
|
||||
<a href="/privacy-policy">Privacy Policy</a>
|
||||
</nav>
|
||||
<a href="#" class="footer-back-top" aria-label="Back to top">↑ Back to top</a>
|
||||
<button type="button" class="footer-back-top" aria-label="Back to top" on:click={scrollToTop}>
|
||||
↑ Back to top
|
||||
</button>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -1,254 +1,246 @@
|
||||
<script lang="ts">
|
||||
import { reveal } from '$lib/actions/reveal';
|
||||
import Icon from '$lib/components/Icon.svelte';
|
||||
import { getEnhancedImage } from '$lib/enhanced-images';
|
||||
import type { FounderStoryContent } from '$lib/types';
|
||||
|
||||
export let founderStory: FounderStoryContent;
|
||||
|
||||
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.',
|
||||
'The Tiny Gang is built around routine, trust, and dogs having the absolute best part of their day together. Older dogs help younger ones settle in, nervous dogs build confidence, and playful dogs get to burn energy with their friends.',
|
||||
'You know exactly who is caring for your dog. Your dog knows who is at the door. And you come home to a happy, fulfilled dog that has had a proper adventure. Ready to join the Tiny Gang?'
|
||||
];
|
||||
|
||||
$: founderStoryEnhanced = getEnhancedImage(founderStory.imageUrl);
|
||||
</script>
|
||||
|
||||
<section id="promise">
|
||||
<div class="promise-inner">
|
||||
<div class="promise-text">
|
||||
<span class="promise-kicker">Founder story</span>
|
||||
<div class="promise-mobile-intro">
|
||||
<div class="promise-mobile-avatar">
|
||||
<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 src={founderStory.imageUrl} alt={founderStory.imageAlt} loading="lazy" decoding="async" />
|
||||
<img
|
||||
class="founder-avatar-img"
|
||||
src={founderStory.imageUrl}
|
||||
alt={founderStory.imageAlt}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<p class="promise-mobile-caption">Auckland Central walks led personally by Aless.</p>
|
||||
<div class="founder-signoff-text">
|
||||
<span class="founder-name">Aless</span>
|
||||
<span class="founder-role">Goodwalk founder</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="promise-heading">
|
||||
<span class="promise-heading-desktop">
|
||||
<span class="promise-title-main">{founderStory.title}</span>
|
||||
<br />
|
||||
<span class="promise-title-highlight">{founderStory.subtitle}</span>
|
||||
</span>
|
||||
<span class="promise-heading-mobile">
|
||||
<span class="promise-title-main">{founderStory.title}</span>
|
||||
<span class="promise-title-highlight">{founderStory.subtitle}</span>
|
||||
</span>
|
||||
</h2>
|
||||
<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>
|
||||
|
||||
{#each founderStory.body as paragraph, idx}
|
||||
<p>
|
||||
{paragraph}
|
||||
{#if idx === founderStory.body.length - 1}
|
||||
<strong>{founderStory.emphasis}</strong>
|
||||
{/if}
|
||||
</p>
|
||||
{/each}
|
||||
|
||||
<a href={founderStory.cta.href} class="btn btn-green">{founderStory.cta.label}</a>
|
||||
</div>
|
||||
|
||||
<div class="promise-img">
|
||||
<div class="promise-img-frame">
|
||||
{#if founderStoryEnhanced}
|
||||
<enhanced:img
|
||||
src={founderStoryEnhanced}
|
||||
alt={founderStory.imageAlt}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
{:else}
|
||||
<img src={founderStory.imageUrl} alt={founderStory.imageAlt} loading="lazy" decoding="async" />
|
||||
{/if}
|
||||
</div>
|
||||
</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>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.promise-kicker {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin: 0 0 14px;
|
||||
padding: 8px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(33, 48, 33, 0.07);
|
||||
color: var(--gw-green);
|
||||
font-family: var(--font-head);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.05);
|
||||
/* A quiet, letter-style sign-off — left-aligned, one clean panel, no ornament. */
|
||||
.founder-inner {
|
||||
max-width: var(--max-w);
|
||||
margin: 0 auto;
|
||||
padding: 0 50px;
|
||||
}
|
||||
|
||||
.promise-heading {
|
||||
margin: 0 0 22px;
|
||||
max-width: 14ch;
|
||||
.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);
|
||||
}
|
||||
|
||||
.promise-mobile-intro {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.promise-heading-desktop {
|
||||
.founder-kicker {
|
||||
display: block;
|
||||
letter-spacing: 0.14em;
|
||||
}
|
||||
|
||||
.promise-heading-mobile {
|
||||
display: none;
|
||||
.founder-heading {
|
||||
margin: 0 0 26px;
|
||||
}
|
||||
|
||||
.promise-heading-mobile .promise-title-main,
|
||||
.promise-heading-mobile .promise-title-highlight {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.promise-title-main {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: rgba(13, 26, 13, 0.68);
|
||||
font-family: var(--font-head);
|
||||
font-size: clamp(15px, 1.3vw, 18px);
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.01em;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.promise-title-highlight {
|
||||
.founder-heading-main {
|
||||
display: block;
|
||||
color: #0d1a0d;
|
||||
font-size: clamp(42px, 5.2vw, 64px);
|
||||
font-family: var(--font-head);
|
||||
font-size: clamp(32px, 3.6vw, 46px);
|
||||
font-weight: 800;
|
||||
letter-spacing: -0.05em;
|
||||
line-height: 0.96;
|
||||
text-wrap: balance;
|
||||
letter-spacing: -0.04em;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.promise-text {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
.founder-body {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.founder-body p {
|
||||
margin: 0;
|
||||
color: #4c5056;
|
||||
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;
|
||||
font-family: var(--font-head);
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.founder-role {
|
||||
color: var(--gray);
|
||||
font-size: 13px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.founder-cta {
|
||||
margin-top: 28px;
|
||||
}
|
||||
|
||||
.founder-contact-note {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
padding: 12px 16px;
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
.founder-contact-wave {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.07);
|
||||
font-size: 16px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.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);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
#promise {
|
||||
padding-top: 42px;
|
||||
padding-bottom: var(--space-section-featured-y);
|
||||
.founder-inner {
|
||||
padding: 0 var(--space-container-x-mobile);
|
||||
}
|
||||
|
||||
.promise-kicker {
|
||||
margin-bottom: 12px;
|
||||
padding: 7px 12px;
|
||||
font-size: 11px;
|
||||
.founder-note {
|
||||
padding: 26px 24px 24px;
|
||||
border-radius: 24px;
|
||||
}
|
||||
|
||||
.promise-text {
|
||||
width: 100%;
|
||||
margin-top: 0;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.promise-heading {
|
||||
max-width: none;
|
||||
margin-bottom: 22px;
|
||||
.founder-heading {
|
||||
margin: 0 0 22px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.promise-heading-desktop {
|
||||
display: none;
|
||||
.founder-body p {
|
||||
font-size: var(--body-copy-size-mobile);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.promise-heading-mobile {
|
||||
.founder-contact-note {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.promise-heading-mobile .promise-title-main,
|
||||
.promise-heading-mobile .promise-title-highlight {
|
||||
display: inline-block;
|
||||
width: fit-content;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.promise-mobile-intro {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
margin: 0 0 16px;
|
||||
padding: 14px 16px;
|
||||
border-radius: 22px;
|
||||
background: linear-gradient(180deg, #fbf6e8 0%, #efe4c8 100%);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.65),
|
||||
0 10px 22px rgba(17, 20, 24, 0.06),
|
||||
inset 0 0 0 1px rgba(242, 191, 47, 0.12);
|
||||
}
|
||||
|
||||
.promise-mobile-avatar {
|
||||
flex: 0 0 auto;
|
||||
width: 68px;
|
||||
height: 68px;
|
||||
overflow: hidden;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 8px 18px rgba(17, 20, 24, 0.08);
|
||||
}
|
||||
|
||||
.promise-mobile-avatar img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center 20%;
|
||||
font-size: 13px;
|
||||
padding: 11px 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.promise-mobile-caption {
|
||||
margin: 0;
|
||||
color: #34363a;
|
||||
font-family: var(--font-head);
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.promise-title-main {
|
||||
margin-bottom: 0;
|
||||
font-size: 14px;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.promise-title-highlight {
|
||||
font-size: clamp(36px, 11vw, 54px);
|
||||
line-height: 0.98;
|
||||
}
|
||||
|
||||
.promise-text p,
|
||||
.promise-text .btn {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.promise-text p {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.promise-text .btn {
|
||||
.founder-cta {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.promise-img {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
|
||||
export let navigation: NavigationContent;
|
||||
|
||||
// Desktop nav is split either side of the centre logo.
|
||||
$: leftLinks = navigation.desktopLinks.slice(0, 2);
|
||||
$: rightLinks = navigation.desktopLinks.slice(2);
|
||||
|
||||
let mobileMenuOpen = false;
|
||||
let headerElement: HTMLElement;
|
||||
let mobileMenuTop = 0;
|
||||
@@ -44,6 +48,7 @@
|
||||
if (href === '/pack-walks') return 'fas fa-paw';
|
||||
if (href === '/dog-walking') return 'fas fa-person-walking';
|
||||
if (href === '/puppy-visits') return 'fas fa-dog';
|
||||
if (href === '/testimonials') return 'fas fa-star';
|
||||
if (href === '/our-pricing') return 'fas fa-tags';
|
||||
if (href === '/about') return 'fas fa-heart';
|
||||
if (href === '/contact-us') return 'fas fa-envelope';
|
||||
@@ -130,8 +135,8 @@
|
||||
|
||||
<header bind:this={headerElement}>
|
||||
<nav>
|
||||
<ul class="nav-links">
|
||||
{#each navigation.desktopLinks as link, i}
|
||||
<ul class="nav-links nav-links-left">
|
||||
{#each leftLinks as link, i}
|
||||
<li class:has-mega={i === 0 && navigation.megaMenuServices?.length}>
|
||||
<a
|
||||
href={link.href}
|
||||
@@ -201,27 +206,43 @@
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
<a href={`tel:${mobilePhoneHref}`} class="mobile-phone" aria-label={`Call Goodwalk on ${mobilePhoneDisplay}`}>
|
||||
<Icon name="fas fa-phone" />
|
||||
<span>{mobilePhoneDisplay}</span>
|
||||
</a>
|
||||
<div class="nav-end">
|
||||
<ul class="nav-links nav-links-right">
|
||||
{#each rightLinks as link}
|
||||
<li>
|
||||
<a
|
||||
href={link.href}
|
||||
target={linkTarget(link.external)}
|
||||
rel={linkRel(link.external)}
|
||||
aria-current={ariaCurrent(link.href)}
|
||||
class:nav-link-active={isActiveLink(link.href)}
|
||||
>
|
||||
{link.label}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
<div class="nav-right">
|
||||
{#if navigation.instagram}
|
||||
<a
|
||||
href={navigation.instagram.href}
|
||||
target={linkTarget(navigation.instagram.external)}
|
||||
rel={linkRel(navigation.instagram.external)}
|
||||
class="instagram-icon"
|
||||
aria-label="Instagram"
|
||||
>
|
||||
<Icon name="fab fa-instagram" />
|
||||
<div class="nav-right">
|
||||
<a href={`tel:${mobilePhoneHref}`} class="mobile-phone" aria-label={`Call Goodwalk on ${mobilePhoneDisplay}`}>
|
||||
<Icon name="fas fa-phone" />
|
||||
</a>
|
||||
{/if}
|
||||
<a
|
||||
href={navigation.cta.href}
|
||||
class="btn btn-yellow"
|
||||
>{navigation.cta.label}</a>
|
||||
{#if navigation.instagram}
|
||||
<a
|
||||
href={navigation.instagram.href}
|
||||
target={linkTarget(navigation.instagram.external)}
|
||||
rel={linkRel(navigation.instagram.external)}
|
||||
class="instagram-icon"
|
||||
aria-label="Instagram"
|
||||
>
|
||||
<Icon name="fab fa-instagram" />
|
||||
</a>
|
||||
{/if}
|
||||
<a
|
||||
href={navigation.cta.href}
|
||||
class="btn btn-yellow"
|
||||
>{navigation.cta.label}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@@ -238,11 +259,7 @@
|
||||
|
||||
{#if $page.url.pathname === '/'}
|
||||
<div class="nav-ribbon">
|
||||
<span class="nav-ribbon-item"><Icon name="fas fa-paw" />Small & Medium Dog Specialists</span>
|
||||
<span class="nav-ribbon-divider"></span>
|
||||
<span class="nav-ribbon-item"><Icon name="fas fa-handshake" />Free Meet & Greet</span>
|
||||
<span class="nav-ribbon-divider"></span>
|
||||
<span class="nav-ribbon-item"><Icon name="fas fa-van-shuttle" />Free Pickup & Drop-off</span>
|
||||
<span class="nav-ribbon-item"><Icon name="fas fa-handshake" />Free Meet & Greet for Auckland Central dogs</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -250,9 +267,14 @@
|
||||
class:open={mobileMenuOpen}
|
||||
class="mobile-menu-shell"
|
||||
style={`--mobile-menu-top: ${mobileMenuTop}px;`}
|
||||
on:click={closeMenu}
|
||||
>
|
||||
<div class="mobile-menu" id="mobile-menu" on:click|stopPropagation>
|
||||
<button
|
||||
type="button"
|
||||
class="mobile-menu-backdrop"
|
||||
aria-label="Close menu"
|
||||
on:click={closeMenu}
|
||||
></button>
|
||||
<div class="mobile-menu" id="mobile-menu">
|
||||
<div class="mobile-menu-links">
|
||||
{#each navigation.mobileLinks as link}
|
||||
<a
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import Icon from '$lib/components/Icon.svelte';
|
||||
import { getEnhancedImage } from '$lib/enhanced-images';
|
||||
import type { CallToAction, HeroContent } from '$lib/types';
|
||||
|
||||
export let hero: HeroContent;
|
||||
@@ -11,7 +10,7 @@
|
||||
$: mobileLead = mobileTitle.includes(hero.highlight)
|
||||
? mobileTitle.slice(0, mobileTitle.lastIndexOf(hero.highlight))
|
||||
: mobileTitle;
|
||||
$: heroEnhanced = getEnhancedImage(hero.imageUrl);
|
||||
$: proofItems = (hero.subtitleChips ?? []).slice(0, 3);
|
||||
|
||||
function splitTitle(title: string) {
|
||||
const trimmed = title.trim();
|
||||
@@ -56,10 +55,6 @@
|
||||
</picture>
|
||||
</div>
|
||||
|
||||
{#if hero.floatingPill}
|
||||
<div class="hero-floating-pill">{hero.floatingPill}</div>
|
||||
{/if}
|
||||
|
||||
<div class="hero-inner">
|
||||
<div class="hero-text">
|
||||
{#if hero.kicker}
|
||||
@@ -84,55 +79,50 @@
|
||||
<p class="hero-subtitle hero-subtitle-desktop">{hero.subtitle}</p>
|
||||
{/if}
|
||||
|
||||
{#if hero.subtitleChips && hero.subtitleChips.length}
|
||||
<div class="hero-chips">
|
||||
{#each hero.subtitleChips as chip}
|
||||
{#if proofItems.length || reviewCta}
|
||||
<div class="hero-chips" aria-label="Why owners choose Goodwalk">
|
||||
{#each proofItems as chip}
|
||||
<span class="hero-chip">
|
||||
<Icon name={chip.icon} />
|
||||
{chip.label}
|
||||
</span>
|
||||
{/each}
|
||||
{#if reviewCta}
|
||||
<a
|
||||
class="hero-trust-chip"
|
||||
href={reviewCta.href}
|
||||
target={reviewCta.external ? '_blank' : undefined}
|
||||
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>
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if reviewCta}
|
||||
<a
|
||||
class="hero-trust-chip"
|
||||
href={reviewCta.href}
|
||||
target={reviewCta.external ? '_blank' : undefined}
|
||||
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 class="hero-trust-stars" aria-hidden="true">
|
||||
{#each Array(5) as _}
|
||||
<Icon name="fas fa-star" />
|
||||
{/each}
|
||||
</span>
|
||||
<span>{reviewCta.label}</span>
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
<div class="hero-buttons">
|
||||
<a
|
||||
href={hero.primaryCta.href}
|
||||
target={linkTarget(hero.primaryCta.external)}
|
||||
rel={linkRel(hero.primaryCta.external)}
|
||||
class="btn btn-yellow"
|
||||
class="btn btn-yellow btn-with-arrow"
|
||||
>
|
||||
{hero.primaryCta.label}
|
||||
<Icon name="fas fa-arrow-right" />
|
||||
</a>
|
||||
<a
|
||||
href={hero.secondaryCta.href}
|
||||
target={linkTarget(hero.secondaryCta.external)}
|
||||
rel={linkRel(hero.secondaryCta.external)}
|
||||
class="btn btn-outline"
|
||||
class="hero-secondary-link"
|
||||
>
|
||||
{hero.secondaryCta.label}
|
||||
<Icon name="fas fa-arrow-down" className="hero-cta-arrow" />
|
||||
|
||||
@@ -4,19 +4,34 @@
|
||||
import type { HowItWorksContent } from '$lib/types';
|
||||
|
||||
export let content: HowItWorksContent;
|
||||
|
||||
const journeyChips = [
|
||||
{ icon: 'fas fa-handshake', label: 'Free Meet & Greet' },
|
||||
{ icon: 'fas fa-clipboard-check', label: 'Assessment walks' },
|
||||
{ icon: 'fas fa-calendar-check', label: 'A regular weekly rhythm' }
|
||||
];
|
||||
</script>
|
||||
|
||||
<section id="how-it-works" use:reveal={{ delay: 30 }} class="reveal-block">
|
||||
<div class="hiw-inner">
|
||||
|
||||
<div class="hiw-header">
|
||||
<span class="hiw-eyebrow">Getting started</span>
|
||||
<div class="section-header hiw-header">
|
||||
<span class="eyebrow hiw-eyebrow">Getting started</span>
|
||||
<h2 class="section-heading">{content.title}</h2>
|
||||
{#if content.intro}
|
||||
<p class="hiw-intro">{content.intro}</p>
|
||||
<p class="section-intro hiw-intro">{content.intro}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="hiw-journey-bar" aria-label="How getting started works">
|
||||
{#each journeyChips as chip}
|
||||
<span class="hiw-journey-pill">
|
||||
<Icon name={chip.icon} className="hiw-journey-icon" />
|
||||
{chip.label}
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="hiw-steps">
|
||||
{#each content.steps as step, index}
|
||||
<div class="hiw-step">
|
||||
@@ -40,7 +55,10 @@
|
||||
</div>
|
||||
|
||||
<div class="hiw-cta">
|
||||
<a href="#newlead" class="btn btn-green btn-mobile-center">Book your free Meet & Greet</a>
|
||||
<a href="#newlead" class="btn btn-green btn-mobile-center btn-with-arrow">
|
||||
Book your free Meet & Greet
|
||||
<Icon name="fas fa-arrow-right" />
|
||||
</a>
|
||||
<p class="hiw-cta-note">Free, no-obligation. We reply within 24 hours.</p>
|
||||
</div>
|
||||
|
||||
@@ -61,30 +79,19 @@
|
||||
|
||||
/* ── Header ── */
|
||||
.hiw-header {
|
||||
text-align: center;
|
||||
margin-bottom: 56px;
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
|
||||
.hiw-eyebrow {
|
||||
display: inline-block;
|
||||
margin-bottom: 14px;
|
||||
padding: 7px 12px;
|
||||
border-radius: 999px;
|
||||
background: rgba(33, 48, 33, 0.08);
|
||||
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);
|
||||
}
|
||||
|
||||
.hiw-intro {
|
||||
max-width: 580px;
|
||||
margin: 16px auto 0;
|
||||
color: #4c5056;
|
||||
font-size: 16px;
|
||||
line-height: 1.65;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
/* ── Steps grid ── */
|
||||
@@ -92,6 +99,19 @@
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
position: relative;
|
||||
gap: 14px;
|
||||
margin-top: 28px;
|
||||
}
|
||||
|
||||
.hiw-steps::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 32px;
|
||||
left: 13%;
|
||||
right: 13%;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, rgba(33, 48, 33, 0.16), rgba(242, 191, 47, 0.4), rgba(33, 48, 33, 0.16));
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.hiw-step {
|
||||
@@ -100,22 +120,15 @@
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 40px 40px 36px;
|
||||
background: #fff;
|
||||
background:
|
||||
radial-gradient(circle at top center, rgba(255, 209, 0, 0.12), transparent 34%),
|
||||
#fff;
|
||||
border: 1px solid rgba(17, 20, 24, 0.06);
|
||||
box-shadow: 0 4px 16px rgba(17, 20, 24, 0.04);
|
||||
transition: box-shadow 0.22s ease, transform 0.18s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.hiw-step:first-child {
|
||||
border-radius: 28px 0 0 28px;
|
||||
}
|
||||
|
||||
.hiw-step:last-child {
|
||||
border-radius: 0 28px 28px 0;
|
||||
}
|
||||
|
||||
.hiw-step + .hiw-step {
|
||||
border-left: none;
|
||||
border-radius: 28px;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
@@ -171,14 +184,14 @@
|
||||
|
||||
.hiw-icon-wrap :global(.hiw-step-icon) {
|
||||
font-size: 26px;
|
||||
color: #fff;
|
||||
color: var(--yellow);
|
||||
}
|
||||
|
||||
/* ── Content ── */
|
||||
.hiw-title {
|
||||
margin: 0 0 14px;
|
||||
font-family: var(--font-head);
|
||||
font-size: 20px;
|
||||
font-size: var(--heading-card-size);
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
color: #0d1a0d;
|
||||
@@ -225,6 +238,38 @@
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.hiw-journey-bar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin: 14px auto 0;
|
||||
max-width: 880px;
|
||||
}
|
||||
|
||||
.hiw-journey-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 34px;
|
||||
padding: 0 14px;
|
||||
border-radius: 999px;
|
||||
background: var(--gw-green);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(255, 255, 255, 0.04),
|
||||
0 10px 22px rgba(17, 20, 24, 0.06);
|
||||
color: #fff;
|
||||
font-family: var(--font-head);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.hiw-journey-pill :global(.hiw-journey-icon) {
|
||||
color: var(--yellow);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* ── Mobile ── */
|
||||
@media (max-width: 768px) {
|
||||
.hiw-inner {
|
||||
@@ -232,31 +277,43 @@
|
||||
}
|
||||
|
||||
.hiw-header {
|
||||
margin-bottom: 32px;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
.hiw-intro {
|
||||
font-size: 15px;
|
||||
line-height: 1.55;
|
||||
max-width: 34ch;
|
||||
}
|
||||
|
||||
.hiw-journey-bar {
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.hiw-journey-pill {
|
||||
min-height: 32px;
|
||||
padding: 0 12px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.hiw-steps {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.hiw-steps::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hiw-step {
|
||||
align-items: flex-start;
|
||||
text-align: left;
|
||||
padding: 28px 24px;
|
||||
border-radius: 24px !important;
|
||||
border-radius: 24px;
|
||||
border: 1px solid rgba(17, 20, 24, 0.06);
|
||||
}
|
||||
|
||||
.hiw-step + .hiw-step {
|
||||
border-left: 1px solid rgba(17, 20, 24, 0.06);
|
||||
}
|
||||
|
||||
.hiw-step-meta {
|
||||
justify-content: flex-start;
|
||||
margin-bottom: 20px;
|
||||
@@ -274,16 +331,16 @@
|
||||
}
|
||||
|
||||
.hiw-title {
|
||||
font-size: 18px;
|
||||
font-size: var(--heading-card-size-mobile);
|
||||
}
|
||||
|
||||
.hiw-body {
|
||||
font-size: 14px;
|
||||
font-size: var(--body-copy-size-mobile);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.hiw-cta {
|
||||
margin-top: 36px;
|
||||
margin-top: 28px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,9 +16,12 @@
|
||||
</script>
|
||||
|
||||
<section id="info">
|
||||
<div class="info-inner">
|
||||
<div class="info-inner">
|
||||
<div class="info-block">
|
||||
<h2><Icon name="fas fa-location-dot" /> {info.title}</h2>
|
||||
<h2>
|
||||
<span class="info-heading-icon"><Icon name="fas fa-location-dot" /></span>
|
||||
{info.title}
|
||||
</h2>
|
||||
<p class="info-lead">{info.intro}</p>
|
||||
<p class="info-support">Regular walks across the inner-west and nearby suburbs.</p>
|
||||
|
||||
@@ -47,7 +50,10 @@
|
||||
</div>
|
||||
|
||||
<div class="info-block">
|
||||
<h2><Icon name="fas fa-circle-question" /> {info.faqTitle}</h2>
|
||||
<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>
|
||||
@@ -61,6 +67,24 @@
|
||||
</section>
|
||||
|
||||
<style>
|
||||
.info-heading-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
margin-right: 10px;
|
||||
border-radius: 12px;
|
||||
background: var(--gw-green);
|
||||
box-shadow: 0 10px 22px rgba(33, 48, 33, 0.16);
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.info-heading-icon :global(.icon) {
|
||||
color: var(--yellow);
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.info-lead {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
@@ -158,7 +182,37 @@
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.info-block {
|
||||
text-align: left;
|
||||
padding: 24px 20px;
|
||||
border-radius: 24px;
|
||||
background: #fff;
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(17, 20, 24, 0.05),
|
||||
0 14px 30px rgba(17, 20, 24, 0.05);
|
||||
}
|
||||
|
||||
.info-lead,
|
||||
.info-support {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.info-block h2 {
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
font-size: clamp(24px, 6.3vw, 28px);
|
||||
}
|
||||
|
||||
.info-heading-icon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-right: 8px;
|
||||
border-radius: 11px;
|
||||
}
|
||||
|
||||
.info-suburb-chips {
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
@@ -172,11 +226,21 @@
|
||||
.info-nearby-card {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
text-align: left;
|
||||
padding: 20px 18px;
|
||||
}
|
||||
|
||||
.info-nearby-cta {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.info-hours-card {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.faq summary,
|
||||
.faq details p {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -27,7 +27,10 @@
|
||||
</a>
|
||||
|
||||
<div class="intro-trust-copy">
|
||||
<p>{intro.text}</p>
|
||||
<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">
|
||||
|
||||
@@ -242,8 +242,9 @@
|
||||
}
|
||||
|
||||
.loc-hero-eyebrow {
|
||||
background: rgba(255, 255, 255, 0.14);
|
||||
color: #fff;
|
||||
background: rgba(255, 209, 0, 0.12);
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 209, 0, 0.2);
|
||||
color: var(--yellow);
|
||||
}
|
||||
|
||||
/* ── Hero ── */
|
||||
@@ -714,6 +715,20 @@
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.loc-eyebrow,
|
||||
.loc-hero-eyebrow {
|
||||
display: block;
|
||||
width: fit-content;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loc-section-header h2,
|
||||
.loc-section-intro {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loc-highlights {
|
||||
margin-top: -24px;
|
||||
padding-bottom: 60px;
|
||||
|
||||
@@ -142,11 +142,11 @@
|
||||
z-index: 50;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
backdrop-filter: blur(10px);
|
||||
padding: 8px 14px calc(8px + env(safe-area-inset-bottom));
|
||||
background: rgba(255, 255, 255, 0.98);
|
||||
backdrop-filter: blur(6px);
|
||||
border-top: 1px solid rgba(17, 20, 24, 0.08);
|
||||
box-shadow: 0 -10px 28px rgba(17, 20, 24, 0.1);
|
||||
box-shadow: 0 -8px 22px rgba(17, 20, 24, 0.08);
|
||||
|
||||
opacity: 0;
|
||||
transform: translateY(110%);
|
||||
@@ -168,17 +168,17 @@
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
width: 100%;
|
||||
max-width: 460px;
|
||||
padding: 13px 22px;
|
||||
max-width: 420px;
|
||||
padding: 12px 18px;
|
||||
border-radius: 999px;
|
||||
background: var(--yellow);
|
||||
color: #000;
|
||||
font-family: var(--font-head);
|
||||
font-size: 15px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.01em;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 8px 18px rgba(255, 209, 0, 0.4);
|
||||
box-shadow: 0 6px 14px rgba(255, 209, 0, 0.24);
|
||||
transition:
|
||||
background 0.18s ease,
|
||||
transform 0.18s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import Icon from '$lib/components/Icon.svelte';
|
||||
|
||||
export let context: 'onboarding' | 'contract' = 'onboarding';
|
||||
$: flowLabel = context === 'contract' ? 'contract' : 'onboarding';
|
||||
|
||||
const dispatch = createEventDispatcher<{ authenticated: { email: string; profile: Record<string, string>; draft: Record<string, unknown> } }>();
|
||||
|
||||
@@ -93,7 +94,7 @@
|
||||
|
||||
{#if stage === 'email'}
|
||||
<h2>Sign in to continue</h2>
|
||||
<p>Enter the email address you used when enquiring with Goodwalk. We'll send you a one-time code.</p>
|
||||
<p>Enter the email address you used when enquiring with Goodwalk. We'll send you a one-time code to continue your {flowLabel}.</p>
|
||||
|
||||
<div class="auth-field">
|
||||
<label for="auth-email">Email address</label>
|
||||
@@ -112,7 +113,7 @@
|
||||
<div class="auth-error">{error}</div>
|
||||
{/if}
|
||||
|
||||
<button class="btn btn-yellow auth-btn" on:click={requestCode} disabled={loading}>
|
||||
<button type="button" class="btn btn-yellow auth-btn" on:click={requestCode} disabled={loading}>
|
||||
{#if loading}Sending…{:else}Send code <Icon name="fas fa-arrow-right" />{/if}
|
||||
</button>
|
||||
|
||||
@@ -141,11 +142,11 @@
|
||||
<div class="auth-error">{error}</div>
|
||||
{/if}
|
||||
|
||||
<button class="btn btn-yellow auth-btn" on:click={verifyCode} disabled={loading}>
|
||||
<button type="button" class="btn btn-yellow auth-btn" on:click={verifyCode} disabled={loading}>
|
||||
{#if loading}Verifying…{:else}Verify code <Icon name="fas fa-arrow-right" />{/if}
|
||||
</button>
|
||||
|
||||
<button class="auth-back" on:click={goBack}>
|
||||
<button type="button" class="auth-back" on:click={goBack}>
|
||||
<Icon name="fas fa-arrow-left" /> Use a different email
|
||||
</button>
|
||||
{/if}
|
||||
|
||||
@@ -32,7 +32,10 @@
|
||||
<Icon name="fas fa-arrow-left" />
|
||||
Back to main site
|
||||
</a>
|
||||
<button class="ob-footer-logout" on:click={logout} disabled={loggingOut}>
|
||||
{#if email}
|
||||
<span class="ob-footer-email">Signed in as {email}</span>
|
||||
{/if}
|
||||
<button type="button" class="ob-footer-logout" on:click={logout} disabled={loggingOut}>
|
||||
<Icon name="fas fa-right-from-bracket" />
|
||||
{loggingOut ? 'Signing out…' : 'Sign out'}
|
||||
</button>
|
||||
@@ -72,6 +75,16 @@
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.ob-footer-email {
|
||||
margin-left: auto;
|
||||
min-width: 0;
|
||||
color: rgba(255, 255, 255, 0.62);
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.ob-footer-logout {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -12,10 +12,11 @@
|
||||
const mobile = logoMobile as Picture;
|
||||
|
||||
export let preview = false;
|
||||
$: contractPageHref = preview ? '/contract?preview=contract' : '/contract';
|
||||
|
||||
const ownerEmail = 'info@goodwalk.co.nz';
|
||||
const ownerPhone = '(022) 642 1011';
|
||||
const services = ['Pack Walks', '1:1 Walks', 'Puppy Visits', 'Unsure yet'];
|
||||
const services = ['Tiny Gang Pack Walks', '1:1 Walks', 'Puppy Visits', 'Unsure yet'];
|
||||
const visitStartedStorageKey = 'goodwalk_visit_started_at';
|
||||
const draftStorageKey = 'goodwalk_onboarding_draft';
|
||||
|
||||
@@ -453,7 +454,7 @@
|
||||
|
||||
<div class="journey-connector" class:journey-connector-done={onboardingCompleted}></div>
|
||||
|
||||
<a href="/contract?preview=contract" class="journey-stage" class:journey-done={contractCompleted}>
|
||||
<a href={contractPageHref} class="journey-stage" class:journey-done={contractCompleted}>
|
||||
<span class="journey-stage-icon">
|
||||
{#if contractCompleted}
|
||||
<Icon name="fas fa-check" />
|
||||
|
||||
@@ -269,6 +269,8 @@
|
||||
.sh-copy {
|
||||
order: 2;
|
||||
padding: 44px 24px 48px;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sh-title {
|
||||
@@ -277,11 +279,17 @@
|
||||
|
||||
.sh-subtitle {
|
||||
font-size: 15px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.sh-chips {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.sh-actions {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
|
||||
@@ -55,14 +55,18 @@
|
||||
return typeof window !== 'undefined' && window.innerWidth <= 768;
|
||||
}
|
||||
|
||||
function benefitCardScrollLeft(card: HTMLElement) {
|
||||
return Math.max(0, card.offsetLeft - 8);
|
||||
}
|
||||
|
||||
async function scrollBenefits(direction: -1 | 1) {
|
||||
if (!benefitScroller || !isMobileViewport()) return;
|
||||
|
||||
const nextIndex = Math.max(0, Math.min(activeBenefitIndex + direction, benefitCards.length - 1));
|
||||
await scrollBenefitTo(nextIndex);
|
||||
await scrollBenefitTo(nextIndex, 'smooth');
|
||||
}
|
||||
|
||||
async function scrollBenefitTo(index: number) {
|
||||
async function scrollBenefitTo(index: number, behavior: ScrollBehavior = 'smooth') {
|
||||
if (!benefitScroller || !isMobileViewport()) return;
|
||||
|
||||
const cards = benefitScroller.querySelectorAll<HTMLElement>('.service-benefit-card');
|
||||
@@ -71,10 +75,9 @@
|
||||
|
||||
activeBenefitIndex = index;
|
||||
await tick();
|
||||
targetCard.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'nearest',
|
||||
inline: 'start'
|
||||
benefitScroller.scrollTo({
|
||||
left: benefitCardScrollLeft(targetCard),
|
||||
behavior
|
||||
});
|
||||
}
|
||||
|
||||
@@ -117,7 +120,7 @@
|
||||
benefitScroller.scrollTo({ left: 0, behavior: 'auto' });
|
||||
}
|
||||
bindMobileBenefitObserver();
|
||||
void scrollBenefitTo(activeBenefitIndex);
|
||||
void scrollBenefitTo(activeBenefitIndex, 'auto');
|
||||
} else {
|
||||
mobileBenefitObserver?.disconnect();
|
||||
}
|
||||
@@ -252,7 +255,7 @@
|
||||
aria-label={`Go to benefit ${index + 1}`}
|
||||
aria-pressed={index === activeBenefitIndex}
|
||||
on:click={() => scrollBenefitTo(index)}
|
||||
/>
|
||||
></button>
|
||||
{/each}
|
||||
</div>
|
||||
<button
|
||||
|
||||
@@ -4,69 +4,524 @@
|
||||
import type { IconCard } from '$lib/types';
|
||||
|
||||
export let services: IconCard[];
|
||||
export let heading = 'What we do';
|
||||
export let heading = 'Choose the walk style that suits your dog best.';
|
||||
export let intro =
|
||||
'Choose the walk style that fits your dog best, then book a free Meet & Greet when you are ready.';
|
||||
'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 requestedServiceStorageKey = 'goodwalk_requested_service';
|
||||
const sharedPromises = [
|
||||
'Familiar walkers',
|
||||
'Small-scale care',
|
||||
'Reliable pickup & drop-off',
|
||||
'Updates you will actually want'
|
||||
];
|
||||
|
||||
function bookingHref() {
|
||||
return '#newlead';
|
||||
}
|
||||
|
||||
function primeBookingService(serviceTitle: string) {
|
||||
try {
|
||||
window.sessionStorage.setItem(requestedServiceStorageKey, serviceTitle);
|
||||
} catch {
|
||||
// Ignore storage failures and continue with the link target.
|
||||
// Lightweight presentation metadata — the card only needs to say *what*
|
||||
// each service is before the visitor opens the full service page.
|
||||
const serviceMeta: Record<
|
||||
string,
|
||||
{
|
||||
eyebrow: string;
|
||||
featured?: boolean;
|
||||
featuredLabel?: string;
|
||||
imageUrl: string;
|
||||
imageAlt: string;
|
||||
lead: string;
|
||||
cues: string[];
|
||||
}
|
||||
> = {
|
||||
'Tiny Gang Pack Walks': {
|
||||
eyebrow: 'Good Walk Signature',
|
||||
featured: true,
|
||||
featuredLabel: 'Most loved',
|
||||
imageUrl: '/images/auckland-pack-walk-small-dogs-group.jpg',
|
||||
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',
|
||||
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',
|
||||
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']
|
||||
}
|
||||
};
|
||||
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('goodwalk:service-selected', {
|
||||
detail: { service: serviceTitle }
|
||||
})
|
||||
);
|
||||
}
|
||||
$: orderedServices = services
|
||||
.map((service, index) => ({ service, index }))
|
||||
.sort((a, b) => {
|
||||
const aFeatured = serviceMeta[a.service.title]?.featured ? 0 : 1;
|
||||
const bFeatured = serviceMeta[b.service.title]?.featured ? 0 : 1;
|
||||
|
||||
if (aFeatured !== bFeatured) {
|
||||
return aFeatured - bFeatured;
|
||||
}
|
||||
|
||||
return a.index - b.index;
|
||||
})
|
||||
.map(({ service }) => service);
|
||||
</script>
|
||||
|
||||
<section id="services" use:reveal={{ delay: 20 }} class="reveal-block">
|
||||
<div class="services-inner">
|
||||
<h2 class="section-heading">{heading}</h2>
|
||||
<p class="services-intro">{intro}</p>
|
||||
<div class="section-header">
|
||||
<h2 class="section-heading">{heading}</h2>
|
||||
<p class="section-intro services-intro">{intro}</p>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="services-grid">
|
||||
{#each services as service}
|
||||
<div class="service-card">
|
||||
<div class="service-icon-bubble">
|
||||
<Icon name={service.icon} className="service-card-icon" />
|
||||
{#each orderedServices as service}
|
||||
{@const meta = serviceMeta[service.title]}
|
||||
<a
|
||||
href={service.href}
|
||||
class:service-card-featured={meta?.featured}
|
||||
class="service-card"
|
||||
aria-label={`${service.title} — view service page`}
|
||||
>
|
||||
<div class="service-card-media">
|
||||
{#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>
|
||||
<h3>{service.title}</h3>
|
||||
<p>{service.body}</p>
|
||||
|
||||
{#if service.priceFrom}
|
||||
<p class="service-card-price">{service.priceFrom}</p>
|
||||
{/if}
|
||||
<div class="service-card-body">
|
||||
<span class="service-card-emblem">
|
||||
<Icon name={service.icon} className="service-card-emblem-glyph" />
|
||||
</span>
|
||||
|
||||
{#if service.href}
|
||||
<div class="service-card-actions">
|
||||
<a href={bookingHref()} class="btn btn-green" on:click={() => primeBookingService(service.title)}>
|
||||
<span>Book {service.title}</span>
|
||||
<Icon name="fas fa-arrow-right" />
|
||||
</a>
|
||||
{#if meta?.eyebrow}
|
||||
<span class="service-card-eyebrow">{meta.eyebrow}</span>
|
||||
{/if}
|
||||
<h3>{service.title}</h3>
|
||||
<p>{meta?.lead ?? service.body}</p>
|
||||
|
||||
<a href={service.href} class="service-card-link">
|
||||
View details & pricing
|
||||
</a>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if meta?.cues?.length}
|
||||
<div class="service-card-cues">
|
||||
{#each meta.cues as cue}
|
||||
<span class="service-card-cue">{cue}</span>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<span class="service-card-cta">
|
||||
View service page
|
||||
<Icon name="fas fa-arrow-right" className="service-card-cta-arrow" />
|
||||
</span>
|
||||
</div>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
/* ── 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;
|
||||
}
|
||||
|
||||
/* ── Service cards ── */
|
||||
.services-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 22px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.service-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
border-radius: 24px;
|
||||
background: #fff;
|
||||
border: 1px solid rgba(17, 20, 24, 0.07);
|
||||
box-shadow: 0 6px 20px rgba(17, 20, 24, 0.05);
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition:
|
||||
box-shadow 0.28s ease,
|
||||
transform 0.24s cubic-bezier(0.22, 1, 0.36, 1),
|
||||
border-color 0.28s ease;
|
||||
}
|
||||
|
||||
.service-card-featured {
|
||||
border-color: rgba(242, 191, 47, 0.45);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(255, 209, 0, 0.22),
|
||||
0 10px 26px rgba(17, 20, 24, 0.07);
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.service-card:hover {
|
||||
transform: translateY(-6px);
|
||||
border-color: rgba(33, 48, 33, 0.16);
|
||||
box-shadow: 0 22px 46px rgba(17, 20, 24, 0.13);
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.service-card:hover .service-card-media img {
|
||||
transform: scale(1.06);
|
||||
}
|
||||
|
||||
.service-card:hover .service-card-cta-arrow {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
.service-card:hover .service-card-emblem::after,
|
||||
.service-card:focus-visible .service-card-emblem::after,
|
||||
.service-card:active .service-card-emblem::after {
|
||||
animation: serviceEmblemShine 0.9s cubic-bezier(0.22, 1, 0.36, 1) 1;
|
||||
}
|
||||
}
|
||||
|
||||
.service-card:active {
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
.service-card-media {
|
||||
position: relative;
|
||||
aspect-ratio: 4 / 3;
|
||||
overflow: hidden;
|
||||
background: #ede4d2;
|
||||
}
|
||||
|
||||
.service-card-media img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
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;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
padding: 42px 26px 26px;
|
||||
}
|
||||
|
||||
/* Brand emblem straddles the photo / body seam */
|
||||
.service-card-emblem {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 24px;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
border-radius: 16px;
|
||||
background: var(--gw-green);
|
||||
box-shadow: 0 10px 22px rgba(33, 48, 33, 0.26);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.service-card-emblem :global(.service-card-emblem-glyph) {
|
||||
font-size: 22px;
|
||||
color: var(--yellow);
|
||||
}
|
||||
|
||||
.service-card-emblem::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -20%;
|
||||
left: -85%;
|
||||
width: 60%;
|
||||
height: 140%;
|
||||
background: linear-gradient(
|
||||
120deg,
|
||||
rgba(255, 255, 255, 0) 0%,
|
||||
rgba(255, 255, 255, 0.18) 35%,
|
||||
rgba(255, 255, 255, 0.65) 50%,
|
||||
rgba(255, 255, 255, 0.18) 65%,
|
||||
rgba(255, 255, 255, 0) 100%
|
||||
);
|
||||
transform: rotate(14deg);
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.service-card-eyebrow {
|
||||
margin-bottom: 8px;
|
||||
color: var(--gw-green);
|
||||
font-family: var(--font-head);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.service-card-body h3 {
|
||||
margin: 0 0 8px;
|
||||
font-family: var(--font-head);
|
||||
font-size: 21px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.02em;
|
||||
color: #0d1a0d;
|
||||
}
|
||||
|
||||
.service-card-body p {
|
||||
margin: 0;
|
||||
color: #4c5056;
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.service-card-cues {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 7px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.service-card-cue {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 28px;
|
||||
padding: 4px 11px;
|
||||
border-radius: 999px;
|
||||
background: rgba(33, 48, 33, 0.06);
|
||||
box-shadow: inset 0 0 0 1px rgba(33, 48, 33, 0.07);
|
||||
color: var(--gw-green);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.service-card-cta {
|
||||
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);
|
||||
font-family: var(--font-head);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.service-card-cta-arrow {
|
||||
font-size: 11px;
|
||||
transition: transform 0.2s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
/* ── Mobile ── */
|
||||
@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-body {
|
||||
padding: 40px 22px 24px;
|
||||
}
|
||||
|
||||
.service-card-emblem {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 15px;
|
||||
}
|
||||
|
||||
.service-card-emblem :global(.service-card-emblem-glyph) {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.service-card-body h3 {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes serviceEmblemShine {
|
||||
0% {
|
||||
left: -85%;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
18% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
82% {
|
||||
left: 130%;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
left: 130%;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Reveal ── */
|
||||
:global(.reveal-ready.reveal-block) {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, var(--reveal-distance, 24px), 0);
|
||||
@@ -80,73 +535,4 @@
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
@media (hover: hover) and (min-width: 769px) {
|
||||
:global(.reveal-visible.reveal-block) .service-card {
|
||||
animation: service-card-settle 0.28s cubic-bezier(0.22, 1, 0.36, 1) both;
|
||||
}
|
||||
|
||||
:global(.reveal-visible.reveal-block) .service-card:nth-child(1) {
|
||||
animation-delay: 0.02s;
|
||||
}
|
||||
|
||||
:global(.reveal-visible.reveal-block) .service-card:nth-child(2) {
|
||||
animation-delay: 0.06s;
|
||||
}
|
||||
|
||||
:global(.reveal-visible.reveal-block) .service-card:nth-child(3) {
|
||||
animation-delay: 0.1s;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes service-card-settle {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(6px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.services-intro {
|
||||
max-width: 700px;
|
||||
margin: 18px auto 0;
|
||||
text-align: center;
|
||||
color: #4c5056;
|
||||
font-size: 17px;
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.service-card-actions {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.service-card-actions :global(.btn) {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.service-card-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 28px;
|
||||
color: var(--gw-green);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.service-card-link:hover {
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 0.18em;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,720 @@
|
||||
<script lang="ts">
|
||||
import BookingSection from '$lib/components/BookingSection.svelte';
|
||||
import Icon from '$lib/components/Icon.svelte';
|
||||
import PageHeader from '$lib/components/PageHeader.svelte';
|
||||
import { getEnhancedImage } from '$lib/enhanced-images';
|
||||
import type { SiteSharedContent, TestimonialContent } from '$lib/types';
|
||||
|
||||
export let content: SiteSharedContent;
|
||||
|
||||
$: testimonials = content.testimonials.filter((testimonial) => testimonial.imageUrl);
|
||||
$: featuredTestimonial = testimonials[0];
|
||||
$: supportingTestimonials = testimonials.slice(1);
|
||||
$: testimonialCards = testimonials.map((testimonial, index) => ({
|
||||
...testimonial,
|
||||
enhanced: getEnhancedImage(testimonial.imageUrl),
|
||||
accentClass: `testimonial-card-accent-${(index % 4) + 1}`
|
||||
}));
|
||||
|
||||
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 reviewAlt(testimonial: TestimonialContent) {
|
||||
const detailLead = testimonial.detail.match(/^([^'’]+)/)?.[1]?.trim();
|
||||
return detailLead
|
||||
? `${detailLead}, a Goodwalk dog in Auckland`
|
||||
: `${testimonial.reviewer}'s dog with Goodwalk in Auckland`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<main class="testimonials-page">
|
||||
<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."
|
||||
>
|
||||
<a
|
||||
class="testimonials-page-trust"
|
||||
href="https://g.page/r/CUsvrWPhkYrAEB0/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
aria-label="Read our Google reviews"
|
||||
>
|
||||
<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>
|
||||
</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}`}>
|
||||
<div class="testimonials-page-card-media">
|
||||
{#if testimonial.enhanced}
|
||||
<enhanced:img
|
||||
src={testimonial.enhanced}
|
||||
alt={reviewAlt(testimonial)}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
{:else}
|
||||
<img
|
||||
src={testimonial.imageUrl}
|
||||
alt={reviewAlt(testimonial)}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="testimonials-page-card-copy">
|
||||
<span class="testimonials-page-quote-mark">"</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 class="testimonials-page-google-cta-wrap">
|
||||
<a
|
||||
class="testimonials-page-google-cta"
|
||||
href="https://g.page/r/CUsvrWPhkYrAEB0/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<img
|
||||
class="testimonials-page-trust-logo"
|
||||
src="/images/google-g-logo.svg"
|
||||
alt=""
|
||||
width="18"
|
||||
height="19"
|
||||
/>
|
||||
<span>Read all our Google reviews</span>
|
||||
<Icon name="fas fa-arrow-right" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<BookingSection booking={content.booking} />
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.testimonials-page {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.testimonials-page-trust {
|
||||
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;
|
||||
}
|
||||
|
||||
.testimonials-page-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.testimonials-page-card {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.testimonials-page-card-media {
|
||||
aspect-ratio: 4 / 3;
|
||||
background: #ede4d2;
|
||||
}
|
||||
|
||||
.testimonials-page-card-media img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.testimonials-page-card-copy {
|
||||
padding: 28px 28px 30px;
|
||||
}
|
||||
|
||||
.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 blockquote {
|
||||
margin: 0;
|
||||
color: #2e3031;
|
||||
font-size: 16px;
|
||||
line-height: 1.68;
|
||||
}
|
||||
|
||||
.testimonials-page-author {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.testimonials-page-author-name {
|
||||
color: #102010;
|
||||
font-family: var(--font-head);
|
||||
font-size: 14px;
|
||||
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;
|
||||
}
|
||||
|
||||
.testimonials-page-google-cta {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
min-height: 50px;
|
||||
padding: 0 18px;
|
||||
border-radius: 999px;
|
||||
background: #fff;
|
||||
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);
|
||||
font-family: var(--font-head);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.testimonials-page-intro-grid,
|
||||
.testimonials-page-featured-grid,
|
||||
.testimonials-page-highlights-grid,
|
||||
.testimonials-page-grid,
|
||||
.testimonials-page-spotlight {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.testimonials-page-highlights-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.testimonials-page-spotlight-media {
|
||||
aspect-ratio: 4 / 3;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.testimonials-page-intro,
|
||||
.testimonials-page-featured-section,
|
||||
.testimonials-page-highlights-section,
|
||||
.testimonials-page-grid-section {
|
||||
padding: 52px 0;
|
||||
}
|
||||
|
||||
.testimonials-page-trust {
|
||||
gap: 10px;
|
||||
padding: 10px 14px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.testimonials-page-copy {
|
||||
padding: 24px 20px 22px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.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-copy,
|
||||
.testimonials-page-proof,
|
||||
.testimonials-page-spotlight,
|
||||
.testimonials-page-mini-card,
|
||||
.testimonials-page-highlight-card,
|
||||
.testimonials-page-card {
|
||||
border-radius: 24px;
|
||||
}
|
||||
|
||||
.testimonials-page-author {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.testimonials-page-author-detail::before {
|
||||
content: '';
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -8,10 +8,10 @@
|
||||
|
||||
export let testimonials: TestimonialContent[];
|
||||
export let eyebrow = '30+ five-star reviews';
|
||||
export let heading = 'Proof your dog is in good hands';
|
||||
export let blurb = 'Peace of mind for busy Auckland dog owners. Happier dogs, smoother routines, and a team owners trust with the important stuff.';
|
||||
export let instagramHref = 'https://www.instagram.com/goodwalk.nz/';
|
||||
export let instagramLabel = 'goodwalk.nz';
|
||||
export let heading = 'What owners notice first';
|
||||
export let blurb = 'Happier dogs. Calmer evenings. A routine that feels easier to trust and easier to keep.';
|
||||
export let testimonialsHref = '/testimonials';
|
||||
export let googleReviewsHref = 'https://g.page/r/CUsvrWPhkYrAEB0/';
|
||||
export let seedKey = '';
|
||||
|
||||
type TestimonialSlide = TestimonialContent & { imageUrl: string };
|
||||
@@ -265,12 +265,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<a
|
||||
class="testimonial-google"
|
||||
href="https://g.page/r/CUsvrWPhkYrAEB0/"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<a class="testimonial-google" href={googleReviewsHref} target="_blank" rel="noopener">
|
||||
<img
|
||||
class="testimonial-google-logo"
|
||||
src="/images/google-g-logo.svg"
|
||||
@@ -296,10 +291,19 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<a href={instagramHref} target="_blank" rel="noopener" class="testimonials-instagram-link">
|
||||
<Icon name="fab fa-instagram" />
|
||||
<span>{instagramLabel}</span>
|
||||
</a>
|
||||
<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>
|
||||
|
||||
@@ -328,45 +332,77 @@
|
||||
.testimonials-intro p {
|
||||
margin: 0;
|
||||
color: #4c5056;
|
||||
font-size: 17px;
|
||||
font-size: var(--body-lead-size);
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.testimonials-instagram-link {
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
.testimonials-cta-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, max-content);
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin: 18px auto 0;
|
||||
padding: 10px 16px;
|
||||
justify-content: center;
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
gap: 12px;
|
||||
margin: 22px auto 0;
|
||||
}
|
||||
|
||||
.testimonials-cta {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 9px;
|
||||
min-height: 42px;
|
||||
padding: 9px 16px;
|
||||
border-radius: 999px;
|
||||
background: rgba(33, 48, 33, 0.06);
|
||||
color: var(--gw-green);
|
||||
font-weight: 700;
|
||||
flex-shrink: 0;
|
||||
text-decoration: none;
|
||||
box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.06);
|
||||
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;
|
||||
}
|
||||
|
||||
:global(.testimonials-instagram-link .icon) {
|
||||
font-size: 18px;
|
||||
.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-logo {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.testimonials-cta-label-mobile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.testimonials-instagram-link: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);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(17, 20, 24, 0.06),
|
||||
0 10px 22px rgba(17, 20, 24, 0.08);
|
||||
}
|
||||
}
|
||||
|
||||
.testimonials-instagram-link:active {
|
||||
transform: translateY(1px) scale(0.985);
|
||||
}
|
||||
|
||||
.testimonials-carousel {
|
||||
@@ -387,14 +423,28 @@
|
||||
}
|
||||
|
||||
.testimonials-intro p {
|
||||
font-size: 15px;
|
||||
font-size: var(--body-lead-size-mobile);
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.testimonials-instagram-link {
|
||||
margin: 14px auto 0;
|
||||
padding: 9px 14px;
|
||||
font-size: 15px;
|
||||
.testimonials-cta-row {
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.testimonials-cta {
|
||||
min-height: 38px;
|
||||
padding: 8px 10px;
|
||||
font-size: 11px;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.testimonials-cta-label-desktop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.testimonials-cta-label-mobile {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -672,7 +722,7 @@
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.testimonials-carousel {
|
||||
margin-top: 32px;
|
||||
margin-top: 26px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@@ -708,7 +758,7 @@
|
||||
|
||||
.testimonial-photo-wrap {
|
||||
justify-content: center;
|
||||
padding: 48px 22px 16px;
|
||||
padding: 34px 20px 12px;
|
||||
}
|
||||
|
||||
.testimonial-photo-frame {
|
||||
@@ -720,7 +770,7 @@
|
||||
}
|
||||
|
||||
.testimonial-copy {
|
||||
padding: 8px 28px 32px;
|
||||
padding: 4px 24px 24px;
|
||||
align-self: start;
|
||||
}
|
||||
|
||||
@@ -744,7 +794,7 @@
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.testimonial-arrow-inline {
|
||||
@@ -765,7 +815,7 @@
|
||||
}
|
||||
|
||||
.testimonial-google {
|
||||
margin-top: 20px;
|
||||
margin-top: 16px;
|
||||
font-size: 16px;
|
||||
gap: 10px;
|
||||
padding: 10px 14px;
|
||||
@@ -776,8 +826,8 @@
|
||||
}
|
||||
|
||||
.testimonial-woof {
|
||||
top: 24px;
|
||||
right: 22px;
|
||||
top: 16px;
|
||||
right: 18px;
|
||||
}
|
||||
|
||||
.testimonial-woof-text {
|
||||
|
||||
@@ -1,12 +1,73 @@
|
||||
<script lang="ts">
|
||||
import { onMount, tick } from 'svelte';
|
||||
import { reveal } from '$lib/actions/reveal';
|
||||
import { getEnhancedImage } from '$lib/enhanced-images';
|
||||
import Icon from '$lib/components/Icon.svelte';
|
||||
import type { IconCard } from '$lib/types';
|
||||
|
||||
export let values: IconCard[];
|
||||
let valuesScroller: HTMLDivElement | undefined;
|
||||
let activeIndex = 0;
|
||||
let mobileCardsObserver: IntersectionObserver | null = null;
|
||||
const stakes = [
|
||||
{
|
||||
label: 'Without the right routine',
|
||||
title: 'By the end of the day, everything feels harder than it should.',
|
||||
body:
|
||||
'Your dog still has energy to burn, you are carrying guilt through the workday, and home does not feel as calm as it could.',
|
||||
points: [
|
||||
'A dog who is restless, wired, or harder to settle at home',
|
||||
'A workday shaped by guilt, logistics, and wondering how they are doing',
|
||||
'Too much uncertainty around who is picking up your dog and what the walk will be like'
|
||||
],
|
||||
footer: 'The walk is not really the point. The evening is.'
|
||||
},
|
||||
{
|
||||
label: 'With Goodwalk',
|
||||
title: 'A good walk changes you & your dogs whole evening.',
|
||||
body:
|
||||
'Your dog comes home happier, the routine feels lighter, and you are not spending the day second-guessing whether they are okay.',
|
||||
points: [
|
||||
'A walker your dog recognises and is happy to see at the door',
|
||||
'Small-group or one-on-one care that genuinely suits your dog',
|
||||
'Clear updates, calmer evenings, and one less thing sitting on your mind'
|
||||
],
|
||||
footer: 'That is what people are really buying: peace of mind, routine, and a dog who feels cared for.'
|
||||
}
|
||||
];
|
||||
const clientPhotos = [
|
||||
{
|
||||
imageUrl: '/images/dog.png',
|
||||
alt: 'Two happy Goodwalk client dogs out on a walk in Auckland',
|
||||
name: 'Happy clients',
|
||||
detail: 'out with Goodwalk'
|
||||
},
|
||||
{
|
||||
imageUrl: '/images/pack-walk-tiny-gang.png',
|
||||
alt: 'Goodwalk Tiny Gang dogs together on a walk in Auckland',
|
||||
name: 'Tiny Gang',
|
||||
detail: 'small group pack walks'
|
||||
},
|
||||
{
|
||||
imageUrl: '/images/tiny-gang-mt-albert-park.png',
|
||||
alt: 'Goodwalk dogs together at Mt Albert Park in Auckland',
|
||||
name: 'Mt Albert Park',
|
||||
detail: 'Goodwalk regulars'
|
||||
},
|
||||
{
|
||||
imageUrl: '/images/otis-auckland-dog-walking-review.jpg',
|
||||
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'
|
||||
}
|
||||
];
|
||||
|
||||
$: clientPhotoCards = clientPhotos.map((photo) => ({
|
||||
...photo,
|
||||
enhanced: getEnhancedImage(photo.imageUrl)
|
||||
}));
|
||||
|
||||
$: orderedValues = values
|
||||
.map((value, index) => ({ value, index }))
|
||||
@@ -21,380 +82,562 @@
|
||||
return a.index - b.index;
|
||||
})
|
||||
.map(({ value }) => value);
|
||||
|
||||
function isMobileViewport() {
|
||||
return typeof window !== 'undefined' && window.innerWidth <= 768;
|
||||
}
|
||||
|
||||
function cardScrollLeft(card: HTMLElement) {
|
||||
return Math.max(0, card.offsetLeft - 8);
|
||||
}
|
||||
|
||||
async function scrollValues(direction: 1 | -1) {
|
||||
if (!valuesScroller || !isMobileViewport()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextIndex = Math.max(0, Math.min(activeIndex + direction, orderedValues.length - 1));
|
||||
await scrollToValue(nextIndex, 'smooth');
|
||||
}
|
||||
|
||||
async function scrollToValue(index: number, behavior: ScrollBehavior = 'smooth') {
|
||||
if (!valuesScroller || !isMobileViewport()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cards = valuesScroller.querySelectorAll<HTMLElement>('.value-card');
|
||||
const targetCard = cards[index];
|
||||
if (!targetCard) {
|
||||
return;
|
||||
}
|
||||
|
||||
activeIndex = index;
|
||||
await tick();
|
||||
valuesScroller.scrollTo({
|
||||
left: cardScrollLeft(targetCard),
|
||||
behavior
|
||||
});
|
||||
}
|
||||
|
||||
function bindMobileCardObserver() {
|
||||
mobileCardsObserver?.disconnect();
|
||||
|
||||
if (!valuesScroller || !isMobileViewport()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cards = valuesScroller.querySelectorAll<HTMLElement>('.value-card');
|
||||
if (!cards.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
mobileCardsObserver = new IntersectionObserver(
|
||||
(entries) => {
|
||||
const visibleEntry = entries
|
||||
.filter((entry) => entry.isIntersecting)
|
||||
.sort((a, b) => b.intersectionRatio - a.intersectionRatio)[0];
|
||||
|
||||
if (!visibleEntry) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextIndex = cards.length ? [...cards].indexOf(visibleEntry.target as HTMLElement) : -1;
|
||||
if (nextIndex >= 0) {
|
||||
activeIndex = nextIndex;
|
||||
}
|
||||
},
|
||||
{
|
||||
root: valuesScroller,
|
||||
threshold: [0.6, 0.75, 0.9]
|
||||
}
|
||||
);
|
||||
|
||||
cards.forEach((card) => mobileCardsObserver?.observe(card));
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
const handleResize = () => {
|
||||
if (!valuesScroller) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMobileViewport()) {
|
||||
if (activeIndex === 0) {
|
||||
valuesScroller.scrollTo({ left: 0, behavior: 'auto' });
|
||||
}
|
||||
bindMobileCardObserver();
|
||||
void scrollToValue(activeIndex, 'auto');
|
||||
} else {
|
||||
mobileCardsObserver?.disconnect();
|
||||
}
|
||||
};
|
||||
|
||||
if (valuesScroller && isMobileViewport()) {
|
||||
valuesScroller.scrollTo({ left: 0, behavior: 'auto' });
|
||||
bindMobileCardObserver();
|
||||
}
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
return () => {
|
||||
mobileCardsObserver?.disconnect();
|
||||
window.removeEventListener('resize', handleResize);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<section id="values">
|
||||
<section id="values" use:reveal={{ delay: 30 }} class="reveal-block">
|
||||
<div class="values-inner">
|
||||
<span class="values-eyebrow">Why owners stay</span>
|
||||
<h2 class="section-heading">Calmer dogs. Clearer routines. Less worry.</h2>
|
||||
<p class="values-intro">
|
||||
Everything is designed to make life easier for busy Auckland dog owners and safer, happier for the dogs in our care.
|
||||
</p>
|
||||
<div class="section-header">
|
||||
<span class="eyebrow values-eyebrow">Why people come to us</span>
|
||||
<h2 class="section-heading">Calmer dogs. Better routines. The Tiny Gang effect.</h2>
|
||||
<p class="section-intro values-intro">
|
||||
Goodwalk was created for busy owners who want reliable, relationship-led care their dog genuinely looks forward to.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="values-shell">
|
||||
<div bind:this={valuesScroller} class="values-grid">
|
||||
{#each orderedValues as value, index}
|
||||
<div class:active={index === activeIndex} class="value-card">
|
||||
<div class="value-icon-wrap">
|
||||
<Icon name={value.icon} className="value-card-icon" />
|
||||
</div>
|
||||
<div class="value-text">
|
||||
<h3>{value.title}</h3>
|
||||
<p>{value.body}</p>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="values-mobile-controls" aria-label="Value cards navigation">
|
||||
<button
|
||||
type="button"
|
||||
class="values-mobile-button"
|
||||
aria-label="Previous value"
|
||||
disabled={activeIndex === 0}
|
||||
on:click={() => scrollValues(-1)}
|
||||
>
|
||||
<Icon name="fas fa-chevron-left" />
|
||||
</button>
|
||||
<div class="values-mobile-pager" aria-label="Current value">
|
||||
{#each orderedValues as _, index}
|
||||
<button
|
||||
type="button"
|
||||
class:active={index === activeIndex}
|
||||
class="values-mobile-dot"
|
||||
aria-label={`Go to value ${index + 1}`}
|
||||
aria-pressed={index === activeIndex}
|
||||
on:click={() => scrollToValue(index)}
|
||||
<div class="values-photo-grid" aria-label="Goodwalk client dogs">
|
||||
{#each clientPhotoCards as photo, index}
|
||||
<figure class:values-photo-card-featured={index === 0} class="values-photo-card">
|
||||
{#if photo.enhanced}
|
||||
<enhanced:img
|
||||
class="values-photo-image"
|
||||
src={photo.enhanced}
|
||||
alt={photo.alt}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
{/each}
|
||||
{:else}
|
||||
<img
|
||||
class="values-photo-image"
|
||||
src={photo.imageUrl}
|
||||
alt={photo.alt}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
{/if}
|
||||
<figcaption class="values-photo-caption">
|
||||
<span class="values-photo-name">{photo.name}</span>
|
||||
<span class="values-photo-detail">{photo.detail}</span>
|
||||
</figcaption>
|
||||
</figure>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="values-bento values-contrast">
|
||||
{#each stakes as stake, index}
|
||||
<article class:values-contrast-cell-good={index === 1} class="values-contrast-cell">
|
||||
<div class="values-contrast-head">
|
||||
<span class:values-contrast-label-good={index === 1} class="values-contrast-label">
|
||||
{stake.label}
|
||||
</span>
|
||||
<span class="values-contrast-num">0{index + 1}</span>
|
||||
</div>
|
||||
<h3>{stake.title}</h3>
|
||||
<p class="values-contrast-body">{stake.body}</p>
|
||||
<ul class="values-contrast-list">
|
||||
{#each stake.points as point}
|
||||
<li>
|
||||
<span class="values-contrast-bullet">
|
||||
<Icon
|
||||
name={index === 1 ? 'fas fa-check' : 'fas fa-minus'}
|
||||
className="values-contrast-glyph"
|
||||
/>
|
||||
</span>
|
||||
<span>{point}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<p class="values-contrast-footer">{stake.footer}</p>
|
||||
</article>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="values-points-header">
|
||||
<span class="eyebrow values-eyebrow">What we stand for</span>
|
||||
<h3 class="values-points-title">The values behind every walk</h3>
|
||||
<p class="values-points-intro">
|
||||
Kind handling, small groups, proper safety training, and honest communication — not extras, just how Goodwalk works.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="values-bento values-points">
|
||||
{#each orderedValues as value}
|
||||
<div class="values-point">
|
||||
<div class="values-point-icon">
|
||||
<Icon name={value.icon} className="values-point-glyph" />
|
||||
</div>
|
||||
<h3>{value.title}</h3>
|
||||
<p>{value.body}</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="values-mobile-button"
|
||||
aria-label="Next value"
|
||||
disabled={activeIndex === orderedValues.length - 1}
|
||||
on:click={() => scrollValues(1)}
|
||||
>
|
||||
<Icon name="fas fa-chevron-right" />
|
||||
</button>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
/* Minimalist, grid-based layout (hairline "bento" cells) with Goodwalk
|
||||
brand colour carried through the icons and the "With Goodwalk" cell. */
|
||||
#values {
|
||||
position: relative;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.values-inner {
|
||||
max-width: var(--max-w);
|
||||
margin: 0 auto;
|
||||
padding: 0 50px;
|
||||
}
|
||||
|
||||
.values-inner .section-heading {
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.values-eyebrow {
|
||||
display: block;
|
||||
width: fit-content;
|
||||
margin: 0 auto 10px;
|
||||
padding: 7px 12px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
color: var(--yellow);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
|
||||
background: rgba(33, 48, 33, 0.06);
|
||||
box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.07);
|
||||
}
|
||||
|
||||
.values-intro {
|
||||
max-width: 760px;
|
||||
margin: 18px auto 0;
|
||||
text-align: center;
|
||||
color: rgba(255, 255, 255, 0.82);
|
||||
font-size: 17px;
|
||||
max-width: 720px;
|
||||
}
|
||||
|
||||
/* ── Client photo gallery ── */
|
||||
.values-photo-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1.25fr 0.9fr 0.9fr;
|
||||
grid-template-rows: repeat(2, clamp(170px, 18vw, 240px));
|
||||
gap: 16px;
|
||||
margin-top: 32px;
|
||||
max-width: 1120px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.values-photo-card {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
border-radius: 28px;
|
||||
background: #ede4d2;
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(17, 20, 24, 0.05),
|
||||
0 18px 34px rgba(17, 20, 24, 0.08);
|
||||
}
|
||||
|
||||
.values-photo-card-featured {
|
||||
grid-row: 1 / span 2;
|
||||
}
|
||||
|
||||
.values-photo-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
transition: transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.values-photo-card:hover .values-photo-image {
|
||||
transform: scale(1.06);
|
||||
}
|
||||
}
|
||||
|
||||
.values-photo-caption {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
right: 16px;
|
||||
bottom: 16px;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 18px;
|
||||
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);
|
||||
}
|
||||
|
||||
.values-photo-name,
|
||||
.values-photo-detail {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.values-photo-name {
|
||||
color: #102010;
|
||||
font-family: var(--font-head);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.values-photo-detail {
|
||||
color: #5a605f;
|
||||
font-size: 13px;
|
||||
line-height: 1.3;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* ── Bento container: hairline grid via 1px gaps over a line-coloured base ── */
|
||||
.values-bento {
|
||||
max-width: 1120px;
|
||||
margin-left: auto;
|
||||
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;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 14px 34px rgba(17, 20, 24, 0.06);
|
||||
}
|
||||
|
||||
/* ── Before / after contrast ── */
|
||||
.values-contrast {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
margin-top: 36px;
|
||||
}
|
||||
|
||||
.values-contrast-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 38px 36px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.values-contrast-cell-good {
|
||||
background: var(--gw-green);
|
||||
}
|
||||
|
||||
.values-contrast-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.values-contrast-label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 5px 11px;
|
||||
border-radius: 999px;
|
||||
background: rgba(17, 20, 24, 0.05);
|
||||
color: var(--gray);
|
||||
font-family: var(--font-head);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.values-contrast-label-good {
|
||||
background: var(--yellow);
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.values-contrast-num {
|
||||
font-family: var(--font-head);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: rgba(17, 20, 24, 0.22);
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.values-contrast-cell h3 {
|
||||
margin: 0 0 12px;
|
||||
font-family: var(--font-head);
|
||||
font-size: clamp(20px, 1.9vw, 25px);
|
||||
font-weight: 700;
|
||||
line-height: 1.22;
|
||||
letter-spacing: -0.02em;
|
||||
color: #0d1a0d;
|
||||
}
|
||||
|
||||
.values-contrast-body {
|
||||
margin: 0 0 20px;
|
||||
color: #4c5056;
|
||||
font-size: 15px;
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
.values-mobile-controls {
|
||||
display: none;
|
||||
.values-contrast-list {
|
||||
display: grid;
|
||||
margin: 0 0 22px;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.values-contrast-list li {
|
||||
display: grid;
|
||||
grid-template-columns: 20px minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
align-items: start;
|
||||
padding: 13px 0;
|
||||
color: #3f4348;
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.values-contrast-list li + li {
|
||||
border-top: 1px solid rgba(17, 20, 24, 0.08);
|
||||
}
|
||||
|
||||
.values-contrast-bullet {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.values-contrast-list :global(.values-contrast-glyph) {
|
||||
font-size: 10px;
|
||||
color: var(--gray);
|
||||
}
|
||||
|
||||
.values-contrast-cell-good .values-contrast-bullet {
|
||||
border-radius: 50%;
|
||||
background: var(--yellow);
|
||||
}
|
||||
|
||||
.values-contrast-cell-good .values-contrast-list :global(.values-contrast-glyph) {
|
||||
font-size: 9px;
|
||||
color: var(--gw-green);
|
||||
}
|
||||
|
||||
.values-contrast-footer {
|
||||
margin: auto 0 0;
|
||||
padding-top: 18px;
|
||||
border-top: 1px solid rgba(17, 20, 24, 0.08);
|
||||
color: var(--gw-green);
|
||||
font-family: var(--font-head);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.values-contrast-cell-good .values-contrast-footer {
|
||||
border-top-color: rgba(255, 255, 255, 0.18);
|
||||
color: var(--yellow);
|
||||
}
|
||||
|
||||
/* Light text for the gw-green "With Goodwalk" cell */
|
||||
.values-contrast-cell-good h3 {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.values-contrast-cell-good .values-contrast-num {
|
||||
color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.values-contrast-cell-good .values-contrast-body {
|
||||
color: rgba(255, 255, 255, 0.82);
|
||||
}
|
||||
|
||||
.values-contrast-cell-good .values-contrast-list li {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.values-contrast-cell-good .values-contrast-list li + li {
|
||||
border-top-color: rgba(255, 255, 255, 0.14);
|
||||
}
|
||||
|
||||
/* ── Values points header ── */
|
||||
.values-points-header {
|
||||
margin-top: 52px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.values-points-title {
|
||||
max-width: 19ch;
|
||||
margin: 12px auto 0;
|
||||
font-family: var(--font-head);
|
||||
font-size: clamp(24px, 2.4vw, 32px);
|
||||
font-weight: 700;
|
||||
line-height: 1.14;
|
||||
letter-spacing: -0.03em;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.values-points-intro {
|
||||
max-width: 560px;
|
||||
margin: 14px auto 0;
|
||||
color: #4c5056;
|
||||
font-size: var(--body-copy-size);
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
/* ── Values points ── */
|
||||
.values-points {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
margin-top: 26px;
|
||||
box-shadow: var(--shadow-panel-elevated);
|
||||
}
|
||||
|
||||
.values-point {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 32px 30px;
|
||||
background: #fff;
|
||||
transition: background 0.18s ease;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.values-point:hover {
|
||||
background: #fcfbf6;
|
||||
}
|
||||
}
|
||||
|
||||
.values-point-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-bottom: 18px;
|
||||
border-radius: 11px;
|
||||
background: var(--gw-green);
|
||||
box-shadow: 0 6px 16px rgba(33, 48, 33, 0.18);
|
||||
}
|
||||
|
||||
.values-point-icon :global(.values-point-glyph) {
|
||||
font-size: 17px;
|
||||
color: var(--yellow);
|
||||
}
|
||||
|
||||
.values-point h3 {
|
||||
margin: 0 0 9px;
|
||||
font-family: var(--font-head);
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
line-height: 1.25;
|
||||
color: #0d1a0d;
|
||||
}
|
||||
|
||||
.values-point p {
|
||||
margin: 0;
|
||||
color: #4c5056;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
@media (min-width: 1600px) {
|
||||
.values-photo-grid,
|
||||
.values-bento {
|
||||
max-width: 1180px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Mobile ── */
|
||||
@media (max-width: 768px) {
|
||||
.values-shell {
|
||||
margin-top: 24px;
|
||||
overflow: hidden;
|
||||
.values-inner {
|
||||
padding: 0 var(--space-container-x-mobile);
|
||||
}
|
||||
|
||||
.values-grid {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: calc(100% - 64px);
|
||||
grid-template-columns: none;
|
||||
align-items: stretch;
|
||||
gap: 10px;
|
||||
margin-top: 0;
|
||||
border-top: none;
|
||||
overflow-x: auto;
|
||||
overscroll-behavior-x: contain;
|
||||
scroll-snap-type: x mandatory;
|
||||
scroll-padding-left: 8px;
|
||||
padding: 0 14px 8px 8px;
|
||||
scrollbar-width: none;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
touch-action: pan-x pinch-zoom;
|
||||
}
|
||||
|
||||
.values-grid::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.values-mobile-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 14px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.values-mobile-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
color: #fff;
|
||||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
touch-action: manipulation;
|
||||
transition:
|
||||
background 0.18s ease,
|
||||
opacity 0.18s ease,
|
||||
transform 0.18s ease;
|
||||
}
|
||||
|
||||
.values-mobile-button:disabled {
|
||||
opacity: 0.35;
|
||||
}
|
||||
|
||||
.values-mobile-pager {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.values-mobile-dot {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.28);
|
||||
transition:
|
||||
width 0.22s ease,
|
||||
background 0.22s ease,
|
||||
transform 0.22s ease;
|
||||
}
|
||||
|
||||
.values-mobile-dot.active {
|
||||
width: 28px;
|
||||
background: var(--yellow);
|
||||
}
|
||||
|
||||
.value-card {
|
||||
display: flex;
|
||||
min-height: clamp(230px, 42svh, 320px);
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
padding: 20px 18px 22px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
border-radius: 24px;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.12), rgba(255, 255, 255, 0.07));
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.08),
|
||||
0 6px 16px rgba(0, 0, 0, 0.06);
|
||||
scroll-snap-align: start;
|
||||
scroll-snap-stop: always;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
gap: 14px;
|
||||
transition:
|
||||
background 0.24s ease,
|
||||
box-shadow 0.24s ease,
|
||||
border-color 0.24s ease;
|
||||
touch-action: pan-x;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.value-card.active {
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0.09));
|
||||
border-color: rgba(255, 255, 255, 0.16);
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.08),
|
||||
0 10px 22px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.value-card:nth-child(odd) {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.value-card:last-child {
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.value-icon-wrap {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 18px;
|
||||
margin-top: 0;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.value-card .value-card-icon {
|
||||
font-size: 23px;
|
||||
}
|
||||
|
||||
.value-text {
|
||||
max-width: 30ch;
|
||||
min-width: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.value-text h3 {
|
||||
margin-bottom: 8px;
|
||||
font-size: 21px;
|
||||
line-height: 1.08;
|
||||
}
|
||||
|
||||
.value-card p {
|
||||
font-size: 14px;
|
||||
line-height: 1.55;
|
||||
opacity: 0.9;
|
||||
.values-intro {
|
||||
max-width: 32ch;
|
||||
}
|
||||
|
||||
.values-eyebrow {
|
||||
margin-bottom: 8px;
|
||||
padding: 6px 10px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.values-intro {
|
||||
margin-top: 14px;
|
||||
font-size: 15px;
|
||||
.values-photo-grid {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
grid-template-rows: auto;
|
||||
gap: 10px;
|
||||
margin-top: 22px;
|
||||
}
|
||||
|
||||
.values-photo-card,
|
||||
.values-photo-card-featured {
|
||||
grid-row: auto;
|
||||
min-height: 178px;
|
||||
border-radius: 22px;
|
||||
}
|
||||
|
||||
.values-photo-caption {
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
padding: 10px 11px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.values-photo-name {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.values-photo-detail {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.values-bento {
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.values-contrast {
|
||||
grid-template-columns: 1fr;
|
||||
margin-top: 26px;
|
||||
}
|
||||
|
||||
.values-contrast-cell {
|
||||
padding: 26px 22px;
|
||||
}
|
||||
|
||||
.values-contrast-body {
|
||||
font-size: var(--body-copy-size-mobile);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.values-contrast-list li {
|
||||
font-size: var(--body-copy-size-mobile);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.values-points-header {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.values-points-title {
|
||||
max-width: 16ch;
|
||||
font-size: clamp(22px, 6.4vw, 27px);
|
||||
}
|
||||
|
||||
.values-points-intro {
|
||||
max-width: 36ch;
|
||||
font-size: var(--body-lead-size-mobile);
|
||||
line-height: 1.55;
|
||||
}
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.values-mobile-button:hover {
|
||||
background: rgba(255, 255, 255, 0.18);
|
||||
.values-points {
|
||||
grid-template-columns: 1fr;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.values-point {
|
||||
padding: 26px 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.values-mobile-button:active {
|
||||
transform: scale(0.95);
|
||||
/* ── Reveal ── */
|
||||
:global(.reveal-ready.reveal-block) {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, var(--reveal-distance, 24px), 0);
|
||||
transition:
|
||||
opacity 0.55s ease,
|
||||
transform 0.7s cubic-bezier(0.2, 0.8, 0.2, 1);
|
||||
transition-delay: var(--reveal-delay, 0ms);
|
||||
}
|
||||
|
||||
:global(.reveal-visible.reveal-block) {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user