From bf9331bb5b504542fbafcb4279cab4dd270d059b Mon Sep 17 00:00:00 2001 From: ponzischeme89 Date: Mon, 4 May 2026 16:30:05 +1200 Subject: [PATCH] Add skeleton, updates to client email formatting --- mail-api/main.py | 8 +- src/lib/components/InstagramSection.svelte | 47 +- src/lib/components/RouteSkeleton.svelte | 730 +++++++++++++++++++++ src/routes/+layout.svelte | 52 +- src/routes/layout.test.ts | 22 + src/routes/robots.txt/robots.test.ts | 2 +- src/routes/sitemap.xml/sitemap.test.ts | 2 +- src/test/mocks/app-stores.ts | 21 +- tsconfig.json | 2 +- vitest.setup.ts | 9 +- 10 files changed, 875 insertions(+), 20 deletions(-) create mode 100644 src/lib/components/RouteSkeleton.svelte 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} +
+
+
+ + +
+
+ +
+
+ +
+
+
+ {/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 = ''; });