Recreating the Bliss of Windows XP

A love letter to the operating system that defined a generation — built with modern web technologies.


Why Recreate Windows XP?

For many of us, Windows XP was the first computer experience. The iconic Bliss wallpaper, the satisfying startup chime, and the unmistakable Luna theme are etched into collective memory. Windows XP Astro is an attempt to preserve that nostalgia — not as a static screenshot, but as a living, interactive experience in the browser.

“The best interface is the one you already know.”

The goal isn’t pixel-perfect emulation of every feature. Instead, it’s about capturing the feeling — the boot sequence anticipation, the satisfying click of the Start menu, and the simple joy of sweeping mines on a lazy afternoon.

Note: This is a personal project built for fun and learning.

It’s also a surprisingly effective way to explore modern frontend architecture while building something that feels familiar.


Features Overview

Windows XP Astro recreates the core desktop experience through several interactive layers:

Boot Sequence & Login

The application follows a realistic page flow that mirrors the original OS:

  1. Index — Initial landing; determines if this is a fresh boot or a return visit
  2. Startup — The classic XP boot animation with the scrolling progress bar
  3. Login — User selection screen with the friendly welcome panel
  4. Desktop — The full interactive environment

Each transition is managed through persisted state, so refreshing the page doesn’t dump you back to the start screen1.

Desktop Environment

The desktop provides the familiar XP experience:

Applications

Currently supported applications include:

ApplicationDescriptionWindow Options
MinesweeperThe classic puzzle gameMinimize, Close
Recycle BinDesktop stapleMinimize, Maximize, Close

Minesweeper is fully playable and includes sub-applications for custom difficulty settings and a scoreboard — spawned dynamically from the main game window.


Tech Stack

The project is built on a modern frontend foundation:

Key Dependencies

json
{
"@astrojs/react": "^4.4.2",
"astro": "^5.16.5",
"react": "^19.2.3",
"react-draggable": "^4.5.0",
"sass-embedded": "^1.96.0"
}

Architecture Highlights

The Astro / React Component Split

This is the most important architectural decision in the project. Astro components handle server-rendered, static UI while React components power client-side interactivity.

The Astro config enforces this boundary explicitly:

javascript

export default defineConfig({
integrations: [
  react({
    include: ["**/clientComponents/**"],
  }),
],
});

Only files under src/clientComponents/ are hydrated as React islands. Placing a React component anywhere else means it won’t hydrate — it’ll render as static HTML and silently break.

Both directories follow Atomic Design principles:

State Management via localStorage

There’s no Redux or Zustand here. State is managed through a type-safe localStorage wrapper:

typescript

export const SetInitialComputerState = () => {
const initialState: ComputerState = {
  currentPage: "index",
  firstLogin: true,
};

UpdateLocalStorageData("ComputerState", initialState);
};

export const UpdateCurrentComputerPage = (page: PageType) => {
const computerState = GetLocalStorageData("ComputerState");
if (computerState) {
  computerState.currentPage = page;
  UpdateLocalStorageData("ComputerState", computerState);
}
};

A StorageDataMap interface maps each storage key to its TypeScript type, so GetLocalStorageData<T>() always returns correctly typed data. No any types, no runtime surprises.

The Event System

Components communicate through custom browser events rather than prop drilling or a global store:

typescript

interface ApplicationEvent {
app: ApplicationWindows;
opened: boolean;
}

interface CreateApplicationEvent {
app: ApplicationWindows;
}

interface CustomMinesweeperEvent {
settings: DifficultySettings;
}

Three core events coordinate the system:

  1. applicationOpened — Fired when an app is opened, closed, or focused
  2. createApplication — Spawns a new application instance (used for sub-apps)
  3. customMinesweeperEvent — Propagates Minesweeper-specific settings changes

The Application Registry

Every application is defined in a central registry with a consistent shape:

typescript
type Application = {
type: ApplicationWindows;
name: string;
icon: Icons;
windowOptions: WindowOptions[]; // "Close" | "Minimize" | "Maximize"
menuBarElements?: MenuBarElement[];
};

Desktop applications are a curated subset of all supported applications — sub-apps like minesweeper-custom-difficulty exist in the registry but aren’t placed on the desktop.

SCSS with ITCSS

Styles follow the Inverted Triangle CSS methodology, layered from generic to specific:

LayerPurpose
01_resetGlobal resets, box-sizing, Tahoma font
02_settingsCSS custom properties — colors, gradients, UI tokens
03_toolsSCSS mixins for responsive breakpoints
04_sharedShared button and event styles
05_client-componentsReact component styling

The classic XP title bar gradient, for instance, is defined as a CSS custom property:

css
--title-bar-bg: linear-gradient(
180deg,
rgba(9, 151, 255, 1) 0%,
rgba(0, 83, 238, 1) 8%,
rgba(0, 80, 238, 1) 40%,
rgba(0, 102, 255, 1) 88%,
rgba(0, 91, 255, 1) 95%,
rgba(0, 61, 215, 1) 100%
);

Page Script Pattern

Astro’s view transitions require a specific two-script pattern to ensure code runs at the right time:

astro
<!-- Inline script: re-runs on every page navigation -->
<script is:inline data-astro-rerun>
document.addEventListener(
  "astro:page-load",
  () => {
    desktopInit();
  },
  { once: true },
);
</script>

<!-- Module script: bundled once, guards against re-registration -->

<script>
import { HandlePageLoad } from "../lib/Logic/PageLogic";
if (!window.desktopInit) {
  window.desktopInit = function () {
    const continueLoad = HandlePageLoad("desktop");
    if (!continueLoad) return;
  };
}
</script>

The inline script re-executes on every navigation, while the module script runs once and attaches the init function to window. The if (!window.desktopInit) guard prevents double-registration.


Adding a New Application

Want to add an app? Here’s the checklist:

Step-by-step guide
  1. Define the application in the SupportedApplications record
  2. Add its type to the ApplicationWindows union in ApplicationTypes.ts
  3. Create the component under src/clientComponents/organisms/
  4. Optionally add it to the DesktopApplications array for a desktop icon
  5. Handle the applicationOpened event in your component
  6. Style it under src/style/05_client-components/

Roadmap

Potential features and improvements for future development:


Known Issues

Everything works perfectly and there are no bugs. Here are some known quirks:


Development

Getting started is straightforward. Clone the repo, install dependencies, and run the dev server:

bash
# Requirements: Node 24.x, npm 11.6.1+
npm install
npm run dev

Other useful commands:

bash
npm run build          # Build for production
npm run typecheck      # Type checking (tsc --noEmit)
npm run lint:fix       # Lint with auto-fix
npm run format         # Format with Prettier

Code style follows strict conventions: 4-space indentation, double quotes, semicolons, and a 100-character print width. The @/* path alias maps to ./src/*.


Built with these excellent tools and resources:


This article was written as a markdown rendering test and project summary for Windows XP Astro.

Footnotes

  1. State is persisted in localStorage via the ComputerStateLogic.ts module, which tracks the current page and first-login status.

  2. This is a known bug documented in the project. The HandlePageLoad() function validates stored state and redirects, but the startup page creates a circular redirect.