4.2.1 final fixes
This commit is contained in:
@@ -1,5 +1,101 @@
|
|||||||
# Mobile Polish — Conversion & Comfort Audit Tracker
|
# Mobile Polish — Conversion & Comfort Audit Tracker
|
||||||
|
|
||||||
|
## New Rescan Items — Mobile Conversion Opportunities
|
||||||
|
|
||||||
|
Fresh opportunities from a second mobile-first pass over the main site.
|
||||||
|
These are intentionally only the new items, kept separate from the
|
||||||
|
existing audit below.
|
||||||
|
|
||||||
|
### High — conversion strategy and flow
|
||||||
|
|
||||||
|
- [ ] **Hero CTA hierarchy still prioritises browsing over booking**
|
||||||
|
- Files: `src/lib/content/homepage.ts:37-39`, `src/lib/components/HeroSection.svelte`
|
||||||
|
- Current: the yellow primary CTA is `Explore our services →`, while
|
||||||
|
`Book a Meet & Greet` is secondary.
|
||||||
|
- Why: on mobile, high-intent users should be able to choose the next
|
||||||
|
step immediately. Making the exploratory path more visually dominant
|
||||||
|
adds friction before the user reaches the lead form.
|
||||||
|
- Opportunity: test flipping the hierarchy on mobile so booking
|
||||||
|
becomes the primary CTA and service exploration becomes secondary.
|
||||||
|
|
||||||
|
- [ ] **Homepage social proof appears too late in the scroll**
|
||||||
|
- File: `src/routes/+page.svelte:143-147`
|
||||||
|
- Current order: `Services -> Values -> Testimonials -> Booking`.
|
||||||
|
- Why: testimonials are one of the strongest conversion levers, but
|
||||||
|
on mobile they arrive after several large sections. Users are asked
|
||||||
|
to keep scrolling before seeing the strongest emotional proof.
|
||||||
|
- Opportunity: move testimonials above values on the homepage, or
|
||||||
|
surface one featured review snippet earlier in the page.
|
||||||
|
|
||||||
|
- [ ] **Hero still relies on the next section for trust**
|
||||||
|
- Files: `src/lib/content/homepage.ts:43-49`, `src/lib/components/HeroSection.svelte`,
|
||||||
|
`src/lib/components/IntroStrip.svelte`
|
||||||
|
- Current: the hero presents the headline and CTAs, but the review
|
||||||
|
proof sits in the intro strip below.
|
||||||
|
- Why: on mobile, the hero needs to answer both "what is this?" and
|
||||||
|
"can I trust them?" before the user scrolls away. Separating those
|
||||||
|
two jobs weakens the first decision moment.
|
||||||
|
- Opportunity: add a compact review/trust chip directly under the
|
||||||
|
hero subtitle or near the hero CTAs on mobile.
|
||||||
|
|
||||||
|
- [ ] **Booking flow asks for dog details before it captures the lead**
|
||||||
|
- File: `src/lib/components/BookingSection.svelte:298-441`
|
||||||
|
- Current: step 1 asks for dog name, location, message, and services;
|
||||||
|
contact details are only requested in step 2.
|
||||||
|
- Why: this is a higher-friction sequence on mobile. Users often feel
|
||||||
|
more comfortable giving owner details first, then expanding into pet
|
||||||
|
specifics once they have mentally committed.
|
||||||
|
- Opportunity: test reversing the step order so step 1 captures name,
|
||||||
|
email, and phone first, then dog details second.
|
||||||
|
|
||||||
|
### Medium — mobile persuasion and CTA timing
|
||||||
|
|
||||||
|
- [ ] **Sticky mobile CTA appears on a fixed pixel threshold rather than page context**
|
||||||
|
- File: `src/lib/components/MobileBookBar.svelte:26-37`
|
||||||
|
- Current: visibility is driven by `SHOW_AFTER_PX = 480` and
|
||||||
|
`HIDE_BELOW_PX = 120`.
|
||||||
|
- Why: a fixed threshold will feel early on some phones and late on
|
||||||
|
others. It also ignores whether the hero or booking section is
|
||||||
|
actually in view.
|
||||||
|
- Opportunity: switch to an `IntersectionObserver` tied to the hero or
|
||||||
|
booking section so the bar appears based on user context rather than
|
||||||
|
raw scroll position.
|
||||||
|
|
||||||
|
- [ ] **Testimonials section pushes users off-site before finishing the proof story**
|
||||||
|
- File: `src/lib/components/TestimonialsSection.svelte:128-134`
|
||||||
|
- Current: the Instagram CTA appears near the top of the testimonials
|
||||||
|
section, before the user has fully consumed the review content.
|
||||||
|
- Why: on mobile, sending users to Instagram this early interrupts the
|
||||||
|
conversion journey and competes with the booking path.
|
||||||
|
- Opportunity: demote the Instagram CTA below the carousel, or replace
|
||||||
|
it with a tighter trust-oriented proof CTA higher up.
|
||||||
|
|
||||||
|
- [ ] **Mobile pricing pages lose the consultative "not sure?" nudge**
|
||||||
|
- File: `src/lib/components/PricingPage.svelte:12-19`
|
||||||
|
- Current: the meet-and-greet reminder prompt is gated behind
|
||||||
|
`min-width: 769px`, so desktop gets a tailored nudge and mobile
|
||||||
|
does not.
|
||||||
|
- Why: mobile users are more likely to feel overwhelmed by stacked
|
||||||
|
pricing cards, not less. Removing the consultative reassurance on
|
||||||
|
the smallest screens is directionally backwards for conversion.
|
||||||
|
- Opportunity: add an inline mobile prompt after the first pricing
|
||||||
|
section that says, in effect, "Not sure which option fits? Book a
|
||||||
|
free Meet & Greet and we’ll help you choose."
|
||||||
|
|
||||||
|
### Medium — stacked-page CTA noise
|
||||||
|
|
||||||
|
- [ ] **Stacked pricing/service cards repeat the same CTA too many times**
|
||||||
|
- Files: `src/lib/components/PricingPage.svelte:141-159`,
|
||||||
|
`src/lib/components/ServiceLandingPage.svelte:94-112`
|
||||||
|
- Current: when cards collapse to one column on mobile, each card
|
||||||
|
keeps a full "Book a Meet & Greet" button.
|
||||||
|
- Why: the repetition turns persuasive choice architecture into visual
|
||||||
|
noise. Instead of helping the user decide, the page starts feeling
|
||||||
|
like a stack of repeated asks.
|
||||||
|
- Opportunity: treat this as a shared mobile pattern across pricing
|
||||||
|
and service pages. Keep one strong CTA per section, let the popular
|
||||||
|
card carry the primary action, and demote the rest.
|
||||||
|
|
||||||
Findings from a focused mobile-experience review (≤768px, with extra
|
Findings from a focused mobile-experience review (≤768px, with extra
|
||||||
attention to 375px small-phones). Desktop is considered done. Each item
|
attention to 375px small-phones). Desktop is considered done. Each item
|
||||||
records the where, why, and the concrete change.
|
records the where, why, and the concrete change.
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import BookingSection from '$lib/components/BookingSection.svelte';
|
import BookingSection from '$lib/components/BookingSection.svelte';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/Icon.svelte';
|
||||||
import type { BookingContent } from '$lib/types';
|
import InfoSection from '$lib/components/InfoSection.svelte';
|
||||||
|
import type { BookingContent, InfoContent } from '$lib/types';
|
||||||
|
|
||||||
export let booking: BookingContent;
|
export let booking: BookingContent;
|
||||||
|
export let info: InfoContent;
|
||||||
export let allowGeneralEnquiry = false;
|
export let allowGeneralEnquiry = false;
|
||||||
|
|
||||||
const email = 'info@goodwalk.co.nz';
|
const email = 'info@goodwalk.co.nz';
|
||||||
@@ -35,6 +37,7 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<BookingSection {booking} {allowGeneralEnquiry} />
|
<BookingSection {booking} {allowGeneralEnquiry} />
|
||||||
|
<InfoSection {info} />
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -74,13 +74,13 @@
|
|||||||
enquiryType = 'booking';
|
enquiryType = 'booking';
|
||||||
}
|
}
|
||||||
$: isGeneralEnquiry = allowGeneralEnquiry && enquiryType === 'general';
|
$: isGeneralEnquiry = allowGeneralEnquiry && enquiryType === 'general';
|
||||||
$: ownerSubtitle = isGeneralEnquiry
|
$: ownerIntro = isGeneralEnquiry
|
||||||
? booking.generalSubtitle?.trim() || defaultGeneralSubtitle
|
? booking.generalSubtitle?.trim() || defaultGeneralSubtitle
|
||||||
: booking.subtitle;
|
: booking.subtitle;
|
||||||
$: ownerStepLabel = booking.ownerStepLabel?.trim() || 'Owner Details';
|
$: ownerStepLabel = booking.ownerStepLabel?.trim() || 'Owner Details';
|
||||||
$: dogStepLabel = booking.dogStepLabel?.trim() || 'Your dog';
|
$: dogStepLabel = booking.dogStepLabel?.trim() || 'Your dog';
|
||||||
$: firstStepLabel = isGeneralEnquiry ? 'Your enquiry' : dogStepLabel;
|
$: detailsStepLabel = isGeneralEnquiry ? 'Your enquiry' : dogStepLabel;
|
||||||
$: firstStepIntro = isGeneralEnquiry ? generalIntro : dogIntro;
|
$: detailsStepIntro = isGeneralEnquiry ? generalIntro : dogIntro;
|
||||||
$: successPetName = petName.trim() || 'your dog';
|
$: successPetName = petName.trim() || 'your dog';
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
@@ -126,7 +126,37 @@
|
|||||||
errors = {};
|
errors = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateFirstStep(): boolean {
|
function validateOwnerStep(): boolean {
|
||||||
|
const next: Record<string, string> = {};
|
||||||
|
|
||||||
|
if (!fullName.trim()) next.fullName = 'Please enter your full name';
|
||||||
|
|
||||||
|
const emailError = validateEmail(email);
|
||||||
|
if (emailError) next.email = emailError;
|
||||||
|
|
||||||
|
if (!phone.trim()) next.phone = 'Please enter your contact number';
|
||||||
|
|
||||||
|
errors = next;
|
||||||
|
|
||||||
|
if (next.fullName) {
|
||||||
|
fullNameInput?.focus();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next.email) {
|
||||||
|
emailInput?.focus();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (next.phone) {
|
||||||
|
phoneInput?.focus();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateDetailsStep(): boolean {
|
||||||
const next: Record<string, string> = {};
|
const next: Record<string, string> = {};
|
||||||
|
|
||||||
if (isGeneralEnquiry) {
|
if (isGeneralEnquiry) {
|
||||||
@@ -156,7 +186,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function goToOwnerStep() {
|
function goToOwnerStep() {
|
||||||
if (!validateFirstStep()) return;
|
if (!validateDetailsStep()) return;
|
||||||
errors = {};
|
errors = {};
|
||||||
step = 2;
|
step = 2;
|
||||||
}
|
}
|
||||||
@@ -169,22 +199,11 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const next: Record<string, string> = {};
|
if (!validateOwnerStep()) {
|
||||||
if (!fullName.trim()) next.fullName = 'Please enter your full name';
|
|
||||||
|
|
||||||
const emailError = validateEmail(email);
|
|
||||||
if (emailError) next.email = emailError;
|
|
||||||
|
|
||||||
if (!phone.trim()) next.phone = 'Please enter your contact number';
|
|
||||||
|
|
||||||
if (Object.keys(next).length > 0) {
|
|
||||||
errors = next;
|
|
||||||
if (next.fullName) fullNameInput?.focus();
|
|
||||||
else if (next.email) emailInput?.focus();
|
|
||||||
else if (next.phone) phoneInput?.focus();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
errors = {};
|
||||||
submitting = true;
|
submitting = true;
|
||||||
submitErrorDetail = '';
|
submitErrorDetail = '';
|
||||||
showErrorModal = false;
|
showErrorModal = false;
|
||||||
@@ -194,26 +213,27 @@
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
enquiryType,
|
enquiryType,
|
||||||
fullName,
|
fullName,
|
||||||
email,
|
email,
|
||||||
phone,
|
phone,
|
||||||
petName: isGeneralEnquiry ? '' : petName,
|
petName: isGeneralEnquiry ? '' : petName,
|
||||||
location: isGeneralEnquiry ? '' : location,
|
location: isGeneralEnquiry ? '' : location,
|
||||||
message,
|
message,
|
||||||
services: isGeneralEnquiry ? [] : selectedServices,
|
services: isGeneralEnquiry ? [] : selectedServices,
|
||||||
website,
|
website,
|
||||||
formStartedAt,
|
formStartedAt,
|
||||||
referrer: document.referrer,
|
referrer: document.referrer,
|
||||||
page: window.location.href,
|
page: window.location.href
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const body = await res.json().catch(() => ({}));
|
const body = await res.json().catch(() => ({}));
|
||||||
const detail = typeof body?.detail === 'string'
|
const detail =
|
||||||
? body.detail
|
typeof body?.detail === 'string'
|
||||||
: body?.detail?.message ?? body?.message ?? `Server responded with ${res.status}`;
|
? body.detail
|
||||||
|
: body?.detail?.message ?? body?.message ?? `Server responded with ${res.status}`;
|
||||||
throw new Error(detail);
|
throw new Error(detail);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,7 +249,6 @@
|
|||||||
|
|
||||||
<section id="newlead" use:reveal={{ delay: 70 }} class="reveal-block">
|
<section id="newlead" use:reveal={{ delay: 70 }} class="reveal-block">
|
||||||
<div class="form-inner">
|
<div class="form-inner">
|
||||||
|
|
||||||
{#if submitted}
|
{#if submitted}
|
||||||
<SuccessModal
|
<SuccessModal
|
||||||
firstName={fullName.split(' ')[0]}
|
firstName={fullName.split(' ')[0]}
|
||||||
@@ -251,7 +270,8 @@
|
|||||||
|
|
||||||
<div class="booking-header">
|
<div class="booking-header">
|
||||||
<h2 class="booking-title">
|
<h2 class="booking-title">
|
||||||
<span class="booking-title-plain">{headingParts.plain}</span>{' '}<span class="booking-title-highlight">{headingParts.highlight}</span>
|
<span class="booking-title-plain">{headingParts.plain}</span>{' '}
|
||||||
|
<span class="booking-title-highlight">{headingParts.highlight}</span>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div class="booking-stepper" aria-label="Booking form steps">
|
<div class="booking-stepper" aria-label="Booking form steps">
|
||||||
@@ -259,10 +279,13 @@
|
|||||||
type="button"
|
type="button"
|
||||||
class:active={step === 1}
|
class:active={step === 1}
|
||||||
class="booking-step"
|
class="booking-step"
|
||||||
on:click={() => (step = 1)}
|
on:click={() => {
|
||||||
|
step = 1;
|
||||||
|
errors = {};
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<span class="booking-step-number">1</span>
|
<span class="booking-step-number">1</span>
|
||||||
<span class="booking-step-label">{firstStepLabel}</span>
|
<span class="booking-step-label">{detailsStepLabel}</span>
|
||||||
</button>
|
</button>
|
||||||
<span class="booking-step-divider" aria-hidden="true"></span>
|
<span class="booking-step-divider" aria-hidden="true"></span>
|
||||||
<button
|
<button
|
||||||
@@ -277,12 +300,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form
|
<form class="booking-form" id="bookingForm" novalidate on:submit={handleSubmit}>
|
||||||
class="booking-form"
|
|
||||||
id="bookingForm"
|
|
||||||
novalidate
|
|
||||||
on:submit={handleSubmit}
|
|
||||||
>
|
|
||||||
<div class="booking-honeypot" aria-hidden="true">
|
<div class="booking-honeypot" aria-hidden="true">
|
||||||
<label for="website">Website</label>
|
<label for="website">Website</label>
|
||||||
<input
|
<input
|
||||||
@@ -296,12 +314,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if step === 1}
|
{#if step === 1}
|
||||||
|
<input type="hidden" name="enquiryType" value={enquiryType} />
|
||||||
<div class="booking-panel">
|
<div class="booking-panel">
|
||||||
{#if firstStepIntro}
|
{#if detailsStepIntro}
|
||||||
<div class="booking-panel-banner">{firstStepIntro}</div>
|
<div class="booking-panel-banner">{detailsStepIntro}</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class:booking-card-grid-with-banner={Boolean(firstStepIntro)} class="booking-card-grid booking-card-grid-dog">
|
<div
|
||||||
|
class:booking-card-grid-with-banner={Boolean(detailsStepIntro)}
|
||||||
|
class="booking-card-grid booking-card-grid-dog"
|
||||||
|
>
|
||||||
{#if allowGeneralEnquiry}
|
{#if allowGeneralEnquiry}
|
||||||
<div class="booking-field-card booking-field-card-full">
|
<div class="booking-field-card booking-field-card-full">
|
||||||
<label>
|
<label>
|
||||||
@@ -361,7 +383,10 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="booking-field-card booking-field-card-wide" class:booking-field-card-invalid={errors.location}>
|
<div
|
||||||
|
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>
|
||||||
@@ -385,7 +410,10 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="booking-field-card booking-field-card-full" class:booking-field-card-invalid={errors.message}>
|
<div
|
||||||
|
class="booking-field-card booking-field-card-full"
|
||||||
|
class:booking-field-card-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" /> {isGeneralEnquiry ? 'Your Message' : 'About Your Dog'}
|
||||||
{#if isGeneralEnquiry}<span class="booking-required">*</span>{/if}
|
{#if isGeneralEnquiry}<span class="booking-required">*</span>{/if}
|
||||||
@@ -438,26 +466,25 @@
|
|||||||
{ownerStepLabel}
|
{ownerStepLabel}
|
||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<input type="hidden" name="enquiryType" value={enquiryType} />
|
<input type="hidden" name="fullName" value={fullName} />
|
||||||
{#if !isGeneralEnquiry}
|
<input type="hidden" name="email" value={email} />
|
||||||
<input type="hidden" name="petName" value={petName} />
|
<input type="hidden" name="phone" value={phone} />
|
||||||
<input type="hidden" name="location" value={location} />
|
<input type="hidden" name="petName" value={petName} />
|
||||||
{/if}
|
<input type="hidden" name="location" value={location} />
|
||||||
<input type="hidden" name="message" value={message} />
|
<input type="hidden" name="message" value={message} />
|
||||||
{#if !isGeneralEnquiry}
|
|
||||||
{#each selectedServices as service}
|
|
||||||
<input type="hidden" name="services" value={service} />
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="booking-panel">
|
<div class="booking-panel">
|
||||||
{#if ownerSubtitle}
|
{#if ownerIntro}
|
||||||
<div class="booking-panel-banner">{ownerSubtitle}</div>
|
<div class="booking-panel-banner">{ownerIntro}</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class:booking-card-grid-with-banner={Boolean(ownerSubtitle)} class="booking-card-grid booking-card-grid-owner">
|
<div
|
||||||
|
class:booking-card-grid-with-banner={Boolean(ownerIntro)}
|
||||||
|
class="booking-card-grid booking-card-grid-owner"
|
||||||
|
>
|
||||||
<div class="booking-field-card booking-field-card-group booking-field-card-full">
|
<div class="booking-field-card booking-field-card-group booking-field-card-full">
|
||||||
<div class="booking-field-group booking-field-group-owner">
|
<div class="booking-field-group booking-field-group-owner">
|
||||||
<div class="booking-field-stack" class:booking-field-stack-invalid={errors.fullName}>
|
<div class="booking-field-stack" class:booking-field-stack-invalid={errors.fullName}>
|
||||||
@@ -542,7 +569,10 @@
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-outline btn-outline-green"
|
class="btn btn-outline btn-outline-green"
|
||||||
on:click={() => { step = 1; errors = {}; }}
|
on:click={() => {
|
||||||
|
step = 1;
|
||||||
|
errors = {};
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Back
|
Back
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -3,6 +3,37 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|||||||
import BookingSection from './BookingSection.svelte';
|
import BookingSection from './BookingSection.svelte';
|
||||||
import { homepageContent } from '$lib/content/homepage';
|
import { homepageContent } from '$lib/content/homepage';
|
||||||
|
|
||||||
|
async function fillOwnerStep() {
|
||||||
|
await fireEvent.input(screen.getByLabelText(/Full Name/i), {
|
||||||
|
target: { value: 'Alex Walker' }
|
||||||
|
});
|
||||||
|
await fireEvent.input(screen.getByLabelText(/^Email/i), {
|
||||||
|
target: { value: 'alex@example.com' }
|
||||||
|
});
|
||||||
|
await fireEvent.input(screen.getByLabelText(/Contact #/i), {
|
||||||
|
target: { value: '021 123 4567' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fillDogStep() {
|
||||||
|
await fireEvent.click(screen.getByLabelText('Pack Walks'));
|
||||||
|
await fireEvent.click(screen.getByLabelText('Other Services'));
|
||||||
|
await fireEvent.input(screen.getByLabelText(/Dog's Name/i), {
|
||||||
|
target: { value: 'Maya' }
|
||||||
|
});
|
||||||
|
await fireEvent.input(screen.getByLabelText(/Location/i), {
|
||||||
|
target: { value: 'Kingsland' }
|
||||||
|
});
|
||||||
|
await fireEvent.input(screen.getByLabelText(/About Your Dog/i), {
|
||||||
|
target: { value: 'Loves small group walks.' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function moveToOwnerStep(container: HTMLElement) {
|
||||||
|
await fillDogStep();
|
||||||
|
await fireEvent.click(container.querySelector('.booking-next-button')!);
|
||||||
|
}
|
||||||
|
|
||||||
describe('BookingSection', () => {
|
describe('BookingSection', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
Object.defineProperty(document, 'referrer', {
|
Object.defineProperty(document, 'referrer', {
|
||||||
@@ -29,14 +60,7 @@ describe('BookingSection', () => {
|
|||||||
booking: homepageContent.booking
|
booking: homepageContent.booking
|
||||||
});
|
});
|
||||||
|
|
||||||
await fireEvent.input(screen.getByLabelText(/Dog's Name/i), {
|
await moveToOwnerStep(container);
|
||||||
target: { value: 'Maya' }
|
|
||||||
});
|
|
||||||
await fireEvent.input(screen.getByLabelText(/Location/i), {
|
|
||||||
target: { value: 'Kingsland' }
|
|
||||||
});
|
|
||||||
|
|
||||||
await fireEvent.click(container.querySelector('.booking-next-button')!);
|
|
||||||
await fireEvent.click(container.querySelector('.booking-submit-button')!);
|
await fireEvent.click(container.querySelector('.booking-submit-button')!);
|
||||||
|
|
||||||
expect(screen.getByText('Please enter your full name')).toBeInTheDocument();
|
expect(screen.getByText('Please enter your full name')).toBeInTheDocument();
|
||||||
@@ -55,30 +79,8 @@ describe('BookingSection', () => {
|
|||||||
booking: homepageContent.booking
|
booking: homepageContent.booking
|
||||||
});
|
});
|
||||||
|
|
||||||
await fireEvent.click(screen.getByLabelText('Pack Walks'));
|
await moveToOwnerStep(container);
|
||||||
await fireEvent.click(screen.getByLabelText('Other Services'));
|
await fillOwnerStep();
|
||||||
|
|
||||||
await fireEvent.input(screen.getByLabelText(/Dog's Name/i), {
|
|
||||||
target: { value: 'Maya' }
|
|
||||||
});
|
|
||||||
await fireEvent.input(screen.getByLabelText(/Location/i), {
|
|
||||||
target: { value: 'Kingsland' }
|
|
||||||
});
|
|
||||||
await fireEvent.input(screen.getByLabelText(/About Your Dog/i), {
|
|
||||||
target: { value: 'Loves small group walks.' }
|
|
||||||
});
|
|
||||||
|
|
||||||
await fireEvent.click(container.querySelector('.booking-next-button')!);
|
|
||||||
|
|
||||||
await fireEvent.input(screen.getByLabelText(/Full Name/i), {
|
|
||||||
target: { value: 'Alex Walker' }
|
|
||||||
});
|
|
||||||
await fireEvent.input(screen.getByLabelText(/^Email/i), {
|
|
||||||
target: { value: 'alex@example.com' }
|
|
||||||
});
|
|
||||||
await fireEvent.input(screen.getByLabelText(/Contact #/i), {
|
|
||||||
target: { value: '021 123 4567' }
|
|
||||||
});
|
|
||||||
|
|
||||||
await fireEvent.click(container.querySelector('.booking-submit-button')!);
|
await fireEvent.click(container.querySelector('.booking-submit-button')!);
|
||||||
|
|
||||||
@@ -128,15 +130,7 @@ describe('BookingSection', () => {
|
|||||||
allowGeneralEnquiry: true
|
allowGeneralEnquiry: true
|
||||||
});
|
});
|
||||||
|
|
||||||
await fireEvent.input(screen.getByLabelText(/Dog's Name/i), {
|
|
||||||
target: { value: 'Maya' }
|
|
||||||
});
|
|
||||||
await fireEvent.input(screen.getByLabelText(/Location/i), {
|
|
||||||
target: { value: 'Grey Lynn' }
|
|
||||||
});
|
|
||||||
await fireEvent.click(screen.getByLabelText('Pack Walks'));
|
|
||||||
await fireEvent.click(screen.getByLabelText(/General enquiry/i));
|
await fireEvent.click(screen.getByLabelText(/General enquiry/i));
|
||||||
|
|
||||||
expect(screen.queryByLabelText(/Dog's Name/i)).not.toBeInTheDocument();
|
expect(screen.queryByLabelText(/Dog's Name/i)).not.toBeInTheDocument();
|
||||||
expect(screen.queryByText('Pack Walks')).not.toBeInTheDocument();
|
expect(screen.queryByText('Pack Walks')).not.toBeInTheDocument();
|
||||||
|
|
||||||
@@ -148,17 +142,7 @@ describe('BookingSection', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await fireEvent.click(container.querySelector('.booking-next-button')!);
|
await fireEvent.click(container.querySelector('.booking-next-button')!);
|
||||||
|
await fillOwnerStep();
|
||||||
await fireEvent.input(screen.getByLabelText(/Full Name/i), {
|
|
||||||
target: { value: 'Alex Walker' }
|
|
||||||
});
|
|
||||||
await fireEvent.input(screen.getByLabelText(/^Email/i), {
|
|
||||||
target: { value: 'alex@example.com' }
|
|
||||||
});
|
|
||||||
await fireEvent.input(screen.getByLabelText(/Contact #/i), {
|
|
||||||
target: { value: '021 123 4567' }
|
|
||||||
});
|
|
||||||
|
|
||||||
await fireEvent.click(container.querySelector('.booking-submit-button')!);
|
await fireEvent.click(container.querySelector('.booking-submit-button')!);
|
||||||
|
|
||||||
await waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(1));
|
await waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(1));
|
||||||
@@ -192,23 +176,8 @@ describe('BookingSection', () => {
|
|||||||
booking: homepageContent.booking
|
booking: homepageContent.booking
|
||||||
});
|
});
|
||||||
|
|
||||||
await fireEvent.input(screen.getByLabelText(/Dog's Name/i), {
|
await moveToOwnerStep(container);
|
||||||
target: { value: 'Maya' }
|
await fillOwnerStep();
|
||||||
});
|
|
||||||
await fireEvent.input(screen.getByLabelText(/Location/i), {
|
|
||||||
target: { value: 'Kingsland' }
|
|
||||||
});
|
|
||||||
await fireEvent.click(container.querySelector('.booking-next-button')!);
|
|
||||||
|
|
||||||
await fireEvent.input(screen.getByLabelText(/Full Name/i), {
|
|
||||||
target: { value: 'Alex Walker' }
|
|
||||||
});
|
|
||||||
await fireEvent.input(screen.getByLabelText(/^Email/i), {
|
|
||||||
target: { value: 'alex@example.com' }
|
|
||||||
});
|
|
||||||
await fireEvent.input(screen.getByLabelText(/Contact #/i), {
|
|
||||||
target: { value: '021 123 4567' }
|
|
||||||
});
|
|
||||||
|
|
||||||
await fireEvent.click(container.querySelector('.booking-submit-button')!);
|
await fireEvent.click(container.querySelector('.booking-submit-button')!);
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,13 @@
|
|||||||
rel="noopener"
|
rel="noopener"
|
||||||
class="footer-reviews"
|
class="footer-reviews"
|
||||||
>
|
>
|
||||||
<Icon name="fab fa-google" />
|
<img
|
||||||
|
class="footer-google-logo"
|
||||||
|
src="/images/google-g-logo.svg"
|
||||||
|
alt=""
|
||||||
|
width="16"
|
||||||
|
height="17"
|
||||||
|
/>
|
||||||
<span>30+ five-star Google reviews</span>
|
<span>30+ five-star Google reviews</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import Icon from '$lib/components/Icon.svelte';
|
||||||
import { getImageMetadata } from '$lib/image-metadata';
|
import { getImageMetadata } from '$lib/image-metadata';
|
||||||
import type { HeroContent } from '$lib/types';
|
import type { CallToAction, HeroContent } from '$lib/types';
|
||||||
|
|
||||||
export let hero: HeroContent;
|
export let hero: HeroContent;
|
||||||
|
export let reviewCta: CallToAction | undefined = undefined;
|
||||||
|
|
||||||
$: titleParts = splitTitle(hero.title);
|
$: titleParts = splitTitle(hero.title);
|
||||||
$: mobileTitle = hero.mobileTitle?.trim() || `${hero.title} ${hero.highlight}`.trim();
|
$: mobileTitle = hero.mobileTitle?.trim() || `${hero.title} ${hero.highlight}`.trim();
|
||||||
@@ -44,6 +46,30 @@
|
|||||||
<p class="hero-subtitle">{hero.subtitle}</p>
|
<p class="hero-subtitle">{hero.subtitle}</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if reviewCta}
|
||||||
|
<a
|
||||||
|
class="hero-trust-chip"
|
||||||
|
href={reviewCta.href}
|
||||||
|
target={reviewCta.external ? '_blank' : undefined}
|
||||||
|
rel={reviewCta.external ? 'noopener' : undefined}
|
||||||
|
aria-label="Read our five-star Google reviews"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
class="hero-trust-logo"
|
||||||
|
src="/images/google-g-logo.svg"
|
||||||
|
alt=""
|
||||||
|
width="18"
|
||||||
|
height="19"
|
||||||
|
/>
|
||||||
|
<span class="hero-trust-stars" aria-hidden="true">
|
||||||
|
{#each Array(5) as _}
|
||||||
|
<Icon name="fas fa-star" />
|
||||||
|
{/each}
|
||||||
|
</span>
|
||||||
|
<span>{reviewCta.label}</span>
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="hero-buttons">
|
<div class="hero-buttons">
|
||||||
<a href={hero.primaryCta.href} class="btn btn-yellow">{hero.primaryCta.label}</a>
|
<a href={hero.primaryCta.href} class="btn btn-yellow">{hero.primaryCta.label}</a>
|
||||||
<a href={hero.secondaryCta.href} class="btn btn-outline">{hero.secondaryCta.label}</a>
|
<a href={hero.secondaryCta.href} class="btn btn-outline">{hero.secondaryCta.label}</a>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount, tick } from 'svelte';
|
||||||
import { afterNavigate } from '$app/navigation';
|
import { afterNavigate } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import Icon from '$lib/components/Icon.svelte';
|
import Icon from '$lib/components/Icon.svelte';
|
||||||
@@ -23,32 +23,78 @@
|
|||||||
$: pathname = $page.url.pathname;
|
$: pathname = $page.url.pathname;
|
||||||
$: hidden = pathname === '/contact-us' || pathname === '/booking';
|
$: hidden = pathname === '/contact-us' || pathname === '/booking';
|
||||||
|
|
||||||
const SHOW_AFTER_PX = 480; // close to one mobile viewport
|
|
||||||
const HIDE_BELOW_PX = 120; // generous so the bar doesn't flicker near the top
|
|
||||||
|
|
||||||
let visible = false;
|
let visible = false;
|
||||||
|
let triggerPassed = false;
|
||||||
|
let bookingInView = false;
|
||||||
|
let triggerObserver: IntersectionObserver | null = null;
|
||||||
|
let bookingObserver: IntersectionObserver | null = null;
|
||||||
|
|
||||||
function evaluateVisibility() {
|
function refreshVisibility() {
|
||||||
const y = window.scrollY;
|
visible = !hidden && triggerPassed && !bookingInView;
|
||||||
if (y > SHOW_AFTER_PX) {
|
}
|
||||||
visible = true;
|
|
||||||
} else if (y < HIDE_BELOW_PX) {
|
function cleanupObservers() {
|
||||||
visible = false;
|
triggerObserver?.disconnect();
|
||||||
|
bookingObserver?.disconnect();
|
||||||
|
triggerObserver = null;
|
||||||
|
bookingObserver = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setupObservers() {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
cleanupObservers();
|
||||||
|
|
||||||
|
const triggerEl =
|
||||||
|
document.getElementById('hero') ?? document.querySelector('main section, section');
|
||||||
|
const bookingEl = document.getElementById('newlead');
|
||||||
|
|
||||||
|
triggerPassed = !triggerEl;
|
||||||
|
bookingInView = false;
|
||||||
|
|
||||||
|
if (triggerEl) {
|
||||||
|
triggerObserver = new IntersectionObserver(
|
||||||
|
([entry]) => {
|
||||||
|
triggerPassed = !entry.isIntersecting && entry.boundingClientRect.top < 0;
|
||||||
|
refreshVisibility();
|
||||||
|
},
|
||||||
|
{ threshold: 0.2 }
|
||||||
|
);
|
||||||
|
|
||||||
|
triggerObserver.observe(triggerEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bookingEl) {
|
||||||
|
bookingObserver = new IntersectionObserver(
|
||||||
|
([entry]) => {
|
||||||
|
bookingInView = entry.isIntersecting;
|
||||||
|
refreshVisibility();
|
||||||
|
},
|
||||||
|
{ threshold: 0.2 }
|
||||||
|
);
|
||||||
|
|
||||||
|
bookingObserver.observe(bookingEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
afterNavigate(() => {
|
afterNavigate(() => {
|
||||||
visible = false;
|
visible = false;
|
||||||
|
triggerPassed = false;
|
||||||
|
bookingInView = false;
|
||||||
|
void setupObservers();
|
||||||
});
|
});
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
evaluateVisibility();
|
void setupObservers();
|
||||||
window.addEventListener('scroll', evaluateVisibility, { passive: true });
|
|
||||||
window.addEventListener('resize', evaluateVisibility, { passive: true });
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener('scroll', evaluateVisibility);
|
cleanupObservers();
|
||||||
window.removeEventListener('resize', evaluateVisibility);
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -104,18 +104,25 @@
|
|||||||
rel="noopener"
|
rel="noopener"
|
||||||
aria-label="Read our 5-star Google reviews"
|
aria-label="Read our 5-star Google reviews"
|
||||||
>
|
>
|
||||||
|
<img
|
||||||
|
class="pricing-trust-logo"
|
||||||
|
src="/images/google-g-logo.svg"
|
||||||
|
alt=""
|
||||||
|
width="18"
|
||||||
|
height="19"
|
||||||
|
/>
|
||||||
<span class="pricing-trust-stars" aria-hidden="true">
|
<span class="pricing-trust-stars" aria-hidden="true">
|
||||||
{#each Array(5) as _}
|
{#each Array(5) as _}
|
||||||
<Icon name="fas fa-star" />
|
<Icon name="fas fa-star" />
|
||||||
{/each}
|
{/each}
|
||||||
</span>
|
</span>
|
||||||
<span class="pricing-trust-label">30+ five-star Google reviews</span>
|
<span class="pricing-trust-label">30+ 5-star Google reviews, trusted by Auckland dog owners</span>
|
||||||
<Icon name="fas fa-arrow-right" className="pricing-trust-arrow" />
|
<Icon name="fas fa-arrow-right" className="pricing-trust-arrow" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{#each pageContent.sections as section}
|
{#each pageContent.sections as section, index}
|
||||||
<section use:reveal class="pricing-section reveal-block">
|
<section use:reveal class="pricing-section reveal-block">
|
||||||
<div class="pricing-inner">
|
<div class="pricing-inner">
|
||||||
<div class="pricing-section-heading">
|
<div class="pricing-section-heading">
|
||||||
@@ -133,7 +140,8 @@
|
|||||||
class={`btn pricing-section-link ${section.detailCta.variant === 'yellow' ? 'btn-yellow' : section.detailCta.variant === 'outline' ? 'btn-outline' : 'btn-green'}`}
|
class={`btn pricing-section-link ${section.detailCta.variant === 'yellow' ? 'btn-yellow' : section.detailCta.variant === 'outline' ? 'btn-outline' : 'btn-green'}`}
|
||||||
href={section.detailCta.href}
|
href={section.detailCta.href}
|
||||||
>
|
>
|
||||||
{section.detailCta.label}
|
<span>{section.detailCta.label}</span>
|
||||||
|
<Icon name="fas fa-arrow-right" />
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@@ -159,6 +167,25 @@
|
|||||||
</article>
|
</article>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<a class="btn btn-yellow pricing-section-mobile-cta" href="#newlead">
|
||||||
|
Book a Meet & Greet
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{#if index === 0}
|
||||||
|
<aside class="pricing-mobile-consult" aria-label="Need help choosing the right option?">
|
||||||
|
<span class="pricing-mobile-consult-kicker">
|
||||||
|
<Icon name="fas fa-comment-dots" />
|
||||||
|
Not sure which option fits?
|
||||||
|
</span>
|
||||||
|
<p>
|
||||||
|
Book a free Meet & Greet and we’ll help you choose the right walk or visit for your dog.
|
||||||
|
</p>
|
||||||
|
<a class="btn btn-outline btn-outline-green pricing-mobile-consult-cta" href="#newlead">
|
||||||
|
Talk it through with us
|
||||||
|
</a>
|
||||||
|
</aside>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -238,6 +265,12 @@
|
|||||||
transform 0.18s cubic-bezier(0.22, 1, 0.36, 1);
|
transform 0.18s cubic-bezier(0.22, 1, 0.36, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pricing-trust-logo {
|
||||||
|
width: 18px;
|
||||||
|
height: 19px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
.pricing-trust:hover {
|
.pricing-trust:hover {
|
||||||
background: rgba(255, 255, 255, 0.18);
|
background: rgba(255, 255, 255, 0.18);
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
@@ -307,6 +340,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pricing-section-link {
|
.pricing-section-link {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
margin-top: 18px;
|
margin-top: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,6 +431,11 @@
|
|||||||
font-family: var(--font-head);
|
font-family: var(--font-head);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pricing-section-mobile-cta,
|
||||||
|
.pricing-mobile-consult {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.meet-greet-prompt {
|
.meet-greet-prompt {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
right: 24px;
|
right: 24px;
|
||||||
@@ -559,6 +600,12 @@
|
|||||||
font-size: 34px;
|
font-size: 34px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pricing-trust {
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
.pricing-section-heading h2 {
|
.pricing-section-heading h2 {
|
||||||
font-size: 26px;
|
font-size: 26px;
|
||||||
}
|
}
|
||||||
@@ -568,7 +615,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.pricing-section-heading {
|
.pricing-section-heading {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 26px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pricing-section-blurb {
|
.pricing-section-blurb {
|
||||||
@@ -576,6 +623,11 @@
|
|||||||
line-height: 1.55;
|
line-height: 1.55;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pricing-section-link {
|
||||||
|
margin-top: 22px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.pricing-plan-grid,
|
.pricing-plan-grid,
|
||||||
.pricing-plan-grid-three {
|
.pricing-plan-grid-three {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
@@ -594,6 +646,51 @@
|
|||||||
font-size: 46px;
|
font-size: 46px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pricing-plan-cta {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-section-mobile-cta {
|
||||||
|
display: flex;
|
||||||
|
width: fit-content;
|
||||||
|
margin: 18px auto 0;
|
||||||
|
font-family: var(--font-head);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-mobile-consult {
|
||||||
|
display: block;
|
||||||
|
margin-top: 18px;
|
||||||
|
padding: 22px 20px;
|
||||||
|
border-radius: 24px;
|
||||||
|
background: linear-gradient(180deg, #fffaf0 0%, #f9f4e7 100%);
|
||||||
|
box-shadow: 0 12px 28px rgba(17, 20, 24, 0.05);
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-mobile-consult-kicker {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: var(--green);
|
||||||
|
font-family: var(--font-head);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-mobile-consult p {
|
||||||
|
margin: 0;
|
||||||
|
color: #34363a;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pricing-mobile-consult-cta {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.meet-greet-prompt {
|
.meet-greet-prompt {
|
||||||
right: 16px;
|
right: 16px;
|
||||||
left: 16px;
|
left: 16px;
|
||||||
|
|||||||
@@ -125,6 +125,8 @@
|
|||||||
Every booking starts with a free, no-obligation Meet & Greet.
|
Every booking starts with a free, no-obligation Meet & Greet.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<a class="btn btn-yellow service-plan-mobile-cta" href="#newlead">Book a Meet & Greet</a>
|
||||||
|
|
||||||
{#if pageContent.pricing.extras?.length}
|
{#if pageContent.pricing.extras?.length}
|
||||||
<div class="service-extras">
|
<div class="service-extras">
|
||||||
<div class="service-extras-heading">Extras</div>
|
<div class="service-extras-heading">Extras</div>
|
||||||
@@ -566,6 +568,10 @@
|
|||||||
font-family: var(--font-head);
|
font-family: var(--font-head);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.service-plan-mobile-cta {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.service-plan-reassurance,
|
.service-plan-reassurance,
|
||||||
.service-plan-scarcity {
|
.service-plan-scarcity {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -731,5 +737,16 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.service-plan-cta {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.service-plan-mobile-cta {
|
||||||
|
display: flex;
|
||||||
|
width: fit-content;
|
||||||
|
margin: 18px auto 0;
|
||||||
|
font-family: var(--font-head);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -27,7 +27,8 @@
|
|||||||
|
|
||||||
{#if service.href}
|
{#if service.href}
|
||||||
<a href={service.href} class="btn btn-green">
|
<a href={service.href} class="btn btn-green">
|
||||||
See {service.title} pricing →
|
<span>See {service.title} pricing</span>
|
||||||
|
<Icon name="fas fa-arrow-right" />
|
||||||
</a>
|
</a>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -127,10 +127,6 @@
|
|||||||
<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>
|
||||||
<a href={instagramHref} target="_blank" rel="noopener" class="testimonials-instagram-link">
|
|
||||||
<Icon name="fab fa-instagram" />
|
|
||||||
<span>{instagramLabel}</span>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if slides.length}
|
{#if slides.length}
|
||||||
@@ -190,13 +186,39 @@
|
|||||||
|
|
||||||
<div class="testimonial-divider"></div>
|
<div class="testimonial-divider"></div>
|
||||||
|
|
||||||
|
<div class="testimonial-mobile-controls" aria-label="Testimonial navigation">
|
||||||
|
<button
|
||||||
|
class="testimonial-arrow testimonial-arrow-inline"
|
||||||
|
type="button"
|
||||||
|
aria-label="Previous testimonial"
|
||||||
|
on:click={showPrevious}
|
||||||
|
>
|
||||||
|
<Icon name="fas fa-chevron-left" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="testimonial-arrow testimonial-arrow-inline"
|
||||||
|
type="button"
|
||||||
|
aria-label="Next testimonial"
|
||||||
|
on:click={showNext}
|
||||||
|
>
|
||||||
|
<Icon name="fas fa-chevron-right" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<a
|
<a
|
||||||
class="testimonial-google"
|
class="testimonial-google"
|
||||||
href="https://g.page/r/CUsvrWPhkYrAEB0/"
|
href="https://g.page/r/CUsvrWPhkYrAEB0/"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
>
|
>
|
||||||
<Icon name="fab fa-google" />
|
<img
|
||||||
|
class="testimonial-google-logo"
|
||||||
|
src="/images/google-g-logo.svg"
|
||||||
|
alt=""
|
||||||
|
width="18"
|
||||||
|
height="19"
|
||||||
|
/>
|
||||||
<span>30+ five-star Google reviews</span>
|
<span>30+ five-star Google reviews</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -214,6 +236,11 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<a href={instagramHref} target="_blank" rel="noopener" class="testimonials-instagram-link">
|
||||||
|
<Icon name="fab fa-instagram" />
|
||||||
|
<span>{instagramLabel}</span>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -232,10 +259,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.testimonials-instagram-link {
|
.testimonials-instagram-link {
|
||||||
display: inline-flex;
|
display: flex;
|
||||||
|
width: fit-content;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
margin-top: 18px;
|
margin: 18px auto 0;
|
||||||
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);
|
||||||
@@ -283,7 +311,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.testimonials-instagram-link {
|
.testimonials-instagram-link {
|
||||||
margin-top: 14px;
|
margin: 14px auto 0;
|
||||||
padding: 9px 14px;
|
padding: 9px 14px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
@@ -443,14 +471,20 @@
|
|||||||
box-shadow: 0 0 0 1px rgba(10, 48, 78, 0.06);
|
box-shadow: 0 0 0 1px rgba(10, 48, 78, 0.06);
|
||||||
}
|
}
|
||||||
|
|
||||||
.testimonial-google :global(.icon) {
|
.testimonial-google-logo {
|
||||||
font-size: 20px;
|
width: 18px;
|
||||||
|
height: 19px;
|
||||||
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.testimonial-google:hover {
|
.testimonial-google:hover {
|
||||||
background: #efe6d5;
|
background: #efe6d5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.testimonial-mobile-controls {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.testimonial-woof {
|
.testimonial-woof {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 40px;
|
top: 40px;
|
||||||
@@ -559,7 +593,7 @@
|
|||||||
|
|
||||||
.testimonial-stage {
|
.testimonial-stage {
|
||||||
min-height: unset;
|
min-height: unset;
|
||||||
padding-bottom: 116px;
|
padding-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.testimonial-slide {
|
.testimonial-slide {
|
||||||
@@ -605,8 +639,24 @@
|
|||||||
margin-top: 28px;
|
margin-top: 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.testimonial-mobile-controls {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.testimonial-arrow-inline {
|
||||||
|
position: static;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
font-size: 18px;
|
||||||
|
transform: none;
|
||||||
|
box-shadow: 0 10px 22px rgba(20, 24, 20, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
.testimonial-google {
|
.testimonial-google {
|
||||||
margin-top: 28px;
|
margin-top: 20px;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding: 10px 14px;
|
padding: 10px 14px;
|
||||||
@@ -649,21 +699,9 @@
|
|||||||
height: 8px;
|
height: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.testimonial-arrow {
|
.testimonial-arrow-left,
|
||||||
top: auto;
|
|
||||||
bottom: 24px;
|
|
||||||
width: 54px;
|
|
||||||
height: 54px;
|
|
||||||
font-size: 20px;
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.testimonial-arrow-left {
|
|
||||||
left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.testimonial-arrow-right {
|
.testimonial-arrow-right {
|
||||||
right: 20px;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ export const homepageContent: HomePageContent = {
|
|||||||
highlight: "Your Dog's Day!",
|
highlight: "Your Dog's Day!",
|
||||||
mobileTitle: "Unleashing Fun in\nYour Dog's Day!",
|
mobileTitle: "Unleashing Fun in\nYour Dog's Day!",
|
||||||
subtitle: 'Trusted, on-time dog walking across Auckland Central — pack walks, 1:1 walks, and puppy visits.',
|
subtitle: 'Trusted, on-time dog walking across Auckland Central — pack walks, 1:1 walks, and puppy visits.',
|
||||||
primaryCta: { label: 'Explore our services →', href: '#services', variant: 'yellow' },
|
primaryCta: { label: 'Book a Meet & Greet', href: '#newlead', variant: 'yellow' },
|
||||||
secondaryCta: { label: 'Book a Meet & Greet', href: '#newlead', variant: 'outline' },
|
secondaryCta: { label: 'Explore our services →', href: '#services', variant: 'outline' },
|
||||||
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'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ describe('content server helpers', () => {
|
|||||||
services: homepageContent.services,
|
services: homepageContent.services,
|
||||||
testimonials: homepageContent.testimonials,
|
testimonials: homepageContent.testimonials,
|
||||||
booking: homepageContent.booking,
|
booking: homepageContent.booking,
|
||||||
|
info: homepageContent.info,
|
||||||
footer: homepageContent.footer
|
footer: homepageContent.footer
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -89,6 +90,7 @@ describe('content server helpers', () => {
|
|||||||
expect(result.services).not.toBe(homepageContent.services);
|
expect(result.services).not.toBe(homepageContent.services);
|
||||||
expect(result.testimonials).not.toBe(homepageContent.testimonials);
|
expect(result.testimonials).not.toBe(homepageContent.testimonials);
|
||||||
expect(result.booking).not.toBe(homepageContent.booking);
|
expect(result.booking).not.toBe(homepageContent.booking);
|
||||||
|
expect(result.info).not.toBe(homepageContent.info);
|
||||||
expect(result.footer).not.toBe(homepageContent.footer);
|
expect(result.footer).not.toBe(homepageContent.footer);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export async function getSharedPageContent(): Promise<SiteSharedContent> {
|
|||||||
services: content.services,
|
services: content.services,
|
||||||
testimonials: content.testimonials,
|
testimonials: content.testimonials,
|
||||||
booking: content.booking,
|
booking: content.booking,
|
||||||
|
info: content.info,
|
||||||
footer: content.footer
|
footer: content.footer
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -367,12 +367,21 @@
|
|||||||
|
|
||||||
.booking-actions-next {
|
.booking-actions-next {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.booking-actions-final {
|
.booking-actions-final {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.booking-next-note {
|
||||||
|
margin: 10px 0 0;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.45;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
.booking-next-button,
|
.booking-next-button,
|
||||||
.booking-submit-button {
|
.booking-submit-button {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|||||||
@@ -195,7 +195,7 @@
|
|||||||
|
|
||||||
.hero-inner {
|
.hero-inner {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 24px;
|
gap: 18px;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -222,6 +222,12 @@
|
|||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hero-trust-chip {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
.hero-heading-desktop {
|
.hero-heading-desktop {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@@ -239,37 +245,38 @@
|
|||||||
|
|
||||||
.hero-buttons {
|
.hero-buttons {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
justify-content: flex-start;
|
justify-content: space-between;
|
||||||
gap: 10px;
|
gap: 8px;
|
||||||
padding-right: 18px;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-buttons .btn {
|
.hero-buttons .btn {
|
||||||
flex: 0 0 auto;
|
flex: 1 1 0;
|
||||||
width: auto;
|
width: 0;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
padding: 17px 28px;
|
padding: 15px 12px;
|
||||||
font-size: 15px;
|
font-size: 13.5px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
|
line-height: 1.25;
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
touch-action: manipulation;
|
touch-action: manipulation;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-buttons .btn:last-child {
|
.hero-buttons .btn:last-child {
|
||||||
margin-right: 12px;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-buttons .btn-yellow {
|
.hero-buttons .btn-yellow {
|
||||||
background: #e8dbc1;
|
background: var(--yellow);
|
||||||
color: #000;
|
color: #000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-buttons .btn-outline {
|
.hero-buttons .btn-outline {
|
||||||
background: var(--yellow);
|
background: transparent;
|
||||||
color: #000;
|
color: #fff;
|
||||||
border: none;
|
border: 2px solid rgba(255, 255, 255, 0.84);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-buttons .btn:active {
|
.hero-buttons .btn:active {
|
||||||
@@ -277,11 +284,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.hero-buttons .btn-yellow:active {
|
.hero-buttons .btn-yellow:active {
|
||||||
background: #dccdb1;
|
background: #e6bb00;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-buttons .btn-outline:active {
|
.hero-buttons .btn-outline:active {
|
||||||
background: #e6bb00;
|
background: rgba(255, 255, 255, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-img {
|
.hero-img {
|
||||||
@@ -295,7 +302,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.hero-img img {
|
.hero-img img {
|
||||||
width: min(100%, 500px);
|
width: min(100%, 460px);
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
margin: 0 auto -7px;
|
margin: 0 auto -7px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
|
|||||||
@@ -33,6 +33,49 @@ section {
|
|||||||
line-height: 1.55;
|
line-height: 1.55;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hero-trust-chip {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 22px;
|
||||||
|
padding: 10px 16px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-decoration: none;
|
||||||
|
transition:
|
||||||
|
background 0.2s ease,
|
||||||
|
transform 0.16s cubic-bezier(0.22, 1, 0.36, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-trust-logo {
|
||||||
|
width: 18px;
|
||||||
|
height: 19px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-trust-stars {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
color: var(--yellow);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (hover: hover) {
|
||||||
|
.hero-trust-chip:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.16);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-trust-chip:active {
|
||||||
|
transform: translateY(1px) scale(0.99);
|
||||||
|
}
|
||||||
|
|
||||||
.hero-buttons {
|
.hero-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
@@ -585,6 +628,12 @@ footer {
|
|||||||
transition: background 0.2s, opacity 0.2s;
|
transition: background 0.2s, opacity 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.footer-google-logo {
|
||||||
|
width: 16px;
|
||||||
|
height: 17px;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
.footer-reviews:hover {
|
.footer-reviews:hover {
|
||||||
background: rgba(255, 255, 255, 0.13);
|
background: rgba(255, 255, 255, 0.13);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|||||||
@@ -232,5 +232,6 @@ export interface SiteSharedContent {
|
|||||||
services: IconCard[];
|
services: IconCard[];
|
||||||
testimonials: TestimonialContent[];
|
testimonials: TestimonialContent[];
|
||||||
booking: BookingContent;
|
booking: BookingContent;
|
||||||
|
info: InfoContent;
|
||||||
footer: FooterContent;
|
footer: FooterContent;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
import HeroSection from '$lib/components/HeroSection.svelte';
|
import HeroSection from '$lib/components/HeroSection.svelte';
|
||||||
import InfoSection from '$lib/components/InfoSection.svelte';
|
import InfoSection from '$lib/components/InfoSection.svelte';
|
||||||
import InstagramSection from '$lib/components/InstagramSection.svelte';
|
import InstagramSection from '$lib/components/InstagramSection.svelte';
|
||||||
import IntroStrip from '$lib/components/IntroStrip.svelte';
|
|
||||||
import BookingSection from '$lib/components/BookingSection.svelte';
|
import BookingSection from '$lib/components/BookingSection.svelte';
|
||||||
import PromiseSection from '$lib/components/PromiseSection.svelte';
|
import PromiseSection from '$lib/components/PromiseSection.svelte';
|
||||||
import ServicesSection from '$lib/components/ServicesSection.svelte';
|
import ServicesSection from '$lib/components/ServicesSection.svelte';
|
||||||
@@ -137,12 +136,11 @@
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<Header navigation={data.content.navigation} />
|
<Header navigation={data.content.navigation} />
|
||||||
<HeroSection hero={data.content.hero} />
|
<HeroSection hero={data.content.hero} reviewCta={data.content.intro.reviewCta} />
|
||||||
<IntroStrip intro={data.content.intro} />
|
|
||||||
<PromiseSection promise={data.content.promise} />
|
<PromiseSection promise={data.content.promise} />
|
||||||
<ServicesSection services={data.content.services} />
|
<ServicesSection services={data.content.services} />
|
||||||
<ValuesSection values={data.content.values} />
|
|
||||||
<TestimonialsSection testimonials={data.content.testimonials} />
|
<TestimonialsSection testimonials={data.content.testimonials} />
|
||||||
|
<ValuesSection values={data.content.values} />
|
||||||
<BookingSection booking={data.content.booking} />
|
<BookingSection booking={data.content.booking} />
|
||||||
<InfoSection info={data.content.info} />
|
<InfoSection info={data.content.info} />
|
||||||
<InstagramSection instagram={data.content.instagram} />
|
<InstagramSection instagram={data.content.instagram} />
|
||||||
|
|||||||
@@ -218,7 +218,11 @@
|
|||||||
{:else if data.slug === 'privacy-policy'}
|
{:else if data.slug === 'privacy-policy'}
|
||||||
<LegalPage pageContent={privacyPolicyContent} />
|
<LegalPage pageContent={privacyPolicyContent} />
|
||||||
{:else if data.slug === 'contact-us'}
|
{:else if data.slug === 'contact-us'}
|
||||||
<BookingPage booking={data.content.booking} allowGeneralEnquiry={data.generalEnquiryEnabled} />
|
<BookingPage
|
||||||
|
booking={data.content.booking}
|
||||||
|
info={data.content.info}
|
||||||
|
allowGeneralEnquiry={data.generalEnquiryEnabled}
|
||||||
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<main class="static-page">
|
<main class="static-page">
|
||||||
<section class="static-page-hero">
|
<section class="static-page-hero">
|
||||||
|
|||||||
@@ -60,4 +60,13 @@ describe('static slug route page', () => {
|
|||||||
|
|
||||||
expect(screen.queryByLabelText(/General enquiry/i)).not.toBeInTheDocument();
|
expect(screen.queryByLabelText(/General enquiry/i)).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders the shared FAQ section on the contact page', () => {
|
||||||
|
render(SlugPage, {
|
||||||
|
data: createStaticRouteData('contact-us')
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText('FAQs')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('Can any dog use your service?')).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export const sharedPageContent = {
|
|||||||
services: homepageContent.services,
|
services: homepageContent.services,
|
||||||
testimonials: homepageContent.testimonials,
|
testimonials: homepageContent.testimonials,
|
||||||
booking: homepageContent.booking,
|
booking: homepageContent.booking,
|
||||||
|
info: homepageContent.info,
|
||||||
footer: homepageContent.footer
|
footer: homepageContent.footer
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user