v4.0.6 0 Simplicity focus
This commit is contained in:
@@ -847,7 +847,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<OnboardingFooter email={userEmail} on:logout={handleLogout} />
|
||||
<OnboardingFooter on:logout={handleLogout} />
|
||||
|
||||
{/if}
|
||||
</main>
|
||||
|
||||
@@ -0,0 +1,737 @@
|
||||
<script lang="ts">
|
||||
/*
|
||||
* Services-focus homepage variant.
|
||||
*
|
||||
* Same design language as HomeClarity (green hero, yellow CTAs, the shared
|
||||
* ServicesSection / ValuesSection / TestimonialsSection), but every section is
|
||||
* pointed at one job: get the visitor to click through into a service page.
|
||||
*
|
||||
* The site gets traffic but few leads, so this variant removes the big-commitment
|
||||
* booking form from the homepage and lowers the first ask to "pick the walk that
|
||||
* fits your dog". The three services are offered as quick-picks in the hero
|
||||
* itself, repeated as the main ServicesSection cards, reinforced with proof, and
|
||||
* offered a final time in a closing band. A view component only: the route that
|
||||
* renders it owns data loading, SEO head, and structured data.
|
||||
*
|
||||
* Preview route: /variants/services-first (noindex).
|
||||
*/
|
||||
import Footer from '$lib/components/ui/Footer.svelte';
|
||||
import Header from '$lib/components/ui/Header.svelte';
|
||||
import Icon from '$lib/components/ui/Icon.svelte';
|
||||
import ServicesSection from '$lib/components/sections/ServicesSection.svelte';
|
||||
import TestimonialsSection from '$lib/components/sections/TestimonialsSection.svelte';
|
||||
import type { HomePageContent } from '$lib/types';
|
||||
|
||||
export let content: HomePageContent;
|
||||
export let pagePath = '/variants/services-first';
|
||||
|
||||
const reviewCta = content.intro.reviewCta;
|
||||
const heroImage = '/images/smiling-happy-dog-maya.webp';
|
||||
const stars = Array.from({ length: 5 });
|
||||
|
||||
// The assurance bar replaces the old grid of value cards: four plain facts,
|
||||
// stated once. Each is a reason to trust the click, not a feature to skim.
|
||||
const assurances = [
|
||||
{ icon: 'fas fa-user-check', label: 'Same walker, every time' },
|
||||
{ icon: 'fas fa-users', label: 'Small, matched groups' },
|
||||
{ icon: 'fas fa-shield-heart', label: 'Pet first-aid certified & insured' },
|
||||
{ icon: 'fas fa-star', label: '5.0 from 30+ owners' }
|
||||
];
|
||||
|
||||
// The hero quick-picks and the closing band both draw from the same services
|
||||
// data the main ServicesSection uses, so the click targets never drift apart.
|
||||
$: services = content.services;
|
||||
</script>
|
||||
|
||||
<Header navigation={content.navigation} />
|
||||
|
||||
<section class="sfhero" data-track-location="hero" aria-label="Goodwalk dog walking">
|
||||
<div class="sfhero-inner">
|
||||
<div class="sfhero-copy">
|
||||
<p class="sfhero-eyebrow">Professional dog walkers</p>
|
||||
<h1 class="sfhero-title">
|
||||
<span class="sfhero-title-main">Come home to a calmer dog.</span>
|
||||
</h1>
|
||||
<p class="sfhero-lead">
|
||||
Small, matched groups for small & medium dogs. The same walker, every time.
|
||||
</p>
|
||||
|
||||
<div class="sfhero-actions">
|
||||
<a
|
||||
href="#services"
|
||||
class="btn btn-yellow btn-with-arrow"
|
||||
data-track-event="cta_click"
|
||||
data-track-type="hero_primary"
|
||||
data-track-label="See our walks"
|
||||
>
|
||||
See our walks
|
||||
<Icon name="fas fa-arrow-right" />
|
||||
</a>
|
||||
|
||||
<a
|
||||
class="sfhero-reviews"
|
||||
href={reviewCta.href}
|
||||
target={reviewCta.external ? '_blank' : undefined}
|
||||
rel={reviewCta.external ? 'noopener' : undefined}
|
||||
aria-label="30+ five-star Google reviews"
|
||||
data-track-event="social_proof_click"
|
||||
data-track-type="hero_reviews"
|
||||
>
|
||||
<span class="sfhero-reviews-mark" aria-hidden="true">
|
||||
<img src="/images/google-g-logo.svg" alt="" width="15" height="16" />
|
||||
</span>
|
||||
<span class="sfhero-reviews-stars" aria-hidden="true">
|
||||
{#each stars as _}
|
||||
<Icon name="fas fa-star" />
|
||||
{/each}
|
||||
</span>
|
||||
<span class="sfhero-reviews-label">30+ five-star reviews</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sfhero-media">
|
||||
<div class="sfhero-photo">
|
||||
<img
|
||||
src={heroImage}
|
||||
alt="Maya, a happy small dog, after a Goodwalk walk in Auckland"
|
||||
width="1536"
|
||||
height="1024"
|
||||
loading="eager"
|
||||
fetchpriority="high"
|
||||
decoding="async"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick-picks: the three services as clickable choices, in the hero, above
|
||||
the fold. This is the conversion move — the visitor's first decision is
|
||||
"which walk", not "fill in a form". -->
|
||||
<nav class="sfhero-picks" aria-label="Choose a service">
|
||||
{#each services as service}
|
||||
<a
|
||||
class="sfhero-pick"
|
||||
href={service.href}
|
||||
data-track-event="service_quickpick_click"
|
||||
data-track-type="hero_quickpick"
|
||||
data-track-label={service.title}
|
||||
>
|
||||
<span class="sfhero-pick-glyph" aria-hidden="true">
|
||||
<Icon name={service.icon} />
|
||||
</span>
|
||||
<span class="sfhero-pick-text">
|
||||
<span class="sfhero-pick-title">{service.title}</span>
|
||||
{#if service.priceFrom}
|
||||
<span class="sfhero-pick-price">{service.priceFrom}</span>
|
||||
{/if}
|
||||
</span>
|
||||
<Icon name="fas fa-arrow-right" className="sfhero-pick-arrow" />
|
||||
</a>
|
||||
{/each}
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- The conversion engine: the service cards themselves link straight through to
|
||||
each service page. -->
|
||||
<ServicesSection
|
||||
services={content.services}
|
||||
heading="Choose your dog's walk."
|
||||
intro="Three ways to walk — pack, solo, or puppy visits. Same walker, small groups, built around your dog."
|
||||
/>
|
||||
|
||||
<!-- One calm assurance beat. Replaces the old wall of value cards: four plain
|
||||
facts, stated once, then straight on to proof. No competing form. -->
|
||||
<section class="sfproof" aria-label="What every Goodwalk includes">
|
||||
<ul class="sfproof-row">
|
||||
{#each assurances as item}
|
||||
<li class="sfproof-item">
|
||||
<Icon name={item.icon} className="sfproof-icon" />
|
||||
<span>{item.label}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<TestimonialsSection testimonials={content.testimonials} seedKey={pagePath} />
|
||||
|
||||
<!-- Closing band: offer the same three choices one more time, on brand green. -->
|
||||
<section class="sfclose" aria-label="Choose your dog's walk">
|
||||
<div class="sfclose-inner">
|
||||
<div class="sfclose-head">
|
||||
<h2 class="sfclose-title">Find the right walk for your dog.</h2>
|
||||
<p class="sfclose-lead">
|
||||
Three options. No commitment. Just pick the one that sounds like your dog.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="sfclose-grid">
|
||||
{#each services as service}
|
||||
<a
|
||||
class="sfclose-card"
|
||||
href={service.href}
|
||||
data-track-event="service_card_click"
|
||||
data-track-type="closing_band"
|
||||
data-track-label={service.title}
|
||||
>
|
||||
<span class="sfclose-card-glyph" aria-hidden="true">
|
||||
<Icon name={service.icon} />
|
||||
</span>
|
||||
<span class="sfclose-card-title">{service.title}</span>
|
||||
<span class="sfclose-card-body">{service.body}</span>
|
||||
{#if service.priceFrom}
|
||||
<span class="sfclose-card-price">{service.priceFrom}</span>
|
||||
{/if}
|
||||
<span class="sfclose-card-cta">
|
||||
View this walk
|
||||
<Icon name="fas fa-arrow-right" className="sfclose-card-arrow" />
|
||||
</span>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<Footer footer={content.footer} />
|
||||
|
||||
<style>
|
||||
/* ── Hero ──────────────────────────────────────────────────────────────
|
||||
Mirrors HomeClarity's .chero so the variant reads as the same site:
|
||||
green field, one soft yellow light, identical type scale. The layout
|
||||
adds a third row for the service quick-picks. */
|
||||
|
||||
.sfhero {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: var(--gw-green);
|
||||
color: var(--text-inverse);
|
||||
}
|
||||
|
||||
.sfhero::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -28%;
|
||||
right: -10%;
|
||||
width: 60%;
|
||||
height: 90%;
|
||||
background: radial-gradient(closest-side, rgba(var(--accent-rgb), 0.16), transparent 70%);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.sfhero-inner {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
max-width: var(--max-w);
|
||||
margin: 0 auto;
|
||||
padding: clamp(48px, 5.5vw, 80px) var(--space-container-x);
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
|
||||
grid-template-areas:
|
||||
'copy media'
|
||||
'picks picks';
|
||||
column-gap: clamp(32px, 5vw, 76px);
|
||||
row-gap: clamp(32px, 4vw, 48px);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.sfhero-copy {
|
||||
grid-area: copy;
|
||||
}
|
||||
|
||||
.sfhero-eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin: 0 0 18px;
|
||||
font-family: var(--font-body);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.16em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(var(--white-rgb), 0.7);
|
||||
}
|
||||
|
||||
.sfhero-eyebrow::before {
|
||||
content: '';
|
||||
width: 26px;
|
||||
height: 2px;
|
||||
border-radius: 2px;
|
||||
background: var(--yellow);
|
||||
}
|
||||
|
||||
.sfhero-title {
|
||||
margin: 0;
|
||||
font-family: var(--font-head);
|
||||
}
|
||||
|
||||
.sfhero-title-main {
|
||||
display: block;
|
||||
font-size: clamp(42px, 6.4vw, 78px);
|
||||
font-weight: 800;
|
||||
line-height: 0.98;
|
||||
letter-spacing: -0.04em;
|
||||
color: var(--yellow);
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.sfhero-lead {
|
||||
margin: 22px 0 0;
|
||||
max-width: 42ch;
|
||||
font-size: clamp(16px, 1.3vw, 19px);
|
||||
line-height: 1.6;
|
||||
color: rgba(var(--white-rgb), 0.82);
|
||||
}
|
||||
|
||||
.sfhero-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 16px 26px;
|
||||
margin-top: 28px;
|
||||
}
|
||||
|
||||
.sfhero-reviews {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 9px;
|
||||
text-decoration: none;
|
||||
color: rgba(var(--white-rgb), 0.9);
|
||||
transition: color var(--motion-fast);
|
||||
}
|
||||
|
||||
.sfhero-reviews-mark {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 50%;
|
||||
background: var(--surface-page);
|
||||
box-shadow: var(--shadow-card);
|
||||
}
|
||||
|
||||
.sfhero-reviews-mark img {
|
||||
display: block;
|
||||
width: 15px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.sfhero-reviews-stars {
|
||||
display: inline-flex;
|
||||
gap: 2px;
|
||||
color: var(--yellow);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.sfhero-reviews-label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: rgba(var(--white-rgb), 0.9);
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.sfhero-reviews:hover {
|
||||
color: var(--text-inverse);
|
||||
}
|
||||
}
|
||||
|
||||
.sfhero-reviews:focus-visible {
|
||||
outline: 2px solid var(--yellow);
|
||||
outline-offset: 3px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
/* Media */
|
||||
.sfhero-media {
|
||||
grid-area: media;
|
||||
position: relative;
|
||||
justify-self: end;
|
||||
width: 100%;
|
||||
max-width: 460px;
|
||||
}
|
||||
|
||||
.sfhero-media::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 8% -6% -10% 10%;
|
||||
border-radius: var(--radius-xl);
|
||||
background: rgba(var(--accent-rgb), 0.22);
|
||||
filter: blur(46px);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.sfhero-photo {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
aspect-ratio: 4 / 5;
|
||||
border-radius: 28px;
|
||||
overflow: hidden;
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(var(--white-rgb), 0.12),
|
||||
var(--shadow-2xl);
|
||||
}
|
||||
|
||||
.sfhero-photo img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center 28%;
|
||||
}
|
||||
|
||||
/* ── Hero quick-picks ────────────────────────────────────────────────── */
|
||||
|
||||
.sfhero-picks {
|
||||
grid-area: picks;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
padding-top: clamp(8px, 1.5vw, 16px);
|
||||
border-top: 1px solid rgba(var(--white-rgb), 0.12);
|
||||
}
|
||||
|
||||
.sfhero-pick {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
padding: 16px 18px;
|
||||
border-radius: 18px;
|
||||
background: rgba(var(--white-rgb), 0.05);
|
||||
box-shadow: inset 0 0 0 1px rgba(var(--white-rgb), 0.1);
|
||||
color: var(--text-inverse);
|
||||
text-decoration: none;
|
||||
transition:
|
||||
background 0.22s ease,
|
||||
box-shadow 0.22s ease,
|
||||
transform 0.22s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.sfhero-pick-glyph {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
flex: 0 0 auto;
|
||||
border-radius: 13px;
|
||||
background: rgba(var(--accent-rgb), 0.16);
|
||||
color: var(--yellow);
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.sfhero-pick-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sfhero-pick-title {
|
||||
font-family: var(--font-head);
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.01em;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.sfhero-pick-price {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: rgba(var(--white-rgb), 0.66);
|
||||
}
|
||||
|
||||
:global(.sfhero-pick-arrow) {
|
||||
font-size: 12px;
|
||||
color: var(--yellow);
|
||||
flex: 0 0 auto;
|
||||
transition: transform 0.2s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.sfhero-pick:hover {
|
||||
background: rgba(var(--white-rgb), 0.09);
|
||||
box-shadow: inset 0 0 0 1px rgba(var(--white-rgb), 0.22);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.sfhero-pick:hover :global(.sfhero-pick-arrow) {
|
||||
transform: translateX(3px);
|
||||
}
|
||||
}
|
||||
|
||||
.sfhero-pick:focus-visible {
|
||||
outline: 2px solid var(--yellow);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
/* Entrance — same choreography as the clarity hero. */
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.sfhero-copy,
|
||||
.sfhero-picks {
|
||||
animation: sfheroRise 0.6s var(--ease-out-soft) both;
|
||||
}
|
||||
|
||||
.sfhero-picks {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.sfhero-photo {
|
||||
animation: sfheroPhoto 0.8s var(--ease-out-soft) both;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes sfheroRise {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(14px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes sfheroPhoto {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(1.04);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Assurance bar ─────────────────────────────────────────────────────
|
||||
A single calm strip of facts — the "say it plainly" beat that replaces a
|
||||
long grid of value cards. Warm cream divider between services and proof. */
|
||||
|
||||
.sfproof {
|
||||
background: var(--surface-panel-cream);
|
||||
border-top: 1px solid var(--border-soft);
|
||||
border-bottom: 1px solid var(--border-soft);
|
||||
}
|
||||
|
||||
.sfproof-row {
|
||||
list-style: none;
|
||||
margin: 0 auto;
|
||||
max-width: var(--max-w);
|
||||
padding: clamp(18px, 2.4vw, 26px) var(--space-container-x);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 14px clamp(24px, 4vw, 52px);
|
||||
}
|
||||
|
||||
.sfproof-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-family: var(--font-head);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--text-heading);
|
||||
}
|
||||
|
||||
:global(.sfproof-icon) {
|
||||
font-size: 15px;
|
||||
color: var(--gw-green);
|
||||
}
|
||||
|
||||
/* ── Closing band ──────────────────────────────────────────────────────
|
||||
Brand-green field that repeats the three service choices. Mirrors the
|
||||
service-card styling so the page closes on the same call it opened with. */
|
||||
|
||||
.sfclose {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background: var(--gw-green);
|
||||
color: var(--text-inverse);
|
||||
padding: var(--space-section-support-y) 0;
|
||||
}
|
||||
|
||||
.sfclose::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -30%;
|
||||
left: -8%;
|
||||
width: 55%;
|
||||
height: 90%;
|
||||
background: radial-gradient(closest-side, rgba(var(--accent-rgb), 0.14), transparent 70%);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.sfclose-inner {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
max-width: min(1180px, calc(var(--max-w) - 40px));
|
||||
margin: 0 auto;
|
||||
padding: 0 var(--space-container-x);
|
||||
}
|
||||
|
||||
.sfclose-head {
|
||||
max-width: 46ch;
|
||||
margin: 0 auto clamp(28px, 4vw, 44px);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sfclose-title {
|
||||
margin: 0 0 12px;
|
||||
font-family: var(--font-head);
|
||||
font-size: clamp(28px, 4vw, 44px);
|
||||
font-weight: 800;
|
||||
line-height: 1.05;
|
||||
letter-spacing: -0.03em;
|
||||
color: var(--text-inverse);
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.sfclose-lead {
|
||||
margin: 0;
|
||||
font-size: clamp(15px, 1.3vw, 18px);
|
||||
line-height: 1.6;
|
||||
color: rgba(var(--white-rgb), 0.82);
|
||||
}
|
||||
|
||||
.sfclose-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.sfclose-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
padding: 28px 24px;
|
||||
border-radius: 22px;
|
||||
background: rgba(var(--white-rgb), 0.05);
|
||||
box-shadow: inset 0 0 0 1px rgba(var(--white-rgb), 0.1);
|
||||
color: var(--text-inverse);
|
||||
text-decoration: none;
|
||||
transition:
|
||||
background 0.24s ease,
|
||||
box-shadow 0.24s ease,
|
||||
transform 0.24s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
.sfclose-card-glyph {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin-bottom: 18px;
|
||||
border-radius: 15px;
|
||||
background: rgba(var(--accent-rgb), 0.16);
|
||||
color: var(--yellow);
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.sfclose-card-title {
|
||||
font-family: var(--font-head);
|
||||
font-size: 19px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.sfclose-card-body {
|
||||
margin-top: 8px;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
color: rgba(var(--white-rgb), 0.78);
|
||||
}
|
||||
|
||||
.sfclose-card-price {
|
||||
margin-top: 14px;
|
||||
font-family: var(--font-head);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: var(--yellow);
|
||||
}
|
||||
|
||||
.sfclose-card-cta {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 20px;
|
||||
padding-top: 18px;
|
||||
width: 100%;
|
||||
border-top: 1px solid rgba(var(--white-rgb), 0.12);
|
||||
font-family: var(--font-head);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: var(--text-inverse);
|
||||
}
|
||||
|
||||
:global(.sfclose-card-arrow) {
|
||||
font-size: 11px;
|
||||
color: var(--yellow);
|
||||
transition: transform 0.2s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.sfclose-card:hover {
|
||||
background: rgba(var(--white-rgb), 0.09);
|
||||
box-shadow: inset 0 0 0 1px rgba(var(--white-rgb), 0.22);
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
|
||||
.sfclose-card:hover :global(.sfclose-card-arrow) {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
}
|
||||
|
||||
.sfclose-card:focus-visible {
|
||||
outline: 2px solid var(--yellow);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
/* ── Mobile ────────────────────────────────────────────────────────────
|
||||
Same single-column logic as the clarity hero: message, photo, action,
|
||||
then the picks stack full width. */
|
||||
@media (max-width: 900px) {
|
||||
.sfhero-picks {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.sfhero-inner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 28px var(--space-container-x-mobile) 36px;
|
||||
row-gap: 0;
|
||||
}
|
||||
|
||||
.sfhero-copy {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.sfhero-media {
|
||||
order: 2;
|
||||
justify-self: stretch;
|
||||
max-width: none;
|
||||
margin: 24px 0 26px;
|
||||
}
|
||||
|
||||
.sfhero-picks {
|
||||
order: 3;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.sfhero-photo {
|
||||
aspect-ratio: 5 / 4;
|
||||
}
|
||||
|
||||
.sfhero-lead {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.sfhero-actions .btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sfclose-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -123,17 +123,6 @@
|
||||
// Computed legacy field for downstream consumers expecting a single full name.
|
||||
$: fullName = `${ownerFirstName.trim()} ${ownerLastName.trim()}`.trim();
|
||||
|
||||
$: displayName = ownerFirstName.trim() || (userEmail ? userEmail.split('@')[0] : '');
|
||||
|
||||
$: accountInitials = (() => {
|
||||
const first = ownerFirstName.trim();
|
||||
const last = ownerLastName.trim();
|
||||
if (first || last) {
|
||||
return `${first.charAt(0)}${last.charAt(0)}`.toUpperCase() || first.charAt(0).toUpperCase();
|
||||
}
|
||||
return (userEmail.charAt(0) || '?').toUpperCase();
|
||||
})();
|
||||
|
||||
let formStartedAt = 0;
|
||||
let visitStartedAt = 0;
|
||||
let pageEnteredAt = 0;
|
||||
@@ -703,12 +692,10 @@ function buildSubmissionSnapshot(submittedAt: string): SubmissionSummary {
|
||||
</a>
|
||||
</div>
|
||||
<div class="onboarding-topbar-right">
|
||||
{#if isAuthenticated && displayName}
|
||||
{#if isAuthenticated}
|
||||
<div class="onboarding-account" aria-label="Account">
|
||||
<span class="onboarding-account-avatar" aria-hidden="true">{accountInitials}</span>
|
||||
<span class="onboarding-account-text">
|
||||
<span class="onboarding-account-prefix">Logged in as</span>
|
||||
<span class="onboarding-account-name">{displayName}</span>
|
||||
<span class="onboarding-account-avatar" aria-hidden="true">
|
||||
<Icon name="fas fa-user" />
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
@@ -749,8 +736,6 @@ function buildSubmissionSnapshot(submittedAt: string): SubmissionSummary {
|
||||
<h1>
|
||||
{#if onboardingCompleted}
|
||||
Your submitted onboarding details.
|
||||
{:else if displayName}
|
||||
Hi {displayName}, let's get your dog onboarded.
|
||||
{:else}
|
||||
A few details so we can care for your dog properly.
|
||||
{/if}
|
||||
@@ -1399,7 +1384,7 @@ function buildSubmissionSnapshot(submittedAt: string): SubmissionSummary {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<OnboardingFooter email={userEmail} on:logout={handleLogout} />
|
||||
<OnboardingFooter on:logout={handleLogout} />
|
||||
|
||||
{/if}
|
||||
</main>
|
||||
@@ -1504,33 +1489,6 @@ function buildSubmissionSnapshot(submittedAt: string): SubmissionSummary {
|
||||
box-shadow: inset 0 0 0 1px rgba(33, 48, 33, 0.12);
|
||||
}
|
||||
|
||||
.onboarding-account-text {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
line-height: 1.15;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.onboarding-account-prefix {
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255, 255, 255, 0.62);
|
||||
}
|
||||
|
||||
.onboarding-account-name {
|
||||
font-family: var(--font-head);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.005em;
|
||||
color: #fff;
|
||||
max-width: 22ch;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.onboarding-account-logout {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -1575,15 +1533,6 @@ function buildSubmissionSnapshot(submittedAt: string): SubmissionSummary {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.onboarding-account-prefix {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.onboarding-account-name {
|
||||
font-size: 12px;
|
||||
max-width: 12ch;
|
||||
}
|
||||
|
||||
.onboarding-account-logout-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,557 @@
|
||||
<script lang="ts">
|
||||
/*
|
||||
* v5 clarity treatment for the pricing page. Keeps each service prominent,
|
||||
* like the live PricingPage, but strips the message right back: one photo,
|
||||
* the name, a single outcome line, the "from" price, then the even plan
|
||||
* cards. No chooser grid, lede, assurance, fit lists, or meta rows.
|
||||
*
|
||||
* View component only. Preview at /variants/our-pricing.
|
||||
*/
|
||||
import Icon from '$lib/components/ui/Icon.svelte';
|
||||
import { reveal } from '$lib/actions/reveal';
|
||||
import ServiceHero from '$lib/components/sections/ServiceHero.svelte';
|
||||
import PricingPlanCard from '$lib/components/ui/PricingPlanCard.svelte';
|
||||
import ServiceAreaCoverage from '$lib/components/sections/ServiceAreaCoverage.svelte';
|
||||
import { decoratePlans } from '$lib/utils/pricing';
|
||||
import { ourPricingContent } from '$lib/content/our-pricing';
|
||||
import { packWalksContent } from '$lib/content/pack-walks';
|
||||
import { dogWalkingContent } from '$lib/content/dog-walking';
|
||||
import { puppyVisitsContent } from '$lib/content/puppy-visits';
|
||||
import type { PricingPageSection, ServicePageContent } from '$lib/types';
|
||||
|
||||
type Block = { section: PricingPageSection; service: ServicePageContent; href: string };
|
||||
|
||||
// Pair each pricing section with its service (for the distilled per-plan copy)
|
||||
// and its variant page. Order matches ourPricingContent.sections.
|
||||
const blocks: Block[] = [
|
||||
{ section: ourPricingContent.sections[0], service: packWalksContent, href: '/pack-walks' },
|
||||
{ section: ourPricingContent.sections[1], service: dogWalkingContent, href: '/dog-walking' },
|
||||
{ section: ourPricingContent.sections[2], service: puppyVisitsContent, href: '/puppy-visits' }
|
||||
];
|
||||
|
||||
function fromValue(section: PricingPageSection): string {
|
||||
return section.meta?.find((m) => m.label === 'From')?.value ?? '';
|
||||
}
|
||||
|
||||
function plansFor(block: Block) {
|
||||
const lines = block.service.clarity?.planLines ?? {};
|
||||
return decoratePlans(
|
||||
block.section.plans.map((plan) => ({
|
||||
...plan,
|
||||
features: lines[plan.title] ?? plan.features
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
// At-a-glance comparison. Distilled, accurate facts per service; the "from"
|
||||
// price is read from content (fromFor) so it stays single-sourced with the
|
||||
// cards below and the pricing in each service page's schema.
|
||||
const comparisonRows = [
|
||||
{
|
||||
href: '/pack-walks',
|
||||
service: 'Tiny Gang Pack Walks',
|
||||
bestFor: 'Sociable small & medium dogs',
|
||||
format: 'Small group of 4–8 dogs',
|
||||
length: '60–75 min'
|
||||
},
|
||||
{
|
||||
href: '/dog-walking',
|
||||
service: 'Solo Walks',
|
||||
bestFor: 'Reactive, anxious, senior or recovering dogs',
|
||||
format: 'One dog, one-on-one',
|
||||
length: '30 / 45 / 60 min'
|
||||
},
|
||||
{
|
||||
href: '/puppy-visits',
|
||||
service: 'Puppy Visits',
|
||||
bestFor: 'Puppies too young for full walks',
|
||||
format: 'In-home, one-on-one',
|
||||
length: '20 / 45 / 60 min'
|
||||
}
|
||||
];
|
||||
|
||||
function fromFor(href: string): string {
|
||||
const block = blocks.find((b) => b.href === href);
|
||||
return block ? fromValue(block.section) : '';
|
||||
}
|
||||
</script>
|
||||
|
||||
<main class="pc">
|
||||
<!-- Standard service hero (green copy left, photo right). -->
|
||||
<ServiceHero
|
||||
eyebrow={ourPricingContent.eyebrow ?? 'Pricing'}
|
||||
title={ourPricingContent.title}
|
||||
subtitle={ourPricingContent.subtitle}
|
||||
imageUrl={ourPricingContent.heroImageUrl}
|
||||
imageAlt={ourPricingContent.heroImageAlt}
|
||||
chips={ourPricingContent.heroChips ?? []}
|
||||
cta={{ label: 'Book a free Meet & Greet', href: '/contact-us', variant: 'yellow' }}
|
||||
/>
|
||||
|
||||
<!-- Value before price: one line, given room to land. -->
|
||||
<section class="pc-value" use:reveal={{ distance: 0 }} aria-labelledby="pc-value-quote">
|
||||
<div class="pc-inner">
|
||||
<span class="pc-value-rule" aria-hidden="true"></span>
|
||||
<p id="pc-value-quote" class="pc-value-quote">
|
||||
The best investment you’ll ever make is in your dog’s happiness.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- At a glance: a compact comparison of the three services before the
|
||||
detailed per-service blocks. A real table, so it reads cleanly and is
|
||||
easy to lift as a unit. -->
|
||||
<section class="pc-compare" use:reveal={{ distance: 0 }} aria-labelledby="pc-compare-heading">
|
||||
<div class="pc-inner">
|
||||
<div class="pc-compare-head">
|
||||
<p class="pc-eyebrow">Compare at a glance</p>
|
||||
<h2 id="pc-compare-heading" class="pc-h2">Three ways Goodwalk fits your dog</h2>
|
||||
</div>
|
||||
<div class="pc-table-wrap">
|
||||
<table class="pc-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Service</th>
|
||||
<th scope="col">Best for</th>
|
||||
<th scope="col">Format</th>
|
||||
<th scope="col">Length</th>
|
||||
<th scope="col">From</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each comparisonRows as row}
|
||||
<tr>
|
||||
<th scope="row"><a href={row.href}>{row.service}</a></th>
|
||||
<td>{row.bestFor}</td>
|
||||
<td>{row.format}</td>
|
||||
<td>{row.length}</td>
|
||||
<td class="pc-table-from">{fromFor(row.href)}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- One prominent block per service: photo + simplified message, then cards. -->
|
||||
{#each blocks as block, i}
|
||||
<section
|
||||
class="pc-service"
|
||||
class:pc-service-tint={i % 2 === 1}
|
||||
use:reveal={{ distance: 0 }}
|
||||
aria-labelledby={`pc-svc-${i}`}
|
||||
>
|
||||
<div class="pc-inner">
|
||||
<div class="pc-row" class:pc-row-reverse={i % 2 === 1}>
|
||||
{#if block.section.imageUrl}
|
||||
<figure class="pc-media">
|
||||
<img
|
||||
src={block.section.imageUrl}
|
||||
alt={block.section.imageAlt ?? block.section.title}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
</figure>
|
||||
{/if}
|
||||
|
||||
<div class="pc-copy">
|
||||
{#if block.section.eyebrow}
|
||||
<p class="pc-eyebrow">{block.section.eyebrow}</p>
|
||||
{/if}
|
||||
<h2 id={`pc-svc-${i}`} class="pc-h2">{block.section.title}</h2>
|
||||
{#if block.section.outcome}
|
||||
<p class="pc-outcome">{block.section.outcome}</p>
|
||||
{/if}
|
||||
{#if fromValue(block.section)}
|
||||
<p class="pc-from"><span>From</span> <strong>{fromValue(block.section)}</strong></p>
|
||||
{/if}
|
||||
<a class="pc-link" href={block.href}>
|
||||
See {block.section.title}
|
||||
<Icon name="fas fa-arrow-right" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pc-plans" class:pc-plans-three={block.section.plans.length === 3}>
|
||||
{#each plansFor(block) as plan}
|
||||
<PricingPlanCard {plan} variant="service" hideCtaOnMobile={true} />
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{/each}
|
||||
|
||||
<!-- Coverage -->
|
||||
<div class="pc-area" use:reveal={{ distance: 0 }}>
|
||||
<ServiceAreaCoverage
|
||||
title="Where we work across Auckland Central"
|
||||
lead="Free door-to-door pickup and drop-off on walks across Auckland Central and the suburbs around it. Tap your suburb for its local parks and routes."
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Close -->
|
||||
<section class="pc-cta" use:reveal={{ distance: 0 }}>
|
||||
<div class="pc-inner">
|
||||
<h2 class="pc-cta-title">Not sure which one fits?</h2>
|
||||
<p class="pc-cta-sub">Every Goodwalk routine starts with a free Meet & Greet. We will tell you what fits, and what does not.</p>
|
||||
<a class="btn btn-yellow" href="/contact-us">Book a free Meet & Greet</a>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<style>
|
||||
.pc {
|
||||
--pc-max: 1180px;
|
||||
background: var(--off-white);
|
||||
color: var(--text-heading);
|
||||
}
|
||||
|
||||
.pc-inner {
|
||||
max-width: var(--pc-max);
|
||||
margin: 0 auto;
|
||||
padding-inline: var(--space-container-x);
|
||||
}
|
||||
|
||||
.pc-service {
|
||||
padding-block: clamp(48px, 6vw, 88px);
|
||||
}
|
||||
|
||||
/* ── Value statement: one line, given room to land ── */
|
||||
.pc-value {
|
||||
padding-block: clamp(64px, 9vw, 128px);
|
||||
background: var(--surface-panel-cream);
|
||||
border-block: 1px solid var(--border-soft);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pc-value-rule {
|
||||
display: block;
|
||||
width: 40px;
|
||||
height: 3px;
|
||||
margin: 0 auto 30px;
|
||||
border-radius: 3px;
|
||||
background: var(--yellow);
|
||||
}
|
||||
|
||||
.pc-value-quote {
|
||||
margin: 0 auto;
|
||||
max-width: 16ch;
|
||||
font-family: var(--font-head);
|
||||
font-size: clamp(30px, 4.8vw, 56px);
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
letter-spacing: -0.03em;
|
||||
color: var(--gw-green);
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
/* ── At-a-glance comparison table ── */
|
||||
.pc-compare {
|
||||
padding-block: clamp(48px, 6vw, 88px);
|
||||
}
|
||||
|
||||
.pc-compare-head {
|
||||
max-width: 40rem;
|
||||
margin: 0 auto clamp(28px, 4vw, 44px);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pc-compare-head .pc-eyebrow {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Scrolls on narrow screens rather than cramping five columns. */
|
||||
.pc-table-wrap {
|
||||
overflow-x: auto;
|
||||
border-radius: var(--radius-xl);
|
||||
box-shadow: var(--shadow-card);
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.pc-table {
|
||||
width: 100%;
|
||||
min-width: 760px;
|
||||
border-collapse: collapse;
|
||||
background: var(--surface-panel);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.pc-table thead th {
|
||||
padding: 16px 20px;
|
||||
background: var(--gw-green);
|
||||
color: rgba(255, 248, 230, 0.96);
|
||||
font-family: var(--font-head);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pc-table tbody th,
|
||||
.pc-table tbody td {
|
||||
padding: 18px 20px;
|
||||
border-top: 1px solid var(--border-soft);
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
color: var(--text-heading-soft);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.pc-table tbody th {
|
||||
font-family: var(--font-head);
|
||||
font-weight: 700;
|
||||
color: var(--text-heading);
|
||||
}
|
||||
|
||||
.pc-table tbody th a {
|
||||
color: var(--gw-green);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.pc-table tbody th a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.pc-table-from {
|
||||
font-family: var(--font-head);
|
||||
font-weight: 700;
|
||||
color: var(--gw-green);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Alternating tint for rhythm, the same trick the service pages use. */
|
||||
.pc-service-tint,
|
||||
.pc-area {
|
||||
background: var(--surface-panel-cream);
|
||||
border-block: 1px solid var(--border-soft);
|
||||
}
|
||||
|
||||
/* ── Prominent service row: photo + simplified message ── */
|
||||
.pc-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: clamp(28px, 5vw, 64px);
|
||||
align-items: center;
|
||||
margin-bottom: clamp(32px, 4.5vw, 56px);
|
||||
}
|
||||
|
||||
.pc-media {
|
||||
order: 0;
|
||||
margin: 0;
|
||||
border-radius: var(--radius-xl);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-card);
|
||||
background: var(--surface-brand);
|
||||
aspect-ratio: 4 / 3;
|
||||
}
|
||||
|
||||
.pc-row-reverse .pc-media {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.pc-media img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.pc-copy {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.pc-row-reverse .pc-copy {
|
||||
order: 0;
|
||||
}
|
||||
|
||||
.pc-eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin: 0 0 14px;
|
||||
font-family: var(--font-body);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
color: var(--gw-green);
|
||||
}
|
||||
|
||||
.pc-eyebrow::before {
|
||||
content: '';
|
||||
width: 24px;
|
||||
height: 2px;
|
||||
border-radius: 2px;
|
||||
background: var(--yellow);
|
||||
}
|
||||
|
||||
.pc-h2 {
|
||||
margin: 0;
|
||||
font-family: var(--font-head);
|
||||
font-size: clamp(28px, 3.4vw, 44px);
|
||||
font-weight: 700;
|
||||
line-height: 1.06;
|
||||
letter-spacing: -0.03em;
|
||||
color: var(--text-heading);
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
/* The simplified message: one line, carried with weight. */
|
||||
.pc-outcome {
|
||||
margin: 16px 0 0;
|
||||
max-width: 34ch;
|
||||
font-family: var(--font-head);
|
||||
font-size: clamp(18px, 1.9vw, 23px);
|
||||
font-weight: 600;
|
||||
line-height: 1.32;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--gw-green);
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.pc-from {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 7px;
|
||||
margin: 22px 0 0;
|
||||
padding: 9px 16px;
|
||||
border-radius: var(--radius-pill);
|
||||
background: var(--surface-brand-soft);
|
||||
color: var(--gw-green);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.pc-from span {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.pc-from strong {
|
||||
font-family: var(--font-head);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.pc-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 22px;
|
||||
font-family: var(--font-head);
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: var(--gw-green);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.pc-from + .pc-link {
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
.pc-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* ── Plans ── */
|
||||
.pc-plans {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.pc-plans-three {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
max-width: 1040px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.pc-plans :global(.plan-card) {
|
||||
padding: 40px 28px 34px;
|
||||
}
|
||||
|
||||
.pc-plans :global(.plan-card--featured) {
|
||||
padding-top: 60px;
|
||||
}
|
||||
|
||||
.pc-plans :global(.plan-card__cta) {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
align-self: stretch;
|
||||
padding-inline: 12px;
|
||||
}
|
||||
|
||||
.pc-plans :global(.plan-card__cta .icon) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ── Close CTA ── */
|
||||
.pc-cta {
|
||||
padding-block: clamp(48px, 6vw, 88px);
|
||||
background: var(--gw-green);
|
||||
color: rgba(255, 248, 230, 0.96);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.pc-cta-title {
|
||||
margin: 0;
|
||||
font-family: var(--font-head);
|
||||
font-size: clamp(26px, 3.2vw, 40px);
|
||||
font-weight: 700;
|
||||
line-height: 1.1;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--yellow);
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.pc-cta-sub {
|
||||
margin: 14px auto 24px;
|
||||
max-width: 38rem;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: rgba(255, 248, 230, 0.9);
|
||||
}
|
||||
|
||||
/* ── Responsive ── */
|
||||
@media (max-width: 860px) {
|
||||
.pc-row {
|
||||
grid-template-columns: 1fr;
|
||||
gap: clamp(20px, 5vw, 32px);
|
||||
}
|
||||
|
||||
.pc-media,
|
||||
.pc-row-reverse .pc-media {
|
||||
order: 0;
|
||||
aspect-ratio: 16 / 10;
|
||||
}
|
||||
|
||||
.pc-copy,
|
||||
.pc-row-reverse .pc-copy {
|
||||
order: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.pc-plans,
|
||||
.pc-plans-three {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.pc-plans,
|
||||
.pc-plans-three {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.pc-plans :global(.plan-card) {
|
||||
padding: 30px 24px 26px;
|
||||
}
|
||||
|
||||
.pc-plans :global(.plan-card--featured) {
|
||||
padding-top: 46px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,725 @@
|
||||
<script lang="ts">
|
||||
/*
|
||||
* Shared v5 "clarity" service page. One design system, one message, said
|
||||
* once in order: what it is -> what's different -> how it works -> fit ->
|
||||
* proof -> price -> FAQ -> where we cover -> book.
|
||||
*
|
||||
* Drives all three service pages (Tiny Gang, Solo Walks, Puppy Visits) from
|
||||
* content. Page-specific copy lives in each content file's `clarity` block;
|
||||
* see docs/v5.md. View component only: the route owns Header/Footer/SEO.
|
||||
*/
|
||||
import Icon from '$lib/components/ui/Icon.svelte';
|
||||
import ServiceHero from '$lib/components/sections/ServiceHero.svelte';
|
||||
import { reveal } from '$lib/actions/reveal';
|
||||
import BookingWizard from '$lib/components/sections/BookingWizard.svelte';
|
||||
import PricingPlanCard from '$lib/components/ui/PricingPlanCard.svelte';
|
||||
import TestimonialsSection from '$lib/components/sections/TestimonialsSection.svelte';
|
||||
import FaqSection from '$lib/components/sections/FaqSection.svelte';
|
||||
import ServiceAreaCoverage from '$lib/components/sections/ServiceAreaCoverage.svelte';
|
||||
import { decoratePlans } from '$lib/utils/pricing';
|
||||
import type { ServicePageContent, SiteSharedContent } from '$lib/types';
|
||||
|
||||
export let content: SiteSharedContent;
|
||||
export let pageContent: ServicePageContent;
|
||||
export let currentPath = '/';
|
||||
|
||||
$: clarity = pageContent.clarity;
|
||||
$: traits = clarity?.traits ?? [];
|
||||
$: steps = clarity?.steps ?? [];
|
||||
$: planLines = clarity?.planLines ?? {};
|
||||
$: priceNotes = clarity?.priceNotes ?? [];
|
||||
$: pricingPlans = decoratePlans(
|
||||
pageContent.pricing.plans.map((plan) => ({
|
||||
...plan,
|
||||
features: planLines[plan.title] ?? plan.features
|
||||
}))
|
||||
);
|
||||
$: faqItems = pageContent.faq?.items?.slice(0, 6) ?? [];
|
||||
</script>
|
||||
|
||||
<main class="tg">
|
||||
<!-- Hero: shared service-page hero (green copy left, photo bleeds right). -->
|
||||
<ServiceHero
|
||||
eyebrow={pageContent.hero.eyebrow}
|
||||
title={pageContent.hero.title}
|
||||
subtitle={pageContent.hero.subtitle}
|
||||
imageUrl={pageContent.hero.imageUrl}
|
||||
imageAlt={pageContent.hero.imageAlt}
|
||||
chips={pageContent.hero.chips ?? []}
|
||||
cta={pageContent.hero.cta}
|
||||
/>
|
||||
|
||||
<!-- What it is: a plain, self-contained definition that opens the page. -->
|
||||
{#if clarity?.definition}
|
||||
<section class="tg-section tg-section-def" use:reveal={{ distance: 0 }} aria-labelledby="tg-def-heading">
|
||||
<div class="tg-inner tg-inner-narrow">
|
||||
<div class="tg-head">
|
||||
<p class="tg-eyebrow">{clarity.definition.eyebrow ?? 'In plain terms'}</p>
|
||||
<h2 id="tg-def-heading" class="tg-h2">{clarity.definition.heading}</h2>
|
||||
</div>
|
||||
<p class="tg-def-body">{clarity.definition.body}</p>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<!-- What makes it different (said once) -->
|
||||
{#if traits.length}
|
||||
<section class="tg-section" use:reveal={{ distance: 0 }} aria-labelledby="tg-diff-heading">
|
||||
<div class="tg-inner">
|
||||
<div class="tg-head">
|
||||
<p class="tg-eyebrow">{clarity?.diffEyebrow ?? 'What makes it different'}</p>
|
||||
<h2 id="tg-diff-heading" class="tg-h2">{clarity?.diffHeading ?? ''}</h2>
|
||||
</div>
|
||||
<ul class="tg-traits">
|
||||
{#each traits as trait}
|
||||
<li class="tg-trait">
|
||||
<span class="tg-trait-icon" aria-hidden="true"><Icon name={trait.icon} /></span>
|
||||
<div>
|
||||
<h3>{trait.title}</h3>
|
||||
<p>{trait.body}</p>
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<!-- How it works (the process, explicit) -->
|
||||
{#if steps.length}
|
||||
<section class="tg-section tg-section-steps" use:reveal={{ distance: 0 }} aria-labelledby="tg-steps-heading">
|
||||
<div class="tg-inner">
|
||||
<div class="tg-head">
|
||||
<p class="tg-eyebrow">{clarity?.stepsEyebrow ?? 'How it works'}</p>
|
||||
<h2 id="tg-steps-heading" class="tg-h2">{clarity?.stepsHeading ?? ''}</h2>
|
||||
</div>
|
||||
<ol class="tg-steps">
|
||||
{#each steps as step, i}
|
||||
<li class="tg-step">
|
||||
<span class="tg-step-num" aria-hidden="true">{i + 1}</span>
|
||||
<h3>{step.title}</h3>
|
||||
<p>{step.body}</p>
|
||||
</li>
|
||||
{/each}
|
||||
</ol>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<!-- Image beat: one deliberate photo that breaks the longest text run. -->
|
||||
{#if clarity?.imageBand}
|
||||
<div class="tg-band-wrap" use:reveal={{ distance: 0 }}>
|
||||
<figure class="tg-band">
|
||||
<img
|
||||
class="tg-band-img"
|
||||
src={clarity.imageBand.imageUrl}
|
||||
alt={clarity.imageBand.imageAlt}
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
<figcaption class="tg-band-cap">{clarity.imageBand.caption}</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Is it right for your dog? -->
|
||||
{#if pageContent.decision}
|
||||
<section class="tg-section" use:reveal={{ distance: 0 }} aria-labelledby="tg-fit-heading">
|
||||
<div class="tg-inner">
|
||||
<div class="tg-head">
|
||||
<p class="tg-eyebrow">{clarity?.fitEyebrow ?? 'Honest fit check'}</p>
|
||||
<h2 id="tg-fit-heading" class="tg-h2">{pageContent.decision.title ?? 'Is this right for your dog?'}</h2>
|
||||
</div>
|
||||
<div class="tg-fit">
|
||||
<div class="tg-fit-col">
|
||||
<h3>{pageContent.decision.fitTitle ?? 'A great fit if your dog:'}</h3>
|
||||
<ul>
|
||||
{#each pageContent.decision.fitItems as item}
|
||||
<li>
|
||||
<span class="tg-fit-mark tg-fit-mark-yes" aria-hidden="true"><Icon name="fas fa-check" /></span>
|
||||
<span>{item}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tg-fit-col">
|
||||
<h3>{pageContent.decision.notFitTitle ?? 'Better with another option if your dog:'}</h3>
|
||||
<ul>
|
||||
{#each pageContent.decision.notFitItems as item}
|
||||
<li>
|
||||
<span class="tg-fit-mark tg-fit-mark-no" aria-hidden="true"><Icon name="fas fa-xmark" /></span>
|
||||
<span>{item}</span>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{#if pageContent.decision.footnote}
|
||||
<p class="tg-fit-note">{pageContent.decision.footnote}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<!-- Proof -->
|
||||
<TestimonialsSection
|
||||
heading={clarity?.testimonialsHeading ?? pageContent.testimonialsHeading}
|
||||
testimonials={content.testimonials}
|
||||
seedKey={currentPath}
|
||||
/>
|
||||
|
||||
<!-- Price -->
|
||||
<section class="tg-section" use:reveal={{ distance: 0 }} aria-labelledby="tg-price-heading">
|
||||
<div class="tg-inner tg-inner-wide">
|
||||
<div class="tg-head">
|
||||
<p class="tg-eyebrow">{clarity?.pricingEyebrow ?? 'Simple pricing'}</p>
|
||||
<h2 id="tg-price-heading" class="tg-h2">{pageContent.pricing.title}</h2>
|
||||
{#if clarity?.pricingTagline}
|
||||
<p class="tg-price-tagline">{clarity.pricingTagline}</p>
|
||||
{/if}
|
||||
{#if pageContent.pricing.intro}
|
||||
<p class="tg-head-sub">{pageContent.pricing.intro}</p>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="tg-plans" class:tg-plans-three={pricingPlans.length === 3}>
|
||||
{#each pricingPlans as plan}
|
||||
<PricingPlanCard {plan} variant="service" hideCtaOnMobile={true} />
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if pageContent.pricing.scarcityNote || priceNotes.length}
|
||||
<div class="tg-price-notes">
|
||||
{#if pageContent.pricing.scarcityNote}
|
||||
<p class="tg-note tg-note-warm"><Icon name="fas fa-clock" />{pageContent.pricing.scarcityNote}</p>
|
||||
{/if}
|
||||
{#each priceNotes as note}
|
||||
<p class="tg-note"><Icon name={note.icon} />{note.label}</p>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<a class="btn btn-yellow btn-mobile-center tg-plans-cta" href="#newlead">Book a Meet & Greet</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- FAQ -->
|
||||
{#if faqItems.length}
|
||||
<section class="tg-section" use:reveal={{ distance: 0 }} aria-labelledby="tg-faq-heading">
|
||||
<div class="tg-inner tg-inner-narrow">
|
||||
<div class="tg-head">
|
||||
<p class="tg-eyebrow">{clarity?.faqEyebrow ?? 'Common questions'}</p>
|
||||
<h2 id="tg-faq-heading" class="tg-h2">{pageContent.faq?.title ?? 'FAQs'}</h2>
|
||||
{#if pageContent.faq?.intro}
|
||||
<p class="tg-head-sub">{pageContent.faq.intro}</p>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="tg-faq">
|
||||
<FaqSection title={pageContent.faq?.title ?? 'FAQs'} faqs={faqItems} showHeading={false} />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
<!-- Service area: a coverage field that works on every screen. -->
|
||||
<div class="tg-section-area" use:reveal={{ distance: 0 }}>
|
||||
<ServiceAreaCoverage
|
||||
title={clarity?.areaTitle ?? 'Where we walk across Auckland Central'}
|
||||
lead={clarity?.areaLead ??
|
||||
'Free door-to-door pickup and drop-off across Auckland Central and the suburbs around it. Tap your suburb for its local parks and routes.'}
|
||||
stats={clarity?.areaStats ?? null}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<BookingWizard booking={pageContent.booking} pagePath={currentPath} />
|
||||
</main>
|
||||
|
||||
<style>
|
||||
/* ── One system: tokens for this page ──────────────────────────────── */
|
||||
.tg {
|
||||
--tg-max: 1120px;
|
||||
--tg-narrow: 820px;
|
||||
background: var(--off-white);
|
||||
color: var(--text-heading);
|
||||
}
|
||||
|
||||
.tg-inner {
|
||||
max-width: var(--tg-max);
|
||||
margin: 0 auto;
|
||||
padding-inline: var(--space-container-x);
|
||||
}
|
||||
|
||||
.tg-inner-narrow {
|
||||
max-width: var(--tg-narrow);
|
||||
}
|
||||
|
||||
/* Pricing breaks out wider than the body so four cards have real width
|
||||
(and the full CTA never clips). */
|
||||
.tg-inner-wide {
|
||||
max-width: 1280px;
|
||||
}
|
||||
|
||||
.tg-section {
|
||||
padding-block: clamp(48px, 6vw, 84px);
|
||||
}
|
||||
|
||||
/* Alternating surface for rhythm, not a new "system": just a tint. */
|
||||
.tg-section-steps,
|
||||
.tg-section-area {
|
||||
background: var(--surface-panel-cream);
|
||||
border-block: 1px solid var(--border-soft);
|
||||
}
|
||||
|
||||
/* One eyebrow, one heading, everywhere. */
|
||||
.tg-eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin: 0 0 14px;
|
||||
font-family: var(--font-body);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
color: var(--gw-green);
|
||||
}
|
||||
|
||||
.tg-eyebrow::before {
|
||||
content: '';
|
||||
width: 24px;
|
||||
height: 2px;
|
||||
border-radius: 2px;
|
||||
background: var(--yellow);
|
||||
}
|
||||
|
||||
.tg-head {
|
||||
max-width: 40rem;
|
||||
margin: 0 auto clamp(28px, 4vw, 44px);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tg-h2 {
|
||||
margin: 0;
|
||||
font-family: var(--font-head);
|
||||
font-size: clamp(28px, 3.4vw, 42px);
|
||||
font-weight: 700;
|
||||
line-height: 1.08;
|
||||
letter-spacing: -0.03em;
|
||||
color: var(--text-heading);
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
.tg-head-sub {
|
||||
margin: 14px auto 0;
|
||||
max-width: 36rem;
|
||||
color: var(--text-muted);
|
||||
font-size: 17px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* ── Definition lede: one calm, readable paragraph under the opening head ── */
|
||||
.tg-section-def {
|
||||
padding-bottom: clamp(8px, 2vw, 24px);
|
||||
}
|
||||
|
||||
.tg-def-body {
|
||||
max-width: 46rem;
|
||||
margin: 0 auto;
|
||||
color: var(--text-heading-soft);
|
||||
font-size: clamp(17px, 1.7vw, 19px);
|
||||
line-height: 1.7;
|
||||
text-align: center;
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
.tg-price-tagline {
|
||||
margin: 14px auto 0;
|
||||
max-width: 32rem;
|
||||
color: var(--gw-green);
|
||||
font-family: var(--font-head);
|
||||
font-size: clamp(17px, 1.7vw, 20px);
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
letter-spacing: -0.01em;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
/* ── Traits: a clean feature list, not boxed cards ─────────────────── */
|
||||
.tg-traits {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: clamp(24px, 3vw, 40px) clamp(32px, 5vw, 64px);
|
||||
}
|
||||
|
||||
.tg-trait {
|
||||
display: grid;
|
||||
grid-template-columns: 44px minmax(0, 1fr);
|
||||
gap: 16px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.tg-trait-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: 14px;
|
||||
background: var(--surface-brand);
|
||||
color: var(--yellow);
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.tg-trait h3 {
|
||||
margin: 2px 0 6px;
|
||||
font-family: var(--font-head);
|
||||
font-size: 19px;
|
||||
line-height: 1.2;
|
||||
color: var(--text-heading);
|
||||
}
|
||||
|
||||
.tg-trait p {
|
||||
margin: 0;
|
||||
color: var(--text-muted);
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
max-width: 38ch;
|
||||
}
|
||||
|
||||
/* ── Steps: numerals, not cards ────────────────────────────────────── */
|
||||
.tg-steps {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: clamp(24px, 4vw, 48px);
|
||||
counter-reset: tg-step;
|
||||
}
|
||||
|
||||
.tg-step {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tg-step-num {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-bottom: 16px;
|
||||
border-radius: 50%;
|
||||
background: var(--yellow);
|
||||
color: var(--gw-green);
|
||||
font-family: var(--font-head);
|
||||
font-size: 16px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.tg-step h3 {
|
||||
margin: 0 0 8px;
|
||||
font-family: var(--font-head);
|
||||
font-size: 19px;
|
||||
line-height: 1.2;
|
||||
color: var(--text-heading);
|
||||
}
|
||||
|
||||
.tg-step p {
|
||||
margin: 0;
|
||||
color: var(--text-muted);
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* ── Fit check: one card, two columns ──────────────────────────────── */
|
||||
.tg-fit {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: clamp(20px, 3vw, 32px);
|
||||
padding: clamp(24px, 3vw, 40px);
|
||||
border-radius: var(--radius-xl);
|
||||
background: var(--surface-panel);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px var(--border-soft-strong),
|
||||
var(--shadow-card);
|
||||
}
|
||||
|
||||
.tg-fit-col h3 {
|
||||
margin: 0 0 16px;
|
||||
font-family: var(--font-head);
|
||||
font-size: 17px;
|
||||
line-height: 1.3;
|
||||
color: var(--text-heading);
|
||||
}
|
||||
|
||||
.tg-fit-col ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.tg-fit-col li {
|
||||
display: grid;
|
||||
grid-template-columns: 22px minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
align-items: start;
|
||||
color: var(--text-heading-soft);
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.tg-fit-mark {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
margin-top: 1px;
|
||||
border-radius: 50%;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.tg-fit-mark-yes {
|
||||
background: rgba(var(--brand-rgb), 0.1);
|
||||
color: var(--gw-green);
|
||||
}
|
||||
|
||||
.tg-fit-mark-no {
|
||||
background: rgba(var(--ink-rgb), 0.06);
|
||||
color: var(--text-subtle);
|
||||
}
|
||||
|
||||
.tg-fit-note {
|
||||
margin: 22px auto 0;
|
||||
max-width: 48rem;
|
||||
color: var(--text-muted);
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ── Image beat: a contained cinematic band between sections ───────────── */
|
||||
.tg-band-wrap {
|
||||
max-width: var(--tg-max);
|
||||
margin: 0 auto;
|
||||
padding-inline: var(--space-container-x);
|
||||
padding-block: clamp(8px, 2vw, 28px);
|
||||
}
|
||||
|
||||
.tg-band {
|
||||
position: relative;
|
||||
margin: 0;
|
||||
border-radius: var(--radius-xl);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-card);
|
||||
background: var(--surface-brand);
|
||||
}
|
||||
|
||||
.tg-band-img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
aspect-ratio: 16 / 7;
|
||||
object-fit: cover;
|
||||
object-position: center 42%;
|
||||
}
|
||||
|
||||
/* Caption rides on the photo: legible over the image, keyword-bearing copy
|
||||
that reads as a moment, not a label. */
|
||||
.tg-band-cap {
|
||||
position: absolute;
|
||||
inset-inline: 0;
|
||||
bottom: 0;
|
||||
margin: 0;
|
||||
padding: clamp(28px, 5vw, 44px) clamp(18px, 4vw, 32px) clamp(16px, 2.4vw, 22px);
|
||||
background: linear-gradient(to top, rgba(33, 48, 33, 0.78), rgba(33, 48, 33, 0));
|
||||
color: rgba(255, 248, 230, 0.96);
|
||||
font-family: var(--font-head);
|
||||
font-size: clamp(15px, 1.8vw, 19px);
|
||||
font-weight: 600;
|
||||
line-height: 1.35;
|
||||
letter-spacing: -0.01em;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
/* ── Pricing ───────────────────────────────────────────────────────── */
|
||||
.tg-plans {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
/* Three-plan pages (solo walks, puppy visits) centre nicely at three-up. */
|
||||
.tg-plans-three {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
max-width: 1040px;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
/* Roomier, consistent cards on this page, scoped via .tg-plans so the shared
|
||||
PricingPlanCard and other pages keep their own sizing. */
|
||||
.tg-plans :global(.plan-card) {
|
||||
padding: 44px 30px 38px;
|
||||
}
|
||||
|
||||
/* The featured card carries the "Goodwalk favourite" ribbon (absolute,
|
||||
top: 16px). Give it enough head room that the ribbon sits clearly above
|
||||
the title instead of over it. */
|
||||
.tg-plans :global(.plan-card--featured) {
|
||||
padding-top: 62px;
|
||||
}
|
||||
|
||||
/* Full-width CTA: never squished, and consistent across all cards.
|
||||
display:flex (block-level) overrides the component's inline-flex +
|
||||
align-self:center, so the button always spans the full card width. */
|
||||
.tg-plans :global(.plan-card__cta) {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
align-self: stretch;
|
||||
padding-inline: 12px;
|
||||
}
|
||||
|
||||
/* The narrow 4-up cards can't fit "Book a Meet & Greet" plus the arrow on
|
||||
one line, and the card clips overflow — so the label was chopped. Drop the
|
||||
arrow here and let the label use the whole width on its own. */
|
||||
.tg-plans :global(.plan-card__cta .icon) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tg-price-notes {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.tg-note {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin: 0;
|
||||
padding: 8px 16px;
|
||||
border-radius: var(--radius-pill);
|
||||
background: var(--surface-brand-soft);
|
||||
color: var(--gw-green);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tg-note-warm {
|
||||
background: var(--surface-accent-muted);
|
||||
color: #5a4500;
|
||||
}
|
||||
|
||||
:global(.tg-note .icon) {
|
||||
color: var(--yellow);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
:global(.tg-note-warm .icon) {
|
||||
color: #b88800;
|
||||
}
|
||||
|
||||
.tg-plans-cta {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ── FAQ: a calm ruled list, not a stack of cards ──────────────────────
|
||||
The shared FaqSection renders each question as a bordered, shadowed white
|
||||
card. On this off-white page that reads as visual noise. Here we strip the
|
||||
card chrome to hairline dividers so the questions sit as one quiet column,
|
||||
matching the page's restraint. Scoped via .tg-faq; every other page keeps
|
||||
the card treatment. */
|
||||
.tg-faq :global(.faq details) {
|
||||
border: 0;
|
||||
border-top: 1px solid var(--border-soft);
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
margin: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.tg-faq :global(.faq details:last-child) {
|
||||
border-bottom: 1px solid var(--border-soft);
|
||||
}
|
||||
|
||||
.tg-faq :global(.faq details[open]) {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Full-bleed rows: no card inset, generous vertical breathing room. */
|
||||
.tg-faq :global(.faq summary) {
|
||||
padding: 22px 4px;
|
||||
border-radius: 0;
|
||||
font-size: 17px;
|
||||
color: var(--text-heading);
|
||||
transition: color 0.18s ease;
|
||||
}
|
||||
|
||||
/* Hover lifts the question to brand green instead of filling a card. */
|
||||
.tg-faq :global(.faq summary:hover) {
|
||||
background: transparent;
|
||||
color: var(--gw-green);
|
||||
}
|
||||
|
||||
.tg-faq :global(.faq details[open] summary) {
|
||||
border-bottom: 0;
|
||||
border-radius: 0;
|
||||
color: var(--gw-green);
|
||||
}
|
||||
|
||||
.tg-faq :global(.faq details p) {
|
||||
padding: 0 44px 24px 4px;
|
||||
}
|
||||
|
||||
/* ── Responsive ────────────────────────────────────────────────────── */
|
||||
@media (max-width: 980px) {
|
||||
.tg-plans,
|
||||
.tg-plans-three {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.tg-traits {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.tg-steps {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.tg-fit {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.tg-band-img {
|
||||
aspect-ratio: 3 / 2;
|
||||
}
|
||||
|
||||
.tg-plans,
|
||||
.tg-plans-three {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.tg-plans :global(.plan-card) {
|
||||
padding: 30px 24px 26px;
|
||||
}
|
||||
|
||||
.tg-plans :global(.plan-card--featured) {
|
||||
padding-top: 46px;
|
||||
}
|
||||
|
||||
.tg-plans-cta {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user