SEO Tweaks
@@ -7,31 +7,6 @@
|
||||
<link rel="icon" href="/images/goodwalk-favicon-32.png" sizes="32x32" type="image/png" />
|
||||
<link rel="icon" href="/images/goodwalk-favicon-192.png" sizes="192x192" type="image/png" />
|
||||
<link rel="apple-touch-icon" href="/images/goodwalk-favicon-192.png" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="anonymous" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Fredoka+One&family=Readex+Pro:wght@400;500;600;700&family=Unbounded:wght@400;600;700;800&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
rel="preconnect"
|
||||
href="https://cdnjs.cloudflare.com"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css"
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
<!-- Google Analytics -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-K7TLSFJVP1"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-K7TLSFJVP1');
|
||||
</script>
|
||||
<style>
|
||||
.no-js-overlay {
|
||||
position: fixed;
|
||||
@@ -100,5 +75,13 @@
|
||||
</div>
|
||||
</noscript>
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
<!-- Google Analytics — deferred to end of body so it never blocks the critical render -->
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-K7TLSFJVP1"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', 'G-K7TLSFJVP1');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -409,15 +409,6 @@
|
||||
</a>
|
||||
<span class="contract-pill">Contract</span>
|
||||
</div>
|
||||
<div class="contract-topbar-right">
|
||||
{#if preview}
|
||||
<span class="contract-preview-flag">Preview</span>
|
||||
{/if}
|
||||
<a href={`tel:${ownerPhone.replace(/[^0-9+]/g, '')}`} class="contract-topbar-phone">
|
||||
<Icon name="fas fa-phone" />
|
||||
<span class="contract-topbar-phone-label">{ownerPhone}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -894,7 +885,7 @@
|
||||
|
||||
/* ── Top nav bar ── */
|
||||
.contract-topbar {
|
||||
background: #213021;
|
||||
background: var(--gw-green);
|
||||
}
|
||||
|
||||
.contract-topbar-inner {
|
||||
@@ -926,49 +917,14 @@
|
||||
.contract-pill {
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
margin-left:10pt;
|
||||
background: var(--yellow);
|
||||
font-family: var(--font-head);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
|
||||
.contract-topbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.contract-topbar-phone {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
padding: 8px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
font-family: var(--font-head);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.contract-topbar-phone:hover {
|
||||
background: rgba(255, 255, 255, 0.18);
|
||||
}
|
||||
|
||||
.contract-preview-flag {
|
||||
padding: 6px 12px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 209, 0, 0.25);
|
||||
font-family: var(--font-head);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: #ffd100;
|
||||
color: var(--gw-green);
|
||||
}
|
||||
|
||||
/* ── Journey bar ── */
|
||||
@@ -1722,10 +1678,6 @@
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.contract-topbar-phone-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.contract-hero {
|
||||
padding: 20px 0 16px;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
<script lang="ts">
|
||||
import { accordion } from '$lib/actions/accordion';
|
||||
import Icon from '$lib/components/Icon.svelte';
|
||||
import { locationPages } from '$lib/content/locations';
|
||||
import type { InfoContent } from '$lib/types';
|
||||
|
||||
export let info: InfoContent;
|
||||
|
||||
const slugBySuburb = new Map(locationPages.map((loc) => [loc.suburb, loc.slug]));
|
||||
|
||||
$: suburbChips = info.suburbs
|
||||
.split(',')
|
||||
.map((suburb) => suburb.trim())
|
||||
.filter(Boolean);
|
||||
.map((suburb) => suburb.trim().replace(/\.$/, ''))
|
||||
.filter(Boolean)
|
||||
.map((suburb) => ({ name: suburb, slug: slugBySuburb.get(suburb) ?? null }));
|
||||
</script>
|
||||
|
||||
<section id="info">
|
||||
@@ -19,8 +23,12 @@
|
||||
<p class="info-support">Regular walks across the inner-west and nearby suburbs.</p>
|
||||
|
||||
<div class="info-suburb-chips" aria-label="Suburbs we cover">
|
||||
{#each suburbChips as suburb}
|
||||
<span class="info-suburb-chip">{suburb}</span>
|
||||
{#each suburbChips as { name, slug }}
|
||||
{#if slug}
|
||||
<a class="info-suburb-chip" href="/locations/{slug}">{name}</a>
|
||||
{:else}
|
||||
<span class="info-suburb-chip">{name}</span>
|
||||
{/if}
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -83,6 +91,13 @@
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.info-suburb-chip:hover {
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(33, 48, 33, 0.25),
|
||||
0 10px 24px rgba(17, 20, 24, 0.08);
|
||||
}
|
||||
|
||||
.info-nearby-card {
|
||||
|
||||
@@ -28,20 +28,15 @@
|
||||
|
||||
<footer class="ob-footer">
|
||||
<div class="ob-footer-inner">
|
||||
<div class="ob-footer-identity">
|
||||
<Icon name="fas fa-circle-check" />
|
||||
<span>Signed in as <strong>{email}</strong></span>
|
||||
</div>
|
||||
<a href="https://goodwalk.co.nz" class="ob-footer-back">
|
||||
<Icon name="fas fa-arrow-left" />
|
||||
Back to main site
|
||||
</a>
|
||||
<button class="ob-footer-logout" on:click={logout} disabled={loggingOut}>
|
||||
<Icon name="fas fa-right-from-bracket" />
|
||||
{loggingOut ? 'Signing out…' : 'Sign out'}
|
||||
</button>
|
||||
</div>
|
||||
<div class="ob-footer-copyright">
|
||||
<a href="https://goodwalk.co.nz">goodwalk.co.nz</a>
|
||||
<span>·</span>
|
||||
<span>© {new Date().getFullYear()} Goodwalk. All rights reserved.</span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<style>
|
||||
@@ -61,17 +56,20 @@
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.ob-footer-identity {
|
||||
display: flex;
|
||||
.ob-footer-back {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-family: var(--font-head);
|
||||
font-size: 13px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
font-weight: 700;
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
text-decoration: none;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
|
||||
.ob-footer-identity strong {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-weight: 600;
|
||||
.ob-footer-back:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.ob-footer-logout {
|
||||
@@ -95,39 +93,9 @@
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.ob-footer-copyright {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 10px 28px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
||||
font-size: 11px;
|
||||
color: rgba(255, 255, 255, 0.25);
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ob-footer-copyright a {
|
||||
color: rgba(255, 255, 255, 0.35);
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.ob-footer-copyright a:hover {
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.ob-footer-inner {
|
||||
padding: 0 18px;
|
||||
}
|
||||
|
||||
.ob-footer-identity span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.ob-footer-copyright {
|
||||
padding: 10px 18px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -406,6 +406,7 @@
|
||||
<div class="onboarding-topbar">
|
||||
<div class="onboarding-shell onboarding-topbar-inner">
|
||||
<div class="onboarding-brand">
|
||||
<span class="onboarding-pill">Onboarding</span>
|
||||
<a href="https://goodwalk.co.nz" class="onboarding-logo" aria-label="Goodwalk home">
|
||||
<picture>
|
||||
{#if desktop.sources?.webp}
|
||||
@@ -420,16 +421,6 @@
|
||||
/>
|
||||
</picture>
|
||||
</a>
|
||||
<span class="onboarding-pill">Onboarding</span>
|
||||
</div>
|
||||
<div class="onboarding-topbar-right">
|
||||
{#if preview}
|
||||
<span class="onboarding-preview-flag">Preview</span>
|
||||
{/if}
|
||||
<a href={`tel:${ownerPhone.replace(/[^0-9+]/g, '')}`} class="onboarding-topbar-phone">
|
||||
<Icon name="fas fa-phone" />
|
||||
<span class="onboarding-topbar-phone-label">{ownerPhone}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1006,49 +997,13 @@
|
||||
.onboarding-pill {
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
background: var(--yellow);
|
||||
font-family: var(--font-head);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
|
||||
.onboarding-topbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.onboarding-topbar-phone {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
padding: 8px 14px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
font-family: var(--font-head);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.onboarding-topbar-phone:hover {
|
||||
background: rgba(255, 255, 255, 0.18);
|
||||
}
|
||||
|
||||
.onboarding-preview-flag {
|
||||
padding: 6px 12px;
|
||||
border-radius: 999px;
|
||||
background: rgba(255, 209, 0, 0.25);
|
||||
font-family: var(--font-head);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: #ffd100;
|
||||
color: var(--gw-green);
|
||||
}
|
||||
|
||||
/* ── Idle warning ── */
|
||||
@@ -1582,10 +1537,6 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.onboarding-topbar-phone-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.onboarding-hero {
|
||||
padding: 20px 0 16px;
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
export let type = 'website';
|
||||
export let structuredData: Record<string, unknown>[] = [];
|
||||
export let noindex = false;
|
||||
export let preloadImage = false;
|
||||
export let preloadImage = false; // kept for API compatibility — preload is handled by fetchpriority="high" on the image element
|
||||
|
||||
const siteName = 'Goodwalk';
|
||||
const siteUrl = 'https://www.goodwalk.co.nz';
|
||||
@@ -50,9 +50,6 @@
|
||||
<meta name="geo.region" content="NZ-AUK" />
|
||||
<meta name="geo.placename" content="Auckland Central" />
|
||||
<link rel="canonical" href={canonicalUrl} />
|
||||
{#if preloadImage}
|
||||
<link rel="preload" as="image" href={imageUrl} />
|
||||
{/if}
|
||||
<link rel="alternate" hreflang="en-NZ" href={canonicalUrl} />
|
||||
<link rel="alternate" hreflang="x-default" href={canonicalUrl} />
|
||||
|
||||
|
||||
@@ -22,28 +22,28 @@
|
||||
detail: "Archie's mum",
|
||||
quote:
|
||||
'Love Aless! She is so amazing with my slightly hyper and anxious dog. She is great with communication if anything on either of our ends need to change. Archie love his walks, and I love the photos she posts of him.',
|
||||
imageUrl: '/images/archie-auckland-dog-walking-review.png'
|
||||
imageUrl: '/images/archie-auckland-dog-walking-review.jpg'
|
||||
},
|
||||
Estelle: {
|
||||
reviewer: 'Estelle',
|
||||
detail: "Monty's mum",
|
||||
quote:
|
||||
'GoodWalk was the best dog walking service for my little pooch ! Aless was very helpful - basically doubled as a second mum to Monty. She always provided feedback on his outings and assisted where possible with any additional training that she felt he could work on and made recommendations where necessary which i feel is what every dog mum wants and needs!',
|
||||
imageUrl: '/images/monty-auckland-dog-walking-review.png'
|
||||
imageUrl: '/images/monty-auckland-dog-walking-review.jpg'
|
||||
},
|
||||
Ross: {
|
||||
reviewer: 'Ross',
|
||||
detail: "Otis's dad",
|
||||
quote:
|
||||
'Truly the best dog walker in Auckland! I feel so lucky to have found Aless and my little terrier Otis absolutely adores her. He enjoys his regular weekly walks and always comes back happy & tired. Love the updates on social media so I can see how my dog is enjoying his day! Aless makes logistics so easy too. Highly highly recommend, there’s a reason she has 5 stars!',
|
||||
imageUrl: '/images/otis-auckland-dog-walking-review.png'
|
||||
imageUrl: '/images/otis-auckland-dog-walking-review.jpg'
|
||||
},
|
||||
Nina: {
|
||||
reviewer: 'Nina',
|
||||
detail: "Wallace's mum",
|
||||
quote:
|
||||
'Alessandra has been walking and spending time with my pup since she was 10 weeks old, coming over and doing puppy visits through to transitioning her to pack walks with her little doggo friends. I know Alassandra loves and cares for my dog as much as I do and my dog has a great time! Cant recommend enough',
|
||||
imageUrl: '/images/wallace-auckland-dog-walking-review.png'
|
||||
imageUrl: '/images/wallace-auckland-dog-walking-review.jpg'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -543,7 +543,7 @@
|
||||
|
||||
.testimonial-woof-text {
|
||||
display: inline-block;
|
||||
font-family: 'Fredoka One', var(--font-head), sans-serif;
|
||||
font-family: 'Fredoka', 'Fredoka One', var(--font-head), sans-serif;
|
||||
font-size: 32px;
|
||||
line-height: 1;
|
||||
letter-spacing: 0.02em;
|
||||
|
||||
@@ -10,7 +10,7 @@ export const aboutPageContent: AboutPageContent = {
|
||||
"Goodwalk is built around Alessandra — who started this because she couldn't find a walker she actually trusted, and hasn't stopped showing up the same way since. She walks every dog herself, posts updates to Instagram so you can see exactly what your dog is up to, and has built relationships with some dogs from as young as ten weeks old. Thirty-plus five-star Google reviews later, the feedback keeps saying the same thing: the dogs adore her, and their owners finally stop worrying.",
|
||||
"We specialise in small and medium dogs because we understand them — not as a category, but as actual dogs with specific needs, specific quirks, and specific ways they fall apart in the wrong environment. The pace of a walk matters. The size of the group matters. The temperament of the other dogs matters. That's why we built a service around them, not just one that fits them in."
|
||||
],
|
||||
imageUrl: '/images/auckland-pack-walk-small-dogs-group.png',
|
||||
imageUrl: '/images/auckland-pack-walk-small-dogs-group.jpg',
|
||||
imageAlt: "Small dogs from Goodwalk's Tiny Gang pack walk sitting together in an Auckland park"
|
||||
},
|
||||
{
|
||||
@@ -32,7 +32,7 @@ export const aboutPageContent: AboutPageContent = {
|
||||
"Alessandra started Goodwalk because she couldn't find a walker she actually trusted with Maya. So she became one. Italian-born and Auckland Central-based, she leads every walk herself — not because she has to, but because handing that off was never something she was willing to do. The dogs she walks have her full attention. Their owners have her number.",
|
||||
"Maya is a Cavalier King Charles cross Shih Tzu, and the reason small dogs sit at the centre of everything Goodwalk does. She is opinionated, dramatic when it rains, and completely impossible to ignore on a walk. She is also the best argument we have for why small dogs deserve a service built specifically around them — not just accommodated by one."
|
||||
],
|
||||
imageUrl: '/images/founder-image-aless-goodwalk.png',
|
||||
imageUrl: '/images/founder-image-aless-goodwalk.jpg',
|
||||
imageAlt: 'Alessandra, founder of Goodwalk Auckland',
|
||||
accent: 'founder'
|
||||
}
|
||||
|
||||
@@ -20,15 +20,15 @@ export const dogWalkingContent: ServicePageContent = {
|
||||
imageAlt: 'Goodwalk dogs gathered together outdoors',
|
||||
collageImages: [
|
||||
{
|
||||
imageUrl: '/images/one-on-one-dog-portrait-1.png',
|
||||
imageUrl: '/images/one-on-one-dog-portrait-1.jpg',
|
||||
imageAlt: 'Happy black dog on a one-on-one Goodwalk walk in Auckland'
|
||||
},
|
||||
{
|
||||
imageUrl: '/images/one-on-one-dog-portrait-2.png',
|
||||
imageUrl: '/images/one-on-one-dog-portrait-2.jpg',
|
||||
imageAlt: 'Older black dog enjoying a calm one-on-one Goodwalk walk in Auckland'
|
||||
},
|
||||
{
|
||||
imageUrl: '/images/one-on-one-dog-portrait-3.png',
|
||||
imageUrl: '/images/one-on-one-dog-portrait-3.jpg',
|
||||
imageAlt: 'Brown curly dog resting during a one-on-one Goodwalk walk in Auckland'
|
||||
}
|
||||
]
|
||||
|
||||
@@ -3,9 +3,9 @@ import { sharedServices } from '$lib/content/services';
|
||||
|
||||
export const homepageContent: HomePageContent = {
|
||||
seo: {
|
||||
title: 'Home | Auckland Dog Walking | Goodwalk',
|
||||
title: 'Auckland Dog Walker | Pack Walks & 1:1 Walks | Goodwalk',
|
||||
description:
|
||||
'At Goodwalk, we offer Tiny Gang pack walks and one on one dog walking services throughout Auckland. Give your dog his best life with Goodwalk!'
|
||||
'Trusted by 30+ Auckland families. Pack walks from $49.50. Free Meet & Greet. Covering Mt Eden, Ponsonby, Grey Lynn, Kingsland & more. Book online today.'
|
||||
},
|
||||
navigation: {
|
||||
desktopLinks: [
|
||||
@@ -65,7 +65,7 @@ export const homepageContent: HomePageContent = {
|
||||
],
|
||||
emphasis: 'TINY GANG?',
|
||||
cta: { label: 'Book a free Meet & Greet', href: '/contact-us', variant: 'green' },
|
||||
imageUrl: '/images/founder-image-aless-goodwalk.png',
|
||||
imageUrl: '/images/founder-image-aless-goodwalk.jpg',
|
||||
imageAlt: 'Alessandra from Goodwalk with a dog in Auckland'
|
||||
},
|
||||
services: sharedServices.map((service) => ({
|
||||
|
||||
@@ -10,13 +10,13 @@ export const packWalksContent: ServicePageContent = {
|
||||
'Tiny Gang is best suited to sociable small and medium dogs who enjoy being around other dogs. If your dog would be better with a quieter, more individual setup, our 1:1 walks may be a better fit.',
|
||||
'We run pack walks across Auckland Central — including Mt Eden, Kingsland, Ponsonby, Grey Lynn, Sandringham, Mt Albert, and surrounding suburbs — with free pickup and drop-off included in every booking.'
|
||||
],
|
||||
imageUrl: '/images/auckland-pack-walk-small-dogs-group.png',
|
||||
imageUrl: '/images/auckland-pack-walk-small-dogs-group.jpg',
|
||||
imageAlt: "Small dogs from Goodwalk's Tiny Gang pack walk sitting together in an Auckland park"
|
||||
},
|
||||
highlight: {
|
||||
eyebrow: 'Small packs. Calm dogs.',
|
||||
title: 'Made specifically for small and medium dogs who do best in a structured social group',
|
||||
imageUrl: '/images/small-medium-dogs-pack-walk.png',
|
||||
imageUrl: '/images/small-medium-dogs-pack-walk.jpg',
|
||||
imageAlt: 'Small and medium dogs together on a Goodwalk pack walk in Auckland'
|
||||
},
|
||||
pricing: {
|
||||
|
||||
@@ -8,16 +8,16 @@ const imageMetadata: Record<string, ImageMetadata> = {
|
||||
'/images/goodwalk-auckland-dog-walking-logo-mobile.png': { width: 206, height: 41 },
|
||||
'/images/auckland-dog-walking-happy-dog-hero.png': { width: 500, height: 500 },
|
||||
'/images/auckland-dog-walking-happy-dogs-happy-humans.webp': { width: 1222, height: 1312 },
|
||||
'/images/archie-auckland-dog-walking-review.png': { width: 1122, height: 1402 },
|
||||
'/images/monty-auckland-dog-walking-review.png': { width: 1254, height: 1254 },
|
||||
'/images/otis-auckland-dog-walking-review.png': { width: 1254, height: 1254 },
|
||||
'/images/wallace-auckland-dog-walking-review.png': { width: 1254, height: 1254 },
|
||||
'/images/archie-auckland-dog-walking-review.jpg': { width: 1122, height: 1402 },
|
||||
'/images/monty-auckland-dog-walking-review.jpg': { width: 1254, height: 1254 },
|
||||
'/images/otis-auckland-dog-walking-review.jpg': { width: 1254, height: 1254 },
|
||||
'/images/wallace-auckland-dog-walking-review.jpg': { width: 1254, height: 1254 },
|
||||
'/images/auckland-small-dog-pack-walk.jpg': { width: 640, height: 480 },
|
||||
'/images/auckland-pack-walk-small-dogs-group.png': { width: 1469, height: 1071 },
|
||||
'/images/small-medium-dogs-pack-walk.png': { width: 1240, height: 1269 },
|
||||
'/images/one-on-one-dog-portrait-1.png': { width: 1054, height: 1492 },
|
||||
'/images/one-on-one-dog-portrait-2.png': { width: 1091, height: 1441 },
|
||||
'/images/one-on-one-dog-portrait-3.png': { width: 1124, height: 1399 },
|
||||
'/images/auckland-pack-walk-small-dogs-group.jpg': { width: 1469, height: 1071 },
|
||||
'/images/small-medium-dogs-pack-walk.jpg': { width: 1240, height: 1269 },
|
||||
'/images/one-on-one-dog-portrait-1.jpg': { width: 1054, height: 1492 },
|
||||
'/images/one-on-one-dog-portrait-2.jpg': { width: 1091, height: 1441 },
|
||||
'/images/one-on-one-dog-portrait-3.jpg': { width: 1124, height: 1399 },
|
||||
'/images/tiny-gang-auckland-dog-pack.jpg': { width: 1024, height: 297 },
|
||||
'/images/auckland-large-dog-one-on-one-walk.jpg': { width: 1024, height: 970 },
|
||||
'/images/auckland-dogs-outdoor-pack.jpg': { width: 1024, height: 297 },
|
||||
@@ -25,7 +25,7 @@ const imageMetadata: Record<string, ImageMetadata> = {
|
||||
'/images/auckland-puppy-visits-cavalier-king-charles-spaniel.jpg': { width: 3327, height: 2217 },
|
||||
'/images/auckland-pack-walk-dog.jpg': { width: 480, height: 640 },
|
||||
'/images/auckland-dog-group-outing.jpg': { width: 640, height: 480 },
|
||||
'/images/founder-image-aless-goodwalk.png': { width: 1076, height: 1461 }
|
||||
'/images/founder-image-aless-goodwalk.jpg': { width: 1076, height: 1461 }
|
||||
};
|
||||
|
||||
export function getImageMetadata(src: string | undefined | null): ImageMetadata | null {
|
||||
|
||||
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 203 KiB |
|
Before Width: | Height: | Size: 619 KiB |
|
Before Width: | Height: | Size: 273 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 261 KiB After Width: | Height: | Size: 183 KiB |
|
After Width: | Height: | Size: 312 KiB |
|
Before Width: | Height: | Size: 852 KiB |
|
Before Width: | Height: | Size: 606 KiB After Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 614 KiB After Width: | Height: | Size: 737 KiB |
|
After Width: | Height: | Size: 206 KiB |
|
Before Width: | Height: | Size: 549 KiB |
|
Before Width: | Height: | Size: 907 KiB After Width: | Height: | Size: 332 KiB |
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 263 KiB |
|
Before Width: | Height: | Size: 762 KiB |
|
After Width: | Height: | Size: 288 KiB |
|
Before Width: | Height: | Size: 2.7 MiB |
|
After Width: | Height: | Size: 182 KiB |
|
Before Width: | Height: | Size: 2.2 MiB |
|
After Width: | Height: | Size: 318 KiB |
|
Before Width: | Height: | Size: 2.8 MiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 382 KiB |
|
Before Width: | Height: | Size: 963 KiB |
|
After Width: | Height: | Size: 291 KiB |
|
Before Width: | Height: | Size: 912 KiB |
|
Before Width: | Height: | Size: 313 KiB After Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 367 KiB |
|
Before Width: | Height: | Size: 922 KiB |
@@ -19,7 +19,7 @@ describe('seo helpers', () => {
|
||||
|
||||
expect(seo.title).toBe('Dog Walkers in Mt Eden | Goodwalk Auckland');
|
||||
expect(seo.canonicalPath).toBe('/locations/mt-eden');
|
||||
expect(seo.image).toBe('/images/auckland-pack-walk-small-dogs-group.png');
|
||||
expect(seo.image).toBe('/images/auckland-pack-walk-small-dogs-group.jpg');
|
||||
expect(seo.imageAlt).toBe('Goodwalk dog walkers in Mt Eden, Auckland');
|
||||
expect(seo.structuredData).toHaveLength(2);
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import { locationPages } from '$lib/content/locations';
|
||||
import type { LocationPageContent } from '$lib/types';
|
||||
|
||||
const siteUrl = 'https://www.goodwalk.co.nz';
|
||||
const defaultLocationImage = '/images/auckland-pack-walk-small-dogs-group.png';
|
||||
const defaultLocationImage = '/images/auckland-pack-walk-small-dogs-group.jpg';
|
||||
const defaultLocationImageAlt = 'Goodwalk Auckland dog walking services';
|
||||
|
||||
interface BreadcrumbItem {
|
||||
@@ -82,11 +82,7 @@ export function buildLocationSeo(location: LocationPageContent) {
|
||||
description,
|
||||
serviceType,
|
||||
provider: {
|
||||
'@type': 'LocalBusiness',
|
||||
name: 'Goodwalk',
|
||||
url: siteUrl,
|
||||
telephone: '+64226421011',
|
||||
email: 'info@goodwalk.co.nz'
|
||||
'@id': `${siteUrl}/#business`
|
||||
},
|
||||
areaServed: {
|
||||
'@type': 'Place',
|
||||
|
||||
@@ -3,7 +3,3 @@ import { parseBooleanFlag } from '$lib/feature-flags';
|
||||
export function isGeneralEnquiryEnabled() {
|
||||
return parseBooleanFlag(process.env.ENABLE_GENERAL_ENQUIRIES, false);
|
||||
}
|
||||
|
||||
export function isHomepageHowItWorksEnabled() {
|
||||
return parseBooleanFlag(process.env.ENABLE_HOMEPAGE_HOW_IT_WORKS, false);
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ html {
|
||||
body {
|
||||
font-family: var(--font-body);
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
line-height: 1.65;
|
||||
color: var(--text);
|
||||
background: var(--off-white);
|
||||
overflow-x: clip;
|
||||
@@ -48,3 +48,19 @@ textarea {
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* Scroll reveal — JS adds [data-reveal] to targets, then toggles .is-visible */
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
[data-reveal] {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
transition:
|
||||
opacity 0.5s ease,
|
||||
transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
}
|
||||
|
||||
[data-reveal].is-visible {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,7 @@
|
||||
*/
|
||||
body {
|
||||
font-size: 16px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
body.mobile-cta-enabled {
|
||||
@@ -203,6 +204,10 @@
|
||||
padding: 50px 20px 0;
|
||||
}
|
||||
|
||||
#hero::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hero-inner {
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
/* Featured sections — more breathing room */
|
||||
#promise,
|
||||
#services,
|
||||
#services {
|
||||
padding: 96px 0;
|
||||
}
|
||||
|
||||
/* Supporting sections */
|
||||
#values,
|
||||
#testimonials,
|
||||
#info,
|
||||
#info {
|
||||
padding: 72px 0;
|
||||
}
|
||||
|
||||
/* Booking / lead form — neutral */
|
||||
#newlead {
|
||||
padding: 80px 0;
|
||||
}
|
||||
@@ -15,6 +24,18 @@
|
||||
align-items: center;
|
||||
min-height: 500px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Gradient blends the hero image's bottom edge into the green background */
|
||||
#hero::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: auto 0 0 42%;
|
||||
height: 120px;
|
||||
background: linear-gradient(to top, var(--gw-green) 0%, transparent 100%);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.hero-inner {
|
||||
@@ -337,6 +358,7 @@
|
||||
border-radius: 28px;
|
||||
padding: 40px 32px;
|
||||
text-align: center;
|
||||
box-shadow: inset 0 0 0 1px rgba(17, 20, 24, 0.06);
|
||||
transition:
|
||||
transform 0.18s cubic-bezier(0.22, 1, 0.36, 1),
|
||||
box-shadow 0.22s ease;
|
||||
@@ -356,7 +378,7 @@
|
||||
}
|
||||
|
||||
.service-icon-bubble {
|
||||
background: linear-gradient(180deg, #ffe173 0%, #ffd54a 100%);
|
||||
background: linear-gradient(135deg, #ffd54a, var(--yellow-soft));
|
||||
transform: translateY(0) rotate(0deg) scale(1);
|
||||
transition:
|
||||
transform 0.2s cubic-bezier(0.22, 1, 0.36, 1),
|
||||
@@ -365,8 +387,11 @@
|
||||
|
||||
@media (hover: hover) {
|
||||
.service-card:hover {
|
||||
transform: translateY(-6px) scale(1.012);
|
||||
box-shadow: 0 18px 38px rgba(0, 0, 0, 0.1);
|
||||
transform: translateY(-2px);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(17, 20, 24, 0.06),
|
||||
0 8px 40px rgba(0, 0, 0, 0.08);
|
||||
filter: brightness(1.02);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -493,10 +518,12 @@ footer {
|
||||
}
|
||||
|
||||
.testimonial-card {
|
||||
background: #fff;
|
||||
background: linear-gradient(180deg, #ffffff 0%, var(--off-white) 100%);
|
||||
border-radius: 28px;
|
||||
padding: 36px 32px;
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.06);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(17, 20, 24, 0.06),
|
||||
0 4px 24px rgba(0, 0, 0, 0.06);
|
||||
transition:
|
||||
transform 0.18s cubic-bezier(0.22, 1, 0.36, 1),
|
||||
box-shadow 0.22s ease;
|
||||
@@ -504,8 +531,11 @@ footer {
|
||||
|
||||
@media (hover: hover) {
|
||||
.testimonial-card:hover {
|
||||
transform: translateY(-6px) scale(1.012);
|
||||
box-shadow: 0 18px 34px rgba(0, 0, 0, 0.09);
|
||||
transform: translateY(-2px);
|
||||
box-shadow:
|
||||
inset 0 0 0 1px rgba(17, 20, 24, 0.06),
|
||||
0 8px 40px rgba(0, 0, 0, 0.08);
|
||||
filter: brightness(1.01);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
font-family: var(--font-head);
|
||||
font-weight: 700;
|
||||
line-height: 1.08;
|
||||
letter-spacing: -0.03em;
|
||||
letter-spacing: -0.02em;
|
||||
text-wrap: balance;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
:root {
|
||||
/* Brand greens */
|
||||
--gw-green: #213021;
|
||||
--green-mid: #2d4230; /* hover states, subtle accents */
|
||||
--green-soft: #344b38; /* softer elevated green surfaces */
|
||||
|
||||
/* Accent */
|
||||
--yellow: #ffd100;
|
||||
--yellow-soft: #f2bf2f; /* warmer, more premium yellow — gradients and icon fills */
|
||||
|
||||
/* Neutrals */
|
||||
--gray: #59606d;
|
||||
--beige: #e5d6c2;
|
||||
--beige: #e5d6c2; /* warm surfaces, image frames */
|
||||
--off-white: #fbfbfb;
|
||||
--surface-light: #f7f8f6; /* elevated light surface — above off-white */
|
||||
--text: #2e3031;
|
||||
|
||||
/* Layout */
|
||||
--max-w: 1280px;
|
||||
|
||||
/* Typography */
|
||||
--font-body: 'Readex Pro', sans-serif;
|
||||
--font-head: 'Unbounded', sans-serif;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,18 @@
|
||||
import MobileBookBar from '$lib/components/MobileBookBar.svelte';
|
||||
import RouteSkeleton from '$lib/components/RouteSkeleton.svelte';
|
||||
import { isMobileCtaButtonEnabled } from '$lib/feature-flags';
|
||||
import '@fontsource/readex-pro/latin-400.css';
|
||||
import '@fontsource/readex-pro/latin-500.css';
|
||||
import '@fontsource/readex-pro/latin-600.css';
|
||||
import '@fontsource/readex-pro/latin-700.css';
|
||||
import '@fontsource/unbounded/latin-400.css';
|
||||
import '@fontsource/unbounded/latin-600.css';
|
||||
import '@fontsource/unbounded/latin-700.css';
|
||||
import '@fontsource/unbounded/latin-800.css';
|
||||
import '@fontsource/fredoka/latin-600.css';
|
||||
import '@fortawesome/fontawesome-free/css/fontawesome.min.css';
|
||||
import '@fortawesome/fontawesome-free/css/solid.min.css';
|
||||
import '@fortawesome/fontawesome-free/css/brands.min.css';
|
||||
import '$lib/styles/variables.css';
|
||||
import '$lib/styles/base.css';
|
||||
import '$lib/styles/layout.css';
|
||||
@@ -17,7 +29,38 @@
|
||||
|
||||
const mobileCtaButtonEnabled = isMobileCtaButtonEnabled();
|
||||
|
||||
onMount(() => initClickTracking());
|
||||
let revealObserver: IntersectionObserver | null = null;
|
||||
|
||||
function initReveal() {
|
||||
revealObserver?.disconnect();
|
||||
|
||||
const targets = document.querySelectorAll<HTMLElement>(
|
||||
'.section-heading, .service-card, .testimonial-card, .value-card'
|
||||
);
|
||||
|
||||
targets.forEach(el => {
|
||||
el.setAttribute('data-reveal', '');
|
||||
});
|
||||
|
||||
revealObserver = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('is-visible');
|
||||
revealObserver?.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
},
|
||||
{ rootMargin: '0px 0px -40px 0px', threshold: 0.08 }
|
||||
);
|
||||
|
||||
targets.forEach(el => revealObserver!.observe(el));
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
initClickTracking();
|
||||
requestAnimationFrame(initReveal);
|
||||
});
|
||||
|
||||
function shouldShowSkeleton() {
|
||||
const navigation = $navigating;
|
||||
@@ -56,6 +99,7 @@
|
||||
window.scrollTo({ top: 0, left: 0, behavior: 'auto' });
|
||||
document.documentElement.scrollTop = 0;
|
||||
document.body.scrollTop = 0;
|
||||
initReveal();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { getHomepageContent } from '$lib/server/content';
|
||||
import { isHomepageHowItWorksEnabled } from '$lib/server/feature-flags';
|
||||
|
||||
const onboardingHosts = new Set(['onboarding.goodwalk.co.nz']);
|
||||
|
||||
@@ -19,7 +18,6 @@ export async function load({ url }) {
|
||||
|
||||
return {
|
||||
siteVariant,
|
||||
content: await getHomepageContent(),
|
||||
howItWorksEnabled: isHomepageHowItWorksEnabled()
|
||||
content: await getHomepageContent()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
import TestimonialsSection from '$lib/components/TestimonialsSection.svelte';
|
||||
import ValuesSection from '$lib/components/ValuesSection.svelte';
|
||||
import OnboardingPage from '$lib/components/OnboardingPage.svelte';
|
||||
import { buildAreaServed } from '$lib/seo';
|
||||
import type { PageData } from './$types';
|
||||
|
||||
export let data: PageData;
|
||||
@@ -38,7 +39,8 @@
|
||||
},
|
||||
{
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'LocalBusiness',
|
||||
'@type': ['LocalBusiness', 'PetCareService'],
|
||||
'@id': 'https://www.goodwalk.co.nz/#business',
|
||||
name: 'Goodwalk',
|
||||
description:
|
||||
'Professional dog walking services across Auckland Central, including pack walks, 1:1 walks, and puppy visits.',
|
||||
@@ -46,7 +48,7 @@
|
||||
logo: `${siteUrl}/images/goodwalk-auckland-dog-walking-logo.png`,
|
||||
image: absoluteUrl(data.content.hero.imageUrl),
|
||||
email: 'info@goodwalk.co.nz',
|
||||
telephone: '+64-22-642-1011',
|
||||
telephone: '+64226421011',
|
||||
sameAs: ['https://www.instagram.com/goodwalk.nz/', 'https://g.page/r/CUsvrWPhkYrAEB0/'],
|
||||
address: {
|
||||
'@type': 'PostalAddress',
|
||||
@@ -54,25 +56,7 @@
|
||||
addressRegion: 'Auckland',
|
||||
addressCountry: 'NZ'
|
||||
},
|
||||
areaServed: [
|
||||
'Morningside',
|
||||
'Kingsland',
|
||||
'Ponsonby',
|
||||
'Grey Lynn',
|
||||
'Mt Albert',
|
||||
'Mt Eden',
|
||||
'Sandringham',
|
||||
'Mt Roskill',
|
||||
'Arch Hill',
|
||||
'Freemans Bay',
|
||||
'Herne Bay',
|
||||
'Pt Chevalier',
|
||||
'Avondale',
|
||||
'Three Kings',
|
||||
'Hillsborough',
|
||||
'Eden Terrace',
|
||||
'Balmoral'
|
||||
],
|
||||
areaServed: buildAreaServed(),
|
||||
openingHoursSpecification: [
|
||||
{
|
||||
'@type': 'OpeningHoursSpecification',
|
||||
@@ -98,7 +82,7 @@
|
||||
ratingValue: '5.0',
|
||||
bestRating: '5',
|
||||
worstRating: '1',
|
||||
reviewCount: String(data.content.testimonials.length)
|
||||
reviewCount: '30'
|
||||
},
|
||||
review: data.content.testimonials.map((testimonial) => ({
|
||||
'@context': 'https://schema.org',
|
||||
@@ -149,9 +133,7 @@
|
||||
<HeroSection hero={content.hero} reviewCta={content.intro.reviewCta} />
|
||||
<PromiseSection promise={content.promise} />
|
||||
<ServicesSection services={content.services} />
|
||||
{#if data.howItWorksEnabled}
|
||||
<HowItWorksSection content={content.howItWorks} />
|
||||
{/if}
|
||||
<HowItWorksSection content={content.howItWorks} />
|
||||
<TestimonialsSection testimonials={content.testimonials} seedKey="/" />
|
||||
<ValuesSection values={content.values} />
|
||||
<BookingSection booking={content.booking} />
|
||||
|
||||
@@ -17,24 +17,24 @@ const routes: SitemapRoute[] = [
|
||||
{ path: '/our-pricing', priority: '0.8', changefreq: 'monthly' },
|
||||
{ path: '/about', priority: '0.7', changefreq: 'monthly' },
|
||||
{ path: '/contact-us', priority: '0.7', changefreq: 'monthly' },
|
||||
...locationPages.map((loc) => ({
|
||||
path: `/locations/${loc.slug}`,
|
||||
priority: '0.8',
|
||||
changefreq: 'monthly'
|
||||
})),
|
||||
{ path: '/terms-and-conditions', priority: '0.3', changefreq: 'yearly' },
|
||||
{ path: '/privacy-policy', priority: '0.3', changefreq: 'yearly' }
|
||||
];
|
||||
|
||||
const locationRoutes: SitemapRoute[] = locationPages.map((loc) => ({
|
||||
path: `/locations/${loc.slug}`,
|
||||
priority: '0.8',
|
||||
changefreq: 'monthly'
|
||||
}));
|
||||
|
||||
export const GET: RequestHandler = () => {
|
||||
const lastmod = new Date().toISOString().split('T')[0];
|
||||
const allRoutes = [...routes, ...locationRoutes];
|
||||
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
${routes
|
||||
${allRoutes
|
||||
.map(
|
||||
({ path, priority, changefreq }) => ` <url>
|
||||
<loc>${siteUrl}${path}</loc>
|
||||
<lastmod>${lastmod}</lastmod>
|
||||
<changefreq>${changefreq}</changefreq>
|
||||
<priority>${priority}</priority>
|
||||
</url>`
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { GET } from './+server';
|
||||
import { locationPages } from '$lib/content/locations';
|
||||
|
||||
describe('sitemap endpoint', () => {
|
||||
afterEach(() => {
|
||||
@@ -19,7 +18,7 @@ describe('sitemap endpoint', () => {
|
||||
expect(body).toContain('<loc>https://www.goodwalk.co.nz/contact-us</loc>');
|
||||
expect(body).toContain('<loc>https://www.goodwalk.co.nz/privacy-policy</loc>');
|
||||
expect(body).toContain('<lastmod>2026-05-01</lastmod>');
|
||||
expect(body).toContain('<loc>https://www.goodwalk.co.nz/locations/mt-eden</loc>');
|
||||
expect(body.match(/<url>/g)).toHaveLength(9 + locationPages.length);
|
||||
expect(body).not.toContain('/locations/');
|
||||
expect(body.match(/<url>/g)).toHaveLength(9);
|
||||
});
|
||||
});
|
||||
|
||||