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
+1 -1
View File
@@ -86,7 +86,7 @@
class="footer-reviews"
>
<Icon name="fab fa-google" />
<span>See our 5&#9733; Google reviews</span>
<span>30+ five-star Google reviews</span>
</a>
{#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}
<p class="pricing-page-sub">{pageContent.subtitle}</p>
{/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>
</section>
@@ -204,6 +220,46 @@
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 {
margin: 0;
text-align: center;
@@ -113,6 +113,18 @@
{/each}
</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}
<div class="service-extras">
<div class="service-extras-heading">Extras</div>
@@ -554,6 +566,42 @@
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 {
margin-top: 30px;
border-radius: 28px;
+1 -1
View File
@@ -27,7 +27,7 @@
{#if service.href}
<a href={service.href} class="btn btn-green">
Learn more<span class="visually-hidden"> about {service.title}</span>
See {service.title} pricing →
</a>
{/if}
</div>
@@ -8,7 +8,7 @@
export let testimonials: TestimonialContent[];
export let heading = 'Why people choose us!';
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 instagramLabel = 'goodwalk.nz';
@@ -197,7 +197,7 @@
rel="noopener"
>
<Icon name="fab fa-google" />
<span>All 5 star reviews on Google!</span>
<span>30+ five-star Google reviews</span>
</a>
</div>
</article>
+6 -6
View File
@@ -34,8 +34,8 @@ export const homepageContent: HomePageContent = {
title: 'Unleashing Fun in',
highlight: "Your 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.',
primaryCta: { label: 'Learn more', href: '#services', variant: 'yellow' },
subtitle: 'Trusted, on-time dog walking across Auckland Central — pack walks, 1:1 walks, and puppy visits.',
primaryCta: { label: 'Explore our services →', href: '#services', variant: 'yellow' },
secondaryCta: { label: 'Book a Meet & Greet', href: '#newlead', variant: 'outline' },
imageUrl: '/images/auckland-dog-walking-happy-dog-hero.png',
imageAlt: 'Happy dog ready for a professional pack walk with Goodwalk Auckland dog walking service'
@@ -43,7 +43,7 @@ export const homepageContent: HomePageContent = {
intro: {
text: 'Trusted by Auckland dog parents.',
reviewCta: {
label: 'All 5 star reviews on Google!',
label: '30+ five-star Google reviews',
href: 'https://g.page/r/CUsvrWPhkYrAEB0/',
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'
],
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',
imageAlt: 'Woman cuddling a dog for Goodwalk Auckland dog walking services'
},
@@ -157,9 +157,9 @@ export const homepageContent: HomePageContent = {
booking: {
title: "Let's meet!",
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:
"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',
serviceOptions: ['Pack Walks', '1:1 Walks', 'Puppy Visits', 'Other Services'],
ownerStepLabel: 'Your details',
+3 -2
View File
@@ -21,7 +21,7 @@ export const packWalksContent: ServicePageContent = {
pricing: {
title: 'Tiny Gang Prices',
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: [
{
title: '1 Walk',
@@ -53,7 +53,8 @@ export const packWalksContent: ServicePageContent = {
{ label: 'Extra Dog', note: 'From same household', price: '$35' },
{ label: 'Muddy Wash', price: '$35' },
{ 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: {
title: 'Tiny Gang membership benefits',
+2 -2
View File
@@ -58,8 +58,8 @@ nav {
}
.nav-links > li > a.nav-link-active {
background: #eadbbf;
color: #000;
background: #fff;
color: var(--green);
}
.mega-chevron {
+48 -5
View File
@@ -35,6 +35,18 @@
}
@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 {
0% {
opacity: 0;
@@ -89,14 +101,16 @@
display: inline-flex;
justify-self: center;
align-self: center;
padding: 9px 12px;
background: rgba(33, 48, 33, 0.06);
min-height: 44px;
padding: 11px 14px;
background: rgba(33, 48, 33, 0.1);
color: var(--green);
font-size: 13px;
font-weight: 600;
}
.mobile-phone .icon {
font-size: 13px;
font-size: 14px;
}
.nav-links {
@@ -453,7 +467,13 @@
.booking-field-card input,
.booking-field-card textarea {
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-radius: 18px;
}
@@ -554,11 +574,34 @@
@media (max-width: 480px) {
.mobile-phone {
gap: 6px;
padding: 9px 10px;
padding: 10px 12px;
font-size: 12px;
}
.mobile-phone span {
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;
}
.promise-text h2 {
text-align: center;
}
.promise-text p {
margin-bottom: 28px;
margin: 0 auto 28px;
font-size: 16px;
max-width: 520px;
}
@@ -231,6 +227,7 @@ section {
.promise-text {
order: 2;
text-align: center;
}
.promise-img {
+1
View File
@@ -124,6 +124,7 @@ export interface ServicePageContent {
intro?: string;
plans: ServicePricingPlan[];
extras?: ServiceExtra[];
scarcityNote?: string;
};
benefits: {
title: string;
+3
View File
@@ -3,6 +3,7 @@
import { navigating, page } from '$app/stores';
import { afterNavigate, disableScrollHandling } from '$app/navigation';
import { initClickTracking, trackPageView } from '$lib/analytics';
import MobileBookBar from '$lib/components/MobileBookBar.svelte';
import RouteSkeleton from '$lib/components/RouteSkeleton.svelte';
import '$lib/styles/variables.css';
import '$lib/styles/base.css';
@@ -72,6 +73,8 @@
{/if}
</div>
<MobileBookBar />
<style>
.layout-shell {
position: relative;