4.2.1 polish

This commit is contained in:
2026-05-06 08:27:24 +12:00
parent 55217f59bd
commit b8b9d12a82
16 changed files with 700 additions and 25 deletions
+236
View File
@@ -0,0 +1,236 @@
# Mobile Polish — Conversion & Comfort Audit Tracker
Findings from a focused mobile-experience review (≤768px, with extra
attention to 375px small-phones). Desktop is considered done. Each item
records the where, why, and the concrete change.
> Important context for prioritisation: a dog-walking business is a
> jobs-to-be-done service that users research on the couch. Mobile
> conversion lower-bound is "they Meet & Greet". So the dial-movers
> are: thumb-reach for the booking CTA, legibility, friction-free
> form, and trust signals visible on the first scroll.
---
## High — direct conversion impact
- [x] **Hero buttons stack awkwardly at ~375px**
- File: `src/lib/styles/responsive.css` (new ≤480px block)
- Implementation: At `@media (max-width: 480px)` the hero buttons
flip to `flex-direction: column; gap: 12px;` and each button
becomes full-width with padding `16px 24px`. The 768px-specific
`padding-right: 18px` on the buttons row and the
`margin-right: 12px` on `:last-child` are both reset so the two
CTAs read as a clean stacked stack.
- Why: At 375px the side-by-side primary + outline CTAs were wrapping
unevenly and the primary's prominence collapsed.
- [x] **Mobile header phone button is low-contrast and small**
- File: `src/lib/styles/responsive.css:88-100`
- Implementation: Background bumped from `rgba(33, 48, 33, 0.06)`
`0.10`; padding 9px 12px → 11px 14px (and at ≤480px, 10px 12px);
`font-weight: 600`; `min-height: 44px` to meet the touch-target
minimum; icon size also bumped from 13px → 14px.
- Why: The tap-to-call on the mobile header is one of the highest
intent actions on the site. It now reads as a button rather than a
barely-visible label.
- [x] **Booking form inputs trigger iOS zoom-on-focus**
- File: `src/lib/styles/responsive.css:453-462`
- Implementation: Input `font-size: 15px``16px` at ≤768px. Comment
added explaining the iOS-Safari 16px threshold so a future edit
doesn't accidentally drop it again.
- Why: Below 16px Safari auto-zooms on focus; the page jolts every
time a field is tapped. Critical at the booking conversion step.
- [ ] **Testimonial carousel arrows hard to reach at 375px**
- File: `src/lib/components/TestimonialsSection.svelte` (mobile rules
lines ~640-660)
- Current: arrows pinned to `bottom: 24px` inside a stage with
`padding-bottom: 116px`. Visually at the bottom of a tall card —
requires deliberate stretching.
- Why: The carousel feels passive. Users don't realise they can advance
it; testimonials sell — losing that engagement matters.
- Fix: Lift arrows on small screens:
```css
@media (max-width: 480px) {
.testimonial-arrow { bottom: 80px; }
}
```
- [x] **No persistent "Book Meet & Greet" CTA on mobile after hero scrolls past**
- Files: `src/lib/components/MobileBookBar.svelte` (new),
`src/routes/+layout.svelte` (mount), `src/lib/styles/responsive.css`
(`body { padding-bottom: 64px }` at ≤768px).
- Implementation v2 — *Airbnb-style soft-container, scroll-triggered*:
- **Soft container**: a translucent white tray (`rgba(255, 255,
255, 0.96)` with backdrop-filter blur) sits flush at the bottom
with a thin top hairline and a soft shadow. The brand-yellow CTA
pill lives *inside* the tray, not as the tray itself — so the
action stays unmistakable but the surrounding chrome is calm.
- **Scroll-triggered**: the bar slides up + fades in only after the
user has scrolled ~480px (≈ one mobile viewport, hero out of
view). Slides back out below ~120px to avoid flicker near the
top. `prefers-reduced-motion` respected — the slide is dropped,
only the opacity fade remains.
- **Hidden on `/contact-us` and `/booking`** (already in the form).
- **Resets on navigation**: `afterNavigate` resets `visible = false`
so each new page starts hidden until the user has earned it again.
- **Accessibility**: `aria-hidden` toggles with visibility; CTA
`tabindex` flips to `-1` while hidden so keyboard users don't
stumble onto an off-screen control; `pointer-events: none` while
hidden so the user can't accidentally tap it during the fade.
- **Safe-area aware**: `env(safe-area-inset-bottom)` so iPhones
with the home-bar gesture area get correct spacing.
- Body bottom-padding of 64px on mobile keeps the footer from sitting
behind the bar when it's visible at the bottom of long pages.
- Why: Sticky mobile CTAs are a validated conversion pattern (Airbnb,
Booking.com, etc.), but the v1 full-yellow bar was tonally wrong
for Goodwalk — it competed with the calm brand voice and dominated
the screen permanently. v2 keeps the conversion benefit (one-tap
booking, always one swipe away after engagement) without yelling.
## Medium — legibility & polish
- [x] **Hero title can wrap awkwardly at 375px**
- File: `src/lib/styles/responsive.css` (≤480px block)
- Implementation: At ≤480px the desktop H1 drops to 32px /
line-height 1.12 and the dedicated `.hero-heading-mobile` element
drops to 30px / 1.12 (it was 33.5px). The two-line "Unleashing Fun
in / Your Dog's Day!" now sits comfortably with breathing room
above the subtitle.
- Why: Previous 38px / 33.5px sizings were a hair too big for 375px;
line-2 felt cramped against the subtitle.
- [x] **Body text 15px feels small on mobile (and on ultra-wide desktop)**
- File: `src/lib/styles/responsive.css` (≤768px body rule)
- Implementation: `body { font-size: 16px; }` at ≤768px, with a
comment noting the iOS-Safari 16px zoom threshold so this rule
isn't accidentally undone.
- Desktop (≥769px) still inherits 15px from `base.css` for now — the
ultra-wide bump is tracked separately at the bottom of this file
so it can be reasoned about independently (clamp vs. breakpoint).
- Why: 16px is the modern legibility standard on mobile, dovetails
with the iOS zoom-on-focus rule for inputs, and reduces read
fatigue on long pages (about, FAQ answers, legal).
- [ ] **FAQ summary tap target too small**
- File: `src/lib/styles/sections.css` (`.faq summary` rules)
- Current: just text height (~22-26px) — below the 44px minimum.
- Fix:
```css
.faq summary {
padding: 12px 0;
min-height: 44px;
display: flex; align-items: center;
}
```
- [ ] **Booking service-option chips wrap awkwardly at 375px**
- File: `src/lib/styles/responsive.css:468-470`
- Fix at ≤480px: 2-up grid with tighter gap:
```css
.booking-service-options { gap: 10px 12px; }
.booking-toggle-option { flex: 1 1 calc(50% - 6px); }
```
- [ ] **Footer social icons spaced too tight for thumbs**
- File: `src/lib/styles/sections.css` (`.social-links { gap: 14px }`)
- Current 14px gap with 40px icons → centres are 54px apart. Apple
HIG wants 8px+ between targets after the 44px minimum.
- Fix:
```css
@media (max-width: 768px) { .social-links { gap: 18px; } }
```
- [ ] **Pricing cards stack to 1-col with multiple "Book" buttons in a row**
- File: `src/lib/components/PricingPage.svelte` (and ServiceLandingPage
plan grids)
- Why: After stacking, the user sees Plan → Book → Plan → Book → Plan
→ Book in vertical sequence. The repetition reads as noise rather
than choice; the "popular" anchor disappears.
- Fix (opinionated): on mobile, only the popular plan keeps its CTA
button. Other plans show a smaller "Choose this plan" link instead,
or no per-card CTA at all (a single CTA appears under the grid):
```css
@media (max-width: 768px) {
.pricing-plan-card:not(.pricing-plan-popular) .pricing-plan-cta {
display: none;
}
}
```
Then keep the existing under-grid `.service-plan-reassurance` pill
and add a single "Book a Meet & Greet" button below it.
- [ ] **Mobile nav header is too tall — eats above-the-fold real estate**
- File: `src/lib/styles/responsive.css:74`
- Current: `nav { padding: 20px 24px; }` + 25px logo = ~65px header.
On iPhone 13 (844px), this leaves ~380px for hero before scroll —
less than what the Goodwalk dog-image needs to feel like a hero.
- Fix at ≤480px:
```css
nav { padding: 14px 20px; }
.logo img { height: 22px; }
```
## Low — incremental polish
- [ ] **Hero top padding generous at 375px**
- File: `src/lib/styles/responsive.css:179`
- Reduce hero `padding-top` from 50px to 32px at ≤480px.
- [ ] **Intro trust badge feels edge-to-edge at 375px**
- File: `src/lib/styles/responsive.css:296-301`
- Add `padding: 18px 16px; margin: 0 12px;` at ≤480px.
- [ ] **Testimonial quote mark too large at 375px**
- File: `src/lib/components/TestimonialsSection.svelte:~595`
- Current: 44px. Reduce to 36px at ≤480px.
- [ ] **Booking form labels could shrink slightly at 375px**
- File: `src/lib/styles/responsive.css:450`
- Optional: 16px → 15px at ≤480px to give field width back to the
input value (where it actually matters for legibility).
- [ ] **No scroll-to-top affordance on long pages (booking, pricing)**
- Currently absent. Low priority but helpful when users have scrolled
past the booking form and want to re-read service details. Could be
folded into the same sticky-book-bar work above (one bar, both jobs).
## Open from elsewhere
- [ ] **Ultra-wide desktop body font feels small** *(noted by user)*
- Currently `body { font-size: 15px }` ([base.css:15](src/lib/styles/base.css#L15)).
On ≥1800px screens with the `--max-w` already widening (per the
1800px breakpoint), 15px in long-form sections (about, FAQ answers,
legal) becomes uncomfortable.
- Suggested fix: bump to `clamp(15px, 0.95vw, 17px)` on `body`, OR
introduce a `@media (min-width: 1600px) { body { font-size: 17px; } }`.
Either keeps the desktop ≤1599px experience identical and only
expands type when there's genuinely more reading width.
## Deliberately not actioning
- **Drop reveal animations on mobile to "save bandwidth".** They're
IntersectionObserver-driven, cost nothing perceivable, and add brand
polish. Removing them would make the mobile site feel cheaper for no
measurable performance gain.
- **Replace the testimonial carousel with a stacked list on mobile.**
Tempting (carousels famously hide content), but the carousel is
central to the brand's "see real dogs" pitch. Better to fix the arrow
reachability and let the autoplay do the work.
---
## Suggested order of attack
If you want one batch that moves the dial: **High items 1, 2, 3, 5**
together is roughly an hour of work — they hit the hero, the header
tap-to-call, the booking form's biggest mobile bug (zoom-on-focus), and
add the sticky CTA. That's the package I'd ship first. Item 4 (carousel
arrows) is a one-line fix once you're already in `responsive.css`.
The Medium list is best as a second pass — body-text bump,
header-padding reduction, FAQ tap-target, and pricing-card-CTA dedupe
all compound into a noticeably more "intentional on mobile" feel
without any structural change.
+143
View File
@@ -0,0 +1,143 @@
# UX Polish — Conversion Audit Tracker
Findings from the senior-marketing-lens audit, with completion status. Each
item has a one-line rationale and the file/line where the change lives (or
will live).
> Only commit to "We'll reply within 24 hours" if Aless can actually hold
> to it. If response time is more like 1-2 business days, soften to
> "within one business day".
---
## High — direct conversion impact
- [x] **Hero primary CTA: "Learn more" → "Explore our services →"**
- File: `src/lib/content/homepage.ts:38`
- Why: "Learn more" is the lowest-intent CTA that exists.
- [x] **Promise CTA: "See our services" → "Book a free Meet & Greet"**
- File: `src/lib/content/homepage.ts:59`
- Also: target changed from `#services` to `/contact-us` so the CTA goes
to the booking page instead of bouncing back up to a service list.
- Why: After the value prop + happy-dog photo, sending visitors to the
services list is a step backwards. Push them to book.
- [x] **Booking subtitle now states response time**
- File: `src/lib/content/homepage.ts:159-162`
- Old: *"...so we can reach out to arrange your free, no-obligation Meet & Greet."*
- New: *"...We'll reply within 24 hours to arrange your free, no-obligation Meet & Greet."*
- General-enquiry variant updated to match.
- Why: Open-ended "we'll reach out" creates anxiety at submit time.
- [x] 1 **Pricing page — Google rating trust signal above plan grid**
- File: `src/lib/components/PricingPage.svelte`
- Implementation: Pill-styled trust badge inside the green hero,
directly under the subtitle — five yellow stars + "30+ five-star
Google reviews" label + arrow, links out to Google. Styled to read
against the green hero (semi-transparent white pill) rather than
reusing the cream IntroStrip, which would have clashed.
- Why: Visitors land on pricing mid-decision; trust signal now appears
before the plan grid.
- [x] 2 **Service plan CTAs — add free / no-obligation reassurance**
- File: `src/lib/components/ServiceLandingPage.svelte`
- Implementation: A subtle green pill *"Every booking starts with a
free, no-obligation Meet & Greet."* (yellow shield-heart icon) sits
centred directly under the plan grid on every service page, above the
Extras block. Reuses the brand-tinted-pill aesthetic so it feels
native, not tacked on.
- Why: The "Book a Meet & Greet" buttons under each plan didn't carry
risk-reversal phrasing in their immediate context. Now they do.
## Medium — trust + polish
- [x] 3 **Quantify the Google rating wherever it appears**
- Files: `src/lib/content/homepage.ts:46`,
`src/lib/components/Footer.svelte:89`,
`src/lib/components/TestimonialsSection.svelte:200`,
`src/lib/components/PricingPage.svelte` (new pricing-trust pill).
- Implementation: "All 5 star reviews on Google!" → "30+ five-star
Google reviews" everywhere. Aless confirmed 30+ as the count.
- Why: A specific number is dramatically more credible than "all".
- [x] 4 **Lean into the "limited spots" angle**
- File: `src/lib/content/pack-walks.ts` (added `scarcityNote` to the
`pricing` block); `src/lib/types.ts` (added optional `scarcityNote?:
string` to ServicePageContent.pricing); rendered in
`src/lib/components/ServiceLandingPage.svelte` directly under the
plan grid as a yellow-tinted pill with a clock icon.
- Copy: *"We keep packs small (4-8 dogs) — popular days fill up fast."*
- Only set on Pack Walks (the 4-8 number is specific to that service);
the field is optional so 1:1 Walks and Puppy Visits get nothing.
- Why: Real, honest scarcity. The 4-8 cap is already a fact; saying it
out loud nudges decision-making.
- [ ] **About page — quantify Aless's expertise**
- File: `src/lib/content/about.ts:29-30`
- Why: "years of experience" is the weakest possible claim. Replace with
concrete numbers Aless can stand behind: years operating, dogs in
rotation, first-aid certification.
- [x] 5 **Pack Walks pricing intro — lead with the differentiator**
- File: `src/lib/content/pack-walks.ts:23-24`
- Implementation: Old intro led with "Our pack walks are a permanent
booking of at least one walk day a week..." (commitment ask first).
New intro leads with the benefits: *"Small packs of 4-8 dogs, 2-hour
outings at Auckland's scenic dog parks and beaches, with free pick-up
and drop-off included. We reinforce recall, car manners, and leash
etiquette while your dog plays. Booked as a permanent weekly slot —
gift your dog the best life!"*
- Why: Buyers scan for benefits before commitments. Lead-with-policy
framing creates resistance; lead-with-benefit framing builds desire.
- [ ] **FAQs — reframe from policy to reassurance**
- File: `src/lib/content/homepage.ts:180-205`
- Why: Answers are correct but read like terms & conditions. Lead with
the *why* (the benefit/reassurance), then the *what*.
## Low — incremental polish
- [x] **Home services-card CTAs: "Learn more" → outcome-oriented**
- File: `src/lib/components/ServicesSection.svelte:29`
- Implementation: Visible label is now derived from the service title —
*"See Pack Walks pricing →"*, *"See 1:1 Walks pricing →"*, *"See
Puppy Visits pricing →"*. The previously-added screen-reader-only
"about <Service>" span was removed since the visible label now carries
that context for everyone, not just assistive tech users.
- Why: "Learn more" was the lowest-intent CTA on the page; the new
label states the destination and the next step.
- [x] **Testimonials intro blurb — sharper jobs-to-be-done framing**
- File: `src/lib/components/TestimonialsSection.svelte:10-11`
- Old: *"Happy owners, even happier dogs. Our Auckland dog walking
clients love what the Tiny Gang brings to their dog's routine — and
you can see why. Follow along on Instagram for daily adventures..."*
- New: *"Busy parents get peace of mind. Dogs come home tired and
happy. See why 30+ Auckland families trust the Tiny Gang — follow
along on Instagram for daily adventures, wagging tails and the odd
zoomie."*
- Why: Leads with the two outcomes buyers actually care about (peace of
mind for them, exercise for the dog), keeps the brand voice + 30+
review proof point, then makes the Instagram nudge feel like a
follow-on rather than the lead.
- [x] **Surface "Reliability / on-time" earlier**
- File: `src/lib/content/homepage.ts:37` (hero subtitle)
- Old: *"Trusted, professional dog walking across Auckland Central..."*
- New: *"Trusted, on-time dog walking across Auckland Central..."*
- Why: Reliability/punctuality is the #1 anxiety for busy parents
booking a service that visits their home. Pulling "on-time" into the
hero subtitle (one-word swap, no length cost) puts the reassurance
above the fold.
## Deliberately not actioning
- **Pack Walks H1 rewrite to "Small-dog pack walks designed for calm,
confident groups."** *"Join our Tiny Gang!"* is doing brand work — it's
memorable and reinforces a phrase used everywhere else. Rewriting kills
the most distinctive asset for marginal headline clarity.
- **Booking submit button: "Send" → "Book my Meet & Greet".** The form
also handles general enquiries, so a "book my…" label would feel wrong
on a complaint email. Better fix would be to switch the label by
`enquiryType` — keep "Send my booking" / "Send my enquiry" contextually.
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "goodwalk-svelte-port", "name": "goodwalk-svelte-port",
"version": "4.2.0", "version": "4.2.1",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
+1 -1
View File
@@ -86,7 +86,7 @@
class="footer-reviews" class="footer-reviews"
> >
<Icon name="fab fa-google" /> <Icon name="fab fa-google" />
<span>See our 5&#9733; Google reviews</span> <span>30+ five-star Google reviews</span>
</a> </a>
{#if footer.email || footer.phone} {#if footer.email || footer.phone}
+147
View File
@@ -0,0 +1,147 @@
<script lang="ts">
import { onMount } from 'svelte';
import { afterNavigate } from '$app/navigation';
import { page } from '$app/stores';
import Icon from '$lib/components/Icon.svelte';
/*
* Sticky bottom CTA shown on mobile only.
*
* Pattern is the Airbnb-style "soft container, scroll-triggered" —
* - white card sits flush against the bottom edge with a thin top
* hairline and a soft shadow so it reads as a tray, not a banner;
* - the brand-yellow pill CTA lives inside the card so the action
* is unmistakable but the surrounding chrome stays calm;
* - the bar only appears after the user has scrolled roughly one
* viewport (~hero out of view), so it doesn't compete with the
* in-page hero CTA.
*
* Hidden on the contact / booking flows (no point reminding someone
* to book while they're already on the form).
*/
$: pathname = $page.url.pathname;
$: hidden = pathname === '/contact-us' || pathname === '/booking';
const SHOW_AFTER_PX = 480; // close to one mobile viewport
const HIDE_BELOW_PX = 120; // generous so the bar doesn't flicker near the top
let visible = false;
function evaluateVisibility() {
const y = window.scrollY;
if (y > SHOW_AFTER_PX) {
visible = true;
} else if (y < HIDE_BELOW_PX) {
visible = false;
}
}
afterNavigate(() => {
visible = false;
});
onMount(() => {
evaluateVisibility();
window.addEventListener('scroll', evaluateVisibility, { passive: true });
window.addEventListener('resize', evaluateVisibility, { passive: true });
return () => {
window.removeEventListener('scroll', evaluateVisibility);
window.removeEventListener('resize', evaluateVisibility);
};
});
</script>
{#if !hidden}
<div
class="mobile-book-bar"
class:mobile-book-bar-visible={visible}
aria-hidden={!visible}
>
<a class="mobile-book-bar-cta" href="/contact-us" tabindex={visible ? 0 : -1}>
<Icon name="fas fa-paw" />
<span>Book a free Meet &amp; Greet</span>
<Icon name="fas fa-arrow-right" className="mobile-book-bar-arrow" />
</a>
</div>
{/if}
<style>
.mobile-book-bar {
display: none;
}
@media (max-width: 768px) {
.mobile-book-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
z-index: 50;
display: flex;
justify-content: center;
padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
background: rgba(255, 255, 255, 0.96);
backdrop-filter: blur(10px);
border-top: 1px solid rgba(17, 20, 24, 0.08);
box-shadow: 0 -10px 28px rgba(17, 20, 24, 0.1);
opacity: 0;
transform: translateY(110%);
pointer-events: none;
transition:
opacity 0.22s ease,
transform 0.28s cubic-bezier(0.22, 1, 0.36, 1);
}
.mobile-book-bar-visible {
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
.mobile-book-bar-cta {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
width: 100%;
max-width: 460px;
padding: 13px 22px;
border-radius: 999px;
background: var(--yellow);
color: #000;
font-family: var(--font-head);
font-size: 15px;
font-weight: 700;
letter-spacing: 0.01em;
text-decoration: none;
box-shadow: 0 8px 18px rgba(255, 209, 0, 0.4);
transition:
background 0.18s ease,
transform 0.18s cubic-bezier(0.22, 1, 0.36, 1);
}
.mobile-book-bar-cta:active {
transform: translateY(1px) scale(0.995);
background: #e6bb00;
}
:global(.mobile-book-bar-cta .icon) {
font-size: 13px;
}
:global(.mobile-book-bar-cta .mobile-book-bar-arrow) {
font-size: 12px;
opacity: 0.75;
}
@media (prefers-reduced-motion: reduce) {
.mobile-book-bar {
transition: opacity 0.22s ease;
transform: none;
}
}
}
</style>
+56
View File
@@ -96,6 +96,22 @@
{#if pageContent.subtitle} {#if pageContent.subtitle}
<p class="pricing-page-sub">{pageContent.subtitle}</p> <p class="pricing-page-sub">{pageContent.subtitle}</p>
{/if} {/if}
<a
class="pricing-trust"
href="https://g.page/r/CUsvrWPhkYrAEB0/"
target="_blank"
rel="noopener"
aria-label="Read our 5-star Google reviews"
>
<span class="pricing-trust-stars" aria-hidden="true">
{#each Array(5) as _}
<Icon name="fas fa-star" />
{/each}
</span>
<span class="pricing-trust-label">30+ five-star Google reviews</span>
<Icon name="fas fa-arrow-right" className="pricing-trust-arrow" />
</a>
</div> </div>
</section> </section>
@@ -204,6 +220,46 @@
color: rgba(255, 255, 255, 0.7); color: rgba(255, 255, 255, 0.7);
} }
.pricing-trust {
display: inline-flex;
align-items: center;
gap: 12px;
margin-top: 22px;
padding: 9px 18px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.18);
color: #fff;
font-size: 14px;
font-weight: 600;
text-decoration: none;
transition:
background 0.2s ease,
transform 0.18s cubic-bezier(0.22, 1, 0.36, 1);
}
.pricing-trust:hover {
background: rgba(255, 255, 255, 0.18);
transform: translateY(-1px);
}
.pricing-trust-stars {
display: inline-flex;
align-items: center;
gap: 2px;
color: var(--yellow);
font-size: 13px;
}
.pricing-trust-label {
letter-spacing: 0.01em;
}
:global(.pricing-trust .pricing-trust-arrow) {
font-size: 12px;
opacity: 0.85;
}
.pricing-section-heading h2 { .pricing-section-heading h2 {
margin: 0; margin: 0;
text-align: center; text-align: center;
@@ -113,6 +113,18 @@
{/each} {/each}
</div> </div>
{#if pageContent.pricing.scarcityNote}
<p class="service-plan-scarcity">
<Icon name="fas fa-clock" />
{pageContent.pricing.scarcityNote}
</p>
{/if}
<p class="service-plan-reassurance">
<Icon name="fas fa-shield-heart" />
Every booking starts with a free, no-obligation Meet &amp; Greet.
</p>
{#if pageContent.pricing.extras?.length} {#if pageContent.pricing.extras?.length}
<div class="service-extras"> <div class="service-extras">
<div class="service-extras-heading">Extras</div> <div class="service-extras-heading">Extras</div>
@@ -554,6 +566,42 @@
font-family: var(--font-head); font-family: var(--font-head);
} }
.service-plan-reassurance,
.service-plan-scarcity {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
width: fit-content;
margin: 24px auto 0;
padding: 8px 16px;
border-radius: 999px;
background: rgba(33, 48, 33, 0.06);
color: var(--green);
font-size: 14px;
font-weight: 600;
}
.service-plan-scarcity {
background: rgba(255, 209, 0, 0.18);
color: #5a4500;
}
.service-plan-reassurance + .service-plan-reassurance,
.service-plan-scarcity + .service-plan-reassurance {
margin-top: 12px;
}
:global(.service-plan-reassurance .icon) {
color: var(--yellow);
font-size: 14px;
}
:global(.service-plan-scarcity .icon) {
color: #b88800;
font-size: 14px;
}
.service-extras { .service-extras {
margin-top: 30px; margin-top: 30px;
border-radius: 28px; border-radius: 28px;
+1 -1
View File
@@ -27,7 +27,7 @@
{#if service.href} {#if service.href}
<a href={service.href} class="btn btn-green"> <a href={service.href} class="btn btn-green">
Learn more<span class="visually-hidden"> about {service.title}</span> See {service.title} pricing →
</a> </a>
{/if} {/if}
</div> </div>
@@ -8,7 +8,7 @@
export let testimonials: TestimonialContent[]; export let testimonials: TestimonialContent[];
export let heading = 'Why people choose us!'; export let heading = 'Why people choose us!';
export let blurb = export let blurb =
"Happy owners, even happier dogs. Our Auckland dog walking clients love what the Tiny Gang brings to their dog's routine — and you can see why. Follow along on Instagram for daily adventures, wagging tails and the odd zoomie"; 'Busy parents get peace of mind. Dogs come home tired and happy. See why 30+ Auckland families trust the Tiny Gang — follow along on Instagram for daily adventures, wagging tails and the odd zoomie.';
export let instagramHref = 'https://www.instagram.com/goodwalk.nz/'; export let instagramHref = 'https://www.instagram.com/goodwalk.nz/';
export let instagramLabel = 'goodwalk.nz'; export let instagramLabel = 'goodwalk.nz';
@@ -197,7 +197,7 @@
rel="noopener" rel="noopener"
> >
<Icon name="fab fa-google" /> <Icon name="fab fa-google" />
<span>All 5 star reviews on Google!</span> <span>30+ five-star Google reviews</span>
</a> </a>
</div> </div>
</article> </article>
+6 -6
View File
@@ -34,8 +34,8 @@ export const homepageContent: HomePageContent = {
title: 'Unleashing Fun in', title: 'Unleashing Fun in',
highlight: "Your Dog's Day!", highlight: "Your Dog's Day!",
mobileTitle: "Unleashing Fun in\nYour Dog's Day!", mobileTitle: "Unleashing Fun in\nYour Dog's Day!",
subtitle: 'Trusted, professional dog walking across Auckland Central — pack walks, 1:1 walks, and puppy visits.', subtitle: 'Trusted, on-time dog walking across Auckland Central — pack walks, 1:1 walks, and puppy visits.',
primaryCta: { label: 'Learn more', href: '#services', variant: 'yellow' }, primaryCta: { label: 'Explore our services →', href: '#services', variant: 'yellow' },
secondaryCta: { label: 'Book a Meet & Greet', href: '#newlead', variant: 'outline' }, secondaryCta: { label: 'Book a Meet & Greet', href: '#newlead', variant: 'outline' },
imageUrl: '/images/auckland-dog-walking-happy-dog-hero.png', imageUrl: '/images/auckland-dog-walking-happy-dog-hero.png',
imageAlt: 'Happy dog ready for a professional pack walk with Goodwalk Auckland dog walking service' imageAlt: 'Happy dog ready for a professional pack walk with Goodwalk Auckland dog walking service'
@@ -43,7 +43,7 @@ export const homepageContent: HomePageContent = {
intro: { intro: {
text: 'Trusted by Auckland dog parents.', text: 'Trusted by Auckland dog parents.',
reviewCta: { reviewCta: {
label: 'All 5 star reviews on Google!', label: '30+ five-star Google reviews',
href: 'https://g.page/r/CUsvrWPhkYrAEB0/', href: 'https://g.page/r/CUsvrWPhkYrAEB0/',
external: true external: true
} }
@@ -56,7 +56,7 @@ export const homepageContent: HomePageContent = {
'Professional dog walking across Auckland for small, medium and large breeds, with tailored pack walks for smaller dogs and one-on-one walks for larger breeds — giving every dog the personalised attention they deserve. Ready to join our' 'Professional dog walking across Auckland for small, medium and large breeds, with tailored pack walks for smaller dogs and one-on-one walks for larger breeds — giving every dog the personalised attention they deserve. Ready to join our'
], ],
emphasis: 'TINY GANG?', emphasis: 'TINY GANG?',
cta: { label: 'See our services', href: '#services', variant: 'green' }, cta: { label: 'Book a free Meet & Greet', href: '/contact-us', variant: 'green' },
imageUrl: '/images/auckland-dog-walking-happy-dogs-happy-humans.webp', imageUrl: '/images/auckland-dog-walking-happy-dogs-happy-humans.webp',
imageAlt: 'Woman cuddling a dog for Goodwalk Auckland dog walking services' imageAlt: 'Woman cuddling a dog for Goodwalk Auckland dog walking services'
}, },
@@ -157,9 +157,9 @@ export const homepageContent: HomePageContent = {
booking: { booking: {
title: "Let's meet!", title: "Let's meet!",
subtitle: subtitle:
"Almost there — just your contact details so we can reach out to arrange your free, no-obligation Meet & Greet.", "Almost there — just your contact details. We'll reply within 24 hours to arrange your free, no-obligation Meet & Greet.",
generalSubtitle: generalSubtitle:
"Almost there — just your contact details so we can reply properly to your message.", "Almost there — just your contact details. We'll reply within 24 hours.",
formAction: '/contact-us', formAction: '/contact-us',
serviceOptions: ['Pack Walks', '1:1 Walks', 'Puppy Visits', 'Other Services'], serviceOptions: ['Pack Walks', '1:1 Walks', 'Puppy Visits', 'Other Services'],
ownerStepLabel: 'Your details', ownerStepLabel: 'Your details',
+3 -2
View File
@@ -21,7 +21,7 @@ export const packWalksContent: ServicePageContent = {
pricing: { pricing: {
title: 'Tiny Gang Prices', title: 'Tiny Gang Prices',
intro: intro:
'Our pack walks are a permanent booking of at least one walk day a week. Our Tiny Gang pack outing typically lasts 2 hours or more, including a one-hour walk at one of Aucklands scenic dog parks or beaches. Additionally, pick-up and drop-off services are provided for your convenience. We assist in reinforcing basic training, including recall, car manners, and leash etiquette. Gift your dog the best life!', 'Small packs of 4-8 dogs, 2-hour outings at Aucklands scenic dog parks and beaches, with free pick-up and drop-off included. We reinforce recall, car manners, and leash etiquette while your dog plays. Booked as a permanent weekly slot — gift your dog the best life!',
plans: [ plans: [
{ {
title: '1 Walk', title: '1 Walk',
@@ -53,7 +53,8 @@ export const packWalksContent: ServicePageContent = {
{ label: 'Extra Dog', note: 'From same household', price: '$35' }, { label: 'Extra Dog', note: 'From same household', price: '$35' },
{ label: 'Muddy Wash', price: '$35' }, { label: 'Muddy Wash', price: '$35' },
{ label: '5 Hour Day Out', note: 'Not suitable for all dogs', price: '$90' } { label: '5 Hour Day Out', note: 'Not suitable for all dogs', price: '$90' }
] ],
scarcityNote: 'We keep packs small (4-8 dogs) — popular days fill up fast.'
}, },
benefits: { benefits: {
title: 'Tiny Gang membership benefits', title: 'Tiny Gang membership benefits',
+2 -2
View File
@@ -58,8 +58,8 @@ nav {
} }
.nav-links > li > a.nav-link-active { .nav-links > li > a.nav-link-active {
background: #eadbbf; background: #fff;
color: #000; color: var(--green);
} }
.mega-chevron { .mega-chevron {
+48 -5
View File
@@ -35,6 +35,18 @@
} }
@media (max-width: 768px) { @media (max-width: 768px) {
/*
* Mobile body type bumps to 16px modern legibility standard, and
* matches the iOS-Safari zoom-on-focus threshold. Reserve room at
* the bottom of the page for the sticky MobileBookBar so the footer
* never sits behind it; the bar adds its own safe-area-inset
* padding on top of this.
*/
body {
font-size: 16px;
padding-bottom: 64px;
}
@keyframes mobileMenuBounceIn { @keyframes mobileMenuBounceIn {
0% { 0% {
opacity: 0; opacity: 0;
@@ -89,14 +101,16 @@
display: inline-flex; display: inline-flex;
justify-self: center; justify-self: center;
align-self: center; align-self: center;
padding: 9px 12px; min-height: 44px;
background: rgba(33, 48, 33, 0.06); padding: 11px 14px;
background: rgba(33, 48, 33, 0.1);
color: var(--green); color: var(--green);
font-size: 13px; font-size: 13px;
font-weight: 600;
} }
.mobile-phone .icon { .mobile-phone .icon {
font-size: 13px; font-size: 14px;
} }
.nav-links { .nav-links {
@@ -453,7 +467,13 @@
.booking-field-card input, .booking-field-card input,
.booking-field-card textarea { .booking-field-card textarea {
padding: 14px 18px; padding: 14px 18px;
font-size: 15px; /*
* 16px is the iOS-Safari threshold for triggering auto-zoom on
* focus. Anything smaller and the page jolts every time a field
* is tapped kills the form's perceived quality at the most
* critical conversion step.
*/
font-size: 16px;
border-width: 2px; border-width: 2px;
border-radius: 18px; border-radius: 18px;
} }
@@ -554,11 +574,34 @@
@media (max-width: 480px) { @media (max-width: 480px) {
.mobile-phone { .mobile-phone {
gap: 6px; gap: 6px;
padding: 9px 10px; padding: 10px 12px;
font-size: 12px; font-size: 12px;
} }
.mobile-phone span { .mobile-phone span {
letter-spacing: -0.01em; letter-spacing: -0.01em;
} }
.hero-text h1,
.hero-heading {
font-size: 32px;
line-height: 1.12;
}
.hero-text h1 .hero-heading-mobile {
font-size: 30px;
line-height: 1.12;
}
.hero-buttons {
flex-direction: column;
gap: 12px;
padding-right: 0;
}
.hero-buttons .btn {
width: 100%;
margin-right: 0 !important;
padding: 16px 24px;
}
} }
+2 -5
View File
@@ -213,12 +213,8 @@ section {
background: #fff; background: #fff;
} }
.promise-text h2 {
text-align: center;
}
.promise-text p { .promise-text p {
margin-bottom: 28px; margin: 0 auto 28px;
font-size: 16px; font-size: 16px;
max-width: 520px; max-width: 520px;
} }
@@ -231,6 +227,7 @@ section {
.promise-text { .promise-text {
order: 2; order: 2;
text-align: center;
} }
.promise-img { .promise-img {
+1
View File
@@ -124,6 +124,7 @@ export interface ServicePageContent {
intro?: string; intro?: string;
plans: ServicePricingPlan[]; plans: ServicePricingPlan[];
extras?: ServiceExtra[]; extras?: ServiceExtra[];
scarcityNote?: string;
}; };
benefits: { benefits: {
title: string; title: string;
+3
View File
@@ -3,6 +3,7 @@
import { navigating, page } from '$app/stores'; import { navigating, page } from '$app/stores';
import { afterNavigate, disableScrollHandling } from '$app/navigation'; import { afterNavigate, disableScrollHandling } from '$app/navigation';
import { initClickTracking, trackPageView } from '$lib/analytics'; import { initClickTracking, trackPageView } from '$lib/analytics';
import MobileBookBar from '$lib/components/MobileBookBar.svelte';
import RouteSkeleton from '$lib/components/RouteSkeleton.svelte'; import RouteSkeleton from '$lib/components/RouteSkeleton.svelte';
import '$lib/styles/variables.css'; import '$lib/styles/variables.css';
import '$lib/styles/base.css'; import '$lib/styles/base.css';
@@ -72,6 +73,8 @@
{/if} {/if}
</div> </div>
<MobileBookBar />
<style> <style>
.layout-shell { .layout-shell {
position: relative; position: relative;