2026-05-11 21:02:24 +12:00
< script lang = "ts" >
import { sharedServices } from '$lib/content/services' ;
import { reveal } from '$lib/actions/reveal' ;
2026-05-13 20:44:01 +12:00
import CtaCard from '$lib/components/CtaCard.svelte' ;
2026-05-11 21:02:24 +12:00
import Icon from '$lib/components/Icon.svelte' ;
import { getEnhancedImage } from '$lib/enhanced-images' ;
import { getSeededTestimonialIndex } from '$lib/testimonials' ;
import type { LocationPageContent , TestimonialContent } from '$lib/types' ;
export let location : LocationPageContent ;
export let testimonials : TestimonialContent [];
type ParkWithImage = LocationPageContent [ 'parks' ][ number ] & {
image : NonNullable < LocationPageContent [ 'parks' ][ number ][ 'image' ] >;
enhanced : ReturnType < typeof getEnhancedImage >;
};
$ : featuredTestimonial = testimonials [ getSeededTestimonialIndex ( testimonials , location . slug )];
$ : parksWithImages = location . parks
. filter (( park ) : park is LocationPageContent [ 'parks' ][ number ] & { image : NonNullable < LocationPageContent [ 'parks' ][ number ][ 'image' ] > } => Boolean ( park . image ))
. map (
( park ) : ParkWithImage => ({
... park ,
enhanced : getEnhancedImage ( park . image . src )
})
);
$ : serviceLinks = sharedServices . map (( service ) => ({
label : service.title ,
href : service.href ,
desc : service.locationDescription ,
icon : service.icon
}));
$ : locationHighlights = [
{
icon : 'fas fa-map-location-dot' ,
label : 'Local routes' ,
value : ` ${ location . parks . length } + parks` ,
detail : `Regular walking options in and around ${ location . suburb } `
},
{
icon : 'fas fa-paw' ,
label : 'Services' ,
value : '3 ways to help' ,
detail : 'Pack walks, 1:1 walks, and puppy visits'
},
{
icon : 'fas fa-van-shuttle' ,
label : 'Included' ,
value : 'Free pickup' ,
detail : 'Pickup and drop-off across the central suburbs'
}
];
</ script >
< main class = "loc-page" >
<!-- ── Hero ── -->
< section class = "loc-hero" >
2026-05-13 20:44:01 +12:00
< div class = "page-inner" >
2026-05-11 21:02:24 +12:00
< span class = "loc-hero-eyebrow" > Auckland Central Dog Walking</ span >
< h1 > Dog walkers in { location . suburb }</ h1 >
< p class = "loc-hero-desc" >{ location . intro }</ p >
< div class = "loc-hero-actions" >
< a href = "/contact-us" class = "btn btn-yellow btn-mobile-center" > Book a free Meet & Greet</ a >
< a href = "tel:+64226421011" class = "loc-hero-phone" > or call (022) 642 1011</ a >
</ div >
< div class = "loc-hero-chips" >
< a
href = "https://g.page/r/CUsvrWPhkYrAEB0/"
target = "_blank"
rel = "noopener"
class = "loc-chip loc-chip-link"
>
< span class = "loc-chip-stars" aria-hidden = "true" > ★★★★★</ span >
30+ five-star Google reviews
</ a >
< span class = "loc-chip" > Small dog specialists</ span >
< span class = "loc-chip" > Free pickup & drop-off</ span >
</ div >
</ div >
</ section >
< section class = "loc-highlights" aria-label = { `Goodwalk highlights in ${ location . suburb } ` } >
2026-05-13 20:44:01 +12:00
< div class = "page-inner" >
2026-05-11 21:02:24 +12:00
< div class = "loc-highlights-grid" >
{ #each locationHighlights as highlight }
< div class = "loc-highlight-card" >
< div class = "loc-highlight-top" >
< div class = "loc-highlight-icon-wrap" >
< Icon name = { highlight . icon } className="loc-highlight-icon" />
</ div >
< span class = "loc-highlight-label" >{ highlight . label }</ span >
</ div >
< strong >{ highlight . value }</ strong >
< p >{ highlight . detail }</ p >
</ div >
{ /each }
</ div >
</ div >
</ section >
<!-- ── Parks ── -->
< section use:reveal = {{ delay : 30 }} class="loc-parks reveal-block " >
2026-05-13 20:44:01 +12:00
< div class = "page-inner" >
2026-05-11 21:02:24 +12:00
< div class = "loc-section-header" >
< span class = "loc-eyebrow" > Where we walk</ span >
< h2 > Parks & walks in { location . suburb }</ h2 >
< p class = "loc-section-intro" >
These are the parks and routes we know well in { location . suburb } . Every walk is planned around your dog's pace, size, and temperament — not just the nearest green space.
</ p >
</ div >
< div class = "loc-parks-grid" >
{ #each location . parks as park }
< div class = "loc-park-card" >
< div class = "loc-park-icon" aria-hidden = "true" > 🐾</ div >
< h3 >{ park . name }</ h3 >
< p >{ park . description }</ p >
{ #if park . leashNote }
< span class = "loc-park-leash" >{ park . leashNote }</ span >
{ /if }
</ div >
{ /each }
</ div >
</ div >
</ section >
{ #if parksWithImages . length > 0 }
< section use:reveal = {{ delay : 30 }} class="loc-gallery reveal-block " >
2026-05-13 20:44:01 +12:00
< div class = "page-inner" >
2026-05-11 21:02:24 +12:00
< div class = "loc-section-header" >
< span class = "loc-eyebrow" > Local parks</ span >
< h2 > Park photos from { location . suburb }</ h2 >
< p class = "loc-section-intro" >
Real images from the parks we mention help each suburb page feel more specific and give search engines clearer local context.
</ p >
</ div >
< div class = "loc-gallery-grid" >
{ #each parksWithImages as park }
< figure class = "loc-gallery-card" >
{ #if park . enhanced }
< picture >
< img src = { park . enhanced . img . src } alt= { park . image . alt } loading = "lazy" decoding = "async" />
</ picture >
{ : else }
< img src = { park . image . src } alt= { park . image . alt } loading = "lazy" decoding = "async" />
{ /if }
< figcaption >
< strong >{ park . name }</ strong >
{ #if park . image . caption }
< span >{ park . image . caption }</ span >
{ /if }
</ figcaption >
</ figure >
{ /each }
</ div >
</ div >
</ section >
{ /if }
<!-- ── Services ── -->
< section use:reveal = {{ delay : 30 }} class="loc-services reveal-block " >
2026-05-13 20:44:01 +12:00
< div class = "page-inner" >
2026-05-11 21:02:24 +12:00
< div class = "loc-section-header" >
< span class = "loc-eyebrow" > What we offer</ span >
< h2 > Goodwalk services in { location . suburb }</ h2 >
< p class = "loc-section-intro" >
We offer pack walks, 1:1 walks, and puppy visits in { location . suburb } , with free pickup and drop-off across the central suburbs. Every service starts with a free Meet & Greet so we can understand your dog and recommend the right fit.
</ p >
</ div >
< div class = "loc-services-grid" >
{ #each serviceLinks as svc }
< a href = { svc . href } class="loc-service-card" >
< div class = "loc-service-icon-bubble" >
< Icon name = { svc . icon } className="loc-service-icon" />
</ div >
< h3 >{ svc . label }</ h3 >
< p >{ svc . desc }</ p >
< span class = "loc-service-link" > Learn more →</ span >
</ a >
{ /each }
</ div >
</ div >
</ section >
<!-- ── Testimonial ── -->
{ #if featuredTestimonial }
< section use:reveal = {{ delay : 30 }} class="loc-review reveal-block " >
2026-05-13 20:44:01 +12:00
< div class = "page-inner" >
2026-05-11 21:02:24 +12:00
< div class = "loc-review-card" >
< span class = "loc-review-stars" aria-hidden = "true" > ★★★★★</ span >
< blockquote class = "loc-review-quote" > "{ featuredTestimonial . quote } "</ blockquote >
< cite class = "loc-review-cite" >
{ featuredTestimonial . reviewer }
{ #if featuredTestimonial . detail }
< span class = "loc-review-detail" > — { featuredTestimonial . detail }</ span >
{ /if }
</ cite >
</ div >
</ div >
</ section >
{ /if }
<!-- ── CTA ── -->
< section use:reveal = {{ delay : 30 }} class="loc-cta reveal-block " >
2026-05-13 20:44:01 +12:00
< div class = "page-inner" >
< CtaCard
title = "Ready to get started in { location . suburb } ?"
description = "A free Meet & Greet is the first step — no commitment, no pressure. We meet your dog, answer your questions, and see if Goodwalk is the right fit."
ctaHref = "/contact-us"
ctaLabel = "Book a free Meet & Greet"
email = "info@goodwalk.co.nz"
phone = "(022) 642 1011"
phoneHref = "tel:+64226421011"
/>
2026-05-11 21:02:24 +12:00
</ div >
</ section >
</ main >
< style >
. loc-page {
background : var ( -- off - white );
}
/* ── Eyebrow ── */
. loc-eyebrow ,
2026-05-13 20:44:01 +12:00
. loc-hero-eyebrow {
2026-05-11 21:02:24 +12:00
display : inline-block ;
margin-bottom : 14 px ;
padding : 7 px 12 px ;
border-radius : 999 px ;
font-size : 12 px ;
font-weight : 800 ;
letter-spacing : 0.08 em ;
text-transform : uppercase ;
}
. loc-eyebrow {
background : rgba ( 33 , 48 , 33 , 0.08 );
color : var ( -- gw - green );
box-shadow : inset 0 0 0 1 px rgba ( 17 , 20 , 24 , 0.05 );
}
2026-05-13 20:44:01 +12:00
. loc-hero-eyebrow {
2026-05-15 01:28:10 +12:00
background : rgba ( 255 , 209 , 0 , 0.12 );
box-shadow : inset 0 0 0 1 px rgba ( 255 , 209 , 0 , 0.2 );
color : var ( -- yellow );
2026-05-11 21:02:24 +12:00
}
/* ── Hero ── */
. loc-hero {
background : var ( -- gw - green );
color : #fff ;
padding : 80 px 0 112 px ;
text-align : center ;
}
. loc-hero h1 {
margin : 0 0 16 px ;
font-family : var ( -- font - head );
font-size : clamp ( 36 px , 5 vw , 64 px );
font-weight : 800 ;
line-height : 1.02 ;
letter-spacing : -0.04 em ;
color : #fff ;
}
. loc-hero-desc {
max-width : 640 px ;
margin : 0 auto 28 px ;
color : rgba ( 255 , 255 , 255 , 0.82 );
font-size : 17 px ;
line-height : 1.65 ;
}
. loc-hero-actions {
display : flex ;
align-items : center ;
justify-content : center ;
gap : 20 px ;
flex-wrap : wrap ;
margin-bottom : 28 px ;
}
. loc-hero-phone {
color : rgba ( 255 , 255 , 255 , 0.75 );
font-size : 15 px ;
text-decoration : none ;
transition : color 0.18 s ease ;
}
. loc-hero-phone : hover {
color : #fff ;
}
. loc-hero-chips {
display : flex ;
justify-content : center ;
gap : 10 px ;
flex-wrap : wrap ;
}
. loc-chip {
display : inline-flex ;
align-items : center ;
gap : 8 px ;
padding : 9 px 18 px ;
border-radius : 999 px ;
background : rgba ( 255 , 255 , 255 , 0.1 );
border : 1 px solid rgba ( 255 , 255 , 255 , 0.14 );
color : #fff ;
font-family : var ( -- font - head );
font-size : 14 px ;
font-weight : 700 ;
line-height : 1.2 ;
letter-spacing : 0.01 em ;
}
. loc-chip-link {
text-decoration : none ;
transition : background 0.18 s ease ;
}
. loc-chip-link : hover {
background : rgba ( 255 , 255 , 255 , 0.18 );
}
. loc-chip-stars {
color : var ( -- yellow );
letter-spacing : 1 px ;
font-size : 13 px ;
}
/* ── Highlights ── */
. loc-highlights {
margin-top : -56 px ;
padding : 0 0 88 px ;
position : relative ;
z-index : 2 ;
}
. loc-highlights-grid {
display : grid ;
grid-template-columns : repeat ( 3 , minmax ( 0 , 1 fr ));
gap : 18 px ;
}
. loc-highlight-card {
position : relative ;
overflow : hidden ;
padding : 24 px 24 px 22 px ;
border-radius : 22 px ;
background :
radial-gradient ( circle at top right , rgba ( 255 , 209 , 71 , 0.22 ), transparent 34 % ),
linear-gradient ( 180 deg , rgba ( 255 , 255 , 255 , 0.99 ) 0 % , #f7f4ec 100 % );
border : 1 px solid rgba ( 17 , 20 , 24 , 0.07 );
box-shadow : 0 18 px 44 px rgba ( 13 , 26 , 13 , 0.09 );
text-align : left ;
}
. loc-highlight-card :: after {
content : '' ;
position : absolute ;
right : -18 px ;
bottom : -18 px ;
width : 96 px ;
height : 96 px ;
border-radius : 50 % ;
background : rgba ( 33 , 48 , 33 , 0.05 );
pointer-events : none ;
}
. loc-highlight-top {
display : flex ;
align-items : center ;
gap : 12 px ;
margin-bottom : 14 px ;
}
. loc-highlight-icon-wrap {
display : inline-flex ;
align-items : center ;
justify-content : center ;
width : 42 px ;
height : 42 px ;
border-radius : 14 px ;
background : linear-gradient ( 180 deg , #ffe173 0 % , #ffd54a 100 % );
box-shadow :
inset 0 0 0 1 px rgba ( 17 , 20 , 24 , 0.05 ),
0 10 px 18 px rgba ( 255 , 209 , 71 , 0.24 );
flex : 0 0 auto ;
}
: global ( . loc-highlight-icon-wrap . loc-highlight-icon ) {
color : var ( -- gw - green );
font-size : 18 px ;
}
. loc-highlight-label {
display : inline-block ;
color : var ( -- gw - green );
font-size : 11 px ;
font-weight : 800 ;
letter-spacing : 0.08 em ;
text-transform : uppercase ;
}
. loc-highlight-card strong {
display : block ;
margin : 0 0 8 px ;
color : #0d1a0d ;
font-family : var ( -- font - head );
font-size : clamp ( 22 px , 2.5 vw , 28 px );
line-height : 1.05 ;
letter-spacing : -0.03 em ;
}
. loc-highlight-card p {
margin : 0 ;
color : #4c5056 ;
font-size : 14 px ;
line-height : 1.6 ;
}
/* ── Section headers ── */
. loc-section-header {
text-align : center ;
margin-bottom : 48 px ;
}
. loc-section-header h2 {
margin : 0 0 12 px ;
font-family : var ( -- font - head );
font-size : clamp ( 26 px , 3 vw , 38 px );
font-weight : 700 ;
line-height : 1.08 ;
letter-spacing : -0.03 em ;
text-wrap : balance ;
color : #0d1a0d ;
}
. loc-section-intro {
max-width : 560 px ;
margin : 0 auto ;
color : #4c5056 ;
font-size : 16 px ;
line-height : 1.65 ;
}
/* ── Parks ── */
. loc-parks {
padding : 0 0 88 px ;
}
. loc-parks-grid {
display : grid ;
grid-template-columns : repeat ( 3 , 1 fr );
gap : 24 px ;
}
. loc-park-card {
background : #fff ;
border-radius : 20 px ;
padding : 32 px 28 px ;
border : 1 px solid rgba ( 17 , 20 , 24 , 0.07 );
box-shadow : 0 4 px 16 px rgba ( 17 , 20 , 24 , 0.04 );
}
. loc-park-icon {
font-size : 28 px ;
margin-bottom : 16 px ;
}
. loc-park-card h3 {
margin : 0 0 10 px ;
font-family : var ( -- font - head );
font-size : 18 px ;
font-weight : 700 ;
line-height : 1.2 ;
color : #0d1a0d ;
}
. loc-park-card p {
margin : 0 0 14 px ;
color : #4c5056 ;
font-size : 15 px ;
line-height : 1.65 ;
}
. loc-park-leash {
display : inline-block ;
padding : 5 px 10 px ;
border-radius : 999 px ;
background : rgba ( 33 , 48 , 33 , 0.07 );
color : var ( -- gw - green );
font-size : 12 px ;
font-weight : 600 ;
}
/* ── Gallery ── */
. loc-gallery {
padding : 0 0 88 px ;
}
. loc-gallery-grid {
display : grid ;
grid-template-columns : repeat ( 3 , 1 fr );
gap : 24 px ;
}
. loc-gallery-card {
margin : 0 ;
overflow : hidden ;
border-radius : 20 px ;
background : #fff ;
border : 1 px solid rgba ( 17 , 20 , 24 , 0.07 );
box-shadow : 0 8 px 28 px rgba ( 17 , 20 , 24 , 0.06 );
}
. loc-gallery-card picture ,
. loc-gallery-card img {
display : block ;
width : 100 % ;
}
. loc-gallery-card img {
aspect-ratio : 4 / 3 ;
object-fit : cover ;
}
. loc-gallery-card figcaption {
display : grid ;
gap : 6 px ;
padding : 18 px 20 px 20 px ;
}
. loc-gallery-card strong {
color : #0d1a0d ;
font-size : 16 px ;
line-height : 1.35 ;
}
. loc-gallery-card span {
color : #4c5056 ;
font-size : 14 px ;
line-height : 1.6 ;
}
/* ── Services ── */
. loc-services {
padding : 0 0 88 px ;
}
. loc-services-grid {
display : grid ;
grid-template-columns : repeat ( 3 , 1 fr );
gap : 20 px ;
}
. loc-service-card {
display : flex ;
flex-direction : column ;
padding : 28 px 24 px ;
background : var ( -- gw - green );
border-radius : 20 px ;
text-decoration : none ;
transition : transform 0.18 s cubic-bezier ( 0.22 , 1 , 0.36 , 1 ), box-shadow 0.18 s ease ;
}
@ media ( hover : hover ) {
. loc-highlight-card : hover {
transform : translateY ( -3 px );
box-shadow : 0 20 px 40 px rgba ( 13 , 26 , 13 , 0.12 );
}
. loc-park-card : hover {
transform : translateY ( -3 px );
box-shadow : 0 16 px 32 px rgba ( 17 , 20 , 24 , 0.08 );
}
. loc-service-card : hover {
transform : translateY ( -3 px );
box-shadow : 0 16 px 36 px rgba ( 33 , 48 , 33 , 0.2 );
}
}
. loc-park-card ,
. loc-highlight-card ,
. loc-gallery-card ,
. loc-service-card {
transition : transform 0.18 s cubic-bezier ( 0.22 , 1 , 0.36 , 1 ), box-shadow 0.18 s ease ;
}
. loc-service-icon-bubble {
display : inline-flex ;
align-items : center ;
justify-content : center ;
width : 72 px ;
height : 72 px ;
margin : 0 0 20 px ;
border-radius : 50 % ;
background : linear-gradient ( 180 deg , #ffe173 0 % , #ffd54a 100 % );
box-shadow :
inset 0 0 0 1 px rgba ( 17 , 20 , 24 , 0.05 ),
0 10 px 24 px rgba ( 17 , 20 , 24 , 0.16 );
}
: global ( . loc-service-icon-bubble . loc-service-icon ) {
color : var ( -- gw - green );
font-size : 28 px ;
}
. loc-service-card h3 {
margin : 0 0 8 px ;
font-family : var ( -- font - head );
font-size : 20 px ;
font-weight : 700 ;
color : #fff ;
}
. loc-service-card p {
margin : 0 ;
flex : 1 ;
color : rgba ( 255 , 255 , 255 , 0.75 );
font-size : 14 px ;
line-height : 1.55 ;
}
. loc-service-link {
display : inline-block ;
margin-top : 18 px ;
color : var ( -- yellow );
font-size : 14 px ;
font-weight : 700 ;
}
/* ── Review ── */
. loc-review {
padding : 0 0 88 px ;
}
. loc-review-card {
background : #fff ;
border-radius : 24 px ;
padding : 48 px 56 px ;
text-align : center ;
border : 1 px solid rgba ( 17 , 20 , 24 , 0.06 );
box-shadow : 0 8 px 32 px rgba ( 17 , 20 , 24 , 0.05 );
}
. loc-review-stars {
display : block ;
color : var ( -- yellow );
font-size : 20 px ;
letter-spacing : 3 px ;
margin-bottom : 20 px ;
}
. loc-review-quote {
margin : 0 0 20 px ;
font-family : var ( -- font - head );
font-size : clamp ( 18 px , 2.2 vw , 24 px );
font-weight : 600 ;
line-height : 1.45 ;
color : #0d1a0d ;
font-style : normal ;
max-width : 720 px ;
margin-left : auto ;
margin-right : auto ;
}
. loc-review-cite {
font-style : normal ;
color : var ( -- gw - green );
font-weight : 700 ;
font-size : 15 px ;
}
. loc-review-detail {
font-weight : 400 ;
color : #888 ;
margin-left : 4 px ;
}
/* ── CTA ── */
. loc-cta {
padding : 0 0 88 px ;
}
/* ── Tablet ── */
@ media ( max-width : 1024px ) {
. loc-highlights-grid ,
. loc-parks-grid ,
. loc-gallery-grid {
grid-template-columns : repeat ( 2 , 1 fr );
}
}
/* ── Mobile ── */
@ media ( max-width : 768px ) {
. loc-hero {
padding : 56 px 0 48 px ;
}
. loc-hero h1 {
font-size : 34 px ;
}
. loc-hero-desc {
font-size : 15 px ;
}
. loc-hero-actions {
flex-direction : column ;
gap : 12 px ;
}
2026-05-15 01:28:10 +12:00
. loc-eyebrow ,
. loc-hero-eyebrow {
display : block ;
width : fit-content ;
margin-left : auto ;
margin-right : auto ;
text-align : center ;
}
. loc-section-header h2 ,
. loc-section-intro {
text-align : center ;
}
2026-05-11 21:02:24 +12:00
. loc-highlights {
margin-top : -24 px ;
padding-bottom : 60 px ;
}
. loc-highlights-grid {
grid-template-columns : 1 fr ;
gap : 14 px ;
}
. loc-highlight-card {
padding : 20 px 18 px 18 px ;
}
. loc-parks {
padding : 60 px 0 ;
}
. loc-parks-grid {
grid-template-columns : 1 fr ;
gap : 16 px ;
}
. loc-gallery {
padding-bottom : 60 px ;
}
. loc-gallery-grid {
grid-template-columns : 1 fr ;
gap : 16 px ;
}
. loc-services {
padding-bottom : 60 px ;
}
. loc-services-grid {
grid-template-columns : 1 fr ;
gap : 14 px ;
}
. loc-review {
padding-bottom : 60 px ;
}
. loc-review-card {
padding : 32 px 24 px ;
}
. loc-cta {
padding-bottom : 60 px ;
}
}
</ style >