A luxury safari booking platform built with SvelteKit and the authentic Abercrombie & Kent design system.
| Feature | React | SvelteKit |
|---|---|---|
| Bundle Size | 584 KB | 97 KB (83% smaller) |
| SSR/SEO | Requires setup | Built-in |
| Routing | React Router | File-based |
| State | useState/Context | Reactive $: |
| Styling | CSS-in-JS/Mantine | Scoped CSS |
| Learning Curve | Medium | Easy |
Based on authentic A&K tokens extracted from their production CSS:
/* Primary Backgrounds */
--color-linen: #f5f2eb
/* Signature Accent Colors */
--color-burnt-sienna: #aa5432
--color-warm-sand: #c3aa84
--color-forest: #335525
/* Typography */
--font-primary: 'Cormorant Garamond' (serif headings)
--font-secondary: 'Inter' (body text)
asis-safaris-svelte/
āāā src/
ā āāā routes/
ā ā āāā +layout.svelte # Root layout (Header + Footer)
ā ā āāā +page.svelte # Main safari itinerary page
ā ā
ā āāā lib/
ā ā āāā components/
ā ā ā āāā Header.svelte
ā ā ā āāā Footer.svelte
ā ā ā āāā ItineraryDay.svelte
ā ā ā āāā BookingPanel.svelte
ā ā ā
ā ā āāā data/
ā ā ā āāā safaris.ts # Safari package data
ā ā ā
ā ā āāā types/
ā ā āāā index.ts # TypeScript interfaces
ā ā
ā āāā app.html # HTML template
ā āāā app.css # Global styles + A&K design tokens
ā
āāā static/ # Static assets (images)
ā āāā ak-forest.webp
ā āāā ak-guide.webp
ā āāā ak-nile.webp
ā āāā ak-japan.webp
ā āāā ak-morocco.webp
ā
āāā svelte.config.js
āāā vite.config.ts
āāā package.json
# Install dependencies
npm install
# Development server
npm run dev
# Production build
npm run build
# Preview production build
npm run preview
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā HERO IMAGE ā
āāāāāāāāāāāāāāāāāāāāāāāāāāā¬āāāāāāāāāāāāāāāāāāāāāāāāāāā¤
ā ITINERARY (scrolls) ā BOOKING PANEL (sticky) ā
ā [Day 1: Nairobi ā¼] ā FROM $2,450 / person ā
ā [Day 2: Nakuru ā¼] ā š
Select Dates ā
ā [Day 3: Mara ā¼] ā [Talk to Us First] ā
āāāāāāāāāāāāāāāāāāāāāāāāāāā“āāāāāāāāāāāāāāāāāāāāāāāāāāā
Click any day to reveal:
Instead of "Book Now", we prioritize human connection:
// React
const [count, setCount] = useState(0);
setCount(count + 1);
<!-- Svelte -->
<script>
let count = 0;
count += 1; // Just assign!
</script>
// React
const total = useMemo(() => price * quantity, [price, quantity]);
<!-- Svelte -->
<script>
$: total = price * quantity; // Automatic reactivity!
</script>
// React
{isOpen && <Modal />}
{isLoading ? <Spinner /> : <Content />}
<!-- Svelte -->
{#if isOpen}
<Modal />
{/if}
{#if isLoading}
<Spinner />
{:else}
<Content />
{/if}
// React
{items.map(item => <Item key={item.id} {...item} />)}
<!-- Svelte -->
{#each items as item (item.id)}
<Item {...item} />
{/each}
// React
<button onClick={() => setCount(c => c + 1)}>
<!-- Svelte -->
<button on:click={() => count += 1}>
<style>
/* These styles only apply to this component */
.button { background: blue; }
</style>
npm i -D @sveltejs/adapter-vercel
// svelte.config.js
import adapter from '@sveltejs/adapter-vercel';
npm i -D @sveltejs/adapter-netlify
npm i -D @sveltejs/adapter-node
Same API endpoints as React version:
POST /api/inquiries
{
packageId: string,
selectedDate: Date,
travelers: { adults: number, children: number },
selectedAddOns: string[],
customerInfo: { name, phone, email, message },
estimatedTotal: number,
preferredContact: 'call' | 'whatsapp' | 'email'
}
Built with SvelteKit + A&K Design System ⢠Nairobi, Kenya š°šŖ