v4.0.6 - Simplicity focus
This commit is contained in:
@@ -0,0 +1,520 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
/*
|
||||||
|
* Clarity homepage design (the live homepage as of the swap).
|
||||||
|
*
|
||||||
|
* Purpose-built green hero that says the specialism in the first second
|
||||||
|
* (small & medium dogs), a founder philosophy strip, then offer-first order:
|
||||||
|
* Services -> How it works -> Values -> the rest. A view component only: the
|
||||||
|
* route that renders it owns data loading, SEO head, and structured data.
|
||||||
|
*
|
||||||
|
* Paired design: HomeClassic.svelte. To roll back, swap the component the
|
||||||
|
* homepage route renders. See src/routes/+page.svelte.
|
||||||
|
*/
|
||||||
|
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 HowItWorksSection from '$lib/components/sections/HowItWorksSection.svelte';
|
||||||
|
import InfoSection from '$lib/components/sections/InfoSection.svelte';
|
||||||
|
import InstagramSection from '$lib/components/sections/InstagramSection.svelte';
|
||||||
|
import BookingWizard from '$lib/components/sections/BookingWizard.svelte';
|
||||||
|
import FounderStorySection from '$lib/components/sections/FounderStorySection.svelte';
|
||||||
|
import ServicesSection from '$lib/components/sections/ServicesSection.svelte';
|
||||||
|
import TestimonialsSection from '$lib/components/sections/TestimonialsSection.svelte';
|
||||||
|
import ValuesSection from '$lib/components/sections/ValuesSection.svelte';
|
||||||
|
import type { HomePageContent } from '$lib/types';
|
||||||
|
import type { AbContext } from '$lib/ab';
|
||||||
|
|
||||||
|
export let content: HomePageContent;
|
||||||
|
export let ab: AbContext | undefined = undefined;
|
||||||
|
export let pagePath = '/';
|
||||||
|
|
||||||
|
const reviewCta = content.intro.reviewCta;
|
||||||
|
const heroImage = '/images/smiling-happy-dog-maya.webp';
|
||||||
|
const stars = Array.from({ length: 5 });
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Header navigation={content.navigation} />
|
||||||
|
|
||||||
|
<section class="chero" data-track-location="hero" aria-label="Goodwalk dog walking">
|
||||||
|
<div class="chero-inner">
|
||||||
|
<div class="chero-copy">
|
||||||
|
<p class="chero-eyebrow">Goodwalk · Auckland dog walking</p>
|
||||||
|
<h1 class="chero-title">
|
||||||
|
<span class="chero-title-lead">All we do is</span>
|
||||||
|
<span class="chero-title-main">small & medium dogs</span>
|
||||||
|
</h1>
|
||||||
|
<p class="chero-lead">
|
||||||
|
Real adventures with the Tiny Gang, in small, matched groups across
|
||||||
|
Auckland. Same walker every time, so your dog comes home happy, tired,
|
||||||
|
and ready to settle.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="chero-media">
|
||||||
|
<div class="chero-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>
|
||||||
|
|
||||||
|
<div class="chero-actions">
|
||||||
|
<a
|
||||||
|
href="#newlead"
|
||||||
|
class="btn btn-yellow btn-with-arrow"
|
||||||
|
data-track-event="cta_click"
|
||||||
|
data-track-type="hero_primary"
|
||||||
|
data-track-label="Book a free Meet & Greet"
|
||||||
|
>
|
||||||
|
Book a free Meet & Greet
|
||||||
|
<Icon name="fas fa-arrow-right" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a
|
||||||
|
class="chero-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="chero-reviews-mark" aria-hidden="true">
|
||||||
|
<img src="/images/google-g-logo.svg" alt="" width="15" height="16" />
|
||||||
|
</span>
|
||||||
|
<span class="chero-reviews-stars" aria-hidden="true">
|
||||||
|
{#each stars as _}
|
||||||
|
<Icon name="fas fa-star" />
|
||||||
|
{/each}
|
||||||
|
</span>
|
||||||
|
<span class="chero-reviews-label">30+ five-star reviews</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Founder philosophy strip: the sincere counter to a competitor's
|
||||||
|
"total freedom in nature" pitch. Frames the upside the owner is paying
|
||||||
|
for: a day built around their dog, not a one-size pack. -->
|
||||||
|
<section class="cfounder" aria-label="Our approach">
|
||||||
|
<div class="cfounder-inner">
|
||||||
|
<figure class="cfounder-quote">
|
||||||
|
<blockquote class="cfounder-blockquote">
|
||||||
|
<p class="cfounder-text">
|
||||||
|
Every dog gets a walk built around them, not a one-size pack: the right
|
||||||
|
small group, a walker who knows their name and their quirks, and
|
||||||
|
adventures that grow with their confidence. That's how a walk becomes
|
||||||
|
the best part of a dog's day.
|
||||||
|
</p>
|
||||||
|
</blockquote>
|
||||||
|
<figcaption class="cfounder-attr">
|
||||||
|
<img
|
||||||
|
class="cfounder-avatar"
|
||||||
|
src="/images/alessandra-goodwalk-founder-auckland.webp"
|
||||||
|
alt="Alessandra, founder of Goodwalk, walking a dog in Auckland"
|
||||||
|
width="96"
|
||||||
|
height="96"
|
||||||
|
loading="lazy"
|
||||||
|
decoding="async"
|
||||||
|
/>
|
||||||
|
<span class="cfounder-byline">
|
||||||
|
<span class="cfounder-name">Alessandra</span>
|
||||||
|
<span class="cfounder-role">Founder of Goodwalk</span>
|
||||||
|
</span>
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Offer first: what can I actually book, and roughly what does it cost? -->
|
||||||
|
<ServicesSection services={content.services} />
|
||||||
|
|
||||||
|
<!-- Then the process: what happens after I enquire? Lowers the unknown. -->
|
||||||
|
<HowItWorksSection content={content.howItWorks} />
|
||||||
|
|
||||||
|
<!-- Only now the reasons to choose us. -->
|
||||||
|
<ValuesSection values={content.values} />
|
||||||
|
|
||||||
|
<TestimonialsSection testimonials={content.testimonials} seedKey={pagePath} />
|
||||||
|
<FounderStorySection founderStory={content.founderStory} />
|
||||||
|
<InfoSection info={content.info} />
|
||||||
|
<BookingWizard booking={content.booking} {pagePath} {ab} />
|
||||||
|
<InstagramSection instagram={content.instagram} />
|
||||||
|
<Footer footer={content.footer} />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* ── Hero ──────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
.chero {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--gw-green);
|
||||||
|
color: var(--text-inverse);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A single soft warm light, top-right, for depth. Not glassmorphism,
|
||||||
|
not a gradient headline: just one breath of the brand yellow. */
|
||||||
|
.chero::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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chero-inner {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
max-width: var(--max-w);
|
||||||
|
min-height: clamp(500px, 68vh, 640px);
|
||||||
|
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'
|
||||||
|
'actions media';
|
||||||
|
column-gap: clamp(32px, 5vw, 76px);
|
||||||
|
row-gap: clamp(22px, 3vw, 32px);
|
||||||
|
align-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chero-copy {
|
||||||
|
grid-area: copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chero-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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chero-eyebrow::before {
|
||||||
|
content: '';
|
||||||
|
width: 26px;
|
||||||
|
height: 2px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: var(--yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chero-title {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--font-head);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chero-title-lead {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-size: clamp(18px, 2.1vw, 26px);
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.15;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
color: rgba(var(--white-rgb), 0.82);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chero-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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chero-lead {
|
||||||
|
margin: 22px 0 0;
|
||||||
|
max-width: 40ch;
|
||||||
|
font-size: clamp(16px, 1.3vw, 19px);
|
||||||
|
line-height: 1.6;
|
||||||
|
color: rgba(var(--white-rgb), 0.82);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chero-actions {
|
||||||
|
grid-area: actions;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chero-reviews {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 9px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: rgba(var(--white-rgb), 0.9);
|
||||||
|
transition: color var(--motion-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chero-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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chero-reviews-mark img {
|
||||||
|
display: block;
|
||||||
|
width: 15px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chero-reviews-stars {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 2px;
|
||||||
|
color: var(--yellow);
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chero-reviews-label {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: rgba(var(--white-rgb), 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (hover: hover) {
|
||||||
|
.chero-reviews:hover {
|
||||||
|
color: var(--text-inverse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chero-reviews:focus-visible {
|
||||||
|
outline: 2px solid var(--yellow);
|
||||||
|
outline-offset: 3px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Media */
|
||||||
|
.chero-media {
|
||||||
|
grid-area: media;
|
||||||
|
position: relative;
|
||||||
|
justify-self: end;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 460px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chero-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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chero-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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chero-photo img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center 28%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Founder philosophy strip ──────────────────────────────────────── */
|
||||||
|
|
||||||
|
.cfounder {
|
||||||
|
background: var(--surface-panel-cream);
|
||||||
|
border-bottom: 1px solid var(--border-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cfounder-inner {
|
||||||
|
max-width: 940px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: clamp(30px, 3.6vw, 48px) var(--space-container-x);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cfounder-quote {
|
||||||
|
margin: 0;
|
||||||
|
position: relative;
|
||||||
|
padding-left: clamp(40px, 5vw, 60px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cfounder-blockquote {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A single large opening quote mark, brand yellow, as the editorial cue.
|
||||||
|
Keeps the strip recognisably a statement without a repeated eyebrow. */
|
||||||
|
.cfounder-quote::before {
|
||||||
|
content: '\201C';
|
||||||
|
position: absolute;
|
||||||
|
left: -4px;
|
||||||
|
top: -0.18em;
|
||||||
|
font-family: var(--font-head);
|
||||||
|
font-size: clamp(64px, 9vw, 104px);
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1;
|
||||||
|
color: var(--yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cfounder-text {
|
||||||
|
margin: 0;
|
||||||
|
max-width: 46ch;
|
||||||
|
font-family: var(--font-body);
|
||||||
|
font-size: clamp(19px, 2.1vw, 27px);
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.45;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
color: var(--text-heading);
|
||||||
|
text-wrap: pretty;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cfounder-attr {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
margin-top: clamp(18px, 2.4vw, 26px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cfounder-avatar {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center 25%;
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 2px rgba(var(--white-rgb), 0.9),
|
||||||
|
var(--shadow-card);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cfounder-byline {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cfounder-name {
|
||||||
|
font-family: var(--font-head);
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-heading);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cfounder-role {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Entrance */
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
.chero-eyebrow,
|
||||||
|
.chero-title,
|
||||||
|
.chero-lead,
|
||||||
|
.chero-actions {
|
||||||
|
animation: cheroRise 0.6s var(--ease-out-soft) both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chero-title {
|
||||||
|
animation-delay: 0.08s;
|
||||||
|
}
|
||||||
|
.chero-lead {
|
||||||
|
animation-delay: 0.16s;
|
||||||
|
}
|
||||||
|
.chero-actions {
|
||||||
|
animation-delay: 0.26s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chero-photo {
|
||||||
|
animation: cheroPhoto 0.8s var(--ease-out-soft) both;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes cheroRise {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(14px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes cheroPhoto {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(1.04);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Mobile: one calm column, message first, then Maya, then action ── */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.chero-inner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 28px var(--space-container-x-mobile) 36px;
|
||||||
|
row-gap: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chero-copy {
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chero-media {
|
||||||
|
order: 2;
|
||||||
|
justify-self: stretch;
|
||||||
|
max-width: none;
|
||||||
|
margin: 24px 0 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chero-actions {
|
||||||
|
order: 3;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chero-photo {
|
||||||
|
aspect-ratio: 5 / 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chero-lead {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chero-actions .btn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cfounder-quote {
|
||||||
|
padding-left: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cfounder-quote::before {
|
||||||
|
left: -2px;
|
||||||
|
top: -0.08em;
|
||||||
|
font-size: 52px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
/*
|
||||||
|
* Classic homepage design (the original layout, pre-clarity swap).
|
||||||
|
*
|
||||||
|
* Hero -> Values -> Services -> How it works -> Testimonials -> Founder ->
|
||||||
|
* Info -> Booking -> Instagram. Preserved so the homepage can be rolled back
|
||||||
|
* to it by swapping the component rendered in src/routes/+page.svelte.
|
||||||
|
*
|
||||||
|
* Paired design: HomeClarity.svelte. A view component only: the route that
|
||||||
|
* renders it owns data loading, SEO head, and structured data.
|
||||||
|
*/
|
||||||
|
import Footer from '$lib/components/ui/Footer.svelte';
|
||||||
|
import Header from '$lib/components/ui/Header.svelte';
|
||||||
|
import HeroSection from '$lib/components/sections/HeroSection.svelte';
|
||||||
|
import HowItWorksSection from '$lib/components/sections/HowItWorksSection.svelte';
|
||||||
|
import InfoSection from '$lib/components/sections/InfoSection.svelte';
|
||||||
|
import InstagramSection from '$lib/components/sections/InstagramSection.svelte';
|
||||||
|
import BookingWizard from '$lib/components/sections/BookingWizard.svelte';
|
||||||
|
import FounderStorySection from '$lib/components/sections/FounderStorySection.svelte';
|
||||||
|
import ServicesSection from '$lib/components/sections/ServicesSection.svelte';
|
||||||
|
import TestimonialsSection from '$lib/components/sections/TestimonialsSection.svelte';
|
||||||
|
import ValuesSection from '$lib/components/sections/ValuesSection.svelte';
|
||||||
|
import type { CallToAction, HomePageContent } from '$lib/types';
|
||||||
|
import type { AbContext } from '$lib/ab';
|
||||||
|
|
||||||
|
export let content: HomePageContent;
|
||||||
|
export let ab: AbContext | undefined = undefined;
|
||||||
|
export let primaryCtaOverride: CallToAction | undefined = undefined;
|
||||||
|
export let pagePath = '/';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Header navigation={content.navigation} />
|
||||||
|
<HeroSection
|
||||||
|
hero={content.hero}
|
||||||
|
reviewCta={content.intro.reviewCta}
|
||||||
|
{primaryCtaOverride}
|
||||||
|
{ab}
|
||||||
|
/>
|
||||||
|
<ValuesSection values={content.values} />
|
||||||
|
<ServicesSection services={content.services} />
|
||||||
|
<HowItWorksSection content={content.howItWorks} />
|
||||||
|
<TestimonialsSection testimonials={content.testimonials} seedKey={pagePath} />
|
||||||
|
<FounderStorySection founderStory={content.founderStory} />
|
||||||
|
<InfoSection info={content.info} />
|
||||||
|
<BookingWizard booking={content.booking} {pagePath} {ab} />
|
||||||
|
<InstagramSection instagram={content.instagram} />
|
||||||
|
<Footer footer={content.footer} />
|
||||||
@@ -0,0 +1,613 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
/*
|
||||||
|
* Tiny Gang pack walks page — clarity redesign.
|
||||||
|
*
|
||||||
|
* One design system, one message. Replaces the multi-card / multi-gradient
|
||||||
|
* ServiceLandingPage treatment (for this page) with a single green hero +
|
||||||
|
* light body that shares the homepage's visual language. The message is said
|
||||||
|
* once, in order: what it is -> what's different -> how joining works -> fit
|
||||||
|
* -> proof -> price -> FAQ -> book.
|
||||||
|
*
|
||||||
|
* View component only: the route owns Header/Footer, SEO, and data.
|
||||||
|
* Preview at /variants/tiny-gang. To go live, render this instead of
|
||||||
|
* ServiceLandingPage for pack-walks in src/routes/[slug]/+page.svelte.
|
||||||
|
*/
|
||||||
|
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 { decoratePlans } from '$lib/utils/pricing';
|
||||||
|
import type { ServicePageContent, SiteSharedContent } from '$lib/types';
|
||||||
|
|
||||||
|
export let content: SiteSharedContent;
|
||||||
|
export let pageContent: ServicePageContent;
|
||||||
|
export let currentPath = '/pack-walks';
|
||||||
|
|
||||||
|
// What makes Tiny Gang different. Said once here, not spread across a
|
||||||
|
// 3-point highlight and a 6-card benefit grid that repeated each other.
|
||||||
|
const traits = [
|
||||||
|
{
|
||||||
|
icon: 'fas fa-users',
|
||||||
|
title: 'Carefully matched groups',
|
||||||
|
body: '4 to 8 dogs, grouped by temperament, energy, and play style. Never a random pack.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'fas fa-shield-dog',
|
||||||
|
title: 'Small & medium dogs only',
|
||||||
|
body: 'Pace, group size, and play style are all built for little dogs, so yours is never the odd one out.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'fas fa-user-check',
|
||||||
|
title: 'The same walker, every week',
|
||||||
|
body: 'One familiar face at the door and on the walk. Your dog is known, not handed around.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'fas fa-eye',
|
||||||
|
title: 'Eyes on every dog',
|
||||||
|
body: 'Small groups mean we notice the little things early, from recall to a quiet day.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// How joining works. The process, made explicit instead of buried in a
|
||||||
|
// paragraph and an FAQ.
|
||||||
|
const steps = [
|
||||||
|
{
|
||||||
|
title: 'Free Meet & Greet',
|
||||||
|
body: 'We come to your home, meet your dog, and talk honestly about whether the pack suits them.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Two assessment walks',
|
||||||
|
body: 'Your dog settles in gently while we confirm the right group, pace, and handling.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Your weekly slot',
|
||||||
|
body: 'A regular spot with the same little crew and the same walker, week in, week out.'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Distilled pricing message. The title already says the routine and the
|
||||||
|
// price says the cost, so each card keeps only the one or two lines that
|
||||||
|
// actually differ. The universal "free pickup" promise is stated once under
|
||||||
|
// the grid instead of repeated on every card.
|
||||||
|
const planLines: Record<string, string[]> = {
|
||||||
|
'1 Walk Per Week': [
|
||||||
|
'A calm weekly outing',
|
||||||
|
'Time to sniff, play, and unwind',
|
||||||
|
'Great for dogs just starting out'
|
||||||
|
],
|
||||||
|
'2-3 Walks Per Week': [
|
||||||
|
'Steady exercise and real social time',
|
||||||
|
'A settled mid-week rhythm',
|
||||||
|
'Our balanced routine'
|
||||||
|
],
|
||||||
|
'4-5 Walks Per Week': [
|
||||||
|
'Best value per walk',
|
||||||
|
'Maximum routine and structure',
|
||||||
|
'For high-energy, social dogs'
|
||||||
|
],
|
||||||
|
'Casual Pack Walk': [
|
||||||
|
'No weekly commitment',
|
||||||
|
'Book only the weeks you need',
|
||||||
|
'For dogs already in the pack'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
$: 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: the shared service-page hero (green copy left, photo bleeds
|
||||||
|
right), identical to /dog-walking and /puppy-visits. -->
|
||||||
|
<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 makes it different (said once) -->
|
||||||
|
<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">What makes Tiny Gang different</p>
|
||||||
|
<h2 id="tg-diff-heading" class="tg-h2">Not a big anonymous pack. A small, matched crew.</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>
|
||||||
|
|
||||||
|
<!-- How joining works (the process, explicit) -->
|
||||||
|
<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">How joining works</p>
|
||||||
|
<h2 id="tg-steps-heading" class="tg-h2">Three simple steps, no pressure.</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>
|
||||||
|
|
||||||
|
<!-- 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">Honest fit check</p>
|
||||||
|
<h2 id="tg-fit-heading" class="tg-h2">{pageContent.decision.title ?? 'Is Tiny Gang 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 on a solo walk 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: owners love it -->
|
||||||
|
<TestimonialsSection
|
||||||
|
heading="Owners love the Tiny Gang"
|
||||||
|
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">Simple weekly pricing</p>
|
||||||
|
<h2 id="tg-price-heading" class="tg-h2">{pageContent.pricing.title}</h2>
|
||||||
|
<p class="tg-price-tagline">The best investment you'll ever make is in your dog's happiness.</p>
|
||||||
|
{#if pageContent.pricing.intro}
|
||||||
|
<p class="tg-head-sub">{pageContent.pricing.intro}</p>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tg-plans">
|
||||||
|
{#each pricingPlans as plan}
|
||||||
|
<PricingPlanCard {plan} variant="service" hideCtaOnMobile={true} />
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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}
|
||||||
|
<p class="tg-note"><Icon name="fas fa-stopwatch" />60+ minute adventures.</p>
|
||||||
|
<p class="tg-note"><Icon name="fas fa-car" />Free pickup & drop-off, every walk.</p>
|
||||||
|
<p class="tg-note"><Icon name="fas fa-shield-heart" />Starts with a free Meet & Greet.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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 }}>
|
||||||
|
<div class="tg-inner tg-inner-narrow">
|
||||||
|
<FaqSection
|
||||||
|
title={pageContent.faq?.title ?? 'Tiny Gang FAQs'}
|
||||||
|
intro={pageContent.faq?.intro}
|
||||||
|
faqs={faqItems}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<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 {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Pricing ───────────────────────────────────────────────────────── */
|
||||||
|
.tg-plans {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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 four 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: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Responsive ────────────────────────────────────────────────────── */
|
||||||
|
@media (max-width: 980px) {
|
||||||
|
.tg-plans {
|
||||||
|
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-plans {
|
||||||
|
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>
|
||||||
@@ -36,9 +36,9 @@ export const homepageContent: HomePageContent = {
|
|||||||
title: 'Come home to a',
|
title: 'Come home to a',
|
||||||
highlight: 'calm, happy dog',
|
highlight: 'calm, happy dog',
|
||||||
mobileTitle: 'Come home to a\ncalm, happy dog',
|
mobileTitle: 'Come home to a\ncalm, happy dog',
|
||||||
seoHeading: 'Dog walking for Auckland\'s small & medium dogs',
|
seoHeading: 'Specialist dog walking for Auckland\'s small & medium dogs',
|
||||||
subtitle:
|
subtitle:
|
||||||
'The same trusted walker every time, in small groups. Never a rotating roster.',
|
'Gentle pacing, familiar routines, and calmer evenings at home.',
|
||||||
primaryCta: { label: 'Book a Meet & Greet', href: '#newlead', variant: 'yellow' },
|
primaryCta: { label: 'Book a Meet & Greet', href: '#newlead', variant: 'yellow' },
|
||||||
secondaryCta: {
|
secondaryCta: {
|
||||||
label: 'See how it works',
|
label: 'See how it works',
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export const packWalksContent: ServicePageContent = {
|
|||||||
hero: {
|
hero: {
|
||||||
eyebrow: 'Tiny Gang Pack Walks',
|
eyebrow: 'Tiny Gang Pack Walks',
|
||||||
title: 'Pack walks that actually suit small & medium dogs',
|
title: 'Pack walks that actually suit small & medium dogs',
|
||||||
subtitle: 'Small groups, thoughtful matching, and off-leash freedom only when a dog is truly ready.',
|
subtitle: 'A proper adventure with a pack of like-minded dogs their own size, so yours comes home happy, completely worn out, and done for the day.',
|
||||||
introEyebrow: 'How Tiny Gang works',
|
introEyebrow: 'How Tiny Gang works',
|
||||||
introHeading: 'What joining Tiny Gang actually looks like',
|
introHeading: 'What joining Tiny Gang actually looks like',
|
||||||
detailHighlights: [
|
detailHighlights: [
|
||||||
|
|||||||
+7
-39
@@ -1,36 +1,19 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import SeoHead from '$lib/components/ui/SeoHead.svelte';
|
import SeoHead from '$lib/components/ui/SeoHead.svelte';
|
||||||
import Footer from '$lib/components/ui/Footer.svelte';
|
|
||||||
import Header from '$lib/components/ui/Header.svelte';
|
|
||||||
import HeroSection from '$lib/components/sections/HeroSection.svelte';
|
|
||||||
import HowItWorksSection from '$lib/components/sections/HowItWorksSection.svelte';
|
|
||||||
import InfoSection from '$lib/components/sections/InfoSection.svelte';
|
|
||||||
import InstagramSection from '$lib/components/sections/InstagramSection.svelte';
|
|
||||||
import BookingWizard from '$lib/components/sections/BookingWizard.svelte';
|
|
||||||
import FounderStorySection from '$lib/components/sections/FounderStorySection.svelte';
|
|
||||||
import ServicesSection from '$lib/components/sections/ServicesSection.svelte';
|
|
||||||
import TestimonialsSection from '$lib/components/sections/TestimonialsSection.svelte';
|
|
||||||
import ValuesSection from '$lib/components/sections/ValuesSection.svelte';
|
|
||||||
import OnboardingPage from '$lib/components/pages/OnboardingPage.svelte';
|
import OnboardingPage from '$lib/components/pages/OnboardingPage.svelte';
|
||||||
|
// ── Homepage design ──────────────────────────────────────────────────
|
||||||
|
// The live homepage renders HomeClarity. To roll back to the original
|
||||||
|
// layout, change the two references below to HomeClassic (its preview lives
|
||||||
|
// at /variants/homepage-original). Both are pure view components; all data
|
||||||
|
// loading, SEO head, and structured data stay here.
|
||||||
|
import HomeClarity from '$lib/components/pages/HomeClarity.svelte';
|
||||||
import { buildAreaServed } from '$lib/seo';
|
import { buildAreaServed } from '$lib/seo';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import type { CallToAction } from '$lib/types';
|
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
const siteUrl = 'https://www.goodwalk.co.nz';
|
const siteUrl = 'https://www.goodwalk.co.nz';
|
||||||
|
|
||||||
// hero_cta experiment: free_emphasis swaps "Book a Meet & Greet"
|
|
||||||
// for "Book a FREE Meet & Greet". Same href, same destination.
|
|
||||||
$: heroAb = data.ab;
|
|
||||||
$: heroOverride =
|
|
||||||
data.siteVariant === 'marketing' && data.content && heroAb?.variant === 'free_emphasis'
|
|
||||||
? ({
|
|
||||||
...data.content.hero.primaryCta,
|
|
||||||
label: 'Book a FREE Meet & Greet',
|
|
||||||
} satisfies CallToAction)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
function absoluteUrl(value: string) {
|
function absoluteUrl(value: string) {
|
||||||
if (value.startsWith('http://') || value.startsWith('https://')) {
|
if (value.startsWith('http://') || value.startsWith('https://')) {
|
||||||
return value;
|
return value;
|
||||||
@@ -166,20 +149,5 @@
|
|||||||
: ''}
|
: ''}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Header navigation={content.navigation} />
|
<HomeClarity {content} ab={data.ab} pagePath="/" />
|
||||||
<HeroSection
|
|
||||||
hero={content.hero}
|
|
||||||
reviewCta={content.intro.reviewCta}
|
|
||||||
primaryCtaOverride={heroOverride}
|
|
||||||
ab={heroAb}
|
|
||||||
/>
|
|
||||||
<ValuesSection values={content.values} />
|
|
||||||
<ServicesSection services={content.services} />
|
|
||||||
<HowItWorksSection content={content.howItWorks} />
|
|
||||||
<TestimonialsSection testimonials={content.testimonials} seedKey="/" />
|
|
||||||
<FounderStorySection founderStory={content.founderStory} />
|
|
||||||
<InfoSection info={content.info} />
|
|
||||||
<BookingWizard booking={content.booking} pagePath="/" ab={heroAb} />
|
|
||||||
<InstagramSection instagram={content.instagram} />
|
|
||||||
<Footer footer={content.footer} />
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
/*
|
||||||
|
* Preview of the clarity homepage design (currently live at /).
|
||||||
|
* Single source of truth is HomeClarity.svelte. noindex so it does not
|
||||||
|
* compete with the real homepage in search.
|
||||||
|
*/
|
||||||
|
import HomeClarity from '$lib/components/pages/HomeClarity.svelte';
|
||||||
|
import { homepageContent } from '$lib/content/homepage';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Homepage clarity design (preview) | Goodwalk</title>
|
||||||
|
<meta name="robots" content="noindex, nofollow" />
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<HomeClarity content={homepageContent} pagePath="/variants/homepage-clarity" />
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
/*
|
||||||
|
* Preview of the original homepage design, kept for rollback reference.
|
||||||
|
* Single source of truth is HomeClassic.svelte. To restore this as the live
|
||||||
|
* homepage, render HomeClassic in src/routes/+page.svelte. noindex so it does
|
||||||
|
* not compete with the real homepage in search.
|
||||||
|
*/
|
||||||
|
import HomeClassic from '$lib/components/pages/HomeClassic.svelte';
|
||||||
|
import { homepageContent } from '$lib/content/homepage';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Homepage original design (preview) | Goodwalk</title>
|
||||||
|
<meta name="robots" content="noindex, nofollow" />
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<HomeClassic content={homepageContent} pagePath="/variants/homepage-original" />
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
/*
|
||||||
|
* Preview of the redesigned Tiny Gang pack walks page.
|
||||||
|
* Single source of truth is TinyGangPage.svelte. noindex so it does not
|
||||||
|
* compete with the live /pack-walks page in search.
|
||||||
|
*
|
||||||
|
* To go live: in src/routes/[slug]/+page.svelte, render TinyGangPage instead
|
||||||
|
* of ServiceLandingPage in the `data.slug === 'pack-walks'` branch.
|
||||||
|
*/
|
||||||
|
import Header from '$lib/components/ui/Header.svelte';
|
||||||
|
import Footer from '$lib/components/ui/Footer.svelte';
|
||||||
|
import TinyGangPage from '$lib/components/pages/TinyGangPage.svelte';
|
||||||
|
import { homepageContent } from '$lib/content/homepage';
|
||||||
|
import { packWalksContent } from '$lib/content/pack-walks';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<title>Tiny Gang pack walks (redesign preview) | Goodwalk</title>
|
||||||
|
<meta name="robots" content="noindex, nofollow" />
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<Header navigation={homepageContent.navigation} />
|
||||||
|
<TinyGangPage content={homepageContent} pageContent={packWalksContent} currentPath="/variants/tiny-gang" />
|
||||||
|
<Footer footer={homepageContent.footer} />
|
||||||
Reference in New Issue
Block a user