A modern, visually stunning meeting booking interface built for the ChaseLabs SDR platform. Features an interactive hexagon background, smooth GSAP animations, and Threlte (Svelte + Three.js) effects for a premium user experience.
This project demonstrates a complete meeting booking flow where prospects can:
| Category | Technology |
|---|---|
| Framework | SvelteKit with Svelte 5 (Runes mode) |
| Language | TypeScript |
| 3D Graphics | Threlte (Svelte + Three.js) |
| Animation | GSAP |
| Styling | Tailwind CSS + Custom CSS |
| Fonts | DM Sans + Fraunces (Google Fonts) |
| Deployment | Vercel |
| API | MeetChase Calendar API |
src/
βββ lib/
β βββ api/
β β βββ CalendarAPI.ts # API client for MeetChase endpoints
β β
β βββ components/
β β βββ BookingForm.svelte # User details form
β β βββ Calendar.svelte # Interactive date picker (GSAP ripple)
β β βββ Confirmation.svelte # Success screen (GSAP checkmark draw)
β β βββ RepCard.svelte # Sales rep info card
β β βββ TimeSlots.svelte # Time slot selector (GSAP stagger)
β β βββ MagneticButton.svelte # Button with magnetic hover effect
β β β
β β βββ BackgroundScene.svelte # Threlte floating orbs (3D)
β β βββ ConfirmationScene.svelte # Threlte confetti burst (3D)
β β β
β β βββ HexagonField/ # Interactive hexagon background
β β βββ HexagonField.svelte # Main component wrapper
β β βββ engine.ts # Canvas rendering engine
β β βββ type.ts # TypeScript definitions
β β
β βββ stores/
β β βββ booking.svelte.ts # Centralized state (Svelte 5 runes)
β β
β βββ types/
β β βββ CalendarDay.ts # Calendar day interface
β β βββ constants.ts # App constants (rep info, config)
β β βββ FormInfo.ts # Form data types
β β βββ RepInfo.ts # Sales rep interface
β β βββ types.ts # Shared type definitions
β β
β βββ utils/
β βββ utils.ts # Date formatting, slot parsing
β βββ animations.ts # GSAP animation utilities
β
βββ routes/
β βββ +layout.svelte # Root layout with HexagonField
β βββ +page.svelte # Main booking page
β βββ layout.css # Layout styles (Tailwind imports)
β βββ page.css # Page-specific styles
β
βββ app.html # HTML template
An interactive canvas-based hexagon grid that responds to mouse movement:
<HexagonField
rows={39}
columns={32}
baseScale={1}
minScale={0.12}
animateJiggle={true}
jiggleDurationSec={2}
fullScreenFixed={true}
mouseRadiusDivisor={4}
colorTransitionSec={1.2}
colorEase="power2.inOut"
jiggleEase="power1.inOut"
jiggleScaleRange={[0.65, 0.85]}
colors={['#667eea', '#764ba2', '#F6AD55', '#F5F3FF']}
>
{@render children()}
</HexagonField>
| Prop | Type | Description |
|---|---|---|
columns |
number |
Number of hexagon columns |
rows |
number |
Number of hexagon rows |
baseScale |
number |
Default hexagon scale |
minScale |
number |
Minimum scale when mouse hovers |
mouseRadiusDivisor |
number |
Mouse influence area (viewport / divisor) |
colors |
string[] |
Array of fill colors |
animateJiggle |
boolean |
Enable breathing animation |
jiggleDurationSec |
number |
Jiggle animation duration |
jiggleScaleRange |
[number, number] |
Scale range for jiggle |
jiggleEase |
string |
GSAP easing for jiggle |
jiggleRecolor |
boolean |
Randomize colors on jiggle repeat |
colorTransitionSec |
number |
Color transition duration |
colorEase |
string |
GSAP easing for color transitions |
fullScreenFixed |
boolean |
Fixed fullscreen positioning |
The app uses Threlte β a Svelte-native Three.js wrapper that provides:
Floating gradient orbs with ambient particles:
<Canvas>
<T.PerspectiveCamera makeDefault position={[0, 0, 30]} fov={75} />
{#each orbs as orb}
<Float speed={orb.floatSpeed} floatIntensity={orb.floatIntensity}>
<T.Mesh position={orb.position}>
<T.IcosahedronGeometry args={[orb.size, 1]} />
<T.MeshBasicMaterial color={orb.color} transparent opacity={orb.opacity} />
</T.Mesh>
</Float>
{/each}
<T.Points><!-- Ambient particles --></T.Points>
</Canvas>
Celebratory confetti burst with physics:
Located in src/lib/utils/animations.ts:
| Function | Used In | Effect |
|---|---|---|
staggerFadeIn() |
TimeSlots, Confirmation | Items cascade in with delay |
calendarRippleIn() |
Calendar | Days ripple from center on month change |
createMagneticButton() |
MagneticButton | Button follows cursor on hover |
buttonClickEffect() |
MagneticButton | Squish effect on click |
drawCheckmark() |
Confirmation | SVG draws itself on success |
celebrationBurst() |
Confirmation | DOM particle burst |
shakeAnimation() |
Error states | Shake effect for errors |
fieldFocusAnimation() |
Form inputs | Glow effect on focus |
The application integrates with the MeetChase Calendar API (calendar.meetchase.ai):
/api/availabilityFetches available time slots for a date range.
const slots = await getAvailability('2025-01-01', '2025-01-31');
Response:
[
{
"start": "2025-01-15T09:00:00+01:00",
"end": "2025-01-15T11:00:00+01:00"
}
]
/api/meetingsSchedules a meeting if the requested time slot is available.
await scheduleMeeting({
start: "2025-01-15T10:00:00+01:00",
end: "2025-01-15T10:30:00+01:00",
attendees: [{ email: "[email protected]", name: "John Doe" }]
});
Response: 201 Created with confirmation string
booking.svelte.ts (State Store)Centralized state using Svelte 5 runes:
const booking = createBookingState();
// State properties
booking.currentMonth // Current calendar month
booking.selectedDate // Selected date (YYYY-MM-DD)
booking.selectedSlot // Selected time slot with ISO timestamps
booking.selectedTime // Display time (e.g., "9:00 AM")
booking.step // Current step: 'calendar' | 'form' | 'confirmed'
booking.availability // Raw API availability data
booking.availableSlots // Parsed time slots for selected date
booking.isSubmitting // Form submission state
booking.isLoadingSlots // API loading state
booking.error // Error message
booking.formData // User form data
// Methods
booking.prevMonth() // Navigate to previous month
booking.nextMonth() // Navigate to next month
booking.resetTimeSelection()
booking.clearError()
utils.ts (Helper Functions)| Function | Description |
|---|---|
generateCalendarDays() |
Creates calendar grid with day metadata |
formatSelectedDate() |
Formats date for display |
formatDateForApi() |
Formats date as YYYY-MM-DD |
getMonthDateRange() |
Gets first/last day of month for API calls |
parseAvailabilityToSlots() |
Converts API availability to 30-min time slots |
getDatesWithAvailability() |
Returns Set of dates that have available slots |
engine.ts)The hexagon background uses a custom canvas rendering engine:
const engine = createHexFieldEngine(options);
engine.attach(canvasElement); // Bind to canvas
engine.start(); // Start animation loop
engine.updateOptions({...}); // Update options reactively
engine.stop(); // Pause animation
engine.destroy(); // Full cleanup
Key Features:
# Clone the repository
git clone <repository-url>
cd book_appointment_task
# Install dependencies
bun install
# Install required packages
bun add gsap three @threlte/core @threlte/extras
bun add -d @types/three
# Start development server
bun run dev
Open http://localhost:5173 to view the app.
bun run build
bun run preview
Edit src/lib/types/constants.ts:
export const ACME_REP: RepInfo = {
name: 'Sarah Chen',
title: 'Account Executive',
avatar: 'SC',
email: '[email protected]',
meetingDuration: 30 // minutes
};
Edit src/lib/api/CalendarAPI.ts:
const API_BASE = 'https://calendar.meetchase.ai';
Edit src/routes/+layout.svelte:
<HexagonField
colors={['#667eea', '#764ba2', '#F6AD55', '#F5F3FF']}
<!-- ... other props -->
>
Edit the color arrays in BackgroundScene.svelte and ConfirmationScene.svelte:
const colors = [0x667eea, 0x764ba2, 0x8b5cf6, 0x6366f1, 0xa78bfa];
The app uses a refined, professional aesthetic with:
#667eea β #764ba2 (purple)#F6AD55 (orange), #F5F3FF (light purple)The project uses Tailwind CSS v4 with plugins:
/* layout.css */
@import 'tailwindcss';
@plugin '@tailwindcss/forms';
@plugin '@tailwindcss/typography';
The layout adapts to different screen sizes:
The application handles various error states:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β +layout.svelte β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β HexagonField (background) β β
β β - Canvas rendering via engine.ts β β
β β - GSAP ticker for smooth 60fps animation β β
β β - Mouse-reactive hexagon scaling β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β +page.svelte β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β onMount / month change β β
β β β β β
β β getAvailability(start, end) βββ CalendarAPI.ts β β
β β β β β
β β booking.availability = response β β
β β β β β
β β parseAvailabilityToSlots() βββ utils.ts β β
β β β β β
β β booking.availableSlots = slots β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββββ β
β β Calendar ββ βTimeSlots ββ β Form ββ βConfirmationβ β
β β (ripple) β β(stagger) β β β β(confetti) β β
β β β β β β β β [Threlte] β β
β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββββ β
β β β β β
β ββββββββββββββββ΄ββββββββββββββ β
β booking.svelte.ts (state) β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Run unit tests
bun run test
# Run e2e tests
bun run test:e2e
// page.svelte.spec.ts
import { page } from 'vitest/browser';
import { describe, expect, it } from 'vitest';
import { render } from 'vitest-browser-svelte';
import Page from './+page.svelte';
describe('/+page.svelte', () => {
it('should render h1', async () => {
render(Page);
const heading = page.getByRole('heading', { level: 1 });
await expect.element(heading).toBeInTheDocument();
});
});
| Package | Purpose |
|---|---|
svelte / @sveltejs/kit |
Framework |
gsap |
Animation library |
three |
3D graphics engine |
@threlte/core |
Svelte Three.js wrapper |
@threlte/extras |
Threlte utilities (Float, etc.) |
| Package | Purpose |
|---|---|
typescript |
Type safety |
@types/three |
Three.js types |
vite |
Build tool |
tailwindcss |
Utility CSS |
vitest |
Testing |
The app deploys seamlessly to Vercel. If you encounter issues:
Paraglide i18n error: If you see project.inlang/settings.json error, either create the config or remove @inlang/paraglide-sveltekit from vite.config.ts
SSR with Three.js: The Threlte components are SSR-safe, but if you add custom Three.js code, wrap it with:
<script>
import { browser } from '$app/environment';
</script>
{#if browser}
<!-- Three.js content -->
{/if}
This project was created as part of the ChaseLabs Design Engineer Assessment.
Built with β€οΈ using SvelteKit, Threlte, GSAP, and the MeetChase API