Skip to main content
← Back to the ledger

Case study

Spark Academy

Spark Academy — a Peoria, IL preschool that needed more than a 4-page website. We built a 26-page SEO site with a blog and a custom real-time lead tracking dashboard so the owner can see her pipeline from every source.

Client Spark Academy (Peoria, IL)Status ActiveStarted Feb 22, 2026sparkacademymorton.com
113Sessions
2,119 HrsHours
$242,211Market value
Highlights

76 deliverables across 11 specialist roles

Full brand strategy with messaging frameworks and customer personas

12-month content roadmap with cornerstone blog posts

Eleventy static site with dual enrollment funnels

Next.jsCloudflare PagesCustom analytics pipelineSEOEleventy
Before

A small preschool was losing leads because their 4-page 'digital brochure' site couldn't show parents what they actually offered. Google barely knew the school existed. The owner had no idea which pages were driving tours or where visitors were bailing.

What we built

A full 26-page SEO-driven site with a blog, plus a custom real-time tracking dashboard. The dashboard shows exactly where every parent came from, which pages they read, where they bounced, and which KPIs they hit — tour scheduling, program registration, phone calls, contact form submissions, directions requests, and social profile visits.

After

She runs the school. The dashboard runs her pipeline. Leads show up in real time with full context on how they got there. The site ranks for local childcare keywords she couldn't touch before, and every ad dollar is now trackable all the way to a booked tour.

26Pages shipped
9 liveKPIs tracked
$5KWebsite redesign
$2KTracking dashboard

Session timeline

The complete record.

DevOps 4Bug Fix 13Feature 44Refactor 1Strategy 34Design 6Docs 11
Fri, Apr 171
DevOpsdevopsexpand

Walked through revenue page KPI logic (Collected, Expected, Remaining, Past Due) and how each is computed · Projected Jan-May 2026 tuition ($102k scheduled; ~$50k actual cadence) + spring supplementals ($18k) + camp ($49k committed)

Thu, Apr 166
Bug Fixsenior-devexpand

Revenue dashboard fiscal year migration — calendar year (Jan 1 - Dec 31) to school year (Aug 1 - Jul 31) across /revenue and /overview briefing · Root cause of ,205 reconciliation gap identified: Collected used calendar year while Expected used school-year season, dropping Aug-Dec 2025 tuition payments from the math

Featuresenior-devexpand

Extracted tuition override dialog into shared TuitionOverrideDialog component (eliminates ~240 lines of duplication) · Added inline Edit button on every student card in the /revenue family balance side panel

Featuresenior-devexpand

Core Programs defaults to latest school year (2026-2027) · Print Sign-In Sheet system across Core, Summer Camp, Discovery Station, After-School + batch print from /overview

Featuresenior-devexpand

Shipped custom /register/ form to production — source files committed, 8 Google Forms links swapped on afterschool/discovery-station/summer-camp, Register added to Programs nav · Pre-launch hardening: reverted register.js rate limit 20→3, stripped emailStatus from API response, updated analytics.js register_now_click tracker to match /register/ links

Featuresenior-devexpand

Scaffolded Next.js 16 admin portal with TypeScript, Tailwind 4, shadcn/ui, Playfair+DM Sans fonts, Morning Mist palette · Built 8 production pages: Overview (editorial briefing), Core Programs (2x2 classroom grid), Summer Camp (calendar), Discovery Station (age bands), After-School, Registrations inbox, Revenue (4 KPIs + charts), Families CRM

Refactorux-designerexpand

Core Programs page: 5 design iterations (greeting+stat cards, Brightwheel-inspired list cards, 2x2 classroom grid with numbered seat charts, season toggle, status pills placeholder) · Backend: added coreRoster SQL query to overview API for per-program student names

Wed, Apr 151
Featuresenior-devexpand

Fixed 24-hour email delivery delay (FROM_EMAIL secret + new Resend API key for all environments)\n- Built complete 4-step registration form replacing Google Form (50% abandonment rate)\n- Age-based program filtering, camp week/day picker, multi-child tab cycling\n- Live availability badges (X spots left) from D1 database\n- 10% sibling discount system with visual treatment (strikethrough, gold badges, savings banner)\n- Returning family detection via email soft-match\n- Branded HTML notification email with Spark logo, per-child pricing, subtotals\n- D1 database: 7 tables, 7 migrations, 41 programs seeded (core + supplemental)\n- Programs API endpoint with enrollment counts\n- Admin portal backend: 13 API endpoints (CRUD, revenue, families, manual enrollment, CSV export)\n- Admin portal frontend built but needs visual redesign (backend solid)\n- Scraped Google Form fields via Playwright, analyzed both Google Sheets\n- Three docs: Enrollment Platform Plan, Registration Admin Guide, Admin Portal Handoff

Wed, Apr 82
Bug Fixexpand

