A day-aware personal start page that changes its theme, greeting, and quick links based on the day of the week. Built with Astro and TypeScript.
- 7 distinct day themes with WCAG AA compliant color contrast
- 5 interactive widgets: clock, weather, quick links, quote, and notes
- Responsive layout (1/2/3 column grid)
- Error boundaries prevent widget failures from crashing the page
- Zero external JS by default (Astro islands architecture)
- Weather via Open-Meteo (no API key required)
- Notes persisted to localStorage
git clone <repo-url>
cd personal-website
npm install
npm run devOpen http://localhost:4321 in your browser.
| Command | Description |
|---|---|
npm run dev |
Start dev server |
npm run build |
Production build to dist/ |
npm run preview |
Preview built site |
npm test |
Run all unit tests |
npm run test:isolation |
Run widget isolation tests |
npm run lint |
Lint with ESLint |
npm run lighthouse |
Run Lighthouse performance audit |
- Create a Preact component in
src/components/YourWidget.tsx:
export function YourWidget() {
return <div class="widget">Content here</div>;
}- Import it in
src/pages/index.astroand add it to the widget grid wrapped in aWidgetWrapper:
<WidgetWrapper name="yourWidget" client:idle>
<YourWidget />
</WidgetWrapper>-
Choose a hydration directive:
client:loadfor widgets that must be interactive immediately (clock, quick links)client:idlefor widgets that can wait for browser idle (weather, quote)client:visiblefor below-the-fold widgets (notes)
-
Add the widget ID to each day config's
widgetsarray insrc/config/days/*.ts:
widgets: [
{ id: "clock" },
{ id: "yourWidget" },
// ...
],- Add a test in
src/components/YourWidget.test.tsx.
Day configs live in src/config/days/. Each file (monday.ts through sunday.ts) exports a DayConfig object:
| Field | Type | Description |
|---|---|---|
name |
string | Day identifier (lowercase) |
theme.primary |
hex | Headings, emphasis elements |
theme.secondary |
hex | Subtext, muted labels (must pass 4.5:1 on white) |
theme.accent |
hex | Hover backgrounds, decorative borders |
theme.background |
hex | Page background |
theme.surface |
hex | Widget card background |
theme.text |
hex | Body text |
greeting.morning/afternoon/evening |
string | Time-of-day greeting message |
widgets |
WidgetSlot[] | Which widgets to show |
quickLinks |
QuickLink[] | Links with name, url, optional icon |
focusText |
string | Motivational text for the day |
After editing, run npm test to verify WCAG contrast ratios still pass.
- Push to a GitHub repository
- Go to Settings > Pages
- Set source to "GitHub Actions"
- The
.github/workflows/deploy.ymlworkflow handles build and deploy automatically on push tomain
src/
config/
types.ts Type definitions (DayConfig, Theme, etc.)
loader.ts getDayConfig(), getGreeting(), getTimeOfDay()
defaults.ts Fallback config values
days/ Per-day config files (monday.ts ... sunday.ts)
components/
WidgetWrapper.tsx Error boundary for widget isolation
ClockWidget.tsx Real-time clock display
WeatherWidget.tsx Open-Meteo weather with geolocation
QuickLinksWidget.tsx Day-specific link grid
QuoteWidget.tsx Daily rotating quote
NotesWidget.tsx localStorage-backed scratchpad
layouts/
Layout.astro HTML shell, CSS custom properties, theme switching
pages/
index.astro Main page composing all widgets
lib/
weather.ts Open-Meteo API client
contrast.ts WCAG contrast ratio utilities
data/
quotes.ts Curated quote collection
Theme switching: An inline <head> script sets data-day on <html> before first paint. CSS custom properties (--color-primary, etc.) are overridden per day via [data-day="monday"] selectors. No theme flash.
Widget isolation: Each widget is an Astro island wrapped in WidgetWrapper, a Preact error boundary. If a widget throws, it shows "Widget unavailable" without affecting siblings.
Hydration strategy: Widgets use progressive hydration. client:load for above-the-fold interactive content, client:idle for deferred content, client:visible for below-fold content.