This commit is contained in:
2026-05-18 09:43:29 +12:00
parent b950229003
commit 6ff970015f
189 changed files with 18603 additions and 2727 deletions
+369 -54
View File
@@ -6,6 +6,7 @@
import PricingPlanCard from '$lib/components/PricingPlanCard.svelte';
import ServiceHero from '$lib/components/ServiceHero.svelte';
import TestimonialsSection from '$lib/components/TestimonialsSection.svelte';
import FaqSection from '$lib/components/FaqSection.svelte';
import { getEnhancedImage } from '$lib/enhanced-images';
import { decoratePlans } from '$lib/utils/pricing';
import type { ServicePageContent, SiteSharedContent } from '$lib/types';
@@ -25,11 +26,17 @@
})) ?? [];
$: relatedServices = content.services.filter((s) => s.href && s.href !== currentPath);
$: pricingPlans = decoratePlans(pageContent.pricing.plans);
$: introLeadParagraph = pageContent.hero.paragraphs?.[0] ?? '';
$: introBodyParagraphs = pageContent.hero.paragraphs?.slice(1) ?? [];
$: benefitCards = pageContent.benefits.items.map((benefit, index) => ({
...benefit,
tintClass: `service-benefit-tint-${(index % 3) + 1}`,
featured: index === 0
}));
$: useSimpleBenefitSwipe =
currentPath === '/pack-walks' ||
currentPath === '/dog-walking' ||
currentPath === '/puppy-visits';
$: showRelatedServices = relatedServices.length > 0 && currentPath !== '/pack-walks';
$: relatedCards = [
@@ -217,7 +224,7 @@
</div>
<div class="service-benefit-shell">
<div bind:this={benefitScroller} class="service-benefit-grid">
<div bind:this={benefitScroller} class:service-benefit-grid-simple-swipe={useSimpleBenefitSwipe} class="service-benefit-grid">
{#each benefitCards as benefit, index}
<article
class:active={index === activeBenefitIndex}
@@ -236,43 +243,81 @@
{/each}
</div>
<div class="service-benefit-mobile-controls" aria-label="Benefit cards navigation">
<button
type="button"
class="service-benefit-mobile-button"
aria-label="Previous benefit"
disabled={activeBenefitIndex === 0}
on:click={() => scrollBenefits(-1)}
>
<Icon name="fas fa-chevron-left" />
</button>
<div class="service-benefit-mobile-pager" aria-label="Current benefit">
{#each benefitCards as _, index}
<button
type="button"
class:active={index === activeBenefitIndex}
class="service-benefit-mobile-dot"
aria-label={`Go to benefit ${index + 1}`}
aria-pressed={index === activeBenefitIndex}
on:click={() => scrollBenefitTo(index)}
></button>
{/each}
{#if !useSimpleBenefitSwipe}
<div class="service-benefit-mobile-controls" aria-label="Benefit cards navigation">
<button
type="button"
class="service-benefit-mobile-button"
aria-label="Previous benefit"
disabled={activeBenefitIndex === 0}
on:click={() => scrollBenefits(-1)}
>
<Icon name="fas fa-chevron-left" />
</button>
<div class="service-benefit-mobile-pager" aria-label="Current benefit">
{#each benefitCards as _, index}
<button
type="button"
class:active={index === activeBenefitIndex}
class="service-benefit-mobile-dot"
aria-label={`Go to benefit ${index + 1}`}
aria-pressed={index === activeBenefitIndex}
on:click={() => scrollBenefitTo(index)}
></button>
{/each}
</div>
<button
type="button"
class="service-benefit-mobile-button"
aria-label="Next benefit"
disabled={activeBenefitIndex === benefitCards.length - 1}
on:click={() => scrollBenefits(1)}
>
<Icon name="fas fa-chevron-right" />
</button>
</div>
<button
type="button"
class="service-benefit-mobile-button"
aria-label="Next benefit"
disabled={activeBenefitIndex === benefitCards.length - 1}
on:click={() => scrollBenefits(1)}
>
<Icon name="fas fa-chevron-right" />
</button>
</div>
{/if}
</div>
</div>
</section>
<section use:reveal class="service-pricing reveal-block">
{#if pageContent.hero.paragraphs?.length}
<section use:reveal class="service-intro reveal-block">
<div class="page-inner">
<article class="service-intro-card">
<div class="service-intro-header">
<div class="service-intro-badge">
<div class="service-intro-mark" aria-hidden="true">
<Icon name="fas fa-paw" />
</div>
<p class="service-intro-eyebrow">
{pageContent.hero.introEyebrow ?? 'The detail'}
</p>
</div>
<h2 class="service-intro-heading">
{pageContent.hero.introHeading ?? `More about ${pageContent.hero.eyebrow}`}
</h2>
</div>
<div class="service-intro-body">
<p class="service-intro-lead">{introLeadParagraph}</p>
{#if introBodyParagraphs.length}
<div class="service-intro-prose">
{#each introBodyParagraphs as paragraph}
<p>{paragraph}</p>
{/each}
</div>
{/if}
</div>
</article>
</div>
</section>
{/if}
<section
use:reveal
class:service-pricing-immediate-mobile={useSimpleBenefitSwipe}
class="service-pricing reveal-block"
>
<div class="page-inner">
<div class="service-section-heading">
<h2>{pageContent.pricing.title}</h2>
@@ -320,6 +365,20 @@
</div>
</section>
{#if pageContent.faq?.items?.length}
<section use:reveal class="service-faq reveal-block">
<div class="page-inner">
<div class="service-faq-card">
<FaqSection
title={pageContent.faq.title ?? 'Frequently asked questions'}
intro={pageContent.faq.intro}
faqs={pageContent.faq.items}
/>
</div>
</div>
</section>
{/if}
{#if showRelatedServices}
<section use:reveal class="service-related reveal-block" aria-label="Other services">
<div class="page-inner">
@@ -366,6 +425,216 @@
background: var(--off-white);
}
.service-intro {
padding: 18px 0 60px;
}
.service-intro-card {
display: grid;
grid-template-columns: minmax(240px, 0.9fr) minmax(0, 1.35fr);
gap: 44px;
max-width: 980px;
margin: 0 auto;
padding: 38px 40px 40px;
border-radius: 30px;
background:
radial-gradient(circle at top right, rgba(255, 209, 0, 0.14), transparent 30%),
linear-gradient(180deg, rgba(252, 250, 243, 0.98) 0%, rgba(244, 239, 228, 0.98) 100%);
box-shadow:
inset 0 0 0 1px rgba(33, 48, 33, 0.08),
0 24px 52px rgba(17, 20, 24, 0.07);
position: relative;
overflow: hidden;
}
.service-intro-card::before {
content: '';
position: absolute;
inset: 16px;
border: 1px solid rgba(255, 255, 255, 0.45);
border-radius: 24px;
pointer-events: none;
}
.service-intro-card::after {
content: '';
position: absolute;
inset: 0 auto auto 0;
width: 160px;
height: 160px;
border-radius: 50%;
background: radial-gradient(circle, rgba(33, 48, 33, 0.08) 0%, transparent 72%);
transform: translate(-28%, -36%);
pointer-events: none;
}
.service-intro-header,
.service-intro-body {
position: relative;
z-index: 1;
}
.service-intro-header {
display: flex;
flex-direction: column;
gap: 18px;
justify-content: space-between;
padding-right: 12px;
}
.service-intro-badge {
display: inline-flex;
align-items: center;
gap: 12px;
width: fit-content;
padding: 10px 16px 10px 10px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.62);
box-shadow:
inset 0 0 0 1px rgba(33, 48, 33, 0.07),
0 12px 24px rgba(17, 20, 24, 0.04);
backdrop-filter: blur(10px);
}
.service-intro-mark {
display: inline-flex;
align-items: center;
justify-content: center;
width: 42px;
height: 42px;
border-radius: 14px;
background: linear-gradient(180deg, var(--gw-green) 0%, var(--green-mid) 100%);
color: var(--yellow);
font-size: 17px;
box-shadow:
inset 0 0 0 1px rgba(255, 255, 255, 0.06),
0 10px 22px rgba(33, 48, 33, 0.18);
}
.service-intro-eyebrow {
margin: 0;
color: var(--gw-green);
font-family: var(--font-head);
font-size: 12px;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
opacity: 0.84;
}
.service-intro-heading {
margin: 0;
color: var(--gw-green);
font-size: clamp(26px, 2.7vw, 34px);
line-height: 1.08;
letter-spacing: -0.03em;
text-wrap: balance;
}
.service-intro-body {
display: grid;
gap: 18px;
align-content: start;
}
.service-intro-prose p {
margin: 0 0 15px;
color: #454a50;
font-size: 16px;
line-height: 1.75;
}
.service-intro-prose p:last-child {
margin-bottom: 0;
}
.service-intro-lead {
margin: 0;
padding: 18px 20px 18px 22px;
border-radius: 22px;
border-left: 4px solid var(--yellow-soft);
background: linear-gradient(180deg, rgba(255, 255, 255, 0.72) 0%, rgba(250, 247, 239, 0.92) 100%);
box-shadow:
inset 0 0 0 1px rgba(33, 48, 33, 0.05),
0 12px 22px rgba(17, 20, 24, 0.04);
color: #1f242a;
font-size: 18px;
line-height: 1.7;
font-weight: 500;
}
@media (max-width: 768px) {
.service-intro {
padding: 6px 0 42px;
}
.service-intro-card {
grid-template-columns: 1fr;
gap: 20px;
padding: 26px 20px 24px;
border-radius: 24px;
}
.service-intro-card::before {
inset: 12px;
border-radius: 18px;
}
.service-intro-header {
gap: 16px;
padding-right: 0;
}
.service-intro-badge {
padding: 8px 14px 8px 8px;
gap: 10px;
}
.service-intro-mark {
width: 38px;
height: 38px;
border-radius: 12px;
font-size: 16px;
}
.service-intro-heading {
font-size: clamp(24px, 7vw, 30px);
line-height: 1.12;
}
.service-intro-prose p {
font-size: 15px;
}
.service-intro-lead {
padding: 16px 16px 16px 18px;
font-size: 16px;
line-height: 1.65;
}
}
.service-faq {
padding: 0 0 var(--space-section-featured-y);
}
.service-faq-card {
max-width: 880px;
margin: 0 auto;
padding: 38px 40px 32px;
border-radius: 30px;
background: #fff;
box-shadow:
inset 0 0 0 1px rgba(33, 48, 33, 0.06),
0 18px 40px rgba(17, 20, 24, 0.06);
}
@media (max-width: 768px) {
.service-faq-card {
padding: 26px 20px 22px;
border-radius: 24px;
}
}
.service-related {
padding: 0 0 var(--space-section-featured-y);
}
@@ -852,7 +1121,7 @@
}
.service-benefit-card-featured::after {
background: linear-gradient(90deg, #ffd54a 0%, rgba(242, 191, 47, 0.72) 100%);
display: none;
}
.service-benefit-icon {
@@ -862,35 +1131,31 @@
width: 54px;
height: 54px;
border-radius: 16px;
background: linear-gradient(180deg, rgba(255, 250, 236, 0.96) 0%, rgba(242, 191, 47, 0.18) 100%);
background: var(--gw-green);
box-shadow:
inset 0 0 0 1px rgba(17, 20, 24, 0.04),
0 8px 20px rgba(17, 20, 24, 0.08);
color: var(--gw-green);
font-size: 17px;
inset 0 0 0 1px rgba(255, 255, 255, 0.06),
0 10px 22px rgba(33, 48, 33, 0.22);
color: var(--yellow);
font-size: 19px;
margin-top: 4px;
margin-bottom: 14px;
position: relative;
z-index: 1;
}
.service-benefit-tint-1 .service-benefit-icon {
background: linear-gradient(180deg, rgba(255, 250, 236, 0.96) 0%, rgba(242, 191, 47, 0.16) 100%);
}
.service-benefit-tint-2 .service-benefit-icon {
background: linear-gradient(180deg, rgba(255, 255, 255, 0.95) 0%, rgba(229, 214, 194, 0.42) 100%);
}
.service-benefit-tint-1 .service-benefit-icon,
.service-benefit-tint-2 .service-benefit-icon,
.service-benefit-tint-3 .service-benefit-icon {
background: linear-gradient(180deg, rgba(250, 252, 248, 0.96) 0%, rgba(33, 48, 33, 0.08) 100%);
background: var(--gw-green);
color: var(--yellow);
}
.service-benefit-card-featured .service-benefit-icon {
background: linear-gradient(180deg, rgba(255, 248, 214, 0.96) 0%, rgba(255, 209, 0, 0.34) 100%);
background: var(--yellow);
color: var(--gw-green);
box-shadow:
inset 0 0 0 1px rgba(255, 255, 255, 0.08),
0 10px 24px rgba(0, 0, 0, 0.16);
inset 0 0 0 1px rgba(255, 255, 255, 0.4),
0 10px 24px rgba(0, 0, 0, 0.22);
}
.service-benefit-card h3,
@@ -1046,6 +1311,36 @@
margin-right: 2px;
}
.service-benefit-grid-simple-swipe {
display: flex;
gap: 12px;
margin: 0 -16px;
padding: 0 16px 8px;
overflow-x: auto;
overscroll-behavior-x: contain;
scroll-snap-type: x mandatory;
scrollbar-width: none;
-webkit-overflow-scrolling: touch;
touch-action: pan-x pinch-zoom;
}
.service-benefit-grid-simple-swipe::-webkit-scrollbar {
display: none;
}
.service-benefit-grid-simple-swipe .service-benefit-card {
flex: 0 0 78%;
min-height: clamp(230px, 42svh, 320px);
min-width: 0;
scroll-snap-align: start;
scroll-snap-stop: always;
}
:global(.reveal-ready.reveal-block).service-pricing-immediate-mobile {
opacity: 1;
transform: none;
transition: none;
}
.service-highlight-layout {
gap: 24px;
@@ -1197,15 +1492,35 @@
}
.service-highlight-collage {
grid-template-columns: 1fr;
display: flex;
gap: 12px;
max-width: 420px;
max-width: none;
margin: 0 -16px;
overflow-x: auto;
overscroll-behavior-x: contain;
scroll-snap-type: x mandatory;
padding: 0 16px 8px;
scrollbar-width: none;
-webkit-overflow-scrolling: touch;
touch-action: pan-x pinch-zoom;
}
.service-highlight-collage::-webkit-scrollbar {
display: none;
}
.service-collage-card {
flex: 0 0 78%;
min-height: 168px;
min-width: 0;
border-radius: 22px;
scroll-snap-align: start;
scroll-snap-stop: always;
}
.service-collage-card-1,
.service-collage-card-2,
.service-collage-card-3 {
min-height: 220px;
transform: none;
}
}