Fixed XSS: escapeHtml() on attacker-controlled data.page in email template\n- KV-based login rate limit (10 failed attempts per 15min)\n- Rate limits on all 4 form endpoints (5/15min) and event endpoint (30/min)\n- Input truncation (500 chars) on tracking string fields\n- CORS scoped to sparkacademymorton.com on all 5 API endpoints (was wildcard)\n- Sanitized verbose err.message in 3 dashboard error handlers to generic messages\n- Deployed Pages + Worker to production

Bug Fixexpand

Moved Live Alerts sound toggle from top to bottom of dashboard — Michelle checks on phone, banner was taking prime mobile real estate\n- Changed default time range from 30 Days to This Year (ytd)\n- Fixed Search Console overview query missing dataState:'all' — was underreporting clicks/impressions by excluding recent days\n- Fixed search queries table sort: primary by clicks desc, secondary by impressions desc\n- Investigated Reached Out count dropping overnight (12→10) — attempted yesterday KV bridge, audited and found double-counting + card inconsistency bugs, rolled back to correct under-report-with-delay approach\n- Established principle: dashboard must under-report with delay rather than over-report false data

Tue, Apr 71
Strategyexpand

Apple Business Connect: full setup — company account, location (address, hours, phone, website), about description, logo, cover photo, 8 location photos, domain verification via Cloudflare DNS TXT record, EIN verification, submitted for review · Bing Places: synced from Google Business Profile — all data imported (22 photos, hours, description, social profiles), pending publish (7-12 days)

Mon, Apr 64
Featureexpand

Live audio notifications via Web Audio API: soft sine-wave chime when new visitor arrives, ascending C-E-G-C arpeggio when reached-out action occurs (tour request, phone call, question, etc.) · Alert banner UX: tap-to-enable flow with friendly copy, collapses to pulsing 'Live alerts on' pill, tap to disable, localStorage persistence, plays preview chime on activation

Featureexpand

Dashboard UX overhaul: center-aligned filter buttons, moved page selector to own section with title/description, friendly date format (April 6, 2026), renamed tabs Overview→Visitors / Funnel→Page Activity, removed redundant select-a-page text from Funnel Overview and Page Comparison · Email routing: consolidated all form notifications (tour, question, consulting) to [email protected], updated site.json, visible page links, error fallbacks, analytics tracking (consulting email detection now uses data-cta-type attribute)

Featureexpand

Fixed TEST_OFFSETS double-subtraction after Chesterfield city filter (recalibrated offsets for non-Chesterfield test data) · Fixed variable hoisting bug causing funnel tab 500 on auto-refresh + parseInt NaN guard

Featureexpand

Built full funnel tracker system: client-side JS (IntersectionObserver + sendBeacon), /api/track endpoint, D1 funnel_sessions table, dashboard Funnel tab with funnel chart, section engagement, dropoff report, page comparison, scroll depth · Added data-section attributes to 25 page templates across 4 page types

Sat, Apr 42
Featureexpand

Fixed phone_call_click test offset from 8 to 3 — restored 1 real Phone Call Tap on dashboard\n- Added This Year (ytd) date range with year-over-year comparison arrows\n- Built All Time and Year/Year buttons that auto-reveal after 1 year from launch (March 2027)\n- Extracted date-range logic into workers/dashboard-dates.js for testability\n- Added 13 unit tests (vitest) covering all 6 date ranges + auto-reveal timing\n- Added test script to package.json

Bug Fixexpand

Fixed duplicate Facebook entries in dashboard 'Where They Found You' chart — GA4 returns separate rows for m.facebook.com, l.facebook.com, facebook.com, lm.facebook.com; added aggregation step after friendlySource() normalization so they combine into a single entry (83+11+3+1 = 98). Deployed to Cloudflare.

Fri, Apr 32
Bug Fixexpand

Standardized address from 'Avenue' to 'Ave' across all live site files to match Google Business Profile (site.json, footer, answer-hub, index, purposeful-play, schedule-tour — 10 instances including display text, Google Maps URLs, and alt text)

Bug Fixexpand

Updated address from '1989 North Morton Avenue' to '1989 N Morton Avenue' across entire site to match Google Business Profile\n- Changed display text, Google Maps URLs, alt attributes, and structured data (site.json) across 6 files\n- Verified Google Maps URLs resolve correctly with abbreviated format

Thu, Apr 21
Strategyexpand

Created CLAUDE.md for site/ subdirectory with build commands, architecture, CSS patterns, frontmatter reference · Fixed homepage blue arrows (undefined --sp-muted) and View all programs link to use brand colors

Wed, Apr 12
Strategyexpand

Created CLAUDE.md for site/ subdirectory with build commands, architecture, CSS patterns, frontmatter reference · Fixed homepage blue arrows (undefined --sp-muted) and 'View all programs' link to use brand colors (Misty Sage, Mist Teal)

Bug Fixexpand

Fixed dashboard test offsets: phone_call_click calibrated to 3, register_now_click calibrated to 4 · Added event-specific page name override system (EVENT_PAGE_OVERRIDES) to correct displayed source pages in conversion detail tables