v4
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user