diff --git a/mail-api/main.py b/mail-api/main.py
index 369c193..73000d8 100644
--- a/mail-api/main.py
+++ b/mail-api/main.py
@@ -392,7 +392,7 @@ def client_email(data: BookingSubmission) -> str:
- We’ve received your enquiry and Aless will be in touch shortly to arrange
+ We’ve received your enquiry and we will be in touch shortly to arrange
a Meet & Greet with you and
{data.petName}.
@@ -431,7 +431,7 @@ def client_email(data: BookingSubmission) -> str:
- We will review your details and reach out within 1–2 business days
+ We will review your details and reach out within 1 business days
to schedule a free Meet & Greet. No commitment required — just a
chance for {data.petName} to make a new best friend.
@@ -441,9 +441,7 @@ def client_email(data: BookingSubmission) -> str:
- Questions? Just reply to this email or reach us at
- {REPLY_TO}.
+ Questions? Just reply to this email or reach us at 022 642 1011.
diff --git a/src/lib/components/InstagramSection.svelte b/src/lib/components/InstagramSection.svelte
index fb36b06..7f68412 100644
--- a/src/lib/components/InstagramSection.svelte
+++ b/src/lib/components/InstagramSection.svelte
@@ -40,11 +40,11 @@
}
.instagram-panel {
- padding: 34px 360px 44px 44px;
+ padding: 42px 44px 112px;
border-radius: 32px;
background:
radial-gradient(circle at top left, rgba(255, 255, 255, 0.52), transparent 42%),
- linear-gradient(135deg, rgba(255, 250, 236, 0.96), rgba(255, 240, 188, 0.94));
+ linear-gradient(135deg, rgba(255, 252, 242, 0.98), rgba(255, 243, 198, 0.96));
box-shadow:
inset 0 0 0 1px rgba(17, 20, 24, 0.06),
0 26px 50px rgba(106, 80, 16, 0.14);
@@ -53,7 +53,9 @@
.instagram-copy {
padding-top: 10px;
- text-align: left;
+ max-width: 620px;
+ margin: 0 auto;
+ text-align: center;
}
.instagram-kicker {
@@ -70,12 +72,16 @@
}
.instagram-copy :global(h2) {
- max-width: 11ch;
+ max-width: 12ch;
+ margin-left: auto;
+ margin-right: auto;
}
.instagram-blurb {
- max-width: 420px;
+ max-width: 520px;
margin-bottom: 0;
+ margin-left: auto;
+ margin-right: auto;
}
.instagram-button {
@@ -84,13 +90,30 @@
.instagram-dog-wrap {
position: absolute;
- right: 24px;
- bottom: -12px;
+ left: 50%;
+ bottom: -74px;
display: flex;
align-items: flex-end;
justify-content: center;
- width: clamp(240px, 30vw, 360px);
+ width: clamp(300px, 36vw, 430px);
pointer-events: none;
+ transform: translateX(-50%);
+ z-index: 1;
+ }
+
+ .instagram-dog-wrap::before {
+ content: '';
+ position: absolute;
+ left: 50%;
+ bottom: 54px;
+ width: 92%;
+ height: 56%;
+ border-radius: 999px 999px 40px 40px;
+ background:
+ radial-gradient(circle at 50% 35%, rgba(255, 244, 194, 0.95), rgba(255, 224, 122, 0.88));
+ transform: translateX(-50%);
+ filter: blur(2px);
+ z-index: -1;
}
.instagram-dog {
@@ -117,7 +140,7 @@
}
.instagram-copy {
- text-align: center;
+ max-width: none;
}
.instagram-copy :global(h2) {
@@ -142,6 +165,12 @@
transform: translateX(-50%);
}
+ .instagram-dog-wrap::before {
+ width: 86%;
+ height: 52%;
+ bottom: 8px;
+ }
+
.instagram-dog {
width: 100%;
}
diff --git a/src/lib/components/RouteSkeleton.svelte b/src/lib/components/RouteSkeleton.svelte
new file mode 100644
index 0000000..bf1d1e3
--- /dev/null
+++ b/src/lib/components/RouteSkeleton.svelte
@@ -0,0 +1,730 @@
+
+
+
+
+
+
+ {#if variant === 'home'}
+
+
+
+
+
+
+
+ {#each heroLines as _}
+
+ {/each}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {#each shortLines as _}
+
+ {/each}
+
+
+ {#each cardTriplet as _}
+
+ {/each}
+
+
+
+
+
+
+
+
+
+
+
+ {#each doubleStack as _}
+
+
+
+
+
+ {/each}
+
+
+
+
+
+
+ {:else if variant === 'service'}
+
+
+
+
+
+
+ {#each heroLines as _}
+
+ {/each}
+
+
+
+
+
+
+
+
+
+
+
+
+ {#each cardTriplet as _}
+
+ {/each}
+
+
+
+
+
+
+
+ {#each cardTriplet as _}
+
+ {/each}
+
+
+
+
+
+
+ {:else if variant === 'about'}
+
+
+
+ {#each doubleStack as index}
+
+
+
+
+ {#each heroLines as _}
+
+ {/each}
+
+
+
+
+ {/each}
+
+
+
+
+
+
+
+ {#each cardTriplet as _}
+
+ {/each}
+
+
+
+
+
+
+ {:else if variant === 'pricing'}
+
+
+
+ {#each doubleStack as _}
+
+
+
+
+
+
+
+ {#each cardTriplet as _}
+
+ {/each}
+
+
+
+ {/each}
+
+
+
+ {:else if variant === 'contact'}
+
+
+
+
+
+ {:else}
+
+
+
+
+
+
+ {#each cardQuartet as _}
+
+ {/each}
+ {#each heroLines as _}
+
+ {/each}
+
+
+
+
+ {/if}
+
+
+
+
+
+
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index eaa20d4..c0882c1 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -1,7 +1,9 @@
-
+
+
+
+
+
+ {#if showRouteSkeleton}
+
+
+
+ {/if}
+
+
+
diff --git a/src/routes/layout.test.ts b/src/routes/layout.test.ts
index 94cd63b..688ef0f 100644
--- a/src/routes/layout.test.ts
+++ b/src/routes/layout.test.ts
@@ -1,5 +1,6 @@
import { render } from '@testing-library/svelte';
import { beforeEach, describe, expect, it, vi } from 'vitest';
+import { setMockNavigating, setMockPage } from '../test/mocks/app-stores';
const afterNavigate = vi.fn();
const disableScrollHandling = vi.fn();
@@ -49,4 +50,25 @@ describe('root layout navigation behavior', () => {
expect(disableScrollHandling).not.toHaveBeenCalled();
});
+
+ it('shows a route skeleton while navigating to a new page', async () => {
+ setMockPage('https://www.goodwalk.co.nz/about');
+ setMockNavigating('https://www.goodwalk.co.nz/contact-us', 'https://www.goodwalk.co.nz/about');
+
+ const { default: Layout } = await import('./+layout.svelte');
+ const { getByLabelText, container } = render(Layout);
+
+ expect(getByLabelText('Loading page')).toBeInTheDocument();
+ expect(container.querySelector('[data-skeleton-variant="contact"]')).toBeInTheDocument();
+ });
+
+ it('does not show the skeleton for hash-only navigation', async () => {
+ setMockPage('https://www.goodwalk.co.nz/about');
+ setMockNavigating('https://www.goodwalk.co.nz/about#team', 'https://www.goodwalk.co.nz/about');
+
+ const { default: Layout } = await import('./+layout.svelte');
+ const { queryByLabelText } = render(Layout);
+
+ expect(queryByLabelText('Loading page')).not.toBeInTheDocument();
+ });
});
diff --git a/src/routes/robots.txt/robots.test.ts b/src/routes/robots.txt/robots.test.ts
index 8b086db..79e0d3d 100644
--- a/src/routes/robots.txt/robots.test.ts
+++ b/src/routes/robots.txt/robots.test.ts
@@ -3,7 +3,7 @@ import { GET } from './+server';
describe('robots endpoint', () => {
it('returns the crawl policy and sitemap location', async () => {
- const response = GET();
+ const response = await GET({} as never);
const body = await response.text();
expect(response.headers.get('content-type')).toBe('text/plain; charset=utf-8');
diff --git a/src/routes/sitemap.xml/sitemap.test.ts b/src/routes/sitemap.xml/sitemap.test.ts
index a86b28a..9743ea3 100644
--- a/src/routes/sitemap.xml/sitemap.test.ts
+++ b/src/routes/sitemap.xml/sitemap.test.ts
@@ -10,7 +10,7 @@ describe('sitemap endpoint', () => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-05-01T09:15:00Z'));
- const response = GET();
+ const response = await GET({} as never);
const body = await response.text();
expect(response.headers.get('content-type')).toBe('application/xml; charset=utf-8');
diff --git a/src/test/mocks/app-stores.ts b/src/test/mocks/app-stores.ts
index b854358..de41d8b 100644
--- a/src/test/mocks/app-stores.ts
+++ b/src/test/mocks/app-stores.ts
@@ -31,7 +31,12 @@ export function createMockPage(
}
export const pageStore = writable(createMockPage());
-export const navigatingStore = writable(null);
+export type MockNavigatingStoreValue = {
+ from: { url: URL } | null;
+ to: { url: URL } | null;
+} | null;
+
+export const navigatingStore = writable(null);
const updatedWritable = writable(false);
export const updatedStore = {
@@ -46,3 +51,17 @@ export function setMockPage(url: string, overrides: Partial
export function resetMockPage() {
pageStore.set(createMockPage());
}
+
+export function setMockNavigating(
+ to: string,
+ from = 'https://www.goodwalk.co.nz/'
+) {
+ navigatingStore.set({
+ from: { url: new URL(from) },
+ to: { url: new URL(to) }
+ });
+}
+
+export function resetMockNavigating() {
+ navigatingStore.set(null);
+}
diff --git a/tsconfig.json b/tsconfig.json
index 2a7be92..e1ac53a 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -9,6 +9,6 @@
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
- "types": ["node"]
+ "types": ["node", "vitest/globals", "@testing-library/jest-dom"]
}
}
diff --git a/vitest.setup.ts b/vitest.setup.ts
index 61a1b35..ad4a49a 100644
--- a/vitest.setup.ts
+++ b/vitest.setup.ts
@@ -1,7 +1,13 @@
import '@testing-library/jest-dom/vitest';
import { cleanup } from '@testing-library/svelte';
import { afterEach, beforeAll, vi } from 'vitest';
-import { navigatingStore, pageStore, resetMockPage, updatedStore } from './src/test/mocks/app-stores';
+import {
+ navigatingStore,
+ pageStore,
+ resetMockNavigating,
+ resetMockPage,
+ updatedStore
+} from './src/test/mocks/app-stores';
vi.mock('$app/stores', () => ({
page: { subscribe: pageStore.subscribe },
@@ -81,6 +87,7 @@ beforeAll(() => {
afterEach(() => {
cleanup();
resetMockPage();
+ resetMockNavigating();
vi.clearAllMocks();
document.head.innerHTML = '';
});