diff --git a/MARKETING.md b/MARKETING.md new file mode 100644 index 0000000..6bed350 --- /dev/null +++ b/MARKETING.md @@ -0,0 +1,133 @@ +# Marketing Principles for Goodwalk + +A working reference for the Goodwalk site rebuild and ongoing marketing decisions. Drawn from Chris Do (The Futur) and Debbie Millman (Design Matters), applied to the goal of acquiring 10 new clients. + +--- + +## Chris Do's Principles + +### 1. Sell the transformation, not the service + +People don't buy "dog walking" — they buy peace of mind at work, a tired happy dog, not feeling guilty. + +The headline shouldn't be "Professional Dog Walking in Wellington." It should speak to the outcome: + +- "Come home to a happy, exercised dog" +- "Your dog's best part of the day, while you're at work" + +### 2. Niche down to stand out + +"Dog walker" competes with everyone. "Dog walker for working professionals in [suburb] with anxious or reactive dogs" competes with almost no one — and can charge more. + +Pick a wedge. + +### 3. Price on value, not time + +Don't lead with "$25 per walk." Lead with packages and outcomes: + +> **The Working Professional Plan** — 3 walks/week, GPS updates, photo reports + +Hide the hourly rate. Make it about what they get, not what you do. + +### 4. Show, don't tell + +Testimonials and proof crush adjectives. "Reliable and caring" is meaningless. + +A photo of a muddy grinning dog with a one-line quote from the owner sells: + +> "Bowie pulls me to the door when he sees Sarah's car." + +### 5. Free is a magnet + +Most dog walking sites just have a contact form — that's a closed door. Open one with: + +- A free first walk +- A free meet-and-greet +- A downloadable "Is your dog getting enough exercise?" checklist + +Get people into the funnel. + +--- + +## Debbie Millman's Principles + +### 1. Brand is a story people tell themselves about you + +Branding is deliberate differentiation through storytelling. + +What's the Goodwalk story? Why do you do this? Are you the ex-vet-nurse who only walks small dogs? The runner who takes high-energy breeds on actual trail runs? + +That story belongs on the homepage, not buried on About. + +### 2. Consistency builds trust + +One voice, one visual identity, everywhere: + +- Website +- Instagram +- Car magnet +- The message sent when running 5 minutes late + +Owners are handing you keys to their house and the life of their dog. Visual and verbal consistency signals "I am organised and reliable" before you've said a word. + +### 3. Design is a tool for clarity, not decoration + +Debbie often quotes Massimo Vignelli — design should make the message clearer. + +In 3 seconds, can a stranger answer: + +- What do you do? +- Who is it for? +- How do I book? + +If they have to scroll or think, you're losing them. + +--- + +## Applied: A Plan for 10 New Clients + +A site rewrite with these principles in mind. + +### 1. Homepage hero + +- Outcome-focused headline +- One strong photo of a happy dog mid-walk +- One button: **"Book a free meet-and-greet"** + +### 2. Pick a niche and say it out loud + +Even just "for [your suburb] working professionals" narrows the field and helps you rank. + +### 3. Three packages, not an hourly rate + +Make the middle one the obvious choice (the "decoy effect" — Chris talks about this). + +### 4. Three testimonials with photos and dog names + +Real names, real dogs. Not "J.S. — Customer." + +### 5. One story section + +Who you are, why you do this, why someone should trust you with their dog and their house key. + +### 6. Lead magnet + +A free PDF like "How much exercise does your dog actually need?" in exchange for an email. Then you have a list to follow up with. + +### 7. Kill booking friction + +One-click to a calendar or a WhatsApp link. Not a 7-field form. + +--- + +## Quick Reference Checklist + +- [ ] Headline sells the outcome, not the service +- [ ] Niche is named explicitly on the homepage +- [ ] Pricing presented as packages, not hourly +- [ ] At least 3 testimonials with real names, dog names, and photos +- [ ] Founder story visible on homepage +- [ ] Lead magnet (PDF or free meet-and-greet) above the fold +- [ ] Booking is one click — calendar link or WhatsApp +- [ ] Visual and verbal identity consistent across site, Instagram, and comms +- [ ] In 3 seconds: what / who / how-to-book is obvious diff --git a/src/app.html b/src/app.html index 055f333..1e483d5 100644 --- a/src/app.html +++ b/src/app.html @@ -52,7 +52,7 @@ box-shadow: 0 24px 60px rgba(17, 20, 24, 0.2); text-align: left; font-family: 'Readex Pro', system-ui, sans-serif; - color: #213021; + color: var(--gw-green); } .no-js-kicker { @@ -65,7 +65,7 @@ font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; - color: #213021; + color: var(--gw-green); } .no-js-card h1 { @@ -74,7 +74,7 @@ font-size: clamp(28px, 4vw, 38px); line-height: 1.05; letter-spacing: -0.04em; - color: #213021; + color: var(--gw-green); } .no-js-card p { diff --git a/src/lib/components/BookingPage.svelte b/src/lib/components/BookingPage.svelte index f4a60ef..8aed747 100644 --- a/src/lib/components/BookingPage.svelte +++ b/src/lib/components/BookingPage.svelte @@ -18,9 +18,9 @@

