Content Rewrite
@@ -0,0 +1,133 @@
|
|||||||
|
# Marketing Principles for Goodwalk
|
||||||
|
|
||||||
|
A working reference for the Goodwalk site rebuild and ongoing marketing decisions. Drawn from Chris Do (The Futur) and Debbie Millman (Design Matters), applied to the goal of acquiring 10 new clients.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Chris Do's Principles
|
||||||
|
|
||||||
|
### 1. Sell the transformation, not the service
|
||||||
|
|
||||||
|
People don't buy "dog walking" — they buy peace of mind at work, a tired happy dog, not feeling guilty.
|
||||||
|
|
||||||
|
The headline shouldn't be "Professional Dog Walking in Wellington." It should speak to the outcome:
|
||||||
|
|
||||||
|
- "Come home to a happy, exercised dog"
|
||||||
|
- "Your dog's best part of the day, while you're at work"
|
||||||
|
|
||||||
|
### 2. Niche down to stand out
|
||||||
|
|
||||||
|
"Dog walker" competes with everyone. "Dog walker for working professionals in [suburb] with anxious or reactive dogs" competes with almost no one — and can charge more.
|
||||||
|
|
||||||
|
Pick a wedge.
|
||||||
|
|
||||||
|
### 3. Price on value, not time
|
||||||
|
|
||||||
|
Don't lead with "$25 per walk." Lead with packages and outcomes:
|
||||||
|
|
||||||
|
> **The Working Professional Plan** — 3 walks/week, GPS updates, photo reports
|
||||||
|
|
||||||
|
Hide the hourly rate. Make it about what they get, not what you do.
|
||||||
|
|
||||||
|
### 4. Show, don't tell
|
||||||
|
|
||||||
|
Testimonials and proof crush adjectives. "Reliable and caring" is meaningless.
|
||||||
|
|
||||||
|
A photo of a muddy grinning dog with a one-line quote from the owner sells:
|
||||||
|
|
||||||
|
> "Bowie pulls me to the door when he sees Sarah's car."
|
||||||
|
|
||||||
|
### 5. Free is a magnet
|
||||||
|
|
||||||
|
Most dog walking sites just have a contact form — that's a closed door. Open one with:
|
||||||
|
|
||||||
|
- A free first walk
|
||||||
|
- A free meet-and-greet
|
||||||
|
- A downloadable "Is your dog getting enough exercise?" checklist
|
||||||
|
|
||||||
|
Get people into the funnel.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Debbie Millman's Principles
|
||||||
|
|
||||||
|
### 1. Brand is a story people tell themselves about you
|
||||||
|
|
||||||
|
Branding is deliberate differentiation through storytelling.
|
||||||
|
|
||||||
|
What's the Goodwalk story? Why do you do this? Are you the ex-vet-nurse who only walks small dogs? The runner who takes high-energy breeds on actual trail runs?
|
||||||
|
|
||||||
|
That story belongs on the homepage, not buried on About.
|
||||||
|
|
||||||
|
### 2. Consistency builds trust
|
||||||
|
|
||||||
|
One voice, one visual identity, everywhere:
|
||||||
|
|
||||||
|
- Website
|
||||||
|
- Instagram
|
||||||
|
- Car magnet
|
||||||
|
- The message sent when running 5 minutes late
|
||||||
|
|
||||||
|
Owners are handing you keys to their house and the life of their dog. Visual and verbal consistency signals "I am organised and reliable" before you've said a word.
|
||||||
|
|
||||||
|
### 3. Design is a tool for clarity, not decoration
|
||||||
|
|
||||||
|
Debbie often quotes Massimo Vignelli — design should make the message clearer.
|
||||||
|
|
||||||
|
In 3 seconds, can a stranger answer:
|
||||||
|
|
||||||
|
- What do you do?
|
||||||
|
- Who is it for?
|
||||||
|
- How do I book?
|
||||||
|
|
||||||
|
If they have to scroll or think, you're losing them.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Applied: A Plan for 10 New Clients
|
||||||
|
|
||||||
|
A site rewrite with these principles in mind.
|
||||||
|
|
||||||
|
### 1. Homepage hero
|
||||||
|
|
||||||
|
- Outcome-focused headline
|
||||||
|
- One strong photo of a happy dog mid-walk
|
||||||
|
- One button: **"Book a free meet-and-greet"**
|
||||||
|
|
||||||
|
### 2. Pick a niche and say it out loud
|
||||||
|
|
||||||
|
Even just "for [your suburb] working professionals" narrows the field and helps you rank.
|
||||||
|
|
||||||
|
### 3. Three packages, not an hourly rate
|
||||||
|
|
||||||
|
Make the middle one the obvious choice (the "decoy effect" — Chris talks about this).
|
||||||
|
|
||||||
|
### 4. Three testimonials with photos and dog names
|
||||||
|
|
||||||
|
Real names, real dogs. Not "J.S. — Customer."
|
||||||
|
|
||||||
|
### 5. One story section
|
||||||
|
|
||||||
|
Who you are, why you do this, why someone should trust you with their dog and their house key.
|
||||||
|
|
||||||
|
### 6. Lead magnet
|
||||||
|
|
||||||
|
A free PDF like "How much exercise does your dog actually need?" in exchange for an email. Then you have a list to follow up with.
|
||||||
|
|
||||||
|
### 7. Kill booking friction
|
||||||
|
|
||||||
|
One-click to a calendar or a WhatsApp link. Not a 7-field form.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Reference Checklist
|
||||||
|
|
||||||
|
- [ ] Headline sells the outcome, not the service
|
||||||
|
- [ ] Niche is named explicitly on the homepage
|
||||||
|
- [ ] Pricing presented as packages, not hourly
|
||||||
|
- [ ] At least 3 testimonials with real names, dog names, and photos
|
||||||
|
- [ ] Founder story visible on homepage
|
||||||
|
- [ ] Lead magnet (PDF or free meet-and-greet) above the fold
|
||||||
|
- [ ] Booking is one click — calendar link or WhatsApp
|
||||||
|
- [ ] Visual and verbal identity consistent across site, Instagram, and comms
|
||||||
|
- [ ] In 3 seconds: what / who / how-to-book is obvious
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
box-shadow: 0 24px 60px rgba(17, 20, 24, 0.2);
|
box-shadow: 0 24px 60px rgba(17, 20, 24, 0.2);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-family: 'Readex Pro', system-ui, sans-serif;
|
font-family: 'Readex Pro', system-ui, sans-serif;
|
||||||
color: #213021;
|
color: var(--gw-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-js-kicker {
|
.no-js-kicker {
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: #213021;
|
color: var(--gw-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-js-card h1 {
|
.no-js-card h1 {
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
font-size: clamp(28px, 4vw, 38px);
|
font-size: clamp(28px, 4vw, 38px);
|
||||||
line-height: 1.05;
|
line-height: 1.05;
|
||||||
letter-spacing: -0.04em;
|
letter-spacing: -0.04em;
|
||||||
color: #213021;
|
color: var(--gw-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-js-card p {
|
.no-js-card p {
|
||||||
|
|||||||
@@ -18,9 +18,9 @@
|
|||||||
<h1>Contact Us</h1>
|
<h1>Contact Us</h1>
|
||||||
<p class="booking-page-sub">
|
<p class="booking-page-sub">
|
||||||
{#if allowGeneralEnquiry}
|
{#if allowGeneralEnquiry}
|
||||||
Fill in the form below to book a Meet & Greet or send a general enquiry.
|
Book a Meet & Greet or send a general enquiry. We’ll come back within 24 hours.
|
||||||
{:else}
|
{:else}
|
||||||
Fill in the form below and we'll be in touch to arrange a free introduction.
|
Tell us a little about your dog and we’ll be in touch within 24 hours to arrange a free Meet & Greet.
|
||||||
{/if}
|
{/if}
|
||||||
</p>
|
</p>
|
||||||
<div class="booking-page-contact">
|
<div class="booking-page-contact">
|
||||||
@@ -46,7 +46,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.booking-page-hero {
|
.booking-page-hero {
|
||||||
background: var(--green);
|
background: var(--gw-green);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 64px 0 72px;
|
padding: 64px 0 72px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,38 @@
|
|||||||
type EnquiryType = 'booking' | 'general';
|
type EnquiryType = 'booking' | 'general';
|
||||||
const visitStartedStorageKey = 'goodwalk_visit_started_at';
|
const visitStartedStorageKey = 'goodwalk_visit_started_at';
|
||||||
const journeyStorageKey = 'goodwalk_journey';
|
const journeyStorageKey = 'goodwalk_journey';
|
||||||
|
const requestedServiceStorageKey = 'goodwalk_requested_service';
|
||||||
const maxJourneyEntries = 8;
|
const maxJourneyEntries = 8;
|
||||||
|
const servicePrompts: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
intro: string;
|
||||||
|
messageLabel: string;
|
||||||
|
messagePlaceholder: string;
|
||||||
|
}
|
||||||
|
> = {
|
||||||
|
'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',
|
||||||
|
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?'
|
||||||
|
},
|
||||||
|
'1:1 Walks': {
|
||||||
|
intro:
|
||||||
|
'Tell us about your dog, your area, and what you want from one-on-one walks so we can plan the right routine.',
|
||||||
|
messageLabel: '1:1 walk needs',
|
||||||
|
messagePlaceholder:
|
||||||
|
'Tell us about your dog’s size, pace, leash manners, confidence, and anything else that would help us tailor a one-on-one walk.'
|
||||||
|
},
|
||||||
|
'Puppy Visits': {
|
||||||
|
intro:
|
||||||
|
'Tell us about your puppy, your area, and the kind of support you need at home so we can plan the right visit.',
|
||||||
|
messageLabel: 'Puppy visit details',
|
||||||
|
messagePlaceholder:
|
||||||
|
'Tell us your puppy’s age, routine, toilet needs, feeding schedule, and anything important we should know before visiting.'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let step = 1;
|
let step = 1;
|
||||||
$: headingParts = splitBookingTitle(booking.title);
|
$: headingParts = splitBookingTitle(booking.title);
|
||||||
@@ -76,7 +107,9 @@
|
|||||||
const defaultGeneralSubtitle =
|
const defaultGeneralSubtitle =
|
||||||
'Almost there — just your contact details so we can reply properly to your message.';
|
'Almost there — just your contact details so we can reply properly to your message.';
|
||||||
|
|
||||||
$: dogIntro = booking.dogIntro?.trim() || defaultDogIntro;
|
$: primarySelectedService = selectedServices[0] ?? '';
|
||||||
|
$: activeServicePrompt = servicePrompts[primarySelectedService];
|
||||||
|
$: dogIntro = activeServicePrompt?.intro || booking.dogIntro?.trim() || defaultDogIntro;
|
||||||
$: generalIntro = booking.generalIntro?.trim() || defaultGeneralIntro;
|
$: generalIntro = booking.generalIntro?.trim() || defaultGeneralIntro;
|
||||||
$: hasServices = booking.serviceOptions.length > 0;
|
$: hasServices = booking.serviceOptions.length > 0;
|
||||||
$: if (!allowGeneralEnquiry && enquiryType === 'general') {
|
$: if (!allowGeneralEnquiry && enquiryType === 'general') {
|
||||||
@@ -90,6 +123,16 @@
|
|||||||
$: dogStepLabel = booking.dogStepLabel?.trim() || 'Your dog';
|
$: dogStepLabel = booking.dogStepLabel?.trim() || 'Your dog';
|
||||||
$: detailsStepLabel = isGeneralEnquiry ? 'Your enquiry' : dogStepLabel;
|
$: detailsStepLabel = isGeneralEnquiry ? 'Your enquiry' : dogStepLabel;
|
||||||
$: detailsStepIntro = isGeneralEnquiry ? generalIntro : dogIntro;
|
$: detailsStepIntro = isGeneralEnquiry ? generalIntro : dogIntro;
|
||||||
|
$: bookingEyebrow = isGeneralEnquiry ? 'Friendly contact' : primarySelectedService || 'Free Meet & Greet';
|
||||||
|
$: bookingIntro = isGeneralEnquiry
|
||||||
|
? 'Send us the details and we’ll point you in the right direction.'
|
||||||
|
: 'Tell us a little about your dog first. We’ll come back within 24 hours with the right next step.';
|
||||||
|
$: detailsMessageLabel = isGeneralEnquiry
|
||||||
|
? 'Your Message'
|
||||||
|
: activeServicePrompt?.messageLabel || 'About Your Dog';
|
||||||
|
$: detailsMessagePlaceholder = isGeneralEnquiry
|
||||||
|
? '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';
|
$: successPetName = petName.trim() || 'your dog';
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
@@ -98,6 +141,19 @@
|
|||||||
pageEnteredAt = now;
|
pageEnteredAt = now;
|
||||||
visitStartedAt = readOrCreateVisitStartedAt(now);
|
visitStartedAt = readOrCreateVisitStartedAt(now);
|
||||||
journey = updateJourneySnapshot(window.location.pathname, window.location.search);
|
journey = updateJourneySnapshot(window.location.pathname, window.location.search);
|
||||||
|
|
||||||
|
applyRequestedService();
|
||||||
|
|
||||||
|
const handleRequestedService = (event: Event) => {
|
||||||
|
const customEvent = event as CustomEvent<{ service?: string }>;
|
||||||
|
applyRequestedService(customEvent.detail?.service?.trim());
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('goodwalk:service-selected', handleRequestedService as EventListener);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('goodwalk:service-selected', handleRequestedService as EventListener);
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
function splitBookingTitle(title: string) {
|
function splitBookingTitle(title: string) {
|
||||||
@@ -169,11 +225,35 @@
|
|||||||
errors = {};
|
errors = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function applyRequestedService(service?: string) {
|
||||||
|
const requestedService =
|
||||||
|
service ||
|
||||||
|
(() => {
|
||||||
|
try {
|
||||||
|
return window.sessionStorage.getItem(requestedServiceStorageKey)?.trim() || '';
|
||||||
|
} catch {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (!requestedService || !booking.serviceOptions.includes(requestedService)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedServices = [requestedService];
|
||||||
|
|
||||||
|
try {
|
||||||
|
window.sessionStorage.removeItem(requestedServiceStorageKey);
|
||||||
|
} catch {
|
||||||
|
// Ignore storage cleanup failures.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function toggleService(service: string, checked: boolean) {
|
function toggleService(service: string, checked: boolean) {
|
||||||
noteInteraction();
|
noteInteraction();
|
||||||
|
|
||||||
if (checked) {
|
if (checked) {
|
||||||
selectedServices = [...selectedServices, service];
|
selectedServices = [service, ...selectedServices.filter((item) => item !== service)];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,10 +422,22 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="booking-header">
|
<div class="booking-header">
|
||||||
|
<span class="booking-eyebrow">{bookingEyebrow}</span>
|
||||||
<h2 class="booking-title">
|
<h2 class="booking-title">
|
||||||
<span class="booking-title-plain">{headingParts.plain}</span>{' '}
|
<span class="booking-title-plain">{headingParts.plain}</span>{' '}
|
||||||
<span class="booking-title-highlight">{headingParts.highlight}</span>
|
<span class="booking-title-highlight">{headingParts.highlight}</span>
|
||||||
</h2>
|
</h2>
|
||||||
|
<p class="booking-intro">{bookingIntro}</p>
|
||||||
|
<div class="booking-trust-row" aria-label="Booking highlights">
|
||||||
|
<span class="booking-trust-chip">
|
||||||
|
<Icon name="fas fa-comment-dots" />
|
||||||
|
Reply within 24 hours
|
||||||
|
</span>
|
||||||
|
<span class="booking-trust-chip">
|
||||||
|
<Icon name="fas fa-paw" />
|
||||||
|
Free, no-obligation Meet & Greet
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="booking-stepper" aria-label="Booking form steps">
|
<div class="booking-stepper" aria-label="Booking form steps">
|
||||||
<button
|
<button
|
||||||
@@ -438,7 +530,9 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if !isGeneralEnquiry}
|
{#if !isGeneralEnquiry}
|
||||||
<div class="booking-field-card" class:booking-field-card-invalid={errors.petName}>
|
<div class="booking-field-card booking-field-card-group booking-field-card-full">
|
||||||
|
<div class="booking-field-group booking-field-group-dog">
|
||||||
|
<div class="booking-field-stack" class:booking-field-stack-invalid={errors.petName}>
|
||||||
<label for="petName">
|
<label for="petName">
|
||||||
<Icon name="fas fa-dog" /> Dog's Name <span class="booking-required">*</span>
|
<Icon name="fas fa-dog" /> Dog's Name <span class="booking-required">*</span>
|
||||||
</label>
|
</label>
|
||||||
@@ -461,10 +555,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div class="booking-field-stack" class:booking-field-stack-invalid={errors.location}>
|
||||||
class="booking-field-card booking-field-card-wide"
|
|
||||||
class:booking-field-card-invalid={errors.location}
|
|
||||||
>
|
|
||||||
<label for="location">
|
<label for="location">
|
||||||
<Icon name="fas fa-location-dot" /> Location <span class="booking-required">*</span>
|
<Icon name="fas fa-location-dot" /> Location <span class="booking-required">*</span>
|
||||||
</label>
|
</label>
|
||||||
@@ -486,24 +577,20 @@
|
|||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="booking-field-card booking-field-card-full"
|
class="booking-field-stack booking-field-stack-full"
|
||||||
class:booking-field-card-invalid={errors.message}
|
class:booking-field-stack-invalid={errors.message}
|
||||||
>
|
>
|
||||||
<label for="message">
|
<label for="message">
|
||||||
<Icon name="fas fa-comment" /> {isGeneralEnquiry ? 'Your Message' : 'About Your Dog'}
|
<Icon name="fas fa-comment" /> {detailsMessageLabel}
|
||||||
{#if isGeneralEnquiry}<span class="booking-required">*</span>{/if}
|
|
||||||
</label>
|
</label>
|
||||||
<textarea
|
<textarea
|
||||||
bind:value={message}
|
bind:value={message}
|
||||||
id="message"
|
id="message"
|
||||||
name="message"
|
name="message"
|
||||||
rows="4"
|
rows="4"
|
||||||
placeholder={isGeneralEnquiry
|
placeholder={detailsMessagePlaceholder}
|
||||||
? 'Tell us if this is feedback, a complaint, a business enquiry, or anything else we should know.'
|
|
||||||
: 'Describe your pet, any special needs, or anything we should know.'}
|
|
||||||
class:input-invalid={errors.message}
|
class:input-invalid={errors.message}
|
||||||
on:input={() => clearError('message')}
|
on:input={() => clearError('message')}
|
||||||
></textarea>
|
></textarea>
|
||||||
@@ -514,10 +601,9 @@
|
|||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if hasServices && !isGeneralEnquiry}
|
{#if hasServices}
|
||||||
<div class="booking-service-row">
|
<div class="booking-field-stack booking-field-stack-full">
|
||||||
<span class="booking-service-label"><Icon name="fas fa-paw" /> Services</span>
|
<span class="booking-service-label"><Icon name="fas fa-paw" /> Services</span>
|
||||||
<div class="booking-service-options">
|
<div class="booking-service-options">
|
||||||
{#each booking.serviceOptions as service}
|
{#each booking.serviceOptions as service}
|
||||||
@@ -538,13 +624,44 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if isGeneralEnquiry}
|
||||||
|
<div
|
||||||
|
class="booking-field-card booking-field-card-full"
|
||||||
|
class:booking-field-card-invalid={errors.message}
|
||||||
|
>
|
||||||
|
<label for="message">
|
||||||
|
<Icon name="fas fa-comment" /> {detailsMessageLabel}
|
||||||
|
<span class="booking-required">*</span>
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
bind:value={message}
|
||||||
|
id="message"
|
||||||
|
name="message"
|
||||||
|
rows="4"
|
||||||
|
placeholder={detailsMessagePlaceholder}
|
||||||
|
class:input-invalid={errors.message}
|
||||||
|
on:input={() => clearError('message')}
|
||||||
|
></textarea>
|
||||||
|
{#if errors.message}
|
||||||
|
<p class="field-error">
|
||||||
|
<Icon name="fas fa-circle-exclamation" />
|
||||||
|
{errors.message}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="booking-actions booking-actions-next">
|
<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 booking-next-button" on:click={goToOwnerStep}>
|
||||||
{ownerStepLabel}
|
Next: {ownerStepLabel.toLowerCase()}
|
||||||
<Icon name="fas fa-arrow-right" />
|
<Icon name="fas fa-arrow-right" />
|
||||||
</button>
|
</button>
|
||||||
<p class="booking-next-note">Response from us within 24 hours</p>
|
<p class="booking-next-note">No payment, no pressure, just the right starting point for your dog.</p>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<input type="hidden" name="fullName" value={fullName} />
|
<input type="hidden" name="fullName" value={fullName} />
|
||||||
@@ -652,7 +769,13 @@
|
|||||||
Back
|
Back
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" class="btn btn-yellow booking-submit-button" disabled={submitting}>
|
<button type="submit" class="btn btn-yellow booking-submit-button" disabled={submitting}>
|
||||||
{#if submitting}Sending…{:else}Send <Icon name="fas fa-arrow-right" />{/if}
|
{#if submitting}
|
||||||
|
Sending…
|
||||||
|
{:else if isGeneralEnquiry}
|
||||||
|
Send enquiry <Icon name="fas fa-arrow-right" />
|
||||||
|
{:else}
|
||||||
|
Request Meet & Greet <Icon name="fas fa-arrow-right" />
|
||||||
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -141,7 +141,7 @@
|
|||||||
margin: 0 0 12px;
|
margin: 0 0 12px;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #213021;
|
color: var(--gw-green);
|
||||||
line-height: 1.25;
|
line-height: 1.25;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +162,7 @@
|
|||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
background: #f7f6f1;
|
background: #f7f6f1;
|
||||||
border: 1px solid #ebe9df;
|
border: 1px solid #ebe9df;
|
||||||
color: #213021;
|
color: var(--gw-green);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition:
|
transition:
|
||||||
background 0.15s ease,
|
background 0.15s ease,
|
||||||
@@ -187,7 +187,7 @@
|
|||||||
.modal-email-address {
|
.modal-email-address {
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #213021;
|
color: var(--gw-green);
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,7 +226,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.modal-btn-primary {
|
.modal-btn-primary {
|
||||||
background: #213021;
|
background: var(--gw-green);
|
||||||
color: #ffd100;
|
color: #ffd100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,13 +237,13 @@
|
|||||||
|
|
||||||
.modal-btn-secondary {
|
.modal-btn-secondary {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: #213021;
|
color: var(--gw-green);
|
||||||
border: 1px solid #d4d2c6;
|
border: 1px solid #d4d2c6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-btn-secondary:hover {
|
.modal-btn-secondary:hover {
|
||||||
background: #f2f2f0;
|
background: #f2f2f0;
|
||||||
border-color: #213021;
|
border-color: var(--gw-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes backdrop-in {
|
@keyframes backdrop-in {
|
||||||
|
|||||||
@@ -25,6 +25,14 @@
|
|||||||
connector: ''
|
connector: ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function linkTarget(external?: boolean) {
|
||||||
|
return external ? '_blank' : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function linkRel(external?: boolean) {
|
||||||
|
return external ? 'noopener' : undefined;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section id="hero">
|
<section id="hero">
|
||||||
@@ -71,8 +79,22 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="hero-buttons">
|
<div class="hero-buttons">
|
||||||
<a href={hero.primaryCta.href} class="btn btn-yellow">{hero.primaryCta.label}</a>
|
<a
|
||||||
<a href={hero.secondaryCta.href} class="btn btn-outline">{hero.secondaryCta.label}</a>
|
href={hero.primaryCta.href}
|
||||||
|
target={linkTarget(hero.primaryCta.external)}
|
||||||
|
rel={linkRel(hero.primaryCta.external)}
|
||||||
|
class="btn btn-yellow"
|
||||||
|
>
|
||||||
|
{hero.primaryCta.label}
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href={hero.secondaryCta.href}
|
||||||
|
target={linkTarget(hero.secondaryCta.external)}
|
||||||
|
rel={linkRel(hero.secondaryCta.external)}
|
||||||
|
class="btn btn-outline"
|
||||||
|
>
|
||||||
|
{hero.secondaryCta.label}
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { reveal } from '$lib/actions/reveal';
|
import { reveal } from '$lib/actions/reveal';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
|
||||||
import type { HowItWorksContent } from '$lib/types';
|
import type { HowItWorksContent } from '$lib/types';
|
||||||
|
|
||||||
export let content: HowItWorksContent;
|
export let content: HowItWorksContent;
|
||||||
@@ -17,28 +16,22 @@
|
|||||||
|
|
||||||
<div class="how-it-works-flow" aria-label="How it works">
|
<div class="how-it-works-flow" aria-label="How it works">
|
||||||
{#each content.steps as step, index}
|
{#each content.steps as step, index}
|
||||||
<article class="how-it-works-step">
|
<article class:how-it-works-step-payoff={index === content.steps.length - 1} class="how-it-works-step">
|
||||||
<div class="how-it-works-badge" aria-hidden="true">
|
<div class="how-it-works-rail-node" aria-hidden="true">
|
||||||
<span class="how-it-works-count">0{index + 1}</span>
|
<span class="how-it-works-rail-dot"></span>
|
||||||
</div>
|
</div>
|
||||||
{#if step.icon}
|
<div class="how-it-works-step-top">
|
||||||
<div class="how-it-works-icon-bubble">
|
<span class="how-it-works-count">{`0${index + 1}`}</span>
|
||||||
<Icon name={step.icon} className="how-it-works-icon" />
|
<span class="how-it-works-phase">{step.phase || `Step ${index + 1}`}</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
<div class="how-it-works-copy">
|
||||||
<h3>{step.title}</h3>
|
<h3>{step.title}</h3>
|
||||||
<p>{step.body}</p>
|
{#if step.benefit}
|
||||||
</article>
|
<p class="how-it-works-benefit">{step.benefit}</p>
|
||||||
|
|
||||||
{#if index < content.steps.length - 1}
|
|
||||||
<div class="how-it-works-connector" aria-hidden="true">
|
|
||||||
<span class="how-it-works-connector-line"></span>
|
|
||||||
<div class="how-it-works-connector-bubble">
|
|
||||||
<Icon name="fas fa-paw" className="how-it-works-connector-icon" />
|
|
||||||
</div>
|
|
||||||
<span class="how-it-works-connector-line"></span>
|
|
||||||
</div>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
|
<p class="how-it-works-body">{step.body}</p>
|
||||||
|
</article>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -84,108 +77,113 @@
|
|||||||
|
|
||||||
.how-it-works-flow {
|
.how-it-works-flow {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr) auto minmax(0, 1fr);
|
position: relative;
|
||||||
gap: 18px;
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 28px;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
margin-top: 34px;
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.how-it-works-flow::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 18px;
|
||||||
|
right: 18px;
|
||||||
|
top: 22px;
|
||||||
|
height: 1px;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(33, 48, 33, 0.12) 0%,
|
||||||
|
rgba(33, 48, 33, 0.28) 50%,
|
||||||
|
rgba(33, 48, 33, 0.12) 100%
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
.how-it-works-step {
|
.how-it-works-step {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 26px 24px;
|
display: flex;
|
||||||
border-radius: 28px;
|
flex-direction: column;
|
||||||
background:
|
padding: 0 18px 0 0;
|
||||||
radial-gradient(circle at top center, rgba(255, 209, 0, 0.18), transparent 36%),
|
text-align: left;
|
||||||
linear-gradient(180deg, #fffaf0 0%, #f8f4ea 100%);
|
|
||||||
box-shadow:
|
|
||||||
inset 0 0 0 1px rgba(17, 20, 24, 0.05),
|
|
||||||
0 14px 28px rgba(17, 20, 24, 0.04);
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.how-it-works-badge {
|
.how-it-works-step-payoff {
|
||||||
display: inline-flex;
|
transform: translateY(-4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.how-it-works-rail-node {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 14px;
|
min-height: 44px;
|
||||||
padding: 8px 12px;
|
margin-bottom: 24px;
|
||||||
border-radius: 999px;
|
}
|
||||||
background: #fff;
|
|
||||||
box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.06);
|
.how-it-works-rail-dot {
|
||||||
|
display: inline-flex;
|
||||||
|
width: 11px;
|
||||||
|
height: 11px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--gw-green);
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 7px #fff,
|
||||||
|
0 0 0 8px rgba(33, 48, 33, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.how-it-works-step-top {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.how-it-works-count {
|
.how-it-works-count {
|
||||||
color: var(--green);
|
color: rgba(33, 48, 33, 0.3);
|
||||||
font-family: var(--font-head);
|
font-family: var(--font-head);
|
||||||
font-size: 13px;
|
font-size: 36px;
|
||||||
font-weight: 700;
|
font-weight: 800;
|
||||||
letter-spacing: 0.04em;
|
line-height: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.how-it-works-icon-bubble {
|
.how-it-works-phase {
|
||||||
display: inline-flex;
|
color: var(--gw-green);
|
||||||
align-items: center;
|
font-family: var(--font-head);
|
||||||
justify-content: center;
|
font-size: 12px;
|
||||||
width: 72px;
|
font-weight: 800;
|
||||||
height: 72px;
|
letter-spacing: 0.12em;
|
||||||
margin: 0 auto 16px;
|
text-transform: uppercase;
|
||||||
border-radius: 50%;
|
|
||||||
background: var(--green);
|
|
||||||
box-shadow:
|
|
||||||
inset 0 0 0 1px rgba(255, 255, 255, 0.08),
|
|
||||||
0 16px 28px rgba(33, 48, 33, 0.16);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.how-it-works-icon.icon) {
|
.how-it-works-copy {
|
||||||
color: #fff;
|
padding: 22px 22px 20px;
|
||||||
font-size: 24px;
|
border-radius: 24px;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(255, 250, 240, 0.92) 0%, rgba(248, 244, 234, 0.92) 100%);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.how-it-works-step h3 {
|
.how-it-works-step h3 {
|
||||||
margin: 0 0 10px;
|
margin: 0;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
|
line-height: 1.18;
|
||||||
}
|
}
|
||||||
|
|
||||||
.how-it-works-step p {
|
.how-it-works-benefit {
|
||||||
margin: 0;
|
margin: 10px 0 0;
|
||||||
|
color: #6b5830;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.how-it-works-body {
|
||||||
|
margin: 14px 0 0;
|
||||||
|
padding: 0 4px 0 22px;
|
||||||
color: #4c5056;
|
color: #4c5056;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.how-it-works-connector {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 10px;
|
|
||||||
min-width: 90px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.how-it-works-connector-line {
|
|
||||||
width: 24px;
|
|
||||||
height: 2px;
|
|
||||||
border-radius: 999px;
|
|
||||||
background: rgba(33, 48, 33, 0.22);
|
|
||||||
}
|
|
||||||
|
|
||||||
.how-it-works-connector-bubble {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: linear-gradient(180deg, #254129 0%, #213021 100%);
|
|
||||||
box-shadow:
|
|
||||||
inset 0 0 0 1px rgba(255, 255, 255, 0.08),
|
|
||||||
0 14px 24px rgba(33, 48, 33, 0.14);
|
|
||||||
}
|
|
||||||
|
|
||||||
:global(.how-it-works-connector-icon.icon) {
|
|
||||||
color: var(--green);
|
|
||||||
color: #ffd54a;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
#how-it-works {
|
#how-it-works {
|
||||||
padding-top: 6px;
|
padding-top: 6px;
|
||||||
@@ -202,58 +200,66 @@
|
|||||||
|
|
||||||
.how-it-works-flow {
|
.how-it-works-flow {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
gap: 14px;
|
gap: 24px;
|
||||||
margin-top: 26px;
|
margin-top: 26px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.how-it-works-flow::before {
|
||||||
|
left: 5px;
|
||||||
|
right: auto;
|
||||||
|
top: 22px;
|
||||||
|
bottom: 22px;
|
||||||
|
width: 1px;
|
||||||
|
height: auto;
|
||||||
|
background: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(33, 48, 33, 0.12) 0%,
|
||||||
|
rgba(33, 48, 33, 0.28) 50%,
|
||||||
|
rgba(33, 48, 33, 0.12) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
.how-it-works-step {
|
.how-it-works-step {
|
||||||
display: grid;
|
padding: 0 0 0 28px;
|
||||||
grid-template-columns: auto 1fr auto;
|
|
||||||
grid-template-areas:
|
|
||||||
'icon title badge'
|
|
||||||
'body body body';
|
|
||||||
column-gap: 14px;
|
|
||||||
row-gap: 10px;
|
|
||||||
padding: 20px 18px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.how-it-works-badge {
|
.how-it-works-step-payoff {
|
||||||
grid-area: badge;
|
transform: none;
|
||||||
justify-self: end;
|
|
||||||
align-self: start;
|
|
||||||
margin-bottom: 0;
|
|
||||||
padding: 7px 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.how-it-works-icon-bubble {
|
.how-it-works-rail-node {
|
||||||
grid-area: icon;
|
position: absolute;
|
||||||
width: 64px;
|
left: 0;
|
||||||
height: 64px;
|
top: 0;
|
||||||
margin: 0;
|
min-height: 0;
|
||||||
align-self: start;
|
margin: 18px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.how-it-works-rail-dot {
|
||||||
box-shadow:
|
box-shadow:
|
||||||
inset 0 0 0 1px rgba(255, 255, 255, 0.08),
|
0 0 0 5px #fff,
|
||||||
0 12px 22px rgba(33, 48, 33, 0.14);
|
0 0 0 6px rgba(33, 48, 33, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.how-it-works-step-top {
|
||||||
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.how-it-works-step h3 {
|
.how-it-works-step h3 {
|
||||||
grid-area: title;
|
|
||||||
align-self: center;
|
|
||||||
margin: 0;
|
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.how-it-works-step p {
|
.how-it-works-benefit {
|
||||||
grid-area: body;
|
margin-top: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.how-it-works-body {
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
|
padding: 14px 2px 0 18px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.55;
|
line-height: 1.55;
|
||||||
}
|
}
|
||||||
|
|
||||||
.how-it-works-connector {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -78,7 +78,7 @@
|
|||||||
box-shadow:
|
box-shadow:
|
||||||
inset 0 0 0 1px rgba(17, 20, 24, 0.06),
|
inset 0 0 0 1px rgba(17, 20, 24, 0.06),
|
||||||
0 10px 24px rgba(17, 20, 24, 0.04);
|
0 10px 24px rgba(17, 20, 24, 0.04);
|
||||||
color: #213021;
|
color: var(--gw-green);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
|
|
||||||
.info-nearby-kicker {
|
.info-nearby-kicker {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
color: var(--green);
|
color: var(--gw-green);
|
||||||
font-family: var(--font-head);
|
font-family: var(--font-head);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
min-height: 48px;
|
min-height: 48px;
|
||||||
padding: 12px 18px;
|
padding: 12px 18px;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
background: var(--green);
|
background: var(--gw-green);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-family: var(--font-head);
|
font-family: var(--font-head);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
export let instagram: HomePageContent['instagram'];
|
export let instagram: HomePageContent['instagram'];
|
||||||
|
|
||||||
const dogCutoutSrc = '/images/smiling-dogs-instagram-cta.png';
|
const dogCutoutSrc = '/images/dog-cutout.png';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<aside id="instagram" aria-label="Follow Goodwalk on Instagram">
|
<aside id="instagram" aria-label="Follow Goodwalk on Instagram">
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
#instagram {
|
#instagram {
|
||||||
overflow: visible;
|
overflow: hidden;
|
||||||
padding-bottom: 40px;
|
padding-bottom: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,12 +89,12 @@
|
|||||||
|
|
||||||
.instagram-dog-wrap {
|
.instagram-dog-wrap {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 24px;
|
right: -56px;
|
||||||
bottom: 0;
|
bottom: -14px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 280px;
|
width: 380px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
@@ -149,8 +149,8 @@
|
|||||||
.instagram-dog-wrap {
|
.instagram-dog-wrap {
|
||||||
left: 50%;
|
left: 50%;
|
||||||
right: auto;
|
right: auto;
|
||||||
bottom: -80px;
|
bottom: -96px;
|
||||||
width: min(260px, calc(100% - 40px));
|
width: min(300px, calc(100% - 32px));
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,7 @@
|
|||||||
.legal-section h2 {
|
.legal-section h2 {
|
||||||
margin: 0 0 16px;
|
margin: 0 0 16px;
|
||||||
padding-left: 14px;
|
padding-left: 14px;
|
||||||
border-left: 3px solid var(--green);
|
border-left: 3px solid var(--gw-green);
|
||||||
font-family: var(--font-head);
|
font-family: var(--font-head);
|
||||||
font-size: clamp(14px, 1.4vw, 17px);
|
font-size: clamp(14px, 1.4vw, 17px);
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
@@ -140,7 +140,7 @@
|
|||||||
content: '–';
|
content: '–';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
left: 0;
|
||||||
color: var(--green);
|
color: var(--gw-green);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.9;
|
line-height: 1.9;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,26 @@
|
|||||||
const scrollDepthThreshold = 0.65;
|
const scrollDepthThreshold = 0.65;
|
||||||
const desktopPromptMediaQuery = '(min-width: 769px)';
|
const desktopPromptMediaQuery = '(min-width: 769px)';
|
||||||
|
|
||||||
|
function numericPrice(price: string) {
|
||||||
|
const value = Number(price.replace(/[^0-9.]/g, ''));
|
||||||
|
return Number.isFinite(value) ? value : Number.POSITIVE_INFINITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
function decoratePlans<T extends { price: string }>(plans: T[]) {
|
||||||
|
const sorted = [...plans]
|
||||||
|
.map((plan, index) => ({ plan, index, value: numericPrice(plan.price) }))
|
||||||
|
.sort((a, b) => a.value - b.value || a.index - b.index);
|
||||||
|
|
||||||
|
const cheapestIndex = sorted[0]?.index ?? -1;
|
||||||
|
const mobileOrder = new Map(sorted.map((entry, order) => [entry.index, order]));
|
||||||
|
|
||||||
|
return plans.map((plan, index) => ({
|
||||||
|
...plan,
|
||||||
|
isPopular: index === cheapestIndex,
|
||||||
|
mobileOrder: mobileOrder.get(index) ?? index
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
let showMeetGreetPrompt = false;
|
let showMeetGreetPrompt = false;
|
||||||
let dismissMeetGreetPrompt = false;
|
let dismissMeetGreetPrompt = false;
|
||||||
let bookingInView = false;
|
let bookingInView = false;
|
||||||
@@ -147,9 +167,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class:pricing-plan-grid-three={section.plans.length === 3} class="pricing-plan-grid">
|
<div class:pricing-plan-grid-three={section.plans.length === 3} class="pricing-plan-grid">
|
||||||
{#each section.plans as plan}
|
{#each decoratePlans(section.plans) as plan}
|
||||||
<article class:pricing-plan-popular={plan.popular} class="pricing-plan-card">
|
<article
|
||||||
{#if plan.popular}
|
class:pricing-plan-popular={plan.isPopular}
|
||||||
|
class="pricing-plan-card"
|
||||||
|
style={`--mobile-order:${plan.mobileOrder};`}
|
||||||
|
>
|
||||||
|
{#if plan.isPopular}
|
||||||
<span class="pricing-plan-ribbon">Popular</span>
|
<span class="pricing-plan-ribbon">Popular</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@@ -226,7 +250,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pricing-page-hero {
|
.pricing-page-hero {
|
||||||
background: var(--green);
|
background: var(--gw-green);
|
||||||
padding: 56px 0 64px;
|
padding: 56px 0 64px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
@@ -320,7 +344,7 @@
|
|||||||
height: 56px;
|
height: 56px;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
background: var(--green);
|
background: var(--gw-green);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
}
|
}
|
||||||
@@ -469,7 +493,7 @@
|
|||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
background: rgba(33, 48, 33, 0.08);
|
background: rgba(33, 48, 33, 0.08);
|
||||||
color: var(--green);
|
color: var(--gw-green);
|
||||||
font-family: var(--font-head);
|
font-family: var(--font-head);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
@@ -635,7 +659,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pricing-plan-popular {
|
.pricing-plan-popular {
|
||||||
order: -1;
|
order: var(--mobile-order, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.pricing-plan-card {
|
.pricing-plan-card {
|
||||||
@@ -672,7 +696,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
color: var(--green);
|
color: var(--gw-green);
|
||||||
font-family: var(--font-head);
|
font-family: var(--font-head);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="promise-img">
|
<div class="promise-img">
|
||||||
|
<div class="promise-img-frame">
|
||||||
<img
|
<img
|
||||||
src={promise.imageUrl}
|
src={promise.imageUrl}
|
||||||
alt={promise.imageAlt}
|
alt={promise.imageAlt}
|
||||||
@@ -38,4 +39,5 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -10,9 +10,35 @@
|
|||||||
export let pageContent: ServicePageContent;
|
export let pageContent: ServicePageContent;
|
||||||
export let currentPath = '';
|
export let currentPath = '';
|
||||||
|
|
||||||
|
function numericPrice(price: string) {
|
||||||
|
const value = Number(price.replace(/[^0-9.]/g, ''));
|
||||||
|
return Number.isFinite(value) ? value : Number.POSITIVE_INFINITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
function decoratePlans<T extends { price: string }>(plans: T[]) {
|
||||||
|
const sorted = [...plans]
|
||||||
|
.map((plan, index) => ({ plan, index, value: numericPrice(plan.price) }))
|
||||||
|
.sort((a, b) => a.value - b.value || a.index - b.index);
|
||||||
|
|
||||||
|
const cheapestIndex = sorted[0]?.index ?? -1;
|
||||||
|
const mobileOrder = new Map(sorted.map((entry, order) => [entry.index, order]));
|
||||||
|
|
||||||
|
return plans.map((plan, index) => ({
|
||||||
|
...plan,
|
||||||
|
isPopular: index === cheapestIndex,
|
||||||
|
mobileOrder: mobileOrder.get(index) ?? index
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
$: heroImage = getImageMetadata(pageContent.hero.imageUrl);
|
$: heroImage = getImageMetadata(pageContent.hero.imageUrl);
|
||||||
$: highlightImage = pageContent.highlight ? getImageMetadata(pageContent.highlight.imageUrl) : null;
|
$: highlightImage = pageContent.highlight ? getImageMetadata(pageContent.highlight.imageUrl) : null;
|
||||||
|
$: highlightCollageImages =
|
||||||
|
pageContent.highlight?.collageImages?.map((image) => ({
|
||||||
|
...image,
|
||||||
|
meta: getImageMetadata(image.imageUrl)
|
||||||
|
})) ?? [];
|
||||||
$: relatedServices = content.services.filter((s) => s.href && s.href !== currentPath);
|
$: relatedServices = content.services.filter((s) => s.href && s.href !== currentPath);
|
||||||
|
$: pricingPlans = decoratePlans(pageContent.pricing.plans);
|
||||||
|
|
||||||
$: relatedCards = [
|
$: relatedCards = [
|
||||||
...relatedServices.map((s) => ({
|
...relatedServices.map((s) => ({
|
||||||
@@ -68,6 +94,22 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="service-inner">
|
<div class="service-inner">
|
||||||
|
{#if highlightCollageImages.length}
|
||||||
|
<div class="service-highlight-collage" aria-label={pageContent.highlight.title}>
|
||||||
|
{#each highlightCollageImages as image, index}
|
||||||
|
<figure class={`service-collage-card service-collage-card-${index + 1}`}>
|
||||||
|
<img
|
||||||
|
src={image.imageUrl}
|
||||||
|
alt={image.imageAlt}
|
||||||
|
width={image.meta?.width}
|
||||||
|
height={image.meta?.height}
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
/>
|
||||||
|
</figure>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
<div class="service-highlight-image">
|
<div class="service-highlight-image">
|
||||||
<img
|
<img
|
||||||
src={pageContent.highlight.imageUrl}
|
src={pageContent.highlight.imageUrl}
|
||||||
@@ -78,6 +120,7 @@
|
|||||||
decoding="async"
|
decoding="async"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -92,9 +135,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class:service-plan-grid-three={pageContent.pricing.plans.length === 3} class="service-plan-grid">
|
<div class:service-plan-grid-three={pageContent.pricing.plans.length === 3} class="service-plan-grid">
|
||||||
{#each pageContent.pricing.plans as plan}
|
{#each pricingPlans as plan}
|
||||||
<article class:service-plan-popular={plan.popular} class="service-plan-card">
|
<article
|
||||||
{#if plan.popular}
|
class:service-plan-popular={plan.isPopular}
|
||||||
|
class="service-plan-card"
|
||||||
|
style={`--mobile-order:${plan.mobileOrder};`}
|
||||||
|
>
|
||||||
|
{#if plan.isPopular}
|
||||||
<span class="service-plan-ribbon">Popular</span>
|
<span class="service-plan-ribbon">Popular</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
@@ -263,11 +310,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.service-related-tint-1 {
|
.service-related-tint-1 {
|
||||||
--card-accent: var(--green);
|
--card-accent: var(--gw-green);
|
||||||
}
|
}
|
||||||
.service-related-tint-1 .service-related-icon {
|
.service-related-tint-1 .service-related-icon {
|
||||||
background: #dce6dc;
|
background: #dce6dc;
|
||||||
color: var(--green);
|
color: var(--gw-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.service-related-tint-2 {
|
.service-related-tint-2 {
|
||||||
@@ -275,7 +322,7 @@
|
|||||||
}
|
}
|
||||||
.service-related-tint-2 .service-related-icon {
|
.service-related-tint-2 .service-related-icon {
|
||||||
background: #efe4d1;
|
background: #efe4d1;
|
||||||
color: var(--green);
|
color: var(--gw-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.service-related-tint-3 {
|
.service-related-tint-3 {
|
||||||
@@ -305,7 +352,7 @@
|
|||||||
height: 52px;
|
height: 52px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: #efe4d1;
|
background: #efe4d1;
|
||||||
color: var(--green);
|
color: var(--gw-green);
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
@@ -334,7 +381,7 @@
|
|||||||
.service-related-price {
|
.service-related-price {
|
||||||
font-family: var(--font-head);
|
font-family: var(--font-head);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--green);
|
color: var(--gw-green);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,7 +399,7 @@
|
|||||||
.service-related-link {
|
.service-related-link {
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
padding-top: 6px;
|
padding-top: 6px;
|
||||||
color: var(--green);
|
color: var(--gw-green);
|
||||||
font-family: var(--font-head);
|
font-family: var(--font-head);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@@ -386,18 +433,69 @@
|
|||||||
line-height: 1.7;
|
line-height: 1.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.service-hero-media,
|
||||||
|
.service-highlight-image {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 28px;
|
||||||
|
background: #f4efe7;
|
||||||
|
box-shadow: 0 16px 40px rgba(17, 20, 24, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-hero-media {
|
||||||
|
aspect-ratio: 4 / 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-highlight-image {
|
||||||
|
aspect-ratio: 4 / 3;
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-highlight-collage {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
max-width: 780px;
|
||||||
|
margin: 0 auto;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-collage-card {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 28px;
|
||||||
|
background: #f4efe7;
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(17, 20, 24, 0.05),
|
||||||
|
0 16px 40px rgba(17, 20, 24, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-collage-card img {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-collage-card-1 {
|
||||||
|
min-height: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-collage-card-2 {
|
||||||
|
min-height: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-collage-card-3 {
|
||||||
|
min-height: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
.service-hero-media img,
|
.service-hero-media img,
|
||||||
.service-highlight-image img {
|
.service-highlight-image img {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-radius: 28px;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
box-shadow: 0 16px 40px rgba(17, 20, 24, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
.service-hero-media img {
|
|
||||||
aspect-ratio: 4 / 3;
|
|
||||||
height: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.service-highlight {
|
.service-highlight {
|
||||||
@@ -418,10 +516,6 @@
|
|||||||
margin: 0 0 16px;
|
margin: 0 0 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.service-highlight-image img {
|
|
||||||
max-height: 620px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.service-pricing,
|
.service-pricing,
|
||||||
.service-benefits {
|
.service-benefits {
|
||||||
padding: 0 0 96px;
|
padding: 0 0 96px;
|
||||||
@@ -460,6 +554,13 @@
|
|||||||
border-color 0.22s ease;
|
border-color 0.22s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.service-plan-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.service-plan-popular {
|
.service-plan-popular {
|
||||||
border: 2px solid var(--yellow);
|
border: 2px solid var(--yellow);
|
||||||
}
|
}
|
||||||
@@ -521,7 +622,7 @@
|
|||||||
font-family: var(--font-head);
|
font-family: var(--font-head);
|
||||||
font-size: 44px;
|
font-size: 44px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
color: var(--green);
|
color: var(--gw-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.service-plan-period {
|
.service-plan-period {
|
||||||
@@ -537,6 +638,7 @@
|
|||||||
margin: 24px 0 0;
|
margin: 24px 0 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
list-style: none;
|
list-style: none;
|
||||||
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.service-plan-features li {
|
.service-plan-features li {
|
||||||
@@ -583,7 +685,7 @@
|
|||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
background: rgba(33, 48, 33, 0.06);
|
background: rgba(33, 48, 33, 0.06);
|
||||||
color: var(--green);
|
color: var(--gw-green);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
@@ -662,7 +764,7 @@
|
|||||||
.service-extra-price {
|
.service-extra-price {
|
||||||
font-family: var(--font-head);
|
font-family: var(--font-head);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--green);
|
color: var(--gw-green);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -680,7 +782,7 @@
|
|||||||
height: 52px;
|
height: 52px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: #efe4d1;
|
background: #efe4d1;
|
||||||
color: var(--green);
|
color: var(--gw-green);
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
margin-bottom: 18px;
|
margin-bottom: 18px;
|
||||||
}
|
}
|
||||||
@@ -702,6 +804,16 @@
|
|||||||
.service-hero-grid {
|
.service-hero-grid {
|
||||||
align-items: start;
|
align-items: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.service-highlight-collage {
|
||||||
|
max-width: 720px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-collage-card-2,
|
||||||
|
.service-collage-card-3,
|
||||||
|
.service-collage-card-1 {
|
||||||
|
min-height: 220px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
@@ -723,7 +835,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.service-plan-popular {
|
.service-plan-popular {
|
||||||
order: -1;
|
order: var(--mobile-order, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.service-highlight,
|
.service-highlight,
|
||||||
@@ -748,5 +860,18 @@
|
|||||||
margin: 18px auto 0;
|
margin: 18px auto 0;
|
||||||
font-family: var(--font-head);
|
font-family: var(--font-head);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.service-highlight-collage {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
max-width: 420px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-collage-card-1,
|
||||||
|
.service-collage-card-2,
|
||||||
|
.service-collage-card-3 {
|
||||||
|
min-height: 220px;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -5,12 +5,35 @@
|
|||||||
|
|
||||||
export let services: IconCard[];
|
export let services: IconCard[];
|
||||||
export let heading = 'What we do';
|
export let heading = 'What we do';
|
||||||
|
export let intro =
|
||||||
|
'Choose the walk style that fits your dog best, then book a free Meet & Greet when you are ready.';
|
||||||
|
|
||||||
|
const requestedServiceStorageKey = 'goodwalk_requested_service';
|
||||||
|
|
||||||
|
function bookingHref() {
|
||||||
|
return '#newlead';
|
||||||
|
}
|
||||||
|
|
||||||
|
function primeBookingService(serviceTitle: string) {
|
||||||
|
try {
|
||||||
|
window.sessionStorage.setItem(requestedServiceStorageKey, serviceTitle);
|
||||||
|
} catch {
|
||||||
|
// Ignore storage failures and continue with the link target.
|
||||||
|
}
|
||||||
|
|
||||||
|
window.dispatchEvent(
|
||||||
|
new CustomEvent('goodwalk:service-selected', {
|
||||||
|
detail: { service: serviceTitle }
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section id="services" use:reveal={{ delay: 20 }} class="reveal-block">
|
<section id="services" use:reveal={{ delay: 20 }} class="reveal-block">
|
||||||
<div class="services-inner">
|
<div class="services-inner">
|
||||||
<h2 class="section-heading">{heading}</h2>
|
<h2 class="section-heading">{heading}</h2>
|
||||||
|
<p class="services-intro">{intro}</p>
|
||||||
|
|
||||||
<div class="services-grid">
|
<div class="services-grid">
|
||||||
{#each services as service}
|
{#each services as service}
|
||||||
@@ -26,10 +49,16 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if service.href}
|
{#if service.href}
|
||||||
<a href={service.href} class="btn btn-green">
|
<div class="service-card-actions">
|
||||||
<span>See {service.title} pricing</span>
|
<a href={bookingHref()} class="btn btn-green" on:click={() => primeBookingService(service.title)}>
|
||||||
|
<span>Book {service.title}</span>
|
||||||
<Icon name="fas fa-arrow-right" />
|
<Icon name="fas fa-arrow-right" />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
<a href={service.href} class="service-card-link">
|
||||||
|
View details & pricing
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -81,4 +110,42 @@
|
|||||||
transform: translateY(0);
|
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>
|
</style>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
export let email: string;
|
export let email: string;
|
||||||
export let enquiryType: 'booking' | 'general' = 'booking';
|
export let enquiryType: 'booking' | 'general' = 'booking';
|
||||||
export let onClose: () => void;
|
export let onClose: () => void;
|
||||||
|
const gwGreenHex = '#213021';
|
||||||
|
|
||||||
$: isGeneralEnquiry = enquiryType === 'general';
|
$: isGeneralEnquiry = enquiryType === 'general';
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@
|
|||||||
angle: 60,
|
angle: 60,
|
||||||
spread: 65,
|
spread: 65,
|
||||||
origin: { x: 0, y: 0.75 },
|
origin: { x: 0, y: 0.75 },
|
||||||
colors: ['#FFD100', '#213021', '#ffffff', '#7aaa7a', '#ffeaa0'],
|
colors: ['#FFD100', gwGreenHex, '#ffffff', '#7aaa7a', '#ffeaa0'],
|
||||||
gravity: 0.9,
|
gravity: 0.9,
|
||||||
scalar: 1.1,
|
scalar: 1.1,
|
||||||
});
|
});
|
||||||
@@ -29,7 +30,7 @@
|
|||||||
angle: 120,
|
angle: 120,
|
||||||
spread: 65,
|
spread: 65,
|
||||||
origin: { x: 1, y: 0.75 },
|
origin: { x: 1, y: 0.75 },
|
||||||
colors: ['#FFD100', '#213021', '#ffffff', '#7aaa7a', '#ffeaa0'],
|
colors: ['#FFD100', gwGreenHex, '#ffffff', '#7aaa7a', '#ffeaa0'],
|
||||||
gravity: 0.9,
|
gravity: 0.9,
|
||||||
scalar: 1.1,
|
scalar: 1.1,
|
||||||
});
|
});
|
||||||
@@ -157,7 +158,7 @@
|
|||||||
margin: 0 0 14px;
|
margin: 0 0 14px;
|
||||||
font-size: 26px;
|
font-size: 26px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #213021;
|
color: var(--gw-green);
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +187,7 @@
|
|||||||
.modal-btn {
|
.modal-btn {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 14px 36px;
|
padding: 14px 36px;
|
||||||
background: #213021;
|
background: var(--gw-green);
|
||||||
color: #FFD100;
|
color: #FFD100;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|||||||
@@ -6,8 +6,9 @@
|
|||||||
import type { TestimonialContent } from '$lib/types';
|
import type { TestimonialContent } from '$lib/types';
|
||||||
|
|
||||||
export let testimonials: TestimonialContent[];
|
export let testimonials: TestimonialContent[];
|
||||||
export let heading = 'Why people choose us!';
|
export let eyebrow = '30+ five-star reviews';
|
||||||
export let blurb = 'Busy parents get peace of mind. Dogs come home tired and happy. See why 30+ Auckland families trust the Tiny Gang — follow along on Instagram for daily adventures, wagging tails and the odd zoomie.';
|
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 instagramHref = 'https://www.instagram.com/goodwalk.nz/';
|
||||||
export let instagramLabel = 'goodwalk.nz';
|
export let instagramLabel = 'goodwalk.nz';
|
||||||
|
|
||||||
@@ -30,7 +31,7 @@
|
|||||||
},
|
},
|
||||||
Ross: {
|
Ross: {
|
||||||
reviewer: 'Ross',
|
reviewer: 'Ross',
|
||||||
detail: "Otis's Dad",
|
detail: "Otis's dad",
|
||||||
quote:
|
quote:
|
||||||
'Truly the best dog walker in Auckland! I feel so lucky to have found Aless and my little terrier Otis absolutely adores her. He enjoys his regular weekly walks and always comes back happy & tired. Love the updates on social media so I can see how my dog is enjoying his day! Aless makes logistics so easy too. Highly highly recommend, there’s a reason she has 5 stars!',
|
'Truly the best dog walker in Auckland! I feel so lucky to have found Aless and my little terrier Otis absolutely adores her. He enjoys his regular weekly walks and always comes back happy & tired. Love the updates on social media so I can see how my dog is enjoying his day! Aless makes logistics so easy too. Highly highly recommend, there’s a reason she has 5 stars!',
|
||||||
imageUrl: '/images/otis-auckland-dog-walking-review.png'
|
imageUrl: '/images/otis-auckland-dog-walking-review.png'
|
||||||
@@ -123,6 +124,7 @@
|
|||||||
|
|
||||||
<section id="testimonials" use:reveal={{ delay: 40 }} class="reveal-block">
|
<section id="testimonials" use:reveal={{ delay: 40 }} class="reveal-block">
|
||||||
<div class="testimonials-inner">
|
<div class="testimonials-inner">
|
||||||
|
<span class="testimonials-eyebrow">{eyebrow}</span>
|
||||||
<h2 class="section-heading">{heading}</h2>
|
<h2 class="section-heading">{heading}</h2>
|
||||||
<div class="testimonials-intro">
|
<div class="testimonials-intro">
|
||||||
<p>{blurb}</p>
|
<p>{blurb}</p>
|
||||||
@@ -244,6 +246,21 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.testimonials-eyebrow {
|
||||||
|
display: block;
|
||||||
|
width: fit-content;
|
||||||
|
margin: 0 auto 10px;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
.testimonials-intro {
|
.testimonials-intro {
|
||||||
max-width: 760px;
|
max-width: 760px;
|
||||||
margin: 18px auto 0;
|
margin: 18px auto 0;
|
||||||
@@ -266,7 +283,7 @@
|
|||||||
padding: 10px 16px;
|
padding: 10px 16px;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
background: rgba(33, 48, 33, 0.06);
|
background: rgba(33, 48, 33, 0.06);
|
||||||
color: var(--green);
|
color: var(--gw-green);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.06);
|
box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.06);
|
||||||
@@ -297,9 +314,16 @@
|
|||||||
.testimonials-carousel {
|
.testimonials-carousel {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-top: 48px;
|
margin-top: 48px;
|
||||||
|
padding: 0 38px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
.testimonials-eyebrow {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
.testimonials-intro {
|
.testimonials-intro {
|
||||||
margin-top: 14px;
|
margin-top: 14px;
|
||||||
}
|
}
|
||||||
@@ -555,11 +579,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.testimonial-arrow-left {
|
.testimonial-arrow-left {
|
||||||
left: -38px;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.testimonial-arrow-right {
|
.testimonial-arrow-right {
|
||||||
right: -38px;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
@@ -588,6 +612,7 @@
|
|||||||
@media (max-width: 767px) {
|
@media (max-width: 767px) {
|
||||||
.testimonials-carousel {
|
.testimonials-carousel {
|
||||||
margin-top: 32px;
|
margin-top: 32px;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.testimonial-stage {
|
.testimonial-stage {
|
||||||
|
|||||||
@@ -21,7 +21,11 @@
|
|||||||
|
|
||||||
<section id="values">
|
<section id="values">
|
||||||
<div class="values-inner">
|
<div class="values-inner">
|
||||||
<h2 class="section-heading">Where dogs come first</h2>
|
<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="values-grid">
|
<div class="values-grid">
|
||||||
{#each orderedValues as value}
|
{#each orderedValues as value}
|
||||||
@@ -34,3 +38,43 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.values-eyebrow {
|
||||||
|
display: block;
|
||||||
|
width: fit-content;
|
||||||
|
margin: 0 auto 10px;
|
||||||
|
padding: 7px 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.values-intro {
|
||||||
|
max-width: 760px;
|
||||||
|
margin: 18px auto 0;
|
||||||
|
text-align: center;
|
||||||
|
color: rgba(255, 255, 255, 0.82);
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 1.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.values-eyebrow {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.values-intro {
|
||||||
|
margin-top: 14px;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.55;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -3,82 +3,100 @@ import type { ServicePageContent } from '$lib/types';
|
|||||||
export const dogWalkingContent: ServicePageContent = {
|
export const dogWalkingContent: ServicePageContent = {
|
||||||
hero: {
|
hero: {
|
||||||
eyebrow: '1:1 Walks',
|
eyebrow: '1:1 Walks',
|
||||||
title: 'Walks for larger breeds, too!',
|
title: 'A calmer walk for dogs who need more attention',
|
||||||
paragraphs: [
|
paragraphs: [
|
||||||
'Looking for personal attention for your big pup? Our professional dog walking service is perfect for larger dogs who walk well on leash and have good recall. Safety is paramount, so we only accommodate dogs with no reactivity issues. At Goodwalk, we pride ourselves on building relationships with both you and your furry family member.',
|
'Goodwalk 1:1 Walks are for dogs who do better with more individual attention, a quieter setup, and a walk tailored to their own pace, confidence, and routine.',
|
||||||
"Give your dog his best life while joining our growing community of happy pet parents! For those seeking extra care, we offer specialised one-on-one walks tailored to your dog's individual needs and personality"
|
'They can be a great fit for larger dogs, dogs who are not suited to group walks, or owners who want a more personal approach with extra care and consistency.',
|
||||||
|
'If your dog needs space, structure, and a walker who can focus fully on them, our one-on-one walks are designed for exactly that.'
|
||||||
],
|
],
|
||||||
imageUrl:
|
imageUrl: '/images/auckland-large-dog-one-on-one-walk.jpg',
|
||||||
'/images/auckland-large-dog-one-on-one-walk.jpg',
|
|
||||||
imageAlt: 'Large breed dog enjoying a Goodwalk one on one dog walk'
|
imageAlt: 'Large breed dog enjoying a Goodwalk one on one dog walk'
|
||||||
},
|
},
|
||||||
highlight: {
|
highlight: {
|
||||||
eyebrow: '▼・ᴥ・▼',
|
eyebrow: 'One dog. Full attention.',
|
||||||
title: 'Personalised adventures for your dog!',
|
title: 'Built for dogs who need a more individual kind of walk',
|
||||||
imageUrl: '/images/auckland-dogs-outdoor-pack.jpg',
|
imageUrl: '/images/auckland-dogs-outdoor-pack.jpg',
|
||||||
imageAlt: 'Goodwalk dogs gathered together outdoors'
|
imageAlt: 'Goodwalk dogs gathered together outdoors',
|
||||||
},
|
collageImages: [
|
||||||
pricing: {
|
|
||||||
title: '1:1 Large Dog Breed Prices',
|
|
||||||
plans: [
|
|
||||||
{
|
{
|
||||||
title: '30 Minutes',
|
imageUrl: '/images/one-on-one-dog-portrait-1.png',
|
||||||
price: '$45',
|
imageAlt: 'Happy black dog on a one-on-one Goodwalk walk in Auckland'
|
||||||
period: 'Per Walk',
|
|
||||||
features: ['Free pickup/dropoff', '30 minute walk', 'Social media updates', 'Basic training']
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '45 Minutes',
|
imageUrl: '/images/one-on-one-dog-portrait-2.png',
|
||||||
price: '$55',
|
imageAlt: 'Older black dog enjoying a calm one-on-one Goodwalk walk in Auckland'
|
||||||
period: 'Per Walk',
|
|
||||||
popular: true,
|
|
||||||
features: ['Free pickup/dropoff', '45 minute walk', 'Social media updates', 'Basic training']
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '60 Minutes',
|
imageUrl: '/images/one-on-one-dog-portrait-3.png',
|
||||||
price: '$65',
|
imageAlt: 'Brown curly dog resting during a one-on-one Goodwalk walk in Auckland'
|
||||||
period: 'Per Walk',
|
|
||||||
features: ['Free pickup/dropoff', '60 minute walk', 'Social media updates', 'Basic training']
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
pricing: {
|
||||||
|
title: 'Choose the walk length that suits your dog',
|
||||||
|
intro:
|
||||||
|
'Our 1:1 walks are shaped around your dog, not a group schedule. Ideal for dogs who need extra attention, a steadier pace, or a more personalised walking routine.',
|
||||||
|
plans: [
|
||||||
|
{
|
||||||
|
title: '30 Minute 1:1 Walk',
|
||||||
|
price: '$45',
|
||||||
|
period: 'Per Walk',
|
||||||
|
features: ['Free pickup and drop-off', 'Shorter one-on-one walk', 'Personal attention throughout', 'Good fit for lower-energy dogs']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '45 Minute 1:1 Walk',
|
||||||
|
price: '$55',
|
||||||
|
period: 'Per Walk',
|
||||||
|
popular: true,
|
||||||
|
features: ['Free pickup and drop-off', 'Balanced walk length for most dogs', 'Time for calm handling and structure', 'Best fit for many routines']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '60 Minute 1:1 Walk',
|
||||||
|
price: '$65',
|
||||||
|
period: 'Per Walk',
|
||||||
|
features: ['Free pickup and drop-off', 'Longer individual walk', 'More time for movement and engagement', 'Best for dogs needing a fuller outing']
|
||||||
|
}
|
||||||
|
],
|
||||||
|
scarcityNote: 'A limited number of 1:1 slots are available each week.'
|
||||||
|
},
|
||||||
benefits: {
|
benefits: {
|
||||||
title: 'Benefits of our 1:1 walks',
|
title: 'Why some dogs do better on 1:1 walks',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
title: 'Individualised Attention',
|
title: 'They get the walker’s full attention',
|
||||||
body: 'Large breeds receive personalised care and undivided attention from the walker, addressing their unique needs and preferences without competition from other dogs.'
|
body: 'One-on-one walks give your dog focused handling and a calmer experience without competing with the needs of a group.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Tailored Exercise',
|
title: 'The walk matches their pace',
|
||||||
body: 'Walkers can customise the pace, duration, and route of the walk to accommodate the energy levels and physical abilities of large breeds, providing an appropriate level of exercise tailored to their specific needs.'
|
body: 'We can tailor the route, speed, and duration to suit your dog’s energy, confidence, and physical needs.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Bonding and Socialisation',
|
title: 'They have more space to relax',
|
||||||
body: 'During one-on-one walks, large breeds bond closely with their walker and socialise with people and animals encountered, promoting confidence and social skills'
|
body: 'Dogs who are not suited to pack walks often feel more comfortable when they can move through the world without the pressure of a group.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Enhanced safety',
|
title: 'You get a more tailored routine',
|
||||||
body: "With one-on-one walks, there's reduced risk of potential conflicts or incidents that may arise in group settings, ensuring a safer walking experience for large breeds."
|
body: 'A 1:1 setup gives us more flexibility to build a walking routine around what works best for your dog and your week.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Training Opportunities',
|
title: 'There is room for better habits',
|
||||||
body: 'One-on-one walks offer dedicated time for training and reinforcement of good behaviours, such as loose leash walking or obedience commands, helping large breeds develop and maintain positive habits.'
|
body: 'One-on-one walks create more opportunity to reinforce calm walking, better focus, and the practical behaviours that make daily life easier.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Stress Reduction',
|
title: 'You can feel more confident leaving them with us',
|
||||||
body: 'Large breeds may feel more relaxed and comfortable during one-on-one walks, as they can explore and enjoy their surroundings without the potential stressors of a group dynamic, leading to a more positive walking experience overall.'
|
body: 'For owners of dogs who need a bit more care, 1:1 walks offer reassurance that your dog is getting a more considered, individual approach.'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
testimonialsHeading: 'What our clients say',
|
testimonialsHeading: 'What our clients say',
|
||||||
booking: {
|
booking: {
|
||||||
title: "Let's meet!",
|
title: 'See if 1:1 walks are the right fit',
|
||||||
subtitle: 'Fill out your details below, and we can arrange a Meet & Greet for a one on one walk!',
|
subtitle: 'Fill out your details below and we’ll arrange a free Meet & Greet to learn more about your dog.',
|
||||||
formAction: '/contact-us',
|
formAction: '/contact-us',
|
||||||
serviceOptions: [],
|
serviceOptions: [],
|
||||||
ownerStepLabel: 'Your details',
|
ownerStepLabel: 'Your details',
|
||||||
dogStepLabel: 'Your dog',
|
dogStepLabel: 'Your dog',
|
||||||
dogIntro: 'Tell us about your dog, your area, and anything important we should know before arranging a one on one Meet & Greet.'
|
dogIntro:
|
||||||
|
'Tell us about your dog, your area, and anything important we should know so we can see whether a 1:1 walk is the right fit.'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,12 +31,18 @@ export const homepageContent: HomePageContent = {
|
|||||||
megaMenuFooter: { label: 'View all pricing', href: '/our-pricing' }
|
megaMenuFooter: { label: 'View all pricing', href: '/our-pricing' }
|
||||||
},
|
},
|
||||||
hero: {
|
hero: {
|
||||||
title: 'Unleashing Fun in',
|
title: 'Come home to a',
|
||||||
highlight: "Your Dog's Day!",
|
highlight: 'calm, happy dog',
|
||||||
mobileTitle: "Unleashing Fun in\nYour Dog's Day!",
|
mobileTitle: 'Come home to a\ncalm, happy dog',
|
||||||
subtitle: "Trusted Auckland Central dog walking — small packs, solo adventures, and puppy visits from a team that knows your dog by name",
|
subtitle:
|
||||||
|
'Dog walking for busy Auckland Central professionals who want a reliable, relationship-led team their dog knows by name.',
|
||||||
primaryCta: { label: 'Book a Meet & Greet', href: '#newlead', variant: 'yellow' },
|
primaryCta: { label: 'Book a Meet & Greet', href: '#newlead', variant: 'yellow' },
|
||||||
secondaryCta: { label: 'Explore our services →', href: '#services', variant: 'outline' },
|
secondaryCta: {
|
||||||
|
label: 'Message us on Instagram',
|
||||||
|
href: 'https://www.instagram.com/goodwalk.nz/',
|
||||||
|
variant: 'outline',
|
||||||
|
external: true
|
||||||
|
},
|
||||||
imageUrl: '/images/auckland-dog-walking-happy-dog-hero.png',
|
imageUrl: '/images/auckland-dog-walking-happy-dog-hero.png',
|
||||||
imageAlt: 'Happy dog ready for a professional pack walk with Goodwalk Auckland dog walking service'
|
imageAlt: 'Happy dog ready for a professional pack walk with Goodwalk Auckland dog walking service'
|
||||||
},
|
},
|
||||||
@@ -49,16 +55,16 @@ export const homepageContent: HomePageContent = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
promise: {
|
promise: {
|
||||||
title: 'Happy pets,',
|
title: 'Meet Aless,',
|
||||||
subtitle: 'happy humans',
|
subtitle: 'the heart of Goodwalk',
|
||||||
body: [
|
body: [
|
||||||
'We specialise in the unique needs of small-to-medium breeds — easing stress and anxiety while keeping tails wagging.',
|
'Goodwalk was built for owners who want more than a basic walk. Alessandra leads the business with a calm, hands-on approach shaped by years of experience, a love of small dogs, and a real focus on trust, routine, and safety.',
|
||||||
'Professional dog walking across Auckland for small, medium and large breeds, with tailored pack walks for smaller dogs and one-on-one walks for larger breeds — giving every dog the personalised attention they deserve. Ready to join our'
|
"From house keys to nervous first walks, we take the responsibility seriously. You'll know who is walking your dog, your dog will know who is at the door, and you'll get a reliable team that treats your dog like family. Ready to join the"
|
||||||
],
|
],
|
||||||
emphasis: 'TINY GANG?',
|
emphasis: 'TINY GANG?',
|
||||||
cta: { label: 'Book a free Meet & Greet', href: '/contact-us', variant: 'green' },
|
cta: { label: 'Book a free Meet & Greet', href: '/contact-us', variant: 'green' },
|
||||||
imageUrl: '/images/auckland-dog-walking-happy-dogs-happy-humans.webp',
|
imageUrl: '/images/goodwalk-dog-walker-alessandra.png',
|
||||||
imageAlt: 'Woman cuddling a dog for Goodwalk Auckland dog walking services'
|
imageAlt: 'Alessandra from Goodwalk with a dog in Auckland'
|
||||||
},
|
},
|
||||||
services: [
|
services: [
|
||||||
{
|
{
|
||||||
@@ -85,21 +91,31 @@ export const homepageContent: HomePageContent = {
|
|||||||
],
|
],
|
||||||
howItWorks: {
|
howItWorks: {
|
||||||
title: 'How it works',
|
title: 'How it works',
|
||||||
//intro: 'A simple onboarding flow designed to make sure the fit is right for both you and your dog.',
|
intro:
|
||||||
|
'A calm, simple start designed to give you confidence quickly and help your dog settle into the right routine.',
|
||||||
steps: [
|
steps: [
|
||||||
{
|
{
|
||||||
title: 'Meet & Greet',
|
phase: 'Meet',
|
||||||
body: 'We meet you and your dog first, talk through routine, temperament, and what support you need.',
|
benefit: 'No pressure, just clarity',
|
||||||
|
title: 'We get to know your dog properly',
|
||||||
|
body:
|
||||||
|
'Your free Meet & Greet gives you a chance to ask questions, talk through routine and temperament, and make sure the fit feels right before anything starts.',
|
||||||
icon: 'fas fa-handshake'
|
icon: 'fas fa-handshake'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Two assessment walks',
|
phase: 'Settle',
|
||||||
body: 'We ease your dog in with two assessment walks so we can check confidence, handling, and group fit.',
|
benefit: 'A smoother start for nervous dogs',
|
||||||
|
title: 'Your dog eases in without overwhelm',
|
||||||
|
body:
|
||||||
|
'We start with assessment walks so your dog can build confidence, settle with the walker, and find the right pace before moving into a regular routine.',
|
||||||
icon: 'fas fa-clipboard-check'
|
icon: 'fas fa-clipboard-check'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Happy dogs, happy humans',
|
phase: 'Thrive',
|
||||||
body: 'Once approved, your dog joins regular walks and comes home tired, settled, and ready for a nap.',
|
benefit: 'The outcome you actually want',
|
||||||
|
title: 'You get a calmer, happier dog at home',
|
||||||
|
body:
|
||||||
|
'Once everything feels right, your dog joins their regular walks and comes home exercised, settled, and ready to relax while you get more peace of mind in your week.',
|
||||||
icon: 'fas fa-heart'
|
icon: 'fas fa-heart'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -107,42 +123,42 @@ export const homepageContent: HomePageContent = {
|
|||||||
values: [
|
values: [
|
||||||
{
|
{
|
||||||
icon: 'fas fa-heart',
|
icon: 'fas fa-heart',
|
||||||
title: 'Kindness',
|
title: 'Calm, kind handling',
|
||||||
body:
|
body:
|
||||||
'With gentle care and genuine affection, we make every walk a calm, happy experience. We use positive reinforcement to encourage good behaviour because kindness is at the heart of everything we do.'
|
'We use positive reinforcement, gentle handling, and patient routines so dogs build confidence instead of stress.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'fas fa-camera',
|
icon: 'fas fa-camera',
|
||||||
title: 'Daily Updates',
|
title: 'Daily updates you will actually want',
|
||||||
body:
|
body:
|
||||||
"Catch your pup in action with daily social updates - showcasing their walks, playtime, and mischief with the Tiny Gang. It's your window into their happiest moments."
|
"You get to see your dog out enjoying the day, which means less wondering and more peace of mind while you're at work."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'fas fa-users',
|
icon: 'fas fa-users',
|
||||||
title: 'Small Pack Sizes',
|
title: 'Small Pack Sizes',
|
||||||
order: 2,
|
order: 2,
|
||||||
body:
|
body:
|
||||||
'With just 4-8 dogs per group, our walks are calm, controlled, and respectful of public spaces - ensuring every dog gets the attention and care they deserve.'
|
'With just 4-8 dogs per group, walks stay calm, structured, and manageable, with enough attention for every dog.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'fas fa-shield-heart',
|
icon: 'fas fa-shield-heart',
|
||||||
title: 'Safety',
|
title: 'Safety-first by default',
|
||||||
order: 1,
|
order: 1,
|
||||||
body:
|
body:
|
||||||
'Our team is fully pet first aid certified and trained to handle any situation calmly and confidently. With proactive safety protocols and constant situational awareness, we create a secure environment for every walk.'
|
'Pet first aid, careful screening, and proactive handling are built into every walk, not added on as a nice extra.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'fas fa-calendar-check',
|
icon: 'fas fa-calendar-check',
|
||||||
title: 'Flexibility',
|
title: 'Built for real schedules',
|
||||||
body:
|
body:
|
||||||
"We know life gets busy - so while we specialise in regular, permanent walks, we're always happy to adapt. Just give us a little notice, and we'll do our best to accommodate your changing schedule."
|
"We specialise in regular walks, but we know life changes. Give us notice and we'll do our best to help keep things running smoothly."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: 'fas fa-clock',
|
icon: 'fas fa-clock',
|
||||||
title: 'Reliability',
|
title: 'Reliable pickup, clear communication',
|
||||||
order: 3,
|
order: 3,
|
||||||
body:
|
body:
|
||||||
"We guarantee punctuality and consistency, so you can count on us. With clear communication, you'll always be in the loop - and your dog's needs will always be our top priority."
|
"You should not have to chase your dog walker. We keep things consistent, communicate clearly, and make the practical side feel easy."
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
testimonials: [
|
testimonials: [
|
||||||
@@ -164,7 +180,7 @@ export const homepageContent: HomePageContent = {
|
|||||||
quote:
|
quote:
|
||||||
'Truly the best dog walker in Auckland! I feel so lucky to have found Aless and my little terrier Otis absolutely adores her. He enjoys his regular weekly walks and always comes back happy & tired. Love the updates on social media so I can see how my dog is enjoying his day! Aless makes logistics so easy too. Highly highly recommend, there’s a reason she has 5 stars!',
|
'Truly the best dog walker in Auckland! I feel so lucky to have found Aless and my little terrier Otis absolutely adores her. He enjoys his regular weekly walks and always comes back happy & tired. Love the updates on social media so I can see how my dog is enjoying his day! Aless makes logistics so easy too. Highly highly recommend, there’s a reason she has 5 stars!',
|
||||||
reviewer: 'Ross',
|
reviewer: 'Ross',
|
||||||
detail: "Otis's Dad",
|
detail: "Otis's dad",
|
||||||
imageUrl: '/images/otis-auckland-dog-walking-review.jpg'
|
imageUrl: '/images/otis-auckland-dog-walking-review.jpg'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -178,15 +194,15 @@ export const homepageContent: HomePageContent = {
|
|||||||
booking: {
|
booking: {
|
||||||
title: "Let's meet!",
|
title: "Let's meet!",
|
||||||
subtitle:
|
subtitle:
|
||||||
"Almost there — just your contact details. We'll reply within 24 hours to arrange your free, no-obligation Meet & Greet.",
|
"A few contact details and we’ll be in touch within 24 hours to arrange your free, no-obligation Meet & Greet.",
|
||||||
generalSubtitle:
|
generalSubtitle:
|
||||||
"Almost there — just your contact details. We'll reply within 24 hours.",
|
"A few contact details and we’ll reply properly within 24 hours.",
|
||||||
formAction: '/contact-us',
|
formAction: '/contact-us',
|
||||||
serviceOptions: ['Pack Walks', '1:1 Walks', 'Puppy Visits', 'Other Services'],
|
serviceOptions: ['Pack Walks', '1:1 Walks', 'Puppy Visits', 'Other Services'],
|
||||||
ownerStepLabel: 'Your details',
|
ownerStepLabel: 'Your details',
|
||||||
dogStepLabel: 'Your dog',
|
dogStepLabel: 'Your dog',
|
||||||
generalIntro:
|
generalIntro:
|
||||||
'Got feedback, a complaint, or a business enquiry? Choose general enquiry and send us the details without filling in dog or service information.'
|
'Need to send feedback, make a complaint, or ask a business question? Choose general enquiry and send us the details without filling in dog or service information.'
|
||||||
},
|
},
|
||||||
info: {
|
info: {
|
||||||
title: 'Locations & Hours',
|
title: 'Locations & Hours',
|
||||||
@@ -208,6 +224,11 @@ export const homepageContent: HomePageContent = {
|
|||||||
question: 'How does payment work?',
|
question: 'How does payment work?',
|
||||||
answer: 'All walks are paid for a week in advance, via invoice.'
|
answer: 'All walks are paid for a week in advance, via invoice.'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
question: 'Do you provide a casual service?',
|
||||||
|
answer:
|
||||||
|
'Yes, we do offer casual rates, but they are priced higher. The best value for money is regular walks.'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
question: 'What requirements does my dog need?',
|
question: 'What requirements does my dog need?',
|
||||||
answer:
|
answer:
|
||||||
@@ -218,6 +239,11 @@ export const homepageContent: HomePageContent = {
|
|||||||
answer:
|
answer:
|
||||||
'All walkers are covered by public liability insurance, and all walkers hold a current First Aid training certificate.'
|
'All walkers are covered by public liability insurance, and all walkers hold a current First Aid training certificate.'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
question: 'Do I need to leave keys with you?',
|
||||||
|
answer:
|
||||||
|
'Usually, yes, if no one will be home when we collect or return your dog. We can go over the best option for access during your Meet & Greet.'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
question: 'What happens if the weather is bad?',
|
question: 'What happens if the weather is bad?',
|
||||||
answer:
|
answer:
|
||||||
|
|||||||
@@ -5,13 +5,13 @@ import { puppyVisitsContent } from './puppy-visits';
|
|||||||
|
|
||||||
export const ourPricingContent: PricingPageContent = {
|
export const ourPricingContent: PricingPageContent = {
|
||||||
title: 'Our Pricing',
|
title: 'Our Pricing',
|
||||||
subtitle: 'Simple, transparent pricing — no lock-in contracts.',
|
subtitle: 'Choose the Goodwalk routine that fits your dog, your week, and the kind of support you need.',
|
||||||
sections: [
|
sections: [
|
||||||
{
|
{
|
||||||
title: 'Pack Walks',
|
title: 'Pack Walks',
|
||||||
icon: 'fas fa-paw',
|
icon: 'fas fa-paw',
|
||||||
blurb:
|
blurb:
|
||||||
'Small group adventures for calm, social dogs who thrive with structure, play, and regular weekly outings.',
|
'Our specialty for sociable small and medium dogs who thrive with calm structure, regular weekly outings, and the right dog company.',
|
||||||
detailCta: {
|
detailCta: {
|
||||||
label: 'View Pack Walks',
|
label: 'View Pack Walks',
|
||||||
href: '/pack-walks',
|
href: '/pack-walks',
|
||||||
@@ -23,7 +23,7 @@ export const ourPricingContent: PricingPageContent = {
|
|||||||
title: '1:1 Walks',
|
title: '1:1 Walks',
|
||||||
icon: 'fas fa-person-walking',
|
icon: 'fas fa-person-walking',
|
||||||
blurb:
|
blurb:
|
||||||
'One-on-one walks tailored to your dog’s pace, confidence, and personality for a more focused outing.',
|
'A more individual option for dogs who need extra attention, more space, or a walk shaped around their own pace and confidence.',
|
||||||
detailCta: {
|
detailCta: {
|
||||||
label: 'View 1:1 Walks',
|
label: 'View 1:1 Walks',
|
||||||
href: '/dog-walking',
|
href: '/dog-walking',
|
||||||
@@ -35,7 +35,7 @@ export const ourPricingContent: PricingPageContent = {
|
|||||||
title: 'Puppy Visits',
|
title: 'Puppy Visits',
|
||||||
icon: 'fas fa-dog',
|
icon: 'fas fa-dog',
|
||||||
blurb:
|
blurb:
|
||||||
'Short home visits for young pups who need company, enrichment, toilet breaks, and gentle routine support.',
|
'Home visits for young puppies who need company, toilet breaks, routine support, and a calmer start before they are ready for bigger adventures.',
|
||||||
detailCta: {
|
detailCta: {
|
||||||
label: 'View Puppy Visits',
|
label: 'View Puppy Visits',
|
||||||
href: '/puppy-visits',
|
href: '/puppy-visits',
|
||||||
@@ -46,13 +46,13 @@ export const ourPricingContent: PricingPageContent = {
|
|||||||
],
|
],
|
||||||
testimonialsHeading: 'What our clients say',
|
testimonialsHeading: 'What our clients say',
|
||||||
booking: {
|
booking: {
|
||||||
title: 'Ready to join the Tiny Gang?',
|
title: 'Tell us about your dog',
|
||||||
subtitle: '',
|
subtitle: '',
|
||||||
formAction: '/contact-us',
|
formAction: '/contact-us',
|
||||||
serviceOptions: [],
|
serviceOptions: [],
|
||||||
ownerStepLabel: 'Your details',
|
ownerStepLabel: 'Your details',
|
||||||
dogStepLabel: 'Dog details',
|
dogStepLabel: 'Your dog',
|
||||||
dogIntro:
|
dogIntro:
|
||||||
'Tell us about your dog, where you are based, and anything important we should know before we arrange a Meet & Greet.'
|
'Tell us about your dog, where you are based, and what kind of support you are looking for so we can help point you to the right Goodwalk service.'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,50 +3,50 @@ import type { ServicePageContent } from '$lib/types';
|
|||||||
export const packWalksContent: ServicePageContent = {
|
export const packWalksContent: ServicePageContent = {
|
||||||
hero: {
|
hero: {
|
||||||
eyebrow: 'Pack Walks',
|
eyebrow: 'Pack Walks',
|
||||||
title: 'Join our Tiny Gang!',
|
title: 'Come home to a calm, happy dog',
|
||||||
paragraphs: [
|
paragraphs: [
|
||||||
'Fun, safe, and specially designed for little paws, these adventures help your dog build friendships and confidence in a calm, friendly group.',
|
'Goodwalk Pack Walks are built for Auckland Central owners of small and medium dogs who want a reliable weekly routine, a well-exercised dog, and more peace of mind during the workday.',
|
||||||
'We only welcome sociable dogs, so every outing feels secure and stress-free. As small dog owners ourselves, we know just what it takes to help your pup feel relaxed, happy, and right at home.',
|
'Our Tiny Gang packs stay small, calm, and carefully matched, so sociable dogs can build confidence, enjoy safe group outings, and come home settled instead of overstimulated.',
|
||||||
'Join the Tiny Gang today—because your dog deserves more than just a walk. They deserve a tail-wagging good time!'
|
'Tiny Gang is best suited to sociable small and medium dogs who enjoy being around other dogs. If your dog would be better with a quieter, more individual setup, our 1:1 walks may be a better fit.'
|
||||||
],
|
],
|
||||||
imageUrl: '/images/auckland-small-dog-pack-walk.jpg',
|
imageUrl: '/images/auckland-pack-walk-small-dogs-group.png',
|
||||||
imageAlt: 'Small dogs together on a Goodwalk Tiny Gang pack walk'
|
imageAlt: "Small dogs from Goodwalk's Tiny Gang pack walk sitting together in an Auckland park"
|
||||||
},
|
},
|
||||||
highlight: {
|
highlight: {
|
||||||
eyebrow: '▼・ᴥ・▼',
|
eyebrow: 'Small packs. Calm dogs.',
|
||||||
title: 'Goodwalk is the best choice for small and medium size dogs!',
|
title: 'Made specifically for small and medium dogs who do best in a structured social group',
|
||||||
imageUrl: '/images/tiny-gang-auckland-dog-pack.jpg',
|
imageUrl: '/images/small-medium-dogs-pack-walk.png',
|
||||||
imageAlt: 'Goodwalk Tiny Gang dogs gathered together in Auckland'
|
imageAlt: 'Small and medium dogs together on a Goodwalk pack walk in Auckland'
|
||||||
},
|
},
|
||||||
pricing: {
|
pricing: {
|
||||||
title: 'Tiny Gang Prices',
|
title: 'Choose the weekly routine that suits your dog',
|
||||||
intro:
|
intro:
|
||||||
'Small packs of 4-8 dogs, 2-hour outings at Auckland’s scenic dog parks and beaches, with free pick-up and drop-off included. We reinforce recall, car manners, and leash etiquette while your dog plays. Booked as a permanent weekly slot — gift your dog the best life!',
|
'Tiny Gang Pack Walks are our specialty: small packs of 4-8 dogs, structured outings, and free pick-up and drop-off across Auckland Central. Best suited to small and medium sociable dogs who thrive with routine, good company, and calm handling.',
|
||||||
plans: [
|
plans: [
|
||||||
{
|
{
|
||||||
title: '1 Walk',
|
title: '1 Walk Per Week',
|
||||||
price: '$58',
|
price: '$58',
|
||||||
period: 'Per Walk',
|
period: 'Per Walk',
|
||||||
features: ['Free pickup/dropoff', '1 hour adventure', 'Social media updates', 'Basic training']
|
features: ['One regular walk each week', 'Free pickup and drop-off', 'Calm small-group outing', 'Best for dogs starting out']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '2-3 Walks',
|
title: '2-3 Walks Per Week',
|
||||||
price: '$55',
|
price: '$55',
|
||||||
period: 'Per Walk',
|
period: 'Per Walk',
|
||||||
popular: true,
|
popular: true,
|
||||||
features: ['Free pickup/dropoff', '1 hour adventure', 'Social media updates', 'Basic training']
|
features: ['Two to three regular walks each week', 'Free pickup and drop-off', 'Consistent exercise and social time', 'Best fit for busy owners']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '4-5 Walks',
|
title: '4-5 Walks Per Week',
|
||||||
price: '$49.50',
|
price: '$49.50',
|
||||||
period: 'Per Walk',
|
period: 'Per Walk',
|
||||||
features: ['Free pickup/dropoff', '1 hour adventure', 'Social media updates', 'Basic training']
|
features: ['Four to five regular walks each week', 'Free pickup and drop-off', 'Maximum consistency and structure', 'Best for high-energy social dogs']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Casual Walk',
|
title: 'Casual Pack Walk',
|
||||||
price: '$65',
|
price: '$65',
|
||||||
period: 'Per Walk',
|
period: 'Per Walk',
|
||||||
features: ['Free pickup/dropoff', '1 hour adventure', 'Social media updates', 'Basic training']
|
features: ['Casual availability only', 'Free pickup and drop-off', 'For dogs already suited to pack walks', 'Higher rate than weekly routines']
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
extras: [
|
extras: [
|
||||||
@@ -57,42 +57,43 @@ export const packWalksContent: ServicePageContent = {
|
|||||||
scarcityNote: 'We keep packs small (4-8 dogs) — popular days fill up fast.'
|
scarcityNote: 'We keep packs small (4-8 dogs) — popular days fill up fast.'
|
||||||
},
|
},
|
||||||
benefits: {
|
benefits: {
|
||||||
title: 'Tiny Gang membership benefits',
|
title: 'Why small and medium dogs do so well in Tiny Gang',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
title: 'Socialisation with other dogs',
|
title: 'They come home more settled',
|
||||||
body: 'Tiny Gang pack walks help small and medium-sized dogs mingle and learn social skills from each other, boosting their confidence and positive behaviour.'
|
body: 'Regular structured outings help dogs burn energy properly, so they are more likely to come home relaxed, content, and ready to rest.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Tailored pace',
|
title: 'They build confidence around the right dogs',
|
||||||
body: 'Our handlers can adjust the pace and intensity of the walk to suit the energy levels and abilities of small and medium-sized dogs, ensuring a pleasant and enjoyable experience for all participants.'
|
body: 'Carefully matched small-group walks help sociable dogs enjoy company without the chaos or pressure that can come with bigger mixed packs.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Comfort',
|
title: 'They are not overwhelmed by size mismatch',
|
||||||
body: 'Smaller groups create a more relaxed and comfortable atmosphere for dogs, allowing them to explore and enjoy the walk without feeling overwhelmed by larger dogs.'
|
body: 'Because we specialise in small and medium dogs, the pace, play, and group dynamic are designed around what helps them feel safe and comfortable.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Increased bonding',
|
title: 'You get a routine you can rely on',
|
||||||
body: 'Tiny Gang pack walks foster stronger bonds between dogs and their walker, as well as between the dogs themselves, enhancing trust and companionship among the group.'
|
body: 'Regular weekly slots make life easier for busy owners who want dependable exercise and less guilt while they are at work.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Individualised attention',
|
title: 'They still get individual attention',
|
||||||
body: 'Small pack sizes allow for more personalised care and attention from the walker, addressing the unique needs and preferences of small and medium-sized breeds.'
|
body: 'Keeping packs to 4-8 dogs means we can pay attention to confidence, handling, and the little things that make a big difference.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Safety',
|
title: 'Safety stays built in',
|
||||||
body: "With a smaller group composed of dogs of similar sizes, there's reduced risk of accidental injury or intimidation, ensuring a safer walking environment."
|
body: 'Unlike overloaded pack walks, our small, compatible groups reduce intimidation and help create a safer, calmer environment than a one-size-fits-all approach.'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
testimonialsHeading: 'What our clients say',
|
testimonialsHeading: 'What our clients say',
|
||||||
booking: {
|
booking: {
|
||||||
title: 'Join the Tiny Gang!',
|
title: 'See if your dog fits our Tiny Gang',
|
||||||
subtitle: '',
|
subtitle: '',
|
||||||
formAction: '/contact-us',
|
formAction: '/contact-us',
|
||||||
serviceOptions: [],
|
serviceOptions: [],
|
||||||
ownerStepLabel: 'Your details',
|
ownerStepLabel: 'Your details',
|
||||||
dogStepLabel: 'Dog details',
|
dogStepLabel: 'Dog details',
|
||||||
dogIntro: 'Tell us about your dog and where you are based so we can plan the right Tiny Gang Meet & Greet.'
|
dogIntro:
|
||||||
|
'Tell us about your small or medium dog, where you are based, and anything important we should know so we can see if Tiny Gang is the right fit.'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,65 +3,85 @@ import type { ServicePageContent } from '$lib/types';
|
|||||||
export const puppyVisitsContent: ServicePageContent = {
|
export const puppyVisitsContent: ServicePageContent = {
|
||||||
hero: {
|
hero: {
|
||||||
eyebrow: 'Puppy Visits',
|
eyebrow: 'Puppy Visits',
|
||||||
title: 'Introducing Puppy Visits: Building strong foundations for our pack walks!',
|
title: 'Give your puppy a calmer start while you are out',
|
||||||
paragraphs: [
|
paragraphs: [
|
||||||
"We love puppies! Our puppy home visits are perfect for young pups not quite ready to join the pack and busy owners with hectic schedules. We lay the groundwork for future pack walks, including fun games, potty breaks, and even feeding if required. Let us help your furry friend thrive while you're away!"
|
'Goodwalk Puppy Visits are designed for busy owners who want their puppy cared for properly during the day, with toilet breaks, play, feeding, and calm one-on-one attention at home.',
|
||||||
|
'They are also the first stage of the Goodwalk journey. For puppies who may later join our Pack Walks, these visits help build familiarity, confidence, and the early routines that make that transition much smoother.',
|
||||||
|
'Instead of just getting through the day, your puppy gets a more thoughtful start, and you get more peace of mind while you are away.'
|
||||||
],
|
],
|
||||||
imageUrl: '/images/auckland-puppy-home-visit.jpg',
|
imageUrl: '/images/auckland-puppy-home-visit.jpg',
|
||||||
imageAlt: 'Puppy Visits page splash image'
|
imageAlt: 'Puppy receiving a calm Goodwalk home visit in Auckland'
|
||||||
|
},
|
||||||
|
highlight: {
|
||||||
|
eyebrow: 'Start well. Grow well.',
|
||||||
|
title: 'A home visit now can help set your puppy up for calmer routines and future Pack Walks later on',
|
||||||
|
imageUrl: '/images/auckland-puppy-visits-cavalier-king-charles-spaniel.jpg',
|
||||||
|
imageAlt: 'Young Cavalier King Charles Spaniel puppy resting at home before future Goodwalk Pack Walk training in Auckland'
|
||||||
},
|
},
|
||||||
pricing: {
|
pricing: {
|
||||||
title: 'Puppy Visits',
|
title: 'Choose the visit length that suits your puppy',
|
||||||
|
intro:
|
||||||
|
'Puppy Visits are built around your puppy’s age, routine, and energy levels, with practical support now and foundations for later social walking if they are a good fit for our Tiny Gang.',
|
||||||
plans: [
|
plans: [
|
||||||
{
|
{
|
||||||
title: '20 Minutes',
|
title: '20 Minute Visit',
|
||||||
price: '$39',
|
price: '$39',
|
||||||
period: 'Per Visit',
|
period: 'Per Visit',
|
||||||
features: ['Bathroom break', 'Pet feed', 'Basic training', 'Enrichment games']
|
features: ['Toilet break and check-in', 'Feeding if needed', 'Gentle one-on-one attention', 'Good for shorter midday support']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '45 Minutes',
|
title: '45 Minute Visit',
|
||||||
price: '$49',
|
price: '$49',
|
||||||
period: 'Per Visit',
|
period: 'Per Visit',
|
||||||
features: ['Bathroom break', 'Pet feed', 'Basic training', 'Enrichment games']
|
features: ['Toilet break and feeding if needed', 'Play and enrichment time', 'Early routine-building support', 'Best fit for many puppies']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '1 Hour',
|
title: '60 Minute Visit',
|
||||||
price: '$55',
|
price: '$55',
|
||||||
period: 'Per Visit',
|
period: 'Per Visit',
|
||||||
features: ['Bathroom break', 'Pet feed', 'Basic training', 'Enrichment games']
|
features: ['Longer home visit', 'More play, settling, and engagement', 'Extra support for younger puppies', 'Best for pups needing more time']
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
scarcityNote: 'Puppy Visit spaces are limited so we can keep care consistent.'
|
||||||
},
|
},
|
||||||
benefits: {
|
benefits: {
|
||||||
title: 'Puppy Visits benefits',
|
title: 'Why Puppy Visits matter early',
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
title: 'Enrichment',
|
title: 'Fewer long stretches alone',
|
||||||
body: 'From stimulating games to sensory toys, we keep those curious minds engaged and little tails wagging.'
|
body: 'Regular visits break up the day, help with toilet timing, and give your puppy company, care, and comfort while you are out.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Setting up the basics for pack walks',
|
title: 'Better foundations for future Pack Walks',
|
||||||
body: "Lay the groundwork for your pup's adult life. We'll guide you through setting the right tone, offering basic training tips and tricks along the way."
|
body: 'For puppies who may later join our Tiny Gang, early visits help build confidence, familiarity, and the routines that support a smoother next step.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Reduce anxiety',
|
title: 'A calmer puppy at home',
|
||||||
body: "With time your pup will know when to expect a visit, reducing the chances of accidents while you're away. With regular visits, your pup will feel loved and secure, minimising any time spent at home alone."
|
body: 'Play, enrichment, and routine help use up some puppy energy in the right way, which can mean a more settled puppy through the rest of the day.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Expert advice',
|
title: 'Support for busy owners too',
|
||||||
body: "As experienced dog pawrents, we've been through it all with many adorable puppies. Consider us your go-to for any questions or concerns as your furry friend grows up."
|
body: 'You get practical help during a demanding stage, plus guidance from a team that understands how much consistency matters when puppies are learning fast.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Early habits start taking shape',
|
||||||
|
body: 'Visits give us time to reinforce the basics around handling, routine, and calm engagement before those small habits become bigger problems.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'A more personal start with Goodwalk',
|
||||||
|
body: 'Puppy Visits help your puppy get to know us early, which builds trust and makes any future transition into other Goodwalk services feel more natural.'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
testimonialsHeading: 'What our clients say',
|
testimonialsHeading: 'What our clients say',
|
||||||
booking: {
|
booking: {
|
||||||
title: 'Ready to join the Tiny Gang?',
|
title: 'See if Puppy Visits are the right start',
|
||||||
subtitle: '',
|
subtitle: '',
|
||||||
formAction: '/contact-us',
|
formAction: '/contact-us',
|
||||||
serviceOptions: [],
|
serviceOptions: [],
|
||||||
ownerStepLabel: 'Your details',
|
ownerStepLabel: 'Your details',
|
||||||
dogStepLabel: 'Dog details',
|
dogStepLabel: 'Puppy details',
|
||||||
dogIntro: 'Tell us about your puppy, your area, and any special needs so we can plan the right visit.'
|
dogIntro:
|
||||||
|
'Tell us about your puppy, your area, routine, and any special needs so we can plan the right visit and see what support fits best.'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
export const staticPages = {
|
export const staticPages = {
|
||||||
'pack-walks': {
|
'pack-walks': {
|
||||||
title: 'Pack Walks | Join Our Tiny Gang',
|
title: 'Pack Walks for Small & Medium Dogs | Auckland Central',
|
||||||
description:
|
description:
|
||||||
'Join our Tiny Gang pack walks. We take our dogs to beautiful parks and beaches around the Auckland region.',
|
'Pack walks for sociable small and medium dogs in Auckland Central. Calm group outings, regular weekly routines, and free Meet & Greet with Goodwalk.',
|
||||||
canonicalPath: '/pack-walks'
|
canonicalPath: '/pack-walks'
|
||||||
},
|
},
|
||||||
'dog-walking': {
|
'dog-walking': {
|
||||||
title: '1 on 1 Walks | Professional Dog Walking | Auckland Wide',
|
title: '1:1 Dog Walks for Dogs Who Need More Attention | Auckland Central',
|
||||||
description:
|
description:
|
||||||
'Our 1:1 (one on one) are perfect for dogs with great recall and leash manners, our walks guarantee a stress-free experience!',
|
'One-on-one dog walks in Auckland Central for dogs who need more attention, more space, or a calmer routine. Free Meet & Greet with Goodwalk.',
|
||||||
canonicalPath: '/dog-walking'
|
canonicalPath: '/dog-walking'
|
||||||
},
|
},
|
||||||
'puppy-visits': {
|
'puppy-visits': {
|
||||||
title: 'Puppy Visits | Auckland In-Home Puppy Care | Goodwalk',
|
title: 'Puppy Visits & In-Home Puppy Care | Auckland Central',
|
||||||
description:
|
description:
|
||||||
'In-home puppy visits across Auckland Central — toilet breaks, feeding, play and gentle early training for pups not yet ready for pack walks.',
|
'In-home puppy visits across Auckland Central with toilet breaks, feeding, play, and early routine support. A calm start before future pack walks.',
|
||||||
canonicalPath: '/puppy-visits'
|
canonicalPath: '/puppy-visits'
|
||||||
},
|
},
|
||||||
'our-pricing': {
|
'our-pricing': {
|
||||||
|
|||||||
@@ -13,10 +13,16 @@ const imageMetadata: Record<string, ImageMetadata> = {
|
|||||||
'/images/otis-auckland-dog-walking-review.png': { width: 1254, height: 1254 },
|
'/images/otis-auckland-dog-walking-review.png': { width: 1254, height: 1254 },
|
||||||
'/images/wallace-auckland-dog-walking-review.png': { width: 1254, height: 1254 },
|
'/images/wallace-auckland-dog-walking-review.png': { width: 1254, height: 1254 },
|
||||||
'/images/auckland-small-dog-pack-walk.jpg': { width: 640, height: 480 },
|
'/images/auckland-small-dog-pack-walk.jpg': { width: 640, height: 480 },
|
||||||
|
'/images/auckland-pack-walk-small-dogs-group.png': { width: 1469, height: 1071 },
|
||||||
|
'/images/small-medium-dogs-pack-walk.png': { width: 1240, height: 1269 },
|
||||||
|
'/images/one-on-one-dog-portrait-1.png': { width: 1054, height: 1492 },
|
||||||
|
'/images/one-on-one-dog-portrait-2.png': { width: 1091, height: 1441 },
|
||||||
|
'/images/one-on-one-dog-portrait-3.png': { width: 1124, height: 1399 },
|
||||||
'/images/tiny-gang-auckland-dog-pack.jpg': { width: 1024, height: 297 },
|
'/images/tiny-gang-auckland-dog-pack.jpg': { width: 1024, height: 297 },
|
||||||
'/images/auckland-large-dog-one-on-one-walk.jpg': { width: 1024, height: 970 },
|
'/images/auckland-large-dog-one-on-one-walk.jpg': { width: 1024, height: 970 },
|
||||||
'/images/auckland-dogs-outdoor-pack.jpg': { width: 1024, height: 297 },
|
'/images/auckland-dogs-outdoor-pack.jpg': { width: 1024, height: 297 },
|
||||||
'/images/auckland-puppy-home-visit.jpg': { width: 640, height: 427 },
|
'/images/auckland-puppy-home-visit.jpg': { width: 640, height: 427 },
|
||||||
|
'/images/auckland-puppy-visits-cavalier-king-charles-spaniel.jpg': { width: 3327, height: 2217 },
|
||||||
'/images/auckland-pack-walk-dog.jpg': { width: 480, height: 640 },
|
'/images/auckland-pack-walk-dog.jpg': { width: 480, height: 640 },
|
||||||
'/images/auckland-dog-group-outing.jpg': { width: 640, height: 480 },
|
'/images/auckland-dog-group-outing.jpg': { width: 640, height: 480 },
|
||||||
'/images/goodwalk-dog-walker-alessandra.png': { width: 640, height: 640 }
|
'/images/goodwalk-dog-walker-alessandra.png': { width: 640, height: 640 }
|
||||||
|
|||||||
@@ -3,3 +3,7 @@ import { parseBooleanFlag } from '$lib/feature-flags';
|
|||||||
export function isGeneralEnquiryEnabled() {
|
export function isGeneralEnquiryEnabled() {
|
||||||
return parseBooleanFlag(process.env.ENABLE_GENERAL_ENQUIRIES, false);
|
return parseBooleanFlag(process.env.ENABLE_GENERAL_ENQUIRIES, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isHomepageHowItWorksEnabled() {
|
||||||
|
return parseBooleanFlag(process.env.ENABLE_HOMEPAGE_HOW_IT_WORKS, false);
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
html {
|
html {
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
|
overflow-x: clip;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@@ -16,6 +17,7 @@ body {
|
|||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
background: var(--off-white);
|
background: var(--off-white);
|
||||||
|
overflow-x: clip;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-green {
|
.btn-green {
|
||||||
background: var(--green);
|
background: var(--gw-green);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,15 +59,15 @@
|
|||||||
|
|
||||||
.btn-outline:hover {
|
.btn-outline:hover {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
color: var(--green);
|
color: var(--gw-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-outline-green {
|
.btn-outline-green {
|
||||||
color: var(--green);
|
color: var(--gw-green);
|
||||||
border-color: var(--green);
|
border-color: var(--gw-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-outline-green:hover {
|
.btn-outline-green:hover {
|
||||||
background: var(--green);
|
background: var(--gw-green);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,20 @@
|
|||||||
margin-bottom: 44px;
|
margin-bottom: 44px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.booking-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);
|
||||||
|
}
|
||||||
|
|
||||||
.booking-title {
|
.booking-title {
|
||||||
display: block;
|
display: block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@@ -28,6 +42,39 @@
|
|||||||
color: var(--yellow);
|
color: var(--yellow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.booking-intro {
|
||||||
|
max-width: 720px;
|
||||||
|
margin: -8px auto 0;
|
||||||
|
color: #4c5056;
|
||||||
|
font-size: 17px;
|
||||||
|
line-height: 1.65;
|
||||||
|
}
|
||||||
|
|
||||||
|
.booking-trust-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin-top: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.booking-trust-chip {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
min-height: 40px;
|
||||||
|
padding: 8px 14px;
|
||||||
|
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.04);
|
||||||
|
color: var(--gw-green);
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
.booking-title-highlight::after {
|
.booking-title-highlight::after {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -37,6 +84,31 @@
|
|||||||
height: 28px;
|
height: 28px;
|
||||||
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 34' fill='none'%3E%3Cpath d='M4 24C67 10 131 4 198 5c43 1 82 6 118 18' stroke='%23192419' stroke-width='8' stroke-linecap='round'/%3E%3C/svg%3E")
|
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 34' fill='none'%3E%3Cpath d='M4 24C67 10 131 4 198 5c43 1 82 6 118 18' stroke='%23192419' stroke-width='8' stroke-linecap='round'/%3E%3C/svg%3E")
|
||||||
center/contain no-repeat;
|
center/contain no-repeat;
|
||||||
|
transform-origin: left center;
|
||||||
|
animation: booking-underline-draw 0.9s cubic-bezier(0.22, 1, 0.36, 1) 0.2s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes booking-underline-draw {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scaleX(0.2) translateY(6px) rotate(-1.5deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
65% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scaleX(1.04) translateY(0) rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scaleX(1) translateY(0) rotate(0deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.booking-title-highlight::after {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.booking-stepper {
|
.booking-stepper {
|
||||||
@@ -44,6 +116,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 28px;
|
gap: 28px;
|
||||||
|
margin-top: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.booking-step {
|
.booking-step {
|
||||||
@@ -63,7 +136,7 @@
|
|||||||
.booking-step-number {
|
.booking-step-number {
|
||||||
width: 56px;
|
width: 56px;
|
||||||
height: 56px;
|
height: 56px;
|
||||||
border: 3px solid #111;
|
border: 2px solid rgba(33, 48, 33, 0.18);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -80,7 +153,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.booking-step.active .booking-step-number {
|
.booking-step.active .booking-step-number {
|
||||||
background: #eadbbf;
|
background: rgba(33, 48, 33, 0.1);
|
||||||
|
border-color: var(--gw-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
@@ -119,14 +193,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.booking-panel-banner {
|
.booking-panel-banner {
|
||||||
background: #eadbbf;
|
background: linear-gradient(180deg, #f6f2ea 0%, #f1ece3 100%);
|
||||||
color: #34363a;
|
color: #34363a;
|
||||||
border-radius: 30px 30px 0 0;
|
border-radius: 30px;
|
||||||
padding: 28px 32px 42px;
|
padding: 24px 28px 34px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-family: var(--font-body);
|
font-family: var(--font-body);
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 1.35;
|
line-height: 1.55;
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.booking-card-grid {
|
.booking-card-grid {
|
||||||
@@ -135,7 +210,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.booking-card-grid-with-banner {
|
.booking-card-grid-with-banner {
|
||||||
margin-top: -30px;
|
margin-top: -18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.booking-card-grid-owner {
|
.booking-card-grid-owner {
|
||||||
@@ -150,7 +225,9 @@
|
|||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 28px;
|
border-radius: 28px;
|
||||||
padding: 32px 38px 30px;
|
padding: 32px 38px 30px;
|
||||||
box-shadow: 0 10px 30px rgba(17, 20, 24, 0.04);
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(17, 20, 24, 0.05),
|
||||||
|
0 10px 30px rgba(17, 20, 24, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
.booking-field-card-group {
|
.booking-field-card-group {
|
||||||
@@ -175,10 +252,18 @@
|
|||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.booking-field-group-dog {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
.booking-field-stack {
|
.booking-field-stack {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.booking-field-stack-full {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
|
||||||
.booking-field-card label,
|
.booking-field-card label,
|
||||||
.booking-service-label {
|
.booking-service-label {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
@@ -198,6 +283,10 @@
|
|||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.booking-field-stack .booking-service-label {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
.booking-required {
|
.booking-required {
|
||||||
color: var(--yellow);
|
color: var(--yellow);
|
||||||
}
|
}
|
||||||
@@ -205,7 +294,7 @@
|
|||||||
.booking-field-card input,
|
.booking-field-card input,
|
||||||
.booking-field-card textarea {
|
.booking-field-card textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: 3px solid #111;
|
border: 2px solid rgba(33, 48, 33, 0.18);
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
padding: 16px 28px;
|
padding: 16px 28px;
|
||||||
@@ -219,14 +308,14 @@
|
|||||||
|
|
||||||
.booking-field-card input:hover,
|
.booking-field-card input:hover,
|
||||||
.booking-field-card textarea:hover {
|
.booking-field-card textarea:hover {
|
||||||
background: #f6f0e5;
|
background: #f8f5ef;
|
||||||
}
|
}
|
||||||
|
|
||||||
.booking-field-card input:focus,
|
.booking-field-card input:focus,
|
||||||
.booking-field-card textarea:focus {
|
.booking-field-card textarea:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: var(--green);
|
border-color: var(--gw-green);
|
||||||
background: #f6f0e5;
|
background: #f8f5ef;
|
||||||
}
|
}
|
||||||
|
|
||||||
.booking-field-card textarea {
|
.booking-field-card textarea {
|
||||||
@@ -243,6 +332,9 @@
|
|||||||
border-radius: 28px;
|
border-radius: 28px;
|
||||||
padding: 22px 28px;
|
padding: 22px 28px;
|
||||||
box-shadow: 0 10px 30px rgba(17, 20, 24, 0.04);
|
box-shadow: 0 10px 30px rgba(17, 20, 24, 0.04);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(17, 20, 24, 0.05),
|
||||||
|
0 10px 30px rgba(17, 20, 24, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
.booking-toggle-group {
|
.booking-toggle-group {
|
||||||
@@ -257,7 +349,7 @@
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
min-height: 56px;
|
min-height: 56px;
|
||||||
padding: 14px 18px;
|
padding: 14px 18px;
|
||||||
border: 3px solid #111;
|
border: 2px solid rgba(33, 48, 33, 0.18);
|
||||||
border-radius: 18px;
|
border-radius: 18px;
|
||||||
font-family: var(--font-head);
|
font-family: var(--font-head);
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
@@ -271,7 +363,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.booking-toggle-option:hover {
|
.booking-toggle-option:hover {
|
||||||
background: #f6f0e5;
|
background: #f8f5ef;
|
||||||
}
|
}
|
||||||
|
|
||||||
.booking-toggle-option input {
|
.booking-toggle-option input {
|
||||||
@@ -283,7 +375,7 @@
|
|||||||
.booking-toggle-indicator {
|
.booking-toggle-indicator {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
border: 3px solid #111;
|
border: 2px solid rgba(33, 48, 33, 0.28);
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -295,8 +387,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.booking-toggle-option input:checked + .booking-toggle-indicator {
|
.booking-toggle-option input:checked + .booking-toggle-indicator {
|
||||||
border-color: #111;
|
border-color: var(--gw-green);
|
||||||
background: var(--yellow);
|
background: rgba(33, 48, 33, 0.12);
|
||||||
}
|
}
|
||||||
|
|
||||||
.booking-toggle-option input:checked + .booking-toggle-indicator::after {
|
.booking-toggle-option input:checked + .booking-toggle-indicator::after {
|
||||||
@@ -304,7 +396,7 @@
|
|||||||
width: 10px;
|
width: 10px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: #111;
|
background: var(--gw-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -334,7 +426,7 @@
|
|||||||
.booking-check-box {
|
.booking-check-box {
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
border: 3px solid #111;
|
border: 2px solid rgba(33, 48, 33, 0.28);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -346,16 +438,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.booking-check-option input:checked + .booking-check-box {
|
.booking-check-option input:checked + .booking-check-box {
|
||||||
background: var(--yellow);
|
background: rgba(33, 48, 33, 0.12);
|
||||||
border-color: #111;
|
border-color: var(--gw-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.booking-check-option input:checked + .booking-check-box::after {
|
.booking-check-option input:checked + .booking-check-box::after {
|
||||||
content: '';
|
content: '';
|
||||||
width: 10px;
|
width: 10px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
border-right: 3px solid #111;
|
border-right: 3px solid var(--gw-green);
|
||||||
border-bottom: 3px solid #111;
|
border-bottom: 3px solid var(--gw-green);
|
||||||
transform: rotate(40deg) translate(-1px, -2px);
|
transform: rotate(40deg) translate(-1px, -2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -379,7 +471,7 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
color: #666;
|
color: #6a6d72;
|
||||||
}
|
}
|
||||||
|
|
||||||
.booking-next-button,
|
.booking-next-button,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ header {
|
|||||||
z-index: 100;
|
z-index: 100;
|
||||||
isolation: isolate;
|
isolation: isolate;
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
background: var(--green);
|
background: var(--gw-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
nav,
|
nav,
|
||||||
@@ -54,12 +54,12 @@ nav {
|
|||||||
|
|
||||||
.nav-links > li > a:hover {
|
.nav-links > li > a:hover {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
color: var(--green);
|
color: var(--gw-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-links > li > a.nav-link-active {
|
.nav-links > li > a.nav-link-active {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
color: var(--green);
|
color: var(--gw-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mega-chevron {
|
.mega-chevron {
|
||||||
@@ -113,7 +113,7 @@ nav {
|
|||||||
padding: 12px 14px;
|
padding: 12px 14px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
background: rgba(33, 48, 33, 0.04);
|
background: rgba(33, 48, 33, 0.04);
|
||||||
color: var(--green);
|
color: var(--gw-green);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@@ -158,7 +158,7 @@ nav {
|
|||||||
.mega-icon {
|
.mega-icon {
|
||||||
width: 64px;
|
width: 64px;
|
||||||
height: 64px;
|
height: 64px;
|
||||||
background: var(--green);
|
background: var(--gw-green);
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -44,6 +44,9 @@
|
|||||||
*/
|
*/
|
||||||
body {
|
body {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.mobile-cta-enabled {
|
||||||
padding-bottom: 64px;
|
padding-bottom: 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +107,7 @@
|
|||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
padding: 11px 14px;
|
padding: 11px 14px;
|
||||||
background: rgba(33, 48, 33, 0.1);
|
background: rgba(33, 48, 33, 0.1);
|
||||||
color: var(--green);
|
color: var(--gw-green);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
@@ -400,12 +403,34 @@
|
|||||||
margin-bottom: 34px;
|
margin-bottom: 34px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.booking-eyebrow {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
.booking-title {
|
.booking-title {
|
||||||
font-size: 34px;
|
font-size: 34px;
|
||||||
line-height: 1.02;
|
line-height: 1.02;
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.booking-intro {
|
||||||
|
margin-top: -4px;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.booking-trust-row {
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.booking-trust-chip {
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
.booking-title-highlight::after {
|
.booking-title-highlight::after {
|
||||||
left: 12px;
|
left: 12px;
|
||||||
right: 12px;
|
right: 12px;
|
||||||
@@ -453,7 +478,8 @@
|
|||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
.booking-field-group-owner {
|
.booking-field-group-owner,
|
||||||
|
.booking-field-group-dog {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
gap: 18px;
|
gap: 18px;
|
||||||
}
|
}
|
||||||
@@ -566,10 +592,6 @@
|
|||||||
border-radius: 24px;
|
border-radius: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-nav {
|
|
||||||
columns: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-action-title {
|
.footer-action-title {
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
}
|
}
|
||||||
@@ -602,6 +624,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
@media (max-width: 480px) {
|
||||||
|
.footer-nav {
|
||||||
|
gap: 2px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.mobile-phone {
|
.mobile-phone {
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ section {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#hero {
|
#hero {
|
||||||
background: var(--green);
|
background: var(--gw-green);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding: 44px 50px 0;
|
padding: 44px 50px 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -207,7 +207,7 @@ section {
|
|||||||
padding: 9px 16px;
|
padding: 9px 16px;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
background: rgba(255, 255, 255, 0.92);
|
background: rgba(255, 255, 255, 0.92);
|
||||||
color: var(--green);
|
color: var(--gw-green);
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
@@ -282,9 +282,35 @@ section {
|
|||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.promise-img-frame {
|
||||||
|
position: relative;
|
||||||
|
width: min(100%, 520px);
|
||||||
|
padding: 18px 18px 32px 32px;
|
||||||
|
border-radius: 32px;
|
||||||
|
background: linear-gradient(180deg, #faf8f3 0%, #f2eee6 100%);
|
||||||
|
box-shadow: 0 18px 34px rgba(17, 20, 24, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.promise-img-frame::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 46px;
|
||||||
|
bottom: 0;
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--gw-green);
|
||||||
|
opacity: 0.14;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.promise-img img {
|
.promise-img img {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 560px;
|
max-width: 560px;
|
||||||
|
display: block;
|
||||||
|
border-radius: 28px 28px 88px 28px;
|
||||||
|
box-shadow: 0 10px 24px rgba(17, 20, 24, 0.1);
|
||||||
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.service-card {
|
.service-card {
|
||||||
@@ -344,7 +370,7 @@ section {
|
|||||||
|
|
||||||
.service-card .service-card-icon {
|
.service-card .service-card-icon {
|
||||||
font-size: 34px;
|
font-size: 34px;
|
||||||
color: var(--green);
|
color: var(--gw-green);
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,7 +381,7 @@ section {
|
|||||||
padding: 6px 14px;
|
padding: 6px 14px;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
background: rgba(33, 48, 33, 0.06);
|
background: rgba(33, 48, 33, 0.06);
|
||||||
color: var(--green);
|
color: var(--gw-green);
|
||||||
font-family: var(--font-head);
|
font-family: var(--font-head);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
@@ -372,7 +398,7 @@ section {
|
|||||||
|
|
||||||
#values,
|
#values,
|
||||||
footer {
|
footer {
|
||||||
background: var(--green);
|
background: var(--gw-green);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -470,7 +496,7 @@ footer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.info-copy a {
|
.info-copy a {
|
||||||
color: var(--green);
|
color: var(--gw-green);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -495,7 +521,7 @@ footer {
|
|||||||
.faq summary::after {
|
.faq summary::after {
|
||||||
content: '+';
|
content: '+';
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
color: var(--green);
|
color: var(--gw-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.faq details[open] summary::after {
|
.faq details[open] summary::after {
|
||||||
@@ -520,7 +546,7 @@ footer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#instagram .btn {
|
#instagram .btn {
|
||||||
background: var(--green);
|
background: var(--gw-green);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -594,13 +620,14 @@ footer {
|
|||||||
|
|
||||||
.footer-nav {
|
.footer-nav {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
columns: 2;
|
display: grid;
|
||||||
column-gap: 24px;
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 2px 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-nav li {
|
.footer-nav li {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
break-inside: avoid;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer-nav a {
|
.footer-nav a {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: 0.08em;
|
letter-spacing: 0.08em;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: var(--green);
|
color: var(--gw-green);
|
||||||
margin: 0 0 14px;
|
margin: 0 0 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
:root {
|
:root {
|
||||||
--green: #213021;
|
--gw-green: #213021;
|
||||||
--yellow: #ffd100;
|
--yellow: #ffd100;
|
||||||
--gray: #59606d;
|
--gray: #59606d;
|
||||||
--beige: #e5d6c2;
|
--beige: #e5d6c2;
|
||||||
@@ -8,9 +8,6 @@
|
|||||||
--max-w: 1280px;
|
--max-w: 1280px;
|
||||||
--font-body: 'Readex Pro', sans-serif;
|
--font-body: 'Readex Pro', sans-serif;
|
||||||
--font-head: 'Unbounded', sans-serif;
|
--font-head: 'Unbounded', sans-serif;
|
||||||
|
|
||||||
/* Legacy "navy" tokens now intentionally render as Goodwalk green. */
|
|
||||||
--navy: var(--green);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1800px) {
|
@media (min-width: 1800px) {
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ export interface TestimonialContent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ProcessStep {
|
export interface ProcessStep {
|
||||||
|
phase?: string;
|
||||||
|
benefit?: string;
|
||||||
title: string;
|
title: string;
|
||||||
body: string;
|
body: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
@@ -117,6 +119,11 @@ export interface ServiceBenefit {
|
|||||||
body: string;
|
body: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ServiceHighlightImage {
|
||||||
|
imageUrl: string;
|
||||||
|
imageAlt: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ServicePageContent {
|
export interface ServicePageContent {
|
||||||
hero: {
|
hero: {
|
||||||
eyebrow: string;
|
eyebrow: string;
|
||||||
@@ -130,6 +137,7 @@ export interface ServicePageContent {
|
|||||||
title: string;
|
title: string;
|
||||||
imageUrl: string;
|
imageUrl: string;
|
||||||
imageAlt: string;
|
imageAlt: string;
|
||||||
|
collageImages?: ServiceHighlightImage[];
|
||||||
};
|
};
|
||||||
pricing: {
|
pricing: {
|
||||||
title: string;
|
title: string;
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
background:
|
background:
|
||||||
radial-gradient(circle at 20% 15%, #2a3e2a 0%, transparent 55%),
|
radial-gradient(circle at 20% 15%, #2a3e2a 0%, transparent 55%),
|
||||||
radial-gradient(circle at 80% 85%, #1a261a 0%, transparent 55%),
|
radial-gradient(circle at 80% 85%, #1a261a 0%, transparent 55%),
|
||||||
var(--green);
|
var(--gw-green);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 48px 24px;
|
padding: 48px 24px;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
import { initClickTracking, trackPageView } from '$lib/analytics';
|
import { initClickTracking, trackPageView } from '$lib/analytics';
|
||||||
import MobileBookBar from '$lib/components/MobileBookBar.svelte';
|
import MobileBookBar from '$lib/components/MobileBookBar.svelte';
|
||||||
import RouteSkeleton from '$lib/components/RouteSkeleton.svelte';
|
import RouteSkeleton from '$lib/components/RouteSkeleton.svelte';
|
||||||
|
import { isMobileCtaButtonEnabled } from '$lib/feature-flags';
|
||||||
import '$lib/styles/variables.css';
|
import '$lib/styles/variables.css';
|
||||||
import '$lib/styles/base.css';
|
import '$lib/styles/base.css';
|
||||||
import '$lib/styles/layout.css';
|
import '$lib/styles/layout.css';
|
||||||
@@ -14,6 +15,8 @@
|
|||||||
import '$lib/styles/sections.css';
|
import '$lib/styles/sections.css';
|
||||||
import '$lib/styles/responsive.css';
|
import '$lib/styles/responsive.css';
|
||||||
|
|
||||||
|
const mobileCtaButtonEnabled = isMobileCtaButtonEnabled();
|
||||||
|
|
||||||
onMount(() => initClickTracking());
|
onMount(() => initClickTracking());
|
||||||
|
|
||||||
function shouldShowSkeleton() {
|
function shouldShowSkeleton() {
|
||||||
@@ -61,6 +64,8 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:body class:mobile-cta-enabled={mobileCtaButtonEnabled} />
|
||||||
|
|
||||||
<div class="layout-shell">
|
<div class="layout-shell">
|
||||||
<div class:layout-content-loading={showRouteSkeleton} class="layout-content" aria-hidden={showRouteSkeleton}>
|
<div class:layout-content-loading={showRouteSkeleton} class="layout-content" aria-hidden={showRouteSkeleton}>
|
||||||
<slot />
|
<slot />
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { getHomepageContent } from '$lib/server/content';
|
import { getHomepageContent } from '$lib/server/content';
|
||||||
|
import { isHomepageHowItWorksEnabled } from '$lib/server/feature-flags';
|
||||||
|
|
||||||
export async function load() {
|
export async function load() {
|
||||||
return {
|
return {
|
||||||
content: await getHomepageContent()
|
content: await getHomepageContent(),
|
||||||
|
howItWorksEnabled: isHomepageHowItWorksEnabled()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,7 +140,9 @@
|
|||||||
<HeroSection hero={data.content.hero} reviewCta={data.content.intro.reviewCta} />
|
<HeroSection hero={data.content.hero} reviewCta={data.content.intro.reviewCta} />
|
||||||
<PromiseSection promise={data.content.promise} />
|
<PromiseSection promise={data.content.promise} />
|
||||||
<ServicesSection services={data.content.services} />
|
<ServicesSection services={data.content.services} />
|
||||||
|
{#if data.howItWorksEnabled}
|
||||||
<HowItWorksSection content={data.content.howItWorks} />
|
<HowItWorksSection content={data.content.howItWorks} />
|
||||||
|
{/if}
|
||||||
<TestimonialsSection testimonials={data.content.testimonials} />
|
<TestimonialsSection testimonials={data.content.testimonials} />
|
||||||
<ValuesSection values={data.content.values} />
|
<ValuesSection values={data.content.values} />
|
||||||
<BookingSection booking={data.content.booking} />
|
<BookingSection booking={data.content.booking} />
|
||||||
|
|||||||
@@ -101,9 +101,9 @@
|
|||||||
{
|
{
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@type': 'Service',
|
'@type': 'Service',
|
||||||
name: packWalksContent.hero.title,
|
name: 'Goodwalk Pack Walks',
|
||||||
description: data.page.description,
|
description: data.page.description,
|
||||||
serviceType: 'Pack Walks',
|
serviceType: 'Pack walks for small and medium dogs',
|
||||||
provider: {
|
provider: {
|
||||||
'@type': 'LocalBusiness',
|
'@type': 'LocalBusiness',
|
||||||
name: 'Goodwalk',
|
name: 'Goodwalk',
|
||||||
@@ -124,9 +124,9 @@
|
|||||||
{
|
{
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@type': 'Service',
|
'@type': 'Service',
|
||||||
name: dogWalkingContent.hero.title,
|
name: 'Goodwalk 1:1 Dog Walks',
|
||||||
description: data.page.description,
|
description: data.page.description,
|
||||||
serviceType: '1:1 Dog Walking',
|
serviceType: 'One-on-one dog walking',
|
||||||
provider: {
|
provider: {
|
||||||
'@type': 'LocalBusiness',
|
'@type': 'LocalBusiness',
|
||||||
name: 'Goodwalk',
|
name: 'Goodwalk',
|
||||||
@@ -147,9 +147,9 @@
|
|||||||
{
|
{
|
||||||
'@context': 'https://schema.org',
|
'@context': 'https://schema.org',
|
||||||
'@type': 'Service',
|
'@type': 'Service',
|
||||||
name: puppyVisitsContent.hero.title,
|
name: 'Goodwalk Puppy Visits',
|
||||||
description: data.page.description,
|
description: data.page.description,
|
||||||
serviceType: 'Puppy Visits',
|
serviceType: 'In-home puppy visits',
|
||||||
provider: {
|
provider: {
|
||||||
'@type': 'LocalBusiness',
|
'@type': 'LocalBusiness',
|
||||||
name: 'Goodwalk',
|
name: 'Goodwalk',
|
||||||
|
|||||||
@@ -1,22 +1,39 @@
|
|||||||
import { describe, expect, it, vi } from 'vitest';
|
import { describe, expect, it, vi } from 'vitest';
|
||||||
import { homepageContent } from '$lib/content/homepage';
|
import { homepageContent } from '$lib/content/homepage';
|
||||||
|
|
||||||
const { getHomepageContent } = vi.hoisted(() => ({
|
const { getHomepageContent, isHomepageHowItWorksEnabled } = vi.hoisted(() => ({
|
||||||
getHomepageContent: vi.fn()
|
getHomepageContent: vi.fn(),
|
||||||
|
isHomepageHowItWorksEnabled: vi.fn()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('$lib/server/content', () => ({
|
vi.mock('$lib/server/content', () => ({
|
||||||
getHomepageContent
|
getHomepageContent
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock('$lib/server/feature-flags', () => ({
|
||||||
|
isHomepageHowItWorksEnabled
|
||||||
|
}));
|
||||||
|
|
||||||
import { load } from './+page.server';
|
import { load } from './+page.server';
|
||||||
|
|
||||||
describe('home page server load', () => {
|
describe('home page server load', () => {
|
||||||
it('returns homepage content', async () => {
|
it('returns homepage content', async () => {
|
||||||
getHomepageContent.mockResolvedValue(homepageContent);
|
getHomepageContent.mockResolvedValue(homepageContent);
|
||||||
|
isHomepageHowItWorksEnabled.mockReturnValue(false);
|
||||||
|
|
||||||
await expect(load()).resolves.toEqual({
|
await expect(load()).resolves.toEqual({
|
||||||
content: homepageContent
|
content: homepageContent,
|
||||||
|
howItWorksEnabled: false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the how it works flag when enabled', async () => {
|
||||||
|
getHomepageContent.mockResolvedValue(homepageContent);
|
||||||
|
isHomepageHowItWorksEnabled.mockReturnValue(true);
|
||||||
|
|
||||||
|
await expect(load()).resolves.toEqual({
|
||||||
|
content: homepageContent,
|
||||||
|
howItWorksEnabled: true
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
|
After Width: | Height: | Size: 852 KiB |
|
After Width: | Height: | Size: 606 KiB |
|
After Width: | Height: | Size: 614 KiB |
|
After Width: | Height: | Size: 2.7 MiB |
|
After Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 2.8 MiB |
|
After Width: | Height: | Size: 912 KiB |