Contact Us

{#if allowGeneralEnquiry} - Fill in the form below to book a Meet & Greet or send a general enquiry. + Book a Meet & Greet or send a general enquiry. We’ll come back within 24 hours. {:else} - Fill in the form below and we'll be in touch to arrange a free introduction. + Tell us a little about your dog and we’ll be in touch within 24 hours to arrange a free Meet & Greet. {/if}

@@ -46,7 +46,7 @@ } .booking-page-hero { - background: var(--green); + background: var(--gw-green); color: #fff; padding: 64px 0 72px; } diff --git a/src/lib/components/BookingSection.svelte b/src/lib/components/BookingSection.svelte index 4052331..c311962 100644 --- a/src/lib/components/BookingSection.svelte +++ b/src/lib/components/BookingSection.svelte @@ -11,7 +11,38 @@ type EnquiryType = 'booking' | 'general'; const visitStartedStorageKey = 'goodwalk_visit_started_at'; const journeyStorageKey = 'goodwalk_journey'; + const requestedServiceStorageKey = 'goodwalk_requested_service'; const maxJourneyEntries = 8; + const servicePrompts: Record< + string, + { + intro: string; + messageLabel: string; + messagePlaceholder: string; + } + > = { + 'Pack Walks': { + intro: + 'Tell us about your dog, your area, and how they feel around other dogs so we can see if Pack Walks are the right fit.', + messageLabel: 'Pack Walks fit', + messagePlaceholder: + 'How old is your dog, how do they feel in groups, and is there anything about confidence, recall, or social behaviour we should know?' + }, + '1:1 Walks': { + intro: + 'Tell us about your dog, your area, and what you want from one-on-one walks so we can plan the right routine.', + messageLabel: '1:1 walk needs', + messagePlaceholder: + 'Tell us about your dog’s size, pace, leash manners, confidence, and anything else that would help us tailor a one-on-one walk.' + }, + 'Puppy Visits': { + intro: + 'Tell us about your puppy, your area, and the kind of support you need at home so we can plan the right visit.', + messageLabel: 'Puppy visit details', + messagePlaceholder: + 'Tell us your puppy’s age, routine, toilet needs, feeding schedule, and anything important we should know before visiting.' + } + }; let step = 1; $: headingParts = splitBookingTitle(booking.title); @@ -76,7 +107,9 @@ const defaultGeneralSubtitle = 'Almost there — just your contact details so we can reply properly to your message.'; - $: dogIntro = booking.dogIntro?.trim() || defaultDogIntro; + $: primarySelectedService = selectedServices[0] ?? ''; + $: activeServicePrompt = servicePrompts[primarySelectedService]; + $: dogIntro = activeServicePrompt?.intro || booking.dogIntro?.trim() || defaultDogIntro; $: generalIntro = booking.generalIntro?.trim() || defaultGeneralIntro; $: hasServices = booking.serviceOptions.length > 0; $: if (!allowGeneralEnquiry && enquiryType === 'general') { @@ -90,6 +123,16 @@ $: dogStepLabel = booking.dogStepLabel?.trim() || 'Your dog'; $: detailsStepLabel = isGeneralEnquiry ? 'Your enquiry' : dogStepLabel; $: detailsStepIntro = isGeneralEnquiry ? generalIntro : dogIntro; + $: bookingEyebrow = isGeneralEnquiry ? 'Friendly contact' : primarySelectedService || 'Free Meet & Greet'; + $: bookingIntro = isGeneralEnquiry + ? 'Send us the details and we’ll point you in the right direction.' + : 'Tell us a little about your dog first. We’ll come back within 24 hours with the right next step.'; + $: detailsMessageLabel = isGeneralEnquiry + ? 'Your Message' + : activeServicePrompt?.messageLabel || 'About Your Dog'; + $: detailsMessagePlaceholder = isGeneralEnquiry + ? 'Tell us if this is feedback, a complaint, a business enquiry, or anything else we should know.' + : activeServicePrompt?.messagePlaceholder || 'Describe your pet, any special needs, or anything we should know.'; $: successPetName = petName.trim() || 'your dog'; onMount(() => { @@ -98,6 +141,19 @@ pageEnteredAt = now; visitStartedAt = readOrCreateVisitStartedAt(now); journey = updateJourneySnapshot(window.location.pathname, window.location.search); + + applyRequestedService(); + + const handleRequestedService = (event: Event) => { + const customEvent = event as CustomEvent<{ service?: string }>; + applyRequestedService(customEvent.detail?.service?.trim()); + }; + + window.addEventListener('goodwalk:service-selected', handleRequestedService as EventListener); + + return () => { + window.removeEventListener('goodwalk:service-selected', handleRequestedService as EventListener); + }; }); function splitBookingTitle(title: string) { @@ -169,11 +225,35 @@ errors = {}; } + function applyRequestedService(service?: string) { + const requestedService = + service || + (() => { + try { + return window.sessionStorage.getItem(requestedServiceStorageKey)?.trim() || ''; + } catch { + return ''; + } + })(); + + if (!requestedService || !booking.serviceOptions.includes(requestedService)) { + return; + } + + selectedServices = [requestedService]; + + try { + window.sessionStorage.removeItem(requestedServiceStorageKey); + } catch { + // Ignore storage cleanup failures. + } + } + function toggleService(service: string, checked: boolean) { noteInteraction(); if (checked) { - selectedServices = [...selectedServices, service]; + selectedServices = [service, ...selectedServices.filter((item) => item !== service)]; return; } @@ -342,10 +422,22 @@ {/if}
+ {bookingEyebrow}

{headingParts.plain}{' '} {headingParts.highlight}

+

{bookingIntro}

+
+ + + Reply within 24 hours + + + + Free, no-obligation Meet & Greet + +
+
+
+
+ + clearError('petName')} + /> + {#if errors.petName} +

+ + {errors.petName} +

+ {/if} +
+
+ + clearError('location')} + /> + {#if errors.location} +

+ + {errors.location} +

+ {/if} +
+ +
+ + + {#if errors.message} +

+ + {errors.message} +

+ {/if} +
+ + {#if hasServices} +
+  Services +
+ {#each booking.serviceOptions as service} + + {/each} +
+
+ {/if} +
+
+ {/if} + + {#if isGeneralEnquiry}
-
{/if} - -
- - - {#if errors.message} -

- - {errors.message} -

- {/if} -
- - {#if hasServices && !isGeneralEnquiry} -
-  Services -
- {#each booking.serviceOptions as service} - - {/each} -
-
- {/if}
-

Response from us within 24 hours

+

No payment, no pressure, just the right starting point for your dog.

{:else} @@ -652,7 +769,13 @@ Back {/if} diff --git a/src/lib/components/ErrorModal.svelte b/src/lib/components/ErrorModal.svelte index cddb65c..8c6460e 100644 --- a/src/lib/components/ErrorModal.svelte +++ b/src/lib/components/ErrorModal.svelte @@ -141,7 +141,7 @@ margin: 0 0 12px; font-size: 24px; font-weight: 700; - color: #213021; + color: var(--gw-green); line-height: 1.25; } @@ -162,7 +162,7 @@ border-radius: 14px; background: #f7f6f1; border: 1px solid #ebe9df; - color: #213021; + color: var(--gw-green); text-decoration: none; transition: background 0.15s ease, @@ -187,7 +187,7 @@ .modal-email-address { font-size: 17px; font-weight: 600; - color: #213021; + color: var(--gw-green); word-break: break-all; } @@ -226,7 +226,7 @@ } .modal-btn-primary { - background: #213021; + background: var(--gw-green); color: #ffd100; } @@ -237,13 +237,13 @@ .modal-btn-secondary { background: transparent; - color: #213021; + color: var(--gw-green); border: 1px solid #d4d2c6; } .modal-btn-secondary:hover { background: #f2f2f0; - border-color: #213021; + border-color: var(--gw-green); } @keyframes backdrop-in { diff --git a/src/lib/components/HeroSection.svelte b/src/lib/components/HeroSection.svelte index 6af4597..9f873e9 100644 --- a/src/lib/components/HeroSection.svelte +++ b/src/lib/components/HeroSection.svelte @@ -25,6 +25,14 @@ connector: '' }; } + + function linkTarget(external?: boolean) { + return external ? '_blank' : undefined; + } + + function linkRel(external?: boolean) { + return external ? 'noopener' : undefined; + }
@@ -71,8 +79,22 @@ {/if}
- {hero.primaryCta.label} - {hero.secondaryCta.label} + + {hero.primaryCta.label} + + + {hero.secondaryCta.label} +
diff --git a/src/lib/components/HowItWorksSection.svelte b/src/lib/components/HowItWorksSection.svelte index 21934c5..cbe9151 100644 --- a/src/lib/components/HowItWorksSection.svelte +++ b/src/lib/components/HowItWorksSection.svelte @@ -1,6 +1,5 @@