# Desktop OS

> A desktop boots itself — one card riffles through every thumbnail, icons burst out from centre, then the dock magnifies under the cursor and windows drag, stack and pop.

A shadcn/ui block from the [@uicapsule](https://uicapsule.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/uicapsule/desktop-os).

- Page, with a live preview: https://shadcn.exchange/r/uicapsule/desktop-os
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/uicapsule/desktop-os-light-6ab069b009f7ebde.png
- Registry item: https://uicapsule.com/r/desktop-os.json
- Categories: pages, skeuomorphism
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://uicapsule.com/r/desktop-os.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `gsap`, `lucide-react`

## Files

### `/app-icons.tsx`

```tsx
import type { ReactNode } from "react";

/**
 * Hand-authored app / file artwork.
 *
 * Every icon is a CSS-gradient "squircle" (macOS uses ~22.4% corner radius,
 * expressed as a percentage so it survives the dock's width/height lerp) with a
 * flat inline-SVG glyph on top. Nothing here is a third-party mark and nothing
 * loads over the network.
 */

export type GlyphId = "files" | "preview" | "photos" | "notes" | "terminal" | "trash" | "folder";

const SQUIRCLE_RADIUS = "22.4%";

const SQUIRCLE_SHADOW =
  "inset 0 1.5% 0 rgba(255,255,255,0.45), inset 0 -2% 0 rgba(0,0,0,0.18), 0 2% 6% rgba(0,0,0,0.28)";

const Squircle = ({
  background,
  children,
}: {
  background: string;
  children: ReactNode;
}): ReactNode => (
  <div
    className="relative size-full"
    style={{
      background,
      borderRadius: SQUIRCLE_RADIUS,
      boxShadow: SQUIRCLE_SHADOW,
    }}
  >
    <svg viewBox="0 0 64 64" className="absolute inset-0 size-full" aria-hidden="true">
      {children}
    </svg>
  </div>
);

const PINWHEEL = [
  { angle: 0, fill: "#f2b33d" },
  { angle: 45, fill: "#ef6b4b" },
  { angle: 90, fill: "#e34b8a" },
  { angle: 135, fill: "#a05ad6" },
  { angle: 180, fill: "#4f7fe0" },
  { angle: 225, fill: "#35b6d8" },
  { angle: 270, fill: "#45c37a" },
  { angle: 315, fill: "#b9d34a" },
] as const;

const GLYPHS = {
  files: (
    <Squircle background="linear-gradient(165deg, #6fc0ff 0%, #2f7fe6 52%, #1f57b8 100%)">
      <path
        d="M14 22h13l4 5h19a3 3 0 0 1 3 3v14a3 3 0 0 1-3 3H14a3 3 0 0 1-3-3V25a3 3 0 0 1 3-3Z"
        fill="rgba(255,255,255,0.94)"
      />
      <path d="M11 32h42v12a3 3 0 0 1-3 3H14a3 3 0 0 1-3-3V32Z" fill="rgba(255,255,255,0.72)" />
    </Squircle>
  ),
  folder: (
    <svg viewBox="0 0 64 64" className="size-full" aria-hidden="true">
      <path
        d="M6 16h18l5 6h29a3 3 0 0 1 3 3v24a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V19a3 3 0 0 1 3-3Z"
        fill="#5aa7ef"
      />
      <path d="M3 27h58v22a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V27Z" fill="#82c2f8" />
      <path d="M3 27h58v3H3z" fill="rgba(255,255,255,0.45)" />
    </svg>
  ),
  notes: (
    <Squircle background="linear-gradient(165deg, #fff0b0 0%, #f7d24a 46%, #eab61f 100%)">
      <rect x="12" y="12" width="40" height="10" rx="2" fill="rgba(255,255,255,0.85)" />
      {[29, 36, 43, 50].map((y) => (
        <rect
          key={y}
          x="17"
          y={y}
          width={y === 50 ? 20 : 30}
          height="2.6"
          rx="1.3"
          fill="rgba(0,0,0,0.24)"
        />
      ))}
    </Squircle>
  ),
  photos: (
    <Squircle background="linear-gradient(165deg, #ffffff 0%, #e9e6df 100%)">
      {PINWHEEL.map((petal) => (
        <ellipse
          key={petal.angle}
          cx="32"
          cy="21"
          rx="6.5"
          ry="12"
          fill={petal.fill}
          opacity="0.88"
          transform={`rotate(${petal.angle} 32 32)`}
        />
      ))}
      <circle cx="32" cy="32" r="4.2" fill="rgba(255,255,255,0.85)" />
    </Squircle>
  ),
  preview: (
    <Squircle background="linear-gradient(165deg, #f6f4ee 0%, #d9d5cb 100%)">
      <rect
        x="13"
        y="14"
        width="30"
        height="34"
        rx="3"
        fill="#ffffff"
        stroke="rgba(0,0,0,0.16)"
        strokeWidth="1.4"
      />
      <path d="M17 41l7-9 5 6 4-5 6 8H17Z" fill="#7fb2e8" />
      <circle cx="35" cy="22" r="3.2" fill="#f2b33d" />
      <circle
        cx="41"
        cy="40"
        r="9.5"
        fill="rgba(255,255,255,0.35)"
        stroke="#2f6fd0"
        strokeWidth="3"
      />
      <path d="M48 47l6.5 6.5" stroke="#2f6fd0" strokeWidth="4" strokeLinecap="round" />
    </Squircle>
  ),
  terminal: (
    <Squircle background="linear-gradient(165deg, #4a4a52 0%, #1d1d22 40%, #0b0b0e 100%)">
      <rect x="9" y="9" width="46" height="46" rx="8" fill="rgba(255,255,255,0.04)" />
      <path
        d="M18 24l8 8-8 8"
        stroke="#ffffff"
        strokeWidth="3.4"
        strokeLinecap="round"
        strokeLinejoin="round"
        fill="none"
      />
      <path d="M31 42h15" stroke="#ffffff" strokeWidth="3.4" strokeLinecap="round" />
    </Squircle>
  ),
  trash: (
    <Squircle background="linear-gradient(165deg, rgba(255,255,255,0.55) 0%, rgba(220,222,228,0.42) 100%)">
      <path
        d="M20 22h24l-2.4 28a4 4 0 0 1-4 3.6H26.4a4 4 0 0 1-4-3.6L20 22Z"
        fill="rgba(255,255,255,0.5)"
        stroke="rgba(40,44,54,0.65)"
        strokeWidth="2.2"
      />
      <path d="M15 20h34" stroke="rgba(40,44,54,0.75)" strokeWidth="3" strokeLinecap="round" />
      <path d="M26 15h12" stroke="rgba(40,44,54,0.75)" strokeWidth="3" strokeLinecap="round" />
      {[27, 32, 37].map((x) => (
        <path
          key={x}
          d={`M${x} 29v17`}
          stroke="rgba(40,44,54,0.45)"
          strokeWidth="2"
          strokeLinecap="round"
        />
      ))}
    </Squircle>
  ),
} satisfies Record<GlyphId, ReactNode>;

/**
 * Fills its square container edge to edge — the dock magnification lerps the
 * wrapper's width/height, so the artwork must honour the `size-full` contract.
 */
export const AppGlyph = ({ id }: { id: GlyphId }): ReactNode => GLYPHS[id];

/* --------------------------------------------------------- status glyphs -- */

export const MacBatteryGlyph = (): ReactNode => (
  <svg width="24" height="12" viewBox="0 0 24 12" fill="none" aria-hidden="true">
    <rect
      x="0.5"
      y="1.5"
      width="19"
      height="9"
      rx="2.4"
      stroke="currentColor"
      strokeOpacity="0.55"
    />
    <rect x="2" y="3" width="13" height="6" rx="1.2" fill="currentColor" />
    <path
      d="M21.5 4.2 v3.6 a1.6 1.6 0 0 0 1.2 -1.8 a1.6 1.6 0 0 0 -1.2 -1.8 Z"
      fill="currentColor"
      fillOpacity="0.55"
    />
  </svg>
);

export const SignalBarsGlyph = (): ReactNode => (
  <svg width="17" height="11" viewBox="0 0 17 11" fill="currentColor" aria-hidden="true">
    <rect x="0" y="8" width="2.6" height="3" rx="0.6" />
    <rect x="4.6" y="6" width="2.6" height="5" rx="0.6" />
    <rect x="9.2" y="3.5" width="2.6" height="7.5" rx="0.6" />
    <rect x="13.8" y="0.5" width="2.6" height="10.5" rx="0.6" />
  </svg>
);

export const WifiGlyph = (): ReactNode => (
  <svg
    width="15"
    height="11"
    viewBox="0 0 15 11"
    fill="none"
    stroke="currentColor"
    strokeWidth="1.4"
    strokeLinecap="round"
    aria-hidden="true"
  >
    <path d="M1.4 3.6 A9 9 0 0 1 13.6 3.6" />
    <path d="M3.6 6 A5.6 5.6 0 0 1 11.4 6" />
    <path d="M5.9 8.4 A2.3 2.3 0 0 1 9.1 8.4" />
    <circle cx="7.5" cy="9.8" r="0.5" fill="currentColor" stroke="none" />
  </svg>
);

export const IOSBatteryGlyph = (): ReactNode => (
  <svg width="28" height="13" viewBox="0 0 28 13" fill="none" aria-hidden="true">
    <rect
      x="0.6"
      y="0.6"
      width="24"
      height="11.8"
      rx="3.4"
      stroke="currentColor"
      strokeOpacity="0.55"
    />
    <rect x="2.1" y="2.1" width="21" height="8.8" rx="2" fill="currentColor" />
    <path d="M26.4 4 v5 a2.1 2.1 0 0 0 0 -5 Z" fill="currentColor" fillOpacity="0.55" />
  </svg>
);

export const BellSlashGlyph = (): ReactNode => (
  <svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
    <path d="M3 10 h8 l-1 -1.5 V6 a3 3 0 0 0 -6 0 v2.5 Z" fill="currentColor" />
    <path
      d="M5.6 11 a1.4 1.4 0 0 0 2.8 0"
      stroke="currentColor"
      strokeWidth="1.1"
      fill="none"
      strokeLinecap="round"
    />
    <line
      x1="1.6"
      y1="12.4"
      x2="12.4"
      y2="1.6"
      stroke="#000000"
      strokeWidth="2.4"
      strokeLinecap="round"
    />
    <line
      x1="1.6"
      y1="12.4"
      x2="12.4"
      y2="1.6"
      stroke="currentColor"
      strokeWidth="1.1"
      strokeLinecap="round"
    />
  </svg>
);
```

### `/desktop-data.ts`

```ts
/**
 * All static content for the desktop simulation.
 *
 * Every image is a remote Unsplash CDN derivative — the package ships no binary
 * assets and the wallpaper is pure CSS, so there is nothing to host.
 */

const unsplash = (id: string, width: number): string =>
  `https://images.unsplash.com/${id}?w=${width}&q=75&auto=format&fit=crop`;

/* ------------------------------------------------------------------ files -- */

export interface SeedPos {
  readonly xPct: number;
  readonly yPct: number;
}

export interface DesktopFile {
  readonly id: string;
  readonly name: string;
  /** Small derivative — desktop tile + boot reel. */
  readonly thumb: string;
  /** Large derivative — Quick Look + lightbox. */
  readonly full: string;
  readonly dimensions: string;
  /** Hand-tuned percentage seed so tiles never collide at any frame size. */
  readonly pos: SeedPos;
}

/**
 * A tuple (not a plain array) so `FILES[0]` is statically known to exist under
 * `noUncheckedIndexedAccess`.
 */
export const FILES = [
  {
    dimensions: "4000 × 2667",
    full: unsplash("photo-1506744038136-46273834b3fb", 1600),
    id: "wetland-dusk",
    name: "Wetland Dusk.jpg",
    pos: { xPct: 8, yPct: 14 },
    thumb: unsplash("photo-1506744038136-46273834b3fb", 400),
  },
  {
    dimensions: "3840 × 2560",
    full: unsplash("photo-1470071459604-3b5ec3a7fe05", 1600),
    id: "fog-pines",
    name: "Fog Pines.jpg",
    pos: { xPct: 22, yPct: 22 },
    thumb: unsplash("photo-1470071459604-3b5ec3a7fe05", 400),
  },
  {
    dimensions: "3600 × 2400",
    full: unsplash("photo-1500534314209-a25ddb2bd429", 1600),
    id: "coast-road",
    name: "Coast Road.jpg",
    pos: { xPct: 38, yPct: 12 },
    thumb: unsplash("photo-1500534314209-a25ddb2bd429", 400),
  },
  {
    dimensions: "5184 × 3456",
    full: unsplash("photo-1441974231531-c6227db76b6e", 1600),
    id: "sunlit-grove",
    name: "Sunlit Grove.jpg",
    pos: { xPct: 56, yPct: 18 },
    thumb: unsplash("photo-1441974231531-c6227db76b6e", 400),
  },
  {
    dimensions: "4288 × 2848",
    full: unsplash("photo-1518837695005-2083093ee35b", 1600),
    id: "open-water",
    name: "Open Water.jpg",
    pos: { xPct: 72, yPct: 26 },
    thumb: unsplash("photo-1518837695005-2083093ee35b", 400),
  },
  {
    dimensions: "3888 × 2592",
    full: unsplash("photo-1439066615861-d1af74d74000", 1600),
    id: "salt-flats",
    name: "Salt Flats.jpg",
    pos: { xPct: 14, yPct: 38 },
    thumb: unsplash("photo-1439066615861-d1af74d74000", 400),
  },
  {
    dimensions: "4608 × 3072",
    full: unsplash("photo-1493246507139-91e8fad9978e", 1600),
    id: "aurora-fjord",
    name: "Aurora Fjord.jpg",
    pos: { xPct: 30, yPct: 46 },
    thumb: unsplash("photo-1493246507139-91e8fad9978e", 400),
  },
  {
    dimensions: "5472 × 3648",
    full: unsplash("photo-1470252649378-9c29740c9fa8", 1600),
    id: "alpine-lake",
    name: "Alpine Lake.jpg",
    pos: { xPct: 46, yPct: 42 },
    thumb: unsplash("photo-1470252649378-9c29740c9fa8", 400),
  },
  {
    dimensions: "4272 × 2848",
    full: unsplash("photo-1447752875215-b2761acb3c5d", 1600),
    id: "forest-path",
    name: "Forest Path.jpg",
    pos: { xPct: 62, yPct: 50 },
    thumb: unsplash("photo-1447752875215-b2761acb3c5d", 400),
  },
  {
    dimensions: "5760 × 3840",
    full: unsplash("photo-1426604966848-d7adac402bff", 1600),
    id: "green-valley",
    name: "Green Valley.jpg",
    pos: { xPct: 78, yPct: 44 },
    thumb: unsplash("photo-1426604966848-d7adac402bff", 400),
  },
] as const satisfies readonly DesktopFile[];

export const [HERO_FILE] = FILES;

export const findFile = (id: string | undefined): DesktopFile | undefined =>
  FILES.find((file) => file.id === id);

/* ------------------------------------------------------------------ tiles -- */

export type DesktopTile =
  | { readonly kind: "file"; readonly file: DesktopFile }
  | {
      readonly kind: "folder";
      readonly id: string;
      readonly name: string;
      readonly pos: SeedPos;
    };

export const tileId = (tile: DesktopTile): string =>
  tile.kind === "file" ? tile.file.id : tile.id;

export const tileName = (tile: DesktopTile): string =>
  tile.kind === "file" ? tile.file.name : tile.name;

export const tilePos = (tile: DesktopTile): SeedPos =>
  tile.kind === "file" ? tile.file.pos : tile.pos;

export const TILES: readonly DesktopTile[] = [
  ...FILES.map((file): DesktopTile => ({ file, kind: "file" })),
  { id: "archive", kind: "folder", name: "Archive", pos: { xPct: 6, yPct: 60 } },
];

/* ----------------------------------------------------------------- photos -- */

export interface DesktopPhoto {
  readonly src: string;
  readonly full: string;
  readonly ratio: "tall" | "wide" | "square";
  readonly caption: string;
}

const photo = (id: string, ratio: DesktopPhoto["ratio"], caption: string): DesktopPhoto => ({
  caption,
  full: unsplash(id, 1600),
  ratio,
  src: unsplash(id, 600),
});

export const PHOTOS: readonly DesktopPhoto[] = [
  photo("photo-1501785888041-af3ef285b470", "wide", "Shoreline, low tide"),
  photo("photo-1472214103451-9374bd1c798e", "tall", "Ridge line"),
  photo("photo-1444927714506-8492d94b4e3d", "tall", "Meadow, mid-morning"),
  photo("photo-1475924156734-496f6cac6ec1", "tall", "Wildflowers"),
  photo("photo-1490750967868-88aa4486c946", "tall", "Field lines"),
  photo("photo-1519681393784-d120267933ba", "wide", "Night sky, north face"),
  photo("photo-1507525428034-b723cf961d3e", "tall", "Warm sand"),
  photo("photo-1454391304352-2bf4678b1a7a", "wide", "Workbench"),
  photo("photo-1465101162946-4377e57745c3", "tall", "First light"),
];

/* ------------------------------------------------------------------ notes -- */

export interface DesktopNote {
  readonly id: string;
  readonly title: string;
  readonly date: string;
  readonly preview: string;
  readonly body: string;
}

export const NOTES = [
  {
    body: `Enter 320ms, exit 180ms. Exits are always faster — the user has already decided.

Anything under 90ms reads as an instant state change, not a transition. Anything over 500ms reads as a wait.

Blur is expensive. Scale and opacity are free. Spend the blur where it is the whole point.`,
    date: "Today",
    id: "timings",
    preview: "Enter 320ms, exit 180ms. Exits are always faster.",
    title: "Motion timings",
  },
  {
    body: `Pick the hue first, then the chroma, then the lightness. In that order, every time.

Warm grey and cold grey are not interchangeable. One of them is wrong for the surface you are on.

Two accents maximum. A third accent is a decision you have not made yet.`,
    date: "Today",
    id: "palette",
    preview: "Warm grey vs cold grey is a whole mood.",
    title: "Palette notes",
  },
  {
    body: `Keyboard path end to end.
Reduced motion honoured.
Empty state drawn, not forgotten.
320px wide without a horizontal scrollbar.
Focus ring visible on every interactive thing.
Nothing depends on hover alone.`,
    date: "Yesterday",
    id: "checklist",
    preview: "Keyboard, reduced motion, empty state, 320px.",
    title: "Shipping checklist",
  },
  {
    body: `Spring constants — the gap between motion that feels cheap and motion that feels expensive is about three numbers.

Optical alignment beats geometric alignment whenever a shape has a point on it.

Subpixel text rendering changes when you promote a layer. Know which of your layers are promoted.`,
    date: "Mar 02",
    id: "read-later",
    preview: "Spring constants, optical alignment, subpixel text.",
    title: "Read later",
  },
] as const satisfies readonly DesktopNote[];

export const [FIRST_NOTE] = NOTES;

/* ------------------------------------------------------------------- dock -- */

export type DockAppId = "files" | "preview" | "photos" | "notes" | "terminal" | "trash";

export interface DockApp {
  readonly id: DockAppId;
  readonly name: string;
}

export const DOCK_APPS: readonly DockApp[] = [
  { id: "files", name: "Files" },
  { id: "preview", name: "Preview" },
  { id: "photos", name: "Photos" },
  { id: "notes", name: "Notes" },
  { id: "terminal", name: "Terminal" },
  { id: "trash", name: "Trash" },
];

export const MOBILE_PRIMARY: readonly DockAppId[] = ["files", "photos", "notes"];
export const GROUP_APPS: readonly DockAppId[] = ["preview", "terminal", "trash"];

/* -------------------------------------------------------------- wallpaper -- */

/**
 * Pure-CSS wallpaper. The thin conic rays are what makes the boot's
 * `blur(24px) → blur(0)` legible on a gradient.
 *
 * Deliberately fixed rather than theme-reactive: the menu bar, dock tooltips
 * and icon labels are all white (as on macOS, where the wallpaper is chosen by
 * the user and does not invert with the system theme). A pale wallpaper drops
 * that chrome to ~1.8:1 contrast, and darkening one far enough to carry white
 * text just reproduces this gradient.
 */
export const WALLPAPER = [
  "repeating-conic-gradient(from 196deg at 50% 118%, rgba(255,255,255,0.045) 0deg 1.6deg, rgba(255,255,255,0) 1.6deg 7deg)",
  "radial-gradient(120% 82% at 20% 8%, #2b3d74 0%, rgba(43,61,116,0) 58%)",
  "radial-gradient(110% 88% at 82% 4%, #4a2a63 0%, rgba(74,42,99,0) 54%)",
  "radial-gradient(140% 110% at 50% 116%, #05070f 0%, rgba(5,7,15,0) 64%)",
  "linear-gradient(158deg, #1d2647 0%, #2a2350 34%, #171634 66%, #08090f 100%)",
].join(", ");

export const VEIL_GRADIENT =
  "radial-gradient(120% 90% at 50% 30%, rgba(0,0,0,0) 0%, rgba(0,0,0,0.18) 70%, rgba(0,0,0,0.35) 100%)";
```

### `/desktop-os.tsx`

```tsx
"use client";

import type {
  KeyboardEvent as ReactKeyboardEvent,
  PointerEvent as ReactPointerEvent,
  ReactNode,
} from "react";

import { Fragment, useCallback, useEffect, useRef, useState } from "react";
import { gsap } from "gsap";

import { useReducedMotion } from "./use-reduced-motion";

import type { DesktopTile, DockAppId } from "./desktop-data";
import type { OpenWindow, WindowCtx, WindowKind } from "./window-types";

import {
  AppGlyph,
  BellSlashGlyph,
  IOSBatteryGlyph,
  MacBatteryGlyph,
  SignalBarsGlyph,
  WifiGlyph,
} from "./app-icons";
import {
  DOCK_APPS,
  FILES,
  GROUP_APPS,
  HERO_FILE,
  MOBILE_PRIMARY,
  TILES,
  VEIL_GRADIENT,
  WALLPAPER,
  tileId,
  tileName,
  tilePos,
} from "./desktop-data";
import { WindowView } from "./window-bodies";
import { PhotoLightbox } from "./window-frame";
import { DEFAULT_WINDOW_SIZES } from "./window-types";

/* ---------------------------------------------------------------- tuning -- */

const DOCK_ICON_BASE = 56;
const DOCK_ICON_MAX = 96;
const DOCK_HOVER_RANGE = 140;
const DOCK_SIZE_LERP = 0.28;
/**
 * Second-order smoothing on the cursor itself. Together with the per-icon size
 * lerp this is what separates the dock from a CSS `:hover { scale }`.
 */
const DOCK_CURSOR_LERP = 0.22;
const MOBILE_ICON_BASE = 72;

const TILE_SIZE = 78;
/** The springboard grid draws the same tile artwork smaller. */
const MOBILE_TILE_SIZE = 64;
const FEATURED_SIZE = 180;
/** Breathing room kept between a tile's box and the frame edge or the dock. */
const TILE_EDGE_MARGIN = 4;
/**
 * The desktop tile's box when no element is available to measure (the seed pass
 * runs for both layouts, and the mobile tree renders a different-sized tile):
 * the wrapper's rendered width, and `TILE_SIZE` plus the 8px gap and the single
 * 11px/1.25 name label beneath it.
 */
const TILE_BOX_W = 96;
const TILE_BOX_H = TILE_SIZE + 24;
/** Boot "slot machine" reel. */
const REEL_FRAME_MS = 100;
const REEL_DELAY_MS = 250;
/** Hard cap on waiting for the thumbnail preload before booting anyway. */
const PRELOAD_CAP_MS = 1400;

const HERO_ID = HERO_FILE.id;

const clamp = (value: number, min: number, max: number): number =>
  Math.min(Math.max(value, min), max);

const topZ = (wins: readonly OpenWindow[]): number => Math.max(100, ...wins.map((win) => win.z));

const topWindow = (wins: readonly OpenWindow[]): OpenWindow | undefined => {
  let top: OpenWindow | undefined;
  for (const win of wins) {
    if (top === undefined || win.z > top.z) {
      top = win;
    }
  }
  return top;
};

const DOCK_SHADOW =
  "0 30px 80px -20px rgba(0,0,0,0.55), inset 0 1px 0 rgba(255,255,255,0.32), inset 0 -1px 0 rgba(0,0,0,0.18)";

/**
 * The one place this component needs real CSS: scrollbar colouring inside the
 * scrollable surfaces (light and dark variants), and the terminal caret blink.
 */
const SCOPED_CSS = `
.dos-scroll { scrollbar-width: thin; scrollbar-color: rgba(0,0,0,0.22) transparent; }
.dos-scroll::-webkit-scrollbar { width: 11px; height: 11px; }
.dos-scroll::-webkit-scrollbar-track { background: transparent; }
.dos-scroll::-webkit-scrollbar-thumb {
  background: rgba(0,0,0,0.22);
  border-radius: 999px;
  border: 3px solid transparent;
  background-clip: content-box;
}
/* Same metrics, inverted thumb — for the terminal and the mobile springboard,
   which scroll over near-black surfaces. */
.dos-scroll-dark { scrollbar-color: rgba(255,255,255,0.22) transparent; }
.dos-scroll-dark::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.22); background-clip: content-box; }
@keyframes dos-blink { 0%, 49% { opacity: 1 } 50%, 100% { opacity: 0 } }
.dos-caret { animation: dos-blink 1.06s steps(1, end) infinite; }
@media (prefers-reduced-motion: reduce) { .dos-caret { animation: none } }
`;

/* ----------------------------------------------------------------- state -- */

interface TilePosition {
  x: number;
  y: number;
  z: number;
}

interface DragState {
  id: string | null;
  startX: number;
  startY: number;
  originX: number;
  originY: number;
  moved: boolean;
  pointerId: number;
  el: HTMLDivElement | null;
  /** Resolved once per gesture — the frame and dock cannot resize mid-drag. */
  maxX: number;
  maxY: number;
  lastX: number;
  lastY: number;
}

const IDLE_DRAG: DragState = {
  el: null,
  id: null,
  lastX: 0,
  lastY: 0,
  maxX: 0,
  maxY: 0,
  moved: false,
  originX: 0,
  originY: 0,
  pointerId: -1,
  startX: 0,
  startY: 0,
};

/**
 * Wipe the gesture back to idle and hand back the element it owned so the caller
 * can drop its one imperative style. Every exit path routes through here, and it
 * resets *every* field: a gesture that leaves `lastX`/`moved` behind is what let
 * a later, unrelated release commit a stale position.
 */
const endTileGesture = (drag: DragState, pointerId: number): HTMLDivElement | null => {
  const { el } = drag;
  // Reset before releasing: the release schedules a `lostpointercapture` that
  // routes into the cancel handler, which must find an already-idle gesture.
  Object.assign(drag, IDLE_DRAG);
  if (el && el.hasPointerCapture(pointerId)) {
    el.releasePointerCapture(pointerId);
  }

  return el;
};

interface OpenSpec {
  kind: WindowKind;
  fileId?: string;
  folderName?: string;
}

export const DesktopOS = (): ReactNode => {
  const [mounted, setMounted] = useState(false);
  const [isMobile, setIsMobile] = useState(false);
  const [seeded, setSeeded] = useState(false);
  const [tilePositions, setTilePositions] = useState<Record<string, TilePosition>>({});
  const [selectedId, setSelectedId] = useState<string | null>(null);
  const reducedMotion = useReducedMotion();
  const [animatedEntranceDone, setAnimatedEntranceDone] = useState(false);
  const entranceDone = reducedMotion || animatedEntranceDone;
  const [windows, setWindows] = useState<readonly OpenWindow[]>([]);
  const [lightbox, setLightbox] = useState<string | null>(null);
  const [groupPanelOpen, setGroupPanelOpen] = useState(false);

  const sectionRef = useRef<HTMLElement>(null);
  const wallpaperWrapRef = useRef<HTMLDivElement>(null);
  const menuBarRef = useRef<HTMLDivElement>(null);
  const dockRef = useRef<HTMLDivElement>(null);
  const featuredThumbRef = useRef<HTMLDivElement>(null);
  const featuredImgRef = useRef<HTMLImageElement>(null);
  const groupBackdropRef = useRef<HTMLDivElement>(null);
  const groupPanelRef = useRef<HTMLDivElement>(null);

  const tileRefs = useRef<Map<string, HTMLDivElement>>(new Map());
  const tilePositionsRef = useRef<Record<string, TilePosition>>({});
  const isMobileRef = useRef(false);
  const winCounter = useRef(0);
  const entranceStartedRef = useRef(false);

  const dockIconEls = useRef<Map<DockAppId, HTMLDivElement>>(new Map());
  const dockIconSizes = useRef<Map<DockAppId, number>>(new Map());
  const dockCursor = useRef({ active: false, smooth: -9999, x: -9999 });
  const dockSettling = useRef(false);
  const rafId = useRef<number | null>(null);

  const dragRef = useRef<DragState>({ ...IDLE_DRAG });

  /* ------------------------------------------------------ window manager -- */

  const openWindow = (spec: OpenSpec): void => {
    const sec = sectionRef.current;
    const sw = sec?.clientWidth ?? 1280;
    const sh = sec?.clientHeight ?? 800;
    const base = DEFAULT_WINDOW_SIZES[spec.kind];

    let w: number;
    let h: number;
    let x: number;
    let y: number;

    if (isMobileRef.current) {
      w = Math.max(200, sw - 32);
      h = Math.max(220, sh - 60 - 128);
      x = 16;
      y = 60;
    } else {
      w = Math.min(base.w, Math.max(280, sw - 32));
      // Reserve 140px of vertical slack so the 28px cascade below always has
      // somewhere to go — otherwise every window opens at exactly y=28 in a
      // short frame and two open windows stack pixel-perfect.
      h = Math.min(base.h, Math.max(200, sh - 140));
      const offset = Math.min(windows.length, 5) * 28;
      x = clamp((sw - w) / 2 - 40 + offset, 8, Math.max(8, sw - w - 8));
      y = clamp(Math.max(28, (sh - h) / 2 - 40) + offset, 28, Math.max(28, sh - h - 8));
    }

    const uid = winCounter.current + 1;
    winCounter.current = uid;

    setWindows((prev) => {
      const z = topZ(prev) + 1;

      return [
        ...prev,
        {
          fileId: spec.fileId,
          folderName: spec.folderName,
          h,
          kind: spec.kind,
          uid,
          w,
          x,
          y,
          z,
        },
      ];
    });
  };

  const closeWindow = useCallback((uid: number): void => {
    setWindows((prev) => prev.filter((win) => win.uid !== uid));
  }, []);

  const focusWindow = (uid: number): void => {
    setWindows((prev) => {
      const max = topZ(prev);
      // Every click inside a window asks for focus. Bailing when it is already
      // frontmost keeps `z` bounded and lets React skip the re-render entirely.
      if (prev.some((win) => win.uid === uid && win.z === max)) {
        return prev;
      }

      return prev.map((win) => (win.uid === uid ? { ...win, z: max + 1 } : win));
    });
  };

  const moveWindow = (uid: number, x: number, y: number): void => {
    setWindows((prev) => prev.map((win) => (win.uid === uid ? { ...win, x, y } : win)));
  };

  const ctx: WindowCtx = {
    isMobile,
    openLightbox: (src) => setLightbox(src),
    openQuickLook: (fileId) => openWindow({ fileId, kind: "quicklook" }),
  };

  const openTile = (tile: DesktopTile): void => {
    if (tile.kind === "folder") {
      openWindow({ folderName: tile.name, kind: "finder" });
    } else {
      openWindow({ fileId: tile.file.id, kind: "quicklook" });
    }
  };

  const handleDockApp = (id: DockAppId): void => {
    switch (id) {
      case "files": {
        openWindow({ folderName: "Archive", kind: "finder" });
        break;
      }
      case "preview": {
        openWindow({ fileId: HERO_ID, kind: "quicklook" });
        break;
      }
      case "photos": {
        openWindow({ kind: "photos" });
        break;
      }
      case "notes": {
        openWindow({ kind: "notes" });
        break;
      }
      case "terminal": {
        openWindow({ kind: "terminal" });
        break;
      }
      case "trash": {
        openWindow({ folderName: "Trash", kind: "finder" });
        break;
      }
      default: {
        break;
      }
    }
  };

  /* ------------------------------------------------------ tile positions -- */

  /**
   * How far a tile of the given box size may travel before it leaves the frame
   * or slides under the dock. Derived from live geometry rather than baked
   * constants so it holds at any frame size and for any dock height.
   */
  const tileBounds = useCallback((boxW: number, boxH: number) => {
    const sec = sectionRef.current;
    const vw = sec?.clientWidth ?? 1280;
    const vh = sec?.clientHeight ?? 800;
    const dock = dockRef.current;
    // The dock is the only chrome that overlaps the tile field from below.
    const floor =
      sec && dock ? dock.getBoundingClientRect().top - sec.getBoundingClientRect().top : vh;

    return {
      maxX: Math.max(TILE_EDGE_MARGIN, vw - boxW - TILE_EDGE_MARGIN),
      maxY: Math.max(TILE_EDGE_MARGIN, floor - boxH - TILE_EDGE_MARGIN),
    };
  }, []);

  /**
   * Percentage-seeded layout, re-clamped on every resize so icons can never
   * leave an arbitrarily-sized frame. Shares `tileBounds` with the drag so a
   * resize cannot nudge a tile the user just placed at the edge.
   */
  const seedPositions = useCallback((): void => {
    const sec = sectionRef.current;
    const vw = sec?.clientWidth ?? 1280;
    const vh = sec?.clientHeight ?? 800;
    const bounds = tileBounds(TILE_BOX_W, TILE_BOX_H);

    setTilePositions((prev) => {
      const next: Record<string, TilePosition> = {};

      for (const [i, tile] of TILES.entries()) {
        const id = tileId(tile);
        const seed = tilePos(tile);
        const existing = prev[id];
        const baseX = existing ? existing.x : (seed.xPct / 100) * vw;
        const baseY = existing ? existing.y : (seed.yPct / 100) * vh;
        next[id] = {
          // Same floor and ceiling the drag enforces, or a resize would nudge a
          // tile the user had just parked against an edge.
          x: clamp(baseX, TILE_EDGE_MARGIN, bounds.maxX),
          y: clamp(baseY, TILE_EDGE_MARGIN, bounds.maxY),
          z: existing ? existing.z : 10 + i,
        };
      }

      return next;
    });
    setSeeded(true);
  }, [tileBounds]);

  /* ------------------------------------------------------ mount + resize -- */

  useEffect(() => {
    isMobileRef.current = window.matchMedia("(max-width: 767px)").matches;
    setIsMobile(isMobileRef.current);

    // The boot timeline does not start until the thumbnail preload settles (up
    // to PRELOAD_CAP_MS). Seed the wallpaper's pre-boot state here instead, or
    // the first thing on screen is a crisp wallpaper that snaps to blur(24px).
    const wrap = wallpaperWrapRef.current;
    if (wrap && !window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
      if (isMobileRef.current) {
        gsap.set(wrap, { opacity: 0 });
      } else {
        gsap.set(wrap, { filter: "blur(24px)", scale: 1.04 });
      }
    }

    setMounted(true);
  }, []);

  useEffect(() => {
    if (!mounted) {
      return;
    }
    seedPositions();

    const mq = window.matchMedia("(max-width: 767px)");
    const onChange = (): void => {
      isMobileRef.current = mq.matches;
      setIsMobile(mq.matches);
      seedPositions();
    };
    const onResize = (): void => seedPositions();

    mq.addEventListener("change", onChange);
    window.addEventListener("resize", onResize);

    return () => {
      mq.removeEventListener("change", onChange);
      window.removeEventListener("resize", onResize);
    };
  }, [mounted, seedPositions]);

  // Mirror positions into a ref so the boot timeline can read them without
  // taking `tilePositions` as a dependency — a tile drag must not replay boot.
  useEffect(() => {
    tilePositionsRef.current = tilePositions;
  }, [tilePositions]);

  /* ------------------------------------------------------- boot sequence -- */

  useEffect(() => {
    if (!mounted || !seeded) {
      return;
    }
    if (entranceStartedRef.current) {
      return;
    }

    const sec = sectionRef.current;
    if (!sec) {
      return;
    }

    entranceStartedRef.current = true;

    const wrap = wallpaperWrapRef.current;
    const menu = menuBarRef.current;
    const dock = dockRef.current;
    const thumb = featuredThumbRef.current;

    let cancelled = false;
    let timeline: ReturnType<typeof gsap.timeline> | null = null;
    let reelTimeout: number | null = null;
    let reelInterval: number | null = null;
    let capTimeout: number | null = null;

    if (reducedMotion) {
      if (wrap) {
        gsap.set(wrap, { filter: "none", opacity: 1, scale: 1 });
      }
      if (menu) {
        gsap.set(menu, { opacity: 1, y: 0 });
      }
      if (dock) {
        gsap.set(dock, { opacity: 1, y: 0 });
      }
      for (const el of tileRefs.current.values()) {
        gsap.set(el, { opacity: 1 });
      }
      return () => {
        entranceStartedRef.current = false;
      };
    }

    const runMobile = (): void => {
      const tl = gsap.timeline({ defaults: { ease: "power3.out" } });
      timeline = tl;

      if (wrap) {
        tl.fromTo(wrap, { opacity: 0, scale: 1.04 }, { duration: 0.45, opacity: 1, scale: 1 }, 0);
      }
      if (menu) {
        tl.fromTo(menu, { opacity: 0, y: -28 }, { duration: 0.45, opacity: 1, y: 0 }, 0.05);
      }

      for (const [i, tile] of TILES.entries()) {
        const el = tileRefs.current.get(tileId(tile));
        if (!el) {
          continue;
        }
        tl.fromTo(
          el,
          { opacity: 0, scale: 0.94, y: 10 },
          { duration: 0.5, opacity: 1, scale: 1, y: 0 },
          0.2 + 0.04 * (i % 10),
        );
      }

      if (dock) {
        tl.fromTo(dock, { opacity: 0, y: 60 }, { duration: 0.5, opacity: 1, y: 0 }, 0.35);
      }
      tl.call(() => setAnimatedEntranceDone(true), undefined, 1);
    };

    const runDesktop = (): void => {
      const positions = tilePositionsRef.current;
      const sw = sec.clientWidth;
      const sh = sec.clientHeight;
      const centreX = sw / 2 - 48;
      const centreY = sh / 2 - 58;
      const heroPos = positions[HERO_ID];
      const heroEl = tileRefs.current.get(HERO_ID);

      const tl = gsap.timeline({ defaults: { ease: "power3.out" } });
      timeline = tl;

      if (wrap) {
        tl.set(wrap, { filter: "blur(24px)", scale: 1.04 }, 0);
        tl.to(wrap, { duration: 0.4, filter: "blur(0px)", scale: 1 }, 0.95);
      }

      if (thumb) {
        gsap.set(thumb, { xPercent: -50, yPercent: -50 });
        tl.fromTo(
          thumb,
          { filter: "blur(8px)", opacity: 0, scale: 0.85, y: 320 },
          { duration: 0.5, filter: "blur(0px)", opacity: 1, scale: 1, y: 0 },
          0,
        );
      }

      for (const [i, tile] of TILES.entries()) {
        const id = tileId(tile);
        if (id === HERO_ID) {
          continue;
        }
        const el = tileRefs.current.get(id);
        const pos = positions[id];
        if (!el || !pos) {
          continue;
        }
        tl.fromTo(
          el,
          {
            filter: "blur(8px)",
            opacity: 0,
            scale: 0.35,
            x: centreX,
            y: centreY,
          },
          {
            duration: 0.55,
            filter: "blur(0px)",
            opacity: 1,
            scale: 1,
            x: pos.x,
            y: pos.y,
          },
          1.15 + 0.02 * (i % 10),
        );
      }

      if (thumb && heroPos) {
        // Hand-off: the 180px featured card shrinks into the 78px hero tile.
        tl.to(
          thumb,
          {
            duration: 0.36,
            ease: "power3.inOut",
            scale: TILE_SIZE / FEATURED_SIZE,
            x: heroPos.x + 48 - sw / 2,
            y: heroPos.y + 39 - sh / 2,
          },
          1.3,
        );
      }

      if (thumb) {
        tl.to(thumb, { duration: 0.15, opacity: 0 }, 1.66);
      }
      if (heroEl) {
        tl.to(heroEl, { duration: 0.15, opacity: 1 }, 1.66);
      }
      if (menu) {
        tl.fromTo(menu, { opacity: 0, y: -32 }, { duration: 0.5, opacity: 1, y: 0 }, 1.75);
      }
      if (dock) {
        tl.fromTo(dock, { opacity: 0, y: 80 }, { duration: 0.55, opacity: 1, y: 0 }, 1.9);
      }
      tl.call(() => setAnimatedEntranceDone(true), undefined, 2.15);

      // Riffle through every thumbnail like a slot-machine reel, landing back
      // on the hero frame just before the card flies to its tile.
      const reel = FILES.map((file) => file.thumb);
      let frame = 0;
      reelTimeout = window.setTimeout(() => {
        reelInterval = window.setInterval(() => {
          frame += 1;
          const img = featuredImgRef.current;
          const next = reel[frame];
          if (next === undefined) {
            if (reelInterval !== null) {
              window.clearInterval(reelInterval);
            }
            if (img) {
              img.src = HERO_FILE.thumb;
            }
            return;
          }
          if (img) {
            img.src = next;
          }
        }, REEL_FRAME_MS);
      }, REEL_DELAY_MS);
    };

    // Layout mode and tile positions are read here, not at effect setup: the
    // preload gate below can delay `start` by up to PRELOAD_CAP_MS, and a
    // breakpoint cross in that window must build the timeline for the tree
    // that is actually on screen.
    const start = (): void => {
      if (cancelled) {
        return;
      }
      if (isMobileRef.current) {
        runMobile();
      } else {
        runDesktop();
      }
    };

    // The reel mutates <img src> every 100ms. Without a preload the whole
    // sequence flashes empty frames against a cold CDN — so wait for the
    // thumbnails, but never longer than PRELOAD_CAP_MS.
    let pending = FILES.length;
    let launched = false;
    const launch = (): void => {
      if (launched || cancelled) {
        return;
      }
      launched = true;
      start();
    };

    const settle = (): void => {
      pending -= 1;
      if (pending <= 0) {
        launch();
      }
    };
    for (const file of FILES) {
      const img = new Image();
      img.addEventListener("load", settle, { once: true });
      img.addEventListener("error", settle, { once: true });
      img.src = file.thumb;
    }
    capTimeout = window.setTimeout(launch, PRELOAD_CAP_MS);

    return () => {
      cancelled = true;
      // Reset so React 19 StrictMode's second mount replays the intro.
      entranceStartedRef.current = false;
      timeline?.kill();
      if (reelTimeout !== null) {
        window.clearTimeout(reelTimeout);
      }
      if (reelInterval !== null) {
        window.clearInterval(reelInterval);
      }
      if (capTimeout !== null) {
        window.clearTimeout(capTimeout);
      }
    };
  }, [mounted, seeded, reducedMotion]);

  /* -------------------------------------------------- dock magnification -- */

  useEffect(() => {
    const tick = (): void => {
      const cursor = dockCursor.current;

      // Idle dock costs nothing: no rect reads, no style writes.
      if (!isMobileRef.current && (cursor.active || dockSettling.current)) {
        cursor.smooth += (cursor.x - cursor.smooth) * DOCK_CURSOR_LERP;

        let settled = true;
        for (const [id, el] of dockIconEls.current) {
          // Live rect each frame: magnified neighbours push each other apart,
          // exactly as they do in the real dock.
          const rect = el.getBoundingClientRect();
          const centre = rect.left + rect.width / 2;
          const dist = Math.abs(cursor.smooth - centre);
          const t = Math.max(0, 1 - dist / DOCK_HOVER_RANGE);
          // smoothstep
          const falloff = t * t * (3 - 2 * t);
          const target = cursor.active
            ? DOCK_ICON_BASE + (DOCK_ICON_MAX - DOCK_ICON_BASE) * falloff
            : DOCK_ICON_BASE;

          const current = dockIconSizes.current.get(id) ?? DOCK_ICON_BASE;
          const next = current + (target - current) * DOCK_SIZE_LERP;
          dockIconSizes.current.set(id, next);
          if (Math.abs(target - next) > 0.1) {
            settled = false;
          }
          el.style.width = `${next.toFixed(2)}px`;
          el.style.height = `${next.toFixed(2)}px`;
        }

        dockSettling.current = cursor.active || !settled;
      }

      rafId.current = requestAnimationFrame(tick);
    };

    rafId.current = requestAnimationFrame(tick);

    return () => {
      if (rafId.current !== null) {
        cancelAnimationFrame(rafId.current);
      }
      rafId.current = null;
    };
  }, []);

  // Sizes live in their own map so a re-render (which detaches and reattaches
  // every ref callback) cannot snap a magnified icon back to its base size.
  const setDockIconRef =
    (id: DockAppId) =>
    (el: HTMLDivElement | null): void => {
      if (el) {
        dockIconEls.current.set(id, el);
      } else {
        dockIconEls.current.delete(id);
      }
    };

  const dockPointerMove = (e: ReactPointerEvent<HTMLDivElement>): void => {
    dockCursor.current.x = e.clientX;
    if (!dockCursor.current.active) {
      dockCursor.current.smooth = e.clientX;
      dockCursor.current.active = true;
    }
    dockSettling.current = true;
  };

  const dockPointerLeave = (): void => {
    dockCursor.current.active = false;
    dockCursor.current.x = -9999;
    dockSettling.current = true;
  };

  /* ------------------------------------------------ group panel entrance -- */

  useEffect(() => {
    if (!groupPanelOpen) {
      return;
    }

    const tweens: gsap.core.Tween[] = [];
    if (groupBackdropRef.current) {
      tweens.push(
        gsap.fromTo(groupBackdropRef.current, { opacity: 0 }, { duration: 0.22, opacity: 1 }),
      );
    }
    if (groupPanelRef.current) {
      tweens.push(
        gsap.fromTo(
          groupPanelRef.current,
          { filter: "blur(8px)", opacity: 0, scale: 0.86 },
          {
            duration: 0.34,
            ease: "power3.out",
            filter: "blur(0px)",
            opacity: 1,
            scale: 1,
          },
        ),
      );
    }

    return () => {
      for (const tween of tweens) {
        tween.kill();
      }
    };
  }, [groupPanelOpen]);

  /* ----------------------------------------------------------- ESC stack -- */

  useEffect(() => {
    const onKey = (e: KeyboardEvent): void => {
      if (e.key !== "Escape") {
        return;
      }

      if (lightbox) {
        setLightbox(null);
        return;
      }
      if (groupPanelOpen) {
        setGroupPanelOpen(false);
        return;
      }
      const top = topWindow(windows);
      if (top) {
        closeWindow(top.uid);
        return;
      }
      if (selectedId) {
        setSelectedId(null);
      }
    };

    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [lightbox, groupPanelOpen, windows, selectedId, closeWindow]);

  /* ----------------------------------------------------------- tile drag -- */

  const onTilePointerDown = (e: ReactPointerEvent<HTMLDivElement>, tile: DesktopTile): void => {
    const id = tileId(tile);

    if (isMobileRef.current) {
      // No capture on mobile, so a finger lifted off the tile never reports
      // back. Each press simply replaces the last rather than being gated on it.
      dragRef.current = {
        ...IDLE_DRAG,
        id,
        pointerId: e.pointerId,
        startX: e.clientX,
        startY: e.clientY,
      };
      return;
    }

    if (e.button !== 0) {
      return;
    }
    // Capture guarantees the active gesture reports its own end, so a second
    // pointer arriving mid-drag is a hijack, not a recovery.
    if (dragRef.current.id !== null) {
      return;
    }
    e.preventDefault();
    const el = e.currentTarget;
    el.setPointerCapture(e.pointerId);

    const pos = tilePositions[id];
    // The z bump goes through state, so React stays the only writer of every
    // style the render owns and cannot be raced by the gesture. The new z is
    // derived inside the updater so two presses in one batch cannot tie.
    setTilePositions((prev) => {
      const current = prev[id];
      if (!current) {
        return prev;
      }
      let top = 10;
      for (const entry of Object.values(prev)) {
        top = Math.max(top, entry.z);
      }
      if (current.z === top) {
        return prev;
      }

      return { ...prev, [id]: { ...current, z: top + 1 } };
    });

    const bounds = tileBounds(el.offsetWidth, el.offsetHeight);
    dragRef.current = {
      el,
      id,
      lastX: pos?.x ?? 0,
      lastY: pos?.y ?? 0,
      maxX: bounds.maxX,
      maxY: bounds.maxY,
      moved: false,
      originX: pos?.x ?? 0,
      originY: pos?.y ?? 0,
      pointerId: e.pointerId,
      startX: e.clientX,
      startY: e.clientY,
    };
    setSelectedId(id);
  };

  const onTilePointerMove = (e: ReactPointerEvent<HTMLDivElement>): void => {
    const drag = dragRef.current;
    if (!drag.id || drag.pointerId !== e.pointerId) {
      return;
    }

    const dx = e.clientX - drag.startX;
    const dy = e.clientY - drag.startY;
    // 4px of travel is the line between "clicked it" and "dragged it".
    if (!drag.moved && Math.hypot(dx, dy) > 4) {
      drag.moved = true;
    }
    if (!drag.moved) {
      return;
    }
    if (isMobileRef.current || !drag.el) {
      return;
    }

    const nx = clamp(drag.originX + dx, TILE_EDGE_MARGIN, drag.maxX);
    const ny = clamp(drag.originY + dy, TILE_EDGE_MARGIN, drag.maxY);
    drag.lastX = nx;
    drag.lastY = ny;
    // `translate` composes on top of the `transform` React renders from state.
    // Keeping the two properties separate means an unrelated re-render mid-drag
    // cannot clobber the gesture, and abandoning the gesture is one reset away.
    drag.el.style.translate = `${nx - drag.originX}px ${ny - drag.originY}px`;
  };

  const onTilePointerUp = (e: ReactPointerEvent<HTMLDivElement>, tile: DesktopTile): void => {
    const drag = dragRef.current;
    const id = tileId(tile);
    // Only the pointer that began this tile's gesture may end it: a secondary
    // button, or a release that started on the desktop, must not open or move it.
    if (drag.id !== id || drag.pointerId !== e.pointerId) {
      return;
    }

    // Read the gesture out before ending it — `endTileGesture` wipes it clean.
    const { moved, lastX, lastY } = drag;
    const el = endTileGesture(drag, e.pointerId);

    if (moved && !isMobileRef.current) {
      setTilePositions((prev) => {
        const current = prev[id];
        if (!current) {
          return prev;
        }

        return { ...prev, [id]: { ...current, x: lastX, y: lastY } };
      });
    }
    // Cleared after the commit: React flushes this handler's updates before the
    // next paint, so the tile never renders at its pre-drag position.
    if (el) {
      el.style.translate = "";
    }
    if (!moved) {
      openTile(tile);
    }
  };

  /**
   * Alt-tab, a context menu or a cancelled touch ends the gesture without a
   * `pointerup`. Drop it and let the last committed position redraw the tile.
   */
  const onTilePointerCancel = (e: ReactPointerEvent<HTMLDivElement>): void => {
    const drag = dragRef.current;
    if (!drag.id || drag.pointerId !== e.pointerId) {
      return;
    }

    const el = endTileGesture(drag, e.pointerId);
    if (el) {
      el.style.translate = "";
    }
  };

  const onTileKeyDown = (e: ReactKeyboardEvent<HTMLDivElement>, tile: DesktopTile): void => {
    if (e.key === "Enter" || e.key === " ") {
      e.preventDefault();
      openTile(tile);
    }
  };

  /* ------------------------------------------------------------- renders -- */

  const renderTileInner = (tile: DesktopTile, size: number): ReactNode => {
    const selected = selectedId === tileId(tile);

    return (
      <>
        {tile.kind === "folder" ? (
          <div
            className={`relative ${selected ? "drop-shadow-[0_0_8px_rgba(255,255,255,0.55)]" : ""}`}
            style={{ height: size, width: size }}
          >
            <AppGlyph id="folder" />
          </div>
        ) : (
          <div
            className={`relative overflow-hidden rounded-[14px] border bg-[rgba(255,255,255,0.04)] transition-[box-shadow,transform] duration-150 ease-out ${
              selected
                ? "border-white/70 shadow-[0_20px_40px_-12px_rgba(0,0,0,0.6),0_0_0_2px_rgba(255,255,255,0.15)]"
                : "border-white/15 shadow-[0_12px_24px_-10px_rgba(0,0,0,0.6)]"
            }`}
            style={{ height: size, width: size }}
          >
            <img
              src={tile.file.thumb}
              alt={tile.file.name}
              draggable={false}
              className="size-full object-cover"
            />
          </div>
        )}

        <div
          className={`mt-2 max-w-[110px] truncate rounded-[5px] px-1.5 py-[1px] text-center text-[11px] leading-tight font-medium drop-shadow-[0_1px_2px_rgba(0,0,0,0.8)] ${
            selected ? "bg-[#3b82f6] text-white" : "text-white"
          }`}
        >
          {tileName(tile)}
        </div>
      </>
    );
  };

  const registerTileRef =
    (id: string) =>
    (el: HTMLDivElement | null): void => {
      if (el) {
        tileRefs.current.set(id, el);
      } else {
        tileRefs.current.delete(id);
      }
    };

  /**
   * Tiles are hidden by CSS only while the boot timeline still owns their
   * opacity. Crossing the 767px breakpoint swaps the desktop tile list for the
   * springboard grid (and back), which mounts brand-new elements that GSAP has
   * never touched — a permanent `opacity-0` class would leave those orphans
   * invisible forever, since the one-shot boot never replays.
   */
  const renderDesktopTile = (tile: DesktopTile, index: number): ReactNode => {
    const id = tileId(tile);
    const pos = tilePositions[id];

    return (
      <div
        key={id}
        // oxlint-disable-next-line jsx-a11y/prefer-tag-over-role -- Safari does not focus a <button> on click, which would break click-then-Enter; the tile also hosts block content a button cannot.
        role="button"
        tabIndex={0}
        aria-label={tileName(tile)}
        ref={registerTileRef(id)}
        onPointerDown={(e) => onTilePointerDown(e, tile)}
        onPointerMove={onTilePointerMove}
        onPointerUp={(e) => onTilePointerUp(e, tile)}
        onPointerCancel={onTilePointerCancel}
        onLostPointerCapture={onTilePointerCancel}
        onKeyDown={(e) => onTileKeyDown(e, tile)}
        className={`absolute z-[4] flex cursor-grab flex-col items-center outline-none select-none active:cursor-grabbing ${entranceDone ? "" : "opacity-0"}`}
        style={{
          touchAction: "none",
          transform: pos ? `translate3d(${pos.x}px, ${pos.y}px, 0)` : "translate3d(0px, 0px, 0)",
          width: TILE_BOX_W,
          zIndex: pos?.z ?? 10 + index,
        }}
      >
        {renderTileInner(tile, TILE_SIZE)}
      </div>
    );
  };

  const renderMobileTile = (tile: DesktopTile): ReactNode => {
    const id = tileId(tile);

    return (
      <div
        key={id}
        // oxlint-disable-next-line jsx-a11y/prefer-tag-over-role -- Safari does not focus a <button> on click, which would break click-then-Enter; the tile also hosts block content a button cannot.
        role="button"
        tabIndex={0}
        aria-label={tileName(tile)}
        ref={registerTileRef(id)}
        onPointerDown={(e) => onTilePointerDown(e, tile)}
        onPointerMove={onTilePointerMove}
        onPointerUp={(e) => onTilePointerUp(e, tile)}
        onPointerCancel={onTilePointerCancel}
        onKeyDown={(e) => onTileKeyDown(e, tile)}
        className={`flex flex-col items-center outline-none select-none ${entranceDone ? "" : "opacity-0"}`}
        style={{ touchAction: "manipulation" }}
      >
        {renderTileInner(tile, MOBILE_TILE_SIZE)}
      </div>
    );
  };

  const desktopDock = (
    <div ref={dockRef} className="absolute inset-x-0 bottom-5 z-30 flex justify-center opacity-0">
      <div
        onPointerMove={dockPointerMove}
        onPointerLeave={dockPointerLeave}
        className="flex items-end gap-1.5 rounded-[26px] border border-white/25 bg-white/[0.14] px-3 pt-3 pb-3 backdrop-blur-2xl backdrop-saturate-150"
        style={{ boxShadow: DOCK_SHADOW, height: DOCK_ICON_BASE + 24 }}
      >
        {DOCK_APPS.map((app) => (
          <Fragment key={app.id}>
            {app.id === "trash" && (
              <span className="mx-0.5 my-2 w-px self-stretch bg-white/[0.22]" aria-hidden="true" />
            )}
            <div className="group relative flex items-end">
              <span
                className="pointer-events-none absolute -top-[44px] left-1/2 -translate-x-1/2 translate-y-1 rounded-[6px] border border-white/20 bg-black/75 px-2.5 py-[5px] text-[12px] font-medium whitespace-nowrap text-white opacity-0 backdrop-blur-md transition-all duration-150 group-focus-within:translate-y-0 group-focus-within:opacity-100 group-hover:translate-y-0 group-hover:opacity-100"
                style={{ boxShadow: "0 8px 18px -6px rgba(0,0,0,0.6)" }}
              >
                {app.name}
                <span
                  className="absolute top-full left-1/2 size-0 -translate-x-1/2"
                  style={{
                    borderLeft: "5px solid transparent",
                    borderRight: "5px solid transparent",
                    borderTop: "5px solid rgba(0,0,0,0.75)",
                  }}
                />
              </span>
              <button
                type="button"
                aria-label={app.name}
                onClick={() => handleDockApp(app.id)}
                className="block"
              >
                <div
                  ref={setDockIconRef(app.id)}
                  className="drop-shadow-[0_4px_10px_rgba(0,0,0,0.35)]"
                  style={{ height: DOCK_ICON_BASE, width: DOCK_ICON_BASE }}
                >
                  <AppGlyph id={app.id} />
                </div>
              </button>
            </div>
          </Fragment>
        ))}
      </div>
    </div>
  );

  const mobileDock = (
    <div ref={dockRef} className="absolute inset-x-0 bottom-4 z-30 px-4 opacity-0">
      <div
        className="flex w-full items-end justify-evenly rounded-[26px] border border-white/25 bg-white/[0.14] px-3 pt-3 pb-3 backdrop-blur-2xl backdrop-saturate-150"
        style={{ boxShadow: DOCK_SHADOW }}
      >
        {MOBILE_PRIMARY.map((id) => {
          const app = DOCK_APPS.find((entry) => entry.id === id);

          return (
            <button
              key={id}
              type="button"
              aria-label={app?.name ?? id}
              onClick={() => handleDockApp(id)}
              className="drop-shadow-[0_4px_10px_rgba(0,0,0,0.35)]"
              style={{ height: MOBILE_ICON_BASE, width: MOBILE_ICON_BASE }}
            >
              <AppGlyph id={id} />
            </button>
          );
        })}
        <button
          type="button"
          aria-label="More apps"
          onClick={() => setGroupPanelOpen(true)}
          className={`rounded-[14px] border border-white/30 bg-white/15 p-[6px] shadow-[inset_0_1px_0_rgba(255,255,255,0.35),0_4px_10px_rgba(0,0,0,0.35)] backdrop-blur-md ${
            groupPanelOpen ? "ring-2 ring-white/40" : ""
          }`}
          style={{ height: MOBILE_ICON_BASE, width: MOBILE_ICON_BASE }}
        >
          {/*
            `grid-rows-3` is load-bearing: with only an implicit row the single
            row of glyphs is stretched to the definite `h-full` height and each
            mini-icon renders as a tall bar.
          */}
          <div className="grid h-full grid-cols-3 grid-rows-3 gap-[3px]">
            {GROUP_APPS.map((id) => (
              <span key={id} className="size-full overflow-hidden rounded-[3px]">
                <AppGlyph id={id} />
              </span>
            ))}
          </div>
        </button>
      </div>
    </div>
  );

  return (
    <main
      ref={sectionRef}
      className="relative isolate h-full w-full overflow-hidden bg-[#1a1612]"
      style={{ fontFamily: "ui-sans-serif, system-ui, sans-serif" }}
    >
      <style>{SCOPED_CSS}</style>

      <div ref={wallpaperWrapRef} className="absolute inset-0 z-0">
        <div className="absolute inset-0" style={{ background: WALLPAPER }} />
      </div>

      <div
        className="pointer-events-none absolute inset-0 z-[1]"
        style={{ background: VEIL_GRADIENT }}
      />

      <div
        role="presentation"
        className="absolute inset-0 z-[3]"
        onMouseDown={(e) => {
          if (e.target === e.currentTarget) {
            setSelectedId(null);
          }
        }}
      />

      {mounted && (
        <>
          {isMobile ? (
            <div className="dos-scroll dos-scroll-dark absolute inset-x-0 top-[64px] bottom-[120px] z-[4] grid grid-cols-3 content-start gap-x-2 gap-y-4 overflow-y-auto px-4 pt-2 pb-4">
              {TILES.map((tile) => renderMobileTile(tile))}
            </div>
          ) : (
            TILES.map((tile, i) => renderDesktopTile(tile, i))
          )}

          {/*
            Both bars switch on `isMobile` rather than a `sm:` utility: the
            springboard/desktop split is decided at 767px, and a 640px Tailwind
            breakpoint would put the macOS bar above the iOS grid at 720px.
          */}
          <div ref={menuBarRef} className="absolute inset-x-0 top-0 z-[5] opacity-0">
            {isMobile ? (
              <div className="flex h-12 items-center justify-between px-4 text-white drop-shadow-[0_1px_2px_rgba(0,0,0,0.6)]">
                <div className="flex items-center gap-1.5">
                  <span className="text-[14px] font-semibold tracking-tight">14:08</span>
                  <BellSlashGlyph />
                </div>
                <div className="flex items-center gap-1">
                  <SignalBarsGlyph />
                  <WifiGlyph />
                  <IOSBatteryGlyph />
                </div>
              </div>
            ) : (
              <div className="flex h-7 items-center justify-between bg-black/15 px-4 backdrop-blur-md">
                <div className="flex items-center gap-4 text-[11px] text-white/80">
                  <span className="size-3 rounded-[2px] bg-white" aria-hidden="true" />
                  <span className="font-bold">Finder</span>
                  <span className="text-white/70">File</span>
                  <span className="text-white/70">Edit</span>
                  <span className="text-white/70">View</span>
                  <span className="text-white/70">Window</span>
                  <span className="text-white/70">Help</span>
                </div>
                <div className="flex items-center gap-3 text-[11px] text-white/80">
                  <span className="text-white">
                    <MacBatteryGlyph />
                  </span>
                  <span>100%</span>
                  <span>Wed 14:08</span>
                </div>
              </div>
            )}
          </div>

          {!isMobile && !entranceDone && (
            <div
              ref={featuredThumbRef}
              className="absolute top-1/2 left-1/2 z-40 size-[180px] overflow-hidden rounded-[28px] border border-white/15 opacity-0"
              style={{
                boxShadow: "0 40px 90px -12px rgba(0,0,0,0.7), 0 0 0 4px rgba(255,255,255,0.06)",
              }}
            >
              <img
                ref={featuredImgRef}
                src={HERO_FILE.thumb}
                alt=""
                draggable={false}
                className="size-full object-cover"
              />
            </div>
          )}

          {isMobile ? mobileDock : desktopDock}

          {groupPanelOpen && (
            <div
              ref={groupBackdropRef}
              role="presentation"
              onMouseDown={(e) => {
                if (e.target === e.currentTarget) {
                  setGroupPanelOpen(false);
                }
              }}
              className="absolute inset-0 z-[45] flex items-center justify-center bg-black/35 p-6 backdrop-blur-md"
            >
              <div
                ref={groupPanelRef}
                className="w-full max-w-[360px] rounded-[28px] border border-white/[0.22] bg-white/[0.13] p-6 backdrop-blur-2xl backdrop-saturate-150"
                style={{ boxShadow: DOCK_SHADOW }}
              >
                <p className="text-[12px] font-semibold tracking-[0.18em] text-white/75 uppercase">
                  More
                </p>
                <div className="mt-4 grid grid-cols-3 gap-x-2 gap-y-5">
                  {GROUP_APPS.map((id) => {
                    const app = DOCK_APPS.find((entry) => entry.id === id);

                    return (
                      <button
                        key={id}
                        type="button"
                        onClick={() => {
                          handleDockApp(id);
                          setGroupPanelOpen(false);
                        }}
                        className="flex flex-col items-center gap-2"
                      >
                        <span
                          className="drop-shadow-[0_4px_10px_rgba(0,0,0,0.35)]"
                          style={{ height: 58, width: 58 }}
                        >
                          <AppGlyph id={id} />
                        </span>
                        <span className="text-[11.5px] font-medium text-white/85">
                          {app?.name ?? id}
                        </span>
                      </button>
                    );
                  })}
                </div>
              </div>
            </div>
          )}

          <div className="pointer-events-none absolute inset-0 z-[50]">
            {windows.map((win) => (
              <WindowView
                key={win.uid}
                win={win}
                ctx={ctx}
                sectionRef={sectionRef}
                onClose={closeWindow}
                onFocus={focusWindow}
                onMove={moveWindow}
              />
            ))}
          </div>

          {lightbox && <PhotoLightbox src={lightbox} onClose={() => setLightbox(null)} />}
        </>
      )}
    </main>
  );
};
```

### `/preview.tsx`

```tsx
"use client";

import { useState } from "react";
import { RotateCcwIcon } from "lucide-react";

import { DesktopOS } from "./desktop-os";

const Preview = () => {
  // The boot sequence writes inline styles that React never clears, so replay
  // has to be a full remount rather than a timeline restart.
  const [bootKey, setBootKey] = useState(0);

  return (
    <div className="relative h-screen w-full overflow-hidden bg-[#1a1612]">
      <DesktopOS key={bootKey} />

      <button
        type="button"
        onClick={() => setBootKey((n) => n + 1)}
        className="absolute bottom-4 left-4 z-[70] flex items-center gap-1.5 rounded-full border border-white/15 bg-black/45 px-3 py-1.5 text-xs font-medium text-white/80 backdrop-blur-md transition-colors hover:bg-black/65 hover:text-white"
      >
        <RotateCcwIcon className="size-3.5" />
        Replay boot
      </button>
    </div>
  );
};

export default Preview;
```

### `/use-reduced-motion.ts`

```ts
import { useSyncExternalStore } from "react";

const QUERY = "(prefers-reduced-motion: reduce)";

const subscribe = (onChange: () => void) => {
  const query = window.matchMedia(QUERY);
  query.addEventListener("change", onChange);
  return () => query.removeEventListener("change", onChange);
};

const getSnapshot = () => window.matchMedia(QUERY).matches;
const getServerSnapshot = () => false;

export const useReducedMotion = () =>
  useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
```

### `/window-bodies.tsx`

```tsx
"use client";

import type { ReactNode } from "react";

import { useEffect, useState } from "react";
import {
  ChevronLeftIcon,
  ChevronRightIcon,
  Columns3Icon,
  FolderIcon,
  ImageIcon,
  LayoutGridIcon,
  ListIcon,
  SearchIcon,
} from "lucide-react";

import type { DesktopFile } from "./desktop-data";
import type { FrameProps } from "./window-frame";
import type { OpenWindow, WindowCtx } from "./window-types";

import { AppGlyph } from "./app-icons";
import { FILES, FIRST_NOTE, NOTES, PHOTOS, findFile } from "./desktop-data";
import { WindowFrame } from "./window-frame";
import { useReducedMotion } from "./use-reduced-motion";

/* -------------------------------------------------------------- Quick Look -- */

const QuickLookBody = ({
  fileId,
  ctx,
}: {
  fileId: string | undefined;
  ctx: WindowCtx;
}): ReactNode => {
  const startIndex = Math.max(
    0,
    FILES.findIndex((file) => file.id === fileId),
  );
  const [index, setIndex] = useState(startIndex);

  const file: DesktopFile = FILES[index] ?? FILES[0];

  const step = (delta: number): void => {
    setIndex((prev) => (prev + delta + FILES.length) % FILES.length);
  };

  // `file.full` is a cold 1600px derivative. Warm both neighbours so an arrow
  // click swaps to a decoded frame instead of blanking for the fetch.
  useEffect(() => {
    const neighbours = [
      FILES[(index + 1) % FILES.length],
      FILES[(index - 1 + FILES.length) % FILES.length],
    ];
    for (const neighbour of neighbours) {
      if (neighbour) {
        const img = new Image();
        img.src = neighbour.full;
      }
    }
  }, [index]);

  return (
    <div className="flex h-full flex-col bg-[#1c1a18]">
      <div className="relative min-h-0 flex-1">
        <button
          type="button"
          onClick={() => ctx.openLightbox(file.full)}
          aria-label={`Open ${file.name} full screen`}
          className="absolute inset-0 flex items-center justify-center p-6"
        >
          {/* No `key`: reusing the element lets the browser hold the previous
              frame until the next one decodes, instead of flashing empty. */}
          <img
            src={file.full}
            alt={file.name}
            draggable={false}
            className="max-h-full max-w-full rounded-[6px] object-contain shadow-[0_18px_50px_-20px_rgba(0,0,0,0.9)]"
          />
        </button>

        <button
          type="button"
          onClick={() => step(-1)}
          aria-label="Previous file"
          className="absolute top-1/2 left-3 flex size-8 -translate-y-1/2 items-center justify-center rounded-full bg-white/12 text-white backdrop-blur-md transition-colors hover:bg-white/25"
        >
          <ChevronLeftIcon className="size-4" />
        </button>
        <button
          type="button"
          onClick={() => step(1)}
          aria-label="Next file"
          className="absolute top-1/2 right-3 flex size-8 -translate-y-1/2 items-center justify-center rounded-full bg-white/12 text-white backdrop-blur-md transition-colors hover:bg-white/25"
        >
          <ChevronRightIcon className="size-4" />
        </button>
      </div>

      <div className="flex shrink-0 items-baseline gap-3 border-t border-white/10 bg-black/40 px-4 py-2.5 text-white">
        <span className="truncate text-[12.5px] font-medium">{file.name}</span>
        <span className="text-[11px] text-white/50">{file.dimensions}</span>
        <span className="ml-auto text-[11px] text-white/40">
          {index + 1} of {FILES.length}
        </span>
      </div>
    </div>
  );
};

/* ------------------------------------------------------------------ Photos -- */

const PhotosBody = ({ ctx }: { ctx: WindowCtx }): ReactNode => (
  <div className="dos-scroll h-full overflow-y-auto px-6 py-7 sm:px-8 sm:py-8">
    <p className="text-[10px] font-semibold tracking-[0.18em] text-black/45 uppercase">
      Recents · {PHOTOS.length}
    </p>
    <div
      className="mt-4 grid auto-rows-[120px] grid-cols-4 gap-3"
      style={{ gridAutoFlow: "row dense" }}
    >
      {PHOTOS.map((photo) => (
        <button
          key={photo.src}
          type="button"
          onClick={() => ctx.openLightbox(photo.full)}
          className={`group relative overflow-hidden rounded-[8px] bg-black/5 ${
            photo.ratio === "tall" ? "row-span-2" : ""
          } ${photo.ratio === "wide" ? "col-span-2" : ""}`}
        >
          <img
            src={photo.src}
            alt={photo.caption}
            loading="lazy"
            draggable={false}
            className="size-full object-cover transition-transform duration-500 ease-out group-hover:scale-[1.05]"
          />
          <span className="pointer-events-none absolute inset-x-0 bottom-0 translate-y-2 bg-gradient-to-t from-black/70 to-transparent px-3 py-2 text-[11px] font-medium text-white opacity-0 transition-all duration-200 group-hover:translate-y-0 group-hover:opacity-100">
            {photo.caption}
          </span>
        </button>
      ))}
    </div>
  </div>
);

/* ------------------------------------------------------------------- Notes -- */

const NotesBody = ({ ctx }: { ctx: WindowCtx }): ReactNode => {
  const [activeId, setActiveId] = useState<string>(FIRST_NOTE.id);
  const [mobileView, setMobileView] = useState<"list" | "detail">("list");

  const active = NOTES.find((note) => note.id === activeId) ?? FIRST_NOTE;
  const showList = !ctx.isMobile || mobileView === "list";
  const showDetail = !ctx.isMobile || mobileView === "detail";

  return (
    <div className="flex h-full">
      {showList && (
        <div
          className={`flex flex-col bg-[#f7f5ee] md:border-r md:border-black/[0.08] ${
            ctx.isMobile ? "w-full" : "w-60"
          }`}
        >
          <p className="border-b border-black/[0.06] px-4 py-2.5 text-[10px] font-semibold tracking-[0.16em] text-black/45 uppercase">
            Notes — {NOTES.length}
          </p>
          <div className="dos-scroll flex-1 overflow-y-auto">
            {NOTES.map((note) => (
              <button
                key={note.id}
                type="button"
                onClick={() => {
                  setActiveId(note.id);
                  if (ctx.isMobile) {
                    setMobileView("detail");
                  }
                }}
                className={`block w-full border-b border-black/[0.05] px-4 py-3 text-left transition-colors ${
                  note.id === activeId && !ctx.isMobile
                    ? "bg-[#f4d34c]/60"
                    : "hover:bg-black/[0.03]"
                }`}
              >
                <p className="truncate text-[13px] font-semibold text-[#1a1612]">{note.title}</p>
                <div className="mt-0.5 flex items-baseline gap-2">
                  <span className="text-[11px] font-medium text-black/70">{note.date}</span>
                  <span className="truncate text-[11px] text-black/55">{note.preview}</span>
                </div>
              </button>
            ))}
          </div>
        </div>
      )}

      {showDetail && (
        <div className="flex min-w-0 flex-1 flex-col overflow-hidden">
          {ctx.isMobile && (
            <button
              type="button"
              onClick={() => setMobileView("list")}
              className="flex items-center gap-1 border-b border-black/[0.06] px-3 py-2.5 text-[13px] font-medium text-[#1a1612]"
            >
              <ChevronLeftIcon className="size-4" />
              Notes
            </button>
          )}
          <div className="dos-scroll flex-1 overflow-y-auto px-5 py-6 sm:px-8 sm:py-7">
            <p className="text-[11px] text-black/45">{active.date}</p>
            <h3 className="mt-1 text-[22px] leading-tight font-semibold tracking-tight text-[#1a1612]">
              {active.title}
            </h3>
            <p className="mt-5 text-[14px] leading-relaxed whitespace-pre-line text-black/80">
              {active.body}
            </p>
          </div>
        </div>
      )}
    </div>
  );
};

/* ------------------------------------------------------------------ Finder -- */

const FINDER_SIDEBAR = [
  { color: "#62a8e8", label: "Desktop" },
  { color: "#7c8595", label: "Applications" },
  { color: "#7c8595", label: "Documents" },
  { color: "#5fd0c9", label: "Captures" },
  { color: "#a8a08b", label: "Exports" },
  { color: "#7c8595", label: "Downloads" },
] as const;

const FinderFolderDot = ({ size = 14 }: { size?: number }): ReactNode => (
  <FolderIcon
    className="shrink-0 text-[#4aa3f0]"
    style={{ height: size, width: size }}
    fill="#4aa3f0"
  />
);

const ViewModeButton = ({
  children,
  active,
  label,
}: {
  children: ReactNode;
  active?: boolean;
  label: string;
}): ReactNode => (
  <button
    type="button"
    aria-label={label}
    className={`rounded p-1 text-black/45 ${
      active ? "bg-white text-black/70 shadow-[0_1px_0_rgba(0,0,0,0.08)]" : ""
    }`}
  >
    {children}
  </button>
);

const FinderBody = ({ win, ctx }: { win: OpenWindow; ctx: WindowCtx }): ReactNode => {
  const folderName = win.folderName ?? "Archive";
  // Trash and Archive hold different slices of the same library.
  const contents = folderName === "Trash" ? FILES.slice(6, 10) : FILES.slice(1, 6);

  return (
    <div className="flex h-full flex-col bg-[#f7f5ef]">
      <div className="flex items-center gap-3 border-b border-black/[0.06] bg-[#ecebe6] px-3 py-1.5">
        <div className="flex items-center text-black/35">
          <button
            type="button"
            aria-label="Back"
            className="rounded-md p-1 transition-colors hover:bg-black/5"
          >
            <ChevronLeftIcon className="size-3.5" />
          </button>
          <button
            type="button"
            aria-label="Forward"
            className="rounded-md p-1 transition-colors hover:bg-black/5"
          >
            <ChevronRightIcon className="size-3.5" />
          </button>
        </div>
        <div className="flex items-center gap-1.5 text-[12px] font-semibold text-[#1a1612]">
          <FinderFolderDot />
          {folderName}
        </div>
        <div className="flex-1" />
        <div className="flex items-center gap-0.5 rounded-md border border-black/10 bg-white/70 p-0.5">
          <ViewModeButton label="Icon view" active>
            <LayoutGridIcon className="size-3.5" />
          </ViewModeButton>
          <ViewModeButton label="List view">
            <ListIcon className="size-3.5" />
          </ViewModeButton>
          <ViewModeButton label="Column view">
            <Columns3Icon className="size-3.5" />
          </ViewModeButton>
          <ViewModeButton label="Gallery view">
            <ImageIcon className="size-3.5" />
          </ViewModeButton>
        </div>
        <button
          type="button"
          aria-label="Search"
          className="rounded-md p-1 text-black/40 transition-colors hover:bg-black/5"
        >
          <SearchIcon className="size-3.5" />
        </button>
      </div>

      <div className="flex min-h-0 flex-1">
        <div className="dos-scroll hidden w-40 shrink-0 overflow-y-auto bg-[#ecebe6] px-2 py-2 sm:block">
          <p className="px-1.5 py-1 text-[10px] font-semibold tracking-[0.06em] text-black/45 uppercase">
            Locations
          </p>
          {["Recents", "Shared"].map((item) => (
            <div
              key={item}
              className="flex items-center gap-2 rounded-[6px] px-1.5 py-1 transition-colors hover:bg-black/[0.06]"
            >
              <span className="size-3 rounded-[3px]" style={{ background: "#7c8595" }} />
              <span className="truncate text-[12px] text-[#1a1612]">{item}</span>
            </div>
          ))}
          <p className="mt-2 px-1.5 py-1 text-[10px] font-semibold tracking-[0.06em] text-black/45 uppercase">
            Favourites
          </p>
          {FINDER_SIDEBAR.map((item) => (
            <div
              key={item.label}
              className="flex items-center gap-2 rounded-[6px] px-1.5 py-1 transition-colors hover:bg-black/[0.06]"
            >
              <span className="size-3 rounded-[3px]" style={{ background: item.color }} />
              <span className="truncate text-[12px] text-[#1a1612]">{item.label}</span>
            </div>
          ))}
        </div>

        <div className="dos-scroll min-w-0 flex-1 overflow-y-auto bg-white px-5 py-4">
          <div
            className="grid gap-x-2 gap-y-4"
            style={{
              gridTemplateColumns: "repeat(auto-fill, minmax(96px, 1fr))",
            }}
          >
            {contents.map((file) => (
              <button
                key={file.id}
                type="button"
                onClick={() => ctx.openQuickLook(file.id)}
                className="group flex flex-col items-center gap-1.5 rounded-md p-2 focus:outline-none focus-visible:bg-[#cce3ff]"
              >
                <span className="size-[68px] overflow-hidden rounded-[6px] border border-black/10 bg-black/5">
                  <img
                    src={file.thumb}
                    alt=""
                    loading="lazy"
                    draggable={false}
                    className="size-full object-cover"
                  />
                </span>
                <span className="line-clamp-2 max-w-[96px] rounded-[4px] px-1 text-center text-[11px] leading-[1.2] font-medium text-[#1a1612] group-focus-visible:bg-[#3b82f6] group-focus-visible:text-white">
                  {file.name}
                </span>
              </button>
            ))}
            <div className="flex flex-col items-center gap-1.5 rounded-md p-2">
              <span className="size-[68px]">
                <AppGlyph id="folder" />
              </span>
              <span className="max-w-[96px] truncate px-1 text-center text-[11px] leading-[1.2] font-medium text-[#1a1612]">
                Older
              </span>
            </div>
          </div>
        </div>
      </div>

      <div className="flex items-center gap-1.5 border-t border-black/[0.06] bg-[#f4f2eb] px-3 py-1.5 text-[10.5px] text-black/55">
        <FinderFolderDot size={12} />
        <span className="flex items-center gap-1">
          Macintosh HD
          <ChevronRightIcon className="size-2.5 text-black/35" />
          Users
          <ChevronRightIcon className="size-2.5 text-black/35" />
          Desktop
          <ChevronRightIcon className="size-2.5 text-black/35" />
          <span className="font-semibold text-black/75">{folderName}</span>
        </span>
        <span className="ml-auto">{contents.length + 1} items</span>
      </div>
    </div>
  );
};

/* ---------------------------------------------------------------- Terminal -- */

interface TerminalLine {
  readonly prompt: boolean;
  readonly text: string;
  readonly tone?: "ok" | "dim";
}

const TONE_CLASS: Record<NonNullable<TerminalLine["tone"]>, string> = {
  dim: "text-white/45",
  ok: "text-[#9fe6c0]",
};

const TERMINAL_LINES: readonly TerminalLine[] = [
  { prompt: true, text: "system --status" },
  { prompt: false, text: "compositor      up     16.6ms/frame", tone: "ok" },
  { prompt: false, text: "window server   up     6 surfaces", tone: "ok" },
  { prompt: false, text: "dock            up     magnification on", tone: "ok" },
  { prompt: false, text: "wallpaper       up     gradient, no bitmap", tone: "dim" },
  { prompt: true, text: "uptime" },
  { prompt: false, text: "2 seconds since boot", tone: "dim" },
];

const TerminalBody = (): ReactNode => {
  const [visible, setVisible] = useState(0);
  const reducedMotion = useReducedMotion();

  useEffect(() => {
    if (reducedMotion) {
      return;
    }

    // The count lives in the closure, not in the updater: React may invoke an
    // updater twice, and stopping the interval from inside one is a side effect
    // that has no business running during a re-render.
    let shown = 0;
    const id = window.setInterval(() => {
      shown += 1;
      setVisible(shown);
      if (shown >= TERMINAL_LINES.length) {
        window.clearInterval(id);
      }
    }, 190);

    return () => window.clearInterval(id);
  }, [reducedMotion]);

  return (
    <div className="dos-scroll dos-scroll-dark h-full overflow-y-auto bg-[#101014] px-4 py-3 font-mono text-[12px] leading-[1.7] text-[#d6d6da]">
      {TERMINAL_LINES.slice(0, reducedMotion ? TERMINAL_LINES.length : visible).map((line) => (
        <div key={line.text} className="flex gap-2">
          {line.prompt && <span className="text-[#5fd0a0]">~ %</span>}
          <span className={line.tone === undefined ? "" : TONE_CLASS[line.tone]}>{line.text}</span>
        </div>
      ))}
      <div className="flex gap-2">
        <span className="text-[#5fd0a0]">~ %</span>
        <span className="dos-caret inline-block h-[14px] w-[7px] bg-[#d6d6da] align-middle" />
      </div>
    </div>
  );
};

/* -------------------------------------------------------------- dispatcher -- */

/**
 * Sibling of `titleForWindow`: the two functions are the whole mapping from a
 * window's kind to what the frame shows, and they stay next to each other so a
 * new kind cannot satisfy one exhaustive switch and miss the other.
 */
const bodyForWindow = (win: OpenWindow, ctx: WindowCtx): ReactNode => {
  switch (win.kind) {
    case "quicklook": {
      return <QuickLookBody fileId={win.fileId} ctx={ctx} />;
    }
    case "photos": {
      return <PhotosBody ctx={ctx} />;
    }
    case "notes": {
      return <NotesBody ctx={ctx} />;
    }
    case "finder": {
      return <FinderBody win={win} ctx={ctx} />;
    }
    case "terminal": {
      return <TerminalBody />;
    }
    default: {
      const exhaustive: never = win.kind;
      return exhaustive;
    }
  }
};

const titleForWindow = (win: OpenWindow): string => {
  switch (win.kind) {
    case "quicklook": {
      return findFile(win.fileId)?.name ?? "Quick Look";
    }
    case "photos": {
      return "Photos";
    }
    case "notes": {
      return "Notes";
    }
    case "finder": {
      return win.folderName ?? "Files";
    }
    case "terminal": {
      return "Terminal — bash";
    }
    default: {
      const exhaustive: never = win.kind;
      return exhaustive;
    }
  }
};

export const WindowView = ({
  win,
  ctx,
  sectionRef,
  onClose,
  onFocus,
  onMove,
}: Pick<FrameProps, "sectionRef" | "onClose" | "onFocus" | "onMove"> & {
  win: OpenWindow;
  ctx: WindowCtx;
}): ReactNode => (
  <WindowFrame
    win={win}
    isMobile={ctx.isMobile}
    sectionRef={sectionRef}
    onClose={onClose}
    onFocus={onFocus}
    onMove={onMove}
    title={titleForWindow(win)}
  >
    {bodyForWindow(win, ctx)}
  </WindowFrame>
);
```

### `/window-frame.tsx`

```tsx
"use client";

import type { PointerEvent as ReactPointerEvent, ReactNode, RefObject } from "react";

import { useEffect, useRef } from "react";
import { gsap } from "gsap";
import { XIcon } from "lucide-react";

import type { OpenWindow } from "./window-types";

export interface FrameProps {
  win: OpenWindow;
  isMobile: boolean;
  sectionRef: RefObject<HTMLElement | null>;
  onClose: (uid: number) => void;
  onFocus: (uid: number) => void;
  onMove: (uid: number, x: number, y: number) => void;
  /** Required: it is both the visible header label and the dialog's accessible name. */
  title: string;
  children: ReactNode;
}

/**
 * Modelled as a union so "not dragging" cannot carry drag coordinates: the
 * previous shape kept a `dragging` boolean beside stale start/origin values,
 * and any path that failed to reset it left the window glued to the cursor.
 */
type HeaderDrag =
  | { readonly phase: "idle" }
  | {
      readonly phase: "active";
      readonly pointerId: number;
      readonly startX: number;
      readonly startY: number;
      readonly originX: number;
      readonly originY: number;
      moved: boolean;
      last: { readonly x: number; readonly y: number } | null;
    };

const clamp = (value: number, min: number, max: number): number =>
  Math.min(Math.max(value, min), max);

/**
 * Releasing capture schedules a `lostpointercapture`, which routes back into the
 * cancel handler — so every caller drops its own state *before* calling this,
 * leaving that handler nothing to undo.
 */
const releaseCapture = (el: HTMLElement, pointerId: number): void => {
  if (el.hasPointerCapture(pointerId)) {
    el.releasePointerCapture(pointerId);
  }
};

export const WindowFrame = ({
  win,
  isMobile,
  sectionRef,
  onClose,
  onFocus,
  onMove,
  title,
  children,
}: FrameProps): ReactNode => {
  const frameRef = useRef<HTMLDivElement>(null);
  const drag = useRef<HeaderDrag>({ phase: "idle" });

  // Mount pop. gsap writes inline styles, so the tween owns the rest state too.
  useEffect(() => {
    const el = frameRef.current;
    if (!el) {
      return;
    }

    const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;

    if (reduced) {
      gsap.set(el, { filter: "none", opacity: 1, scale: 1, y: 0 });
      return;
    }

    const tween = gsap.fromTo(
      el,
      { filter: "blur(6px)", opacity: 0, scale: 0.94, y: 14 },
      {
        duration: 0.36,
        ease: "power3.out",
        filter: "blur(0px)",
        opacity: 1,
        scale: 1,
        y: 0,
      },
    );

    return () => {
      tween.kill();
    };
  }, []);

  /** Undo the gesture's imperative writes and hand `left`/`top` back to React. */
  const resetFramePosition = (): void => {
    const el = frameRef.current;
    if (!el) {
      return;
    }
    el.style.left = `${win.x}px`;
    el.style.top = `${win.y}px`;
  };

  const onHeaderPointerDown = (e: ReactPointerEvent<HTMLDivElement>): void => {
    if (isMobile) {
      return;
    }
    if (e.button !== 0) {
      return;
    }
    if (drag.current.phase === "active") {
      return;
    }
    if (!(e.target instanceof Element)) {
      return;
    }
    // Traffic lights are inside the drag handle but must stay clickable.
    if (e.target.closest("[data-traffic-light]")) {
      return;
    }
    if (!frameRef.current) {
      return;
    }

    onFocus(win.uid);
    // Capture on the element that owns these handlers. Capturing on an ancestor
    // retargets every later event to that ancestor, where nothing is listening —
    // which is to say the drag would never move and never end.
    e.currentTarget.setPointerCapture(e.pointerId);
    drag.current = {
      last: null,
      moved: false,
      originX: win.x,
      originY: win.y,
      phase: "active",
      pointerId: e.pointerId,
      startX: e.clientX,
      startY: e.clientY,
    };
  };

  const onHeaderPointerMove = (e: ReactPointerEvent<HTMLDivElement>): void => {
    const state = drag.current;
    if (state.phase !== "active" || state.pointerId !== e.pointerId) {
      return;
    }
    const el = frameRef.current;
    const section = sectionRef.current;
    if (!el || !section) {
      return;
    }

    const dx = e.clientX - state.startX;
    const dy = e.clientY - state.startY;
    if (!state.moved && Math.hypot(dx, dy) < 3) {
      return;
    }
    state.moved = true;

    const nx = clamp(state.originX + dx, -win.w + 80, section.clientWidth - 80);
    const ny = clamp(state.originY + dy, 28, Math.max(28, section.clientHeight - 60));
    el.style.left = `${nx}px`;
    el.style.top = `${ny}px`;
    state.last = { x: nx, y: ny };
  };

  const onHeaderPointerUp = (e: ReactPointerEvent<HTMLDivElement>): void => {
    const state = drag.current;
    if (state.phase !== "active" || state.pointerId !== e.pointerId) {
      return;
    }

    drag.current = { phase: "idle" };
    releaseCapture(e.currentTarget, e.pointerId);

    const committed = state.last;
    if (state.moved && committed) {
      onMove(win.uid, committed.x, committed.y);
    }
  };

  /**
   * Alt-tab, a context menu or a cancelled touch ends the gesture without a
   * `pointerup`. Drop the drag and redraw from the last committed position
   * rather than stranding half a move in the inline style.
   */
  const onHeaderPointerCancel = (e: ReactPointerEvent<HTMLDivElement>): void => {
    const state = drag.current;
    if (state.phase !== "active" || state.pointerId !== e.pointerId) {
      return;
    }

    drag.current = { phase: "idle" };
    releaseCapture(e.currentTarget, e.pointerId);
    resetFramePosition();
  };

  return (
    // oxlint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- mousedown only raises the window's z-order; focusing anything inside the window reaches the same state from the keyboard
    <div
      ref={frameRef}
      // oxlint-disable-next-line jsx-a11y/prefer-tag-over-role -- a <dialog> element's UA inset/fit-content sizing and open/close model fight this absolutely positioned, pointer-dragged frame
      role="dialog"
      aria-label={title}
      onMouseDown={() => onFocus(win.uid)}
      className="pointer-events-auto absolute flex flex-col overflow-hidden rounded-[12px] border border-black/15 bg-[#fbfaf7] text-[#1a1612] shadow-[0_30px_80px_-20px_rgba(0,0,0,0.45),0_8px_24px_-8px_rgba(0,0,0,0.35)]"
      style={{
        height: win.h,
        left: win.x,
        top: win.y,
        width: win.w,
        zIndex: win.z,
      }}
    >
      <div
        onPointerDown={onHeaderPointerDown}
        onPointerMove={onHeaderPointerMove}
        onPointerUp={onHeaderPointerUp}
        onPointerCancel={onHeaderPointerCancel}
        onLostPointerCapture={onHeaderPointerCancel}
        className={`flex shrink-0 select-none items-center gap-3 border-b border-black/[0.07] bg-[#ecebe6] px-3 py-2 ${
          isMobile ? "cursor-default" : "cursor-grab active:cursor-grabbing"
        }`}
        style={{ touchAction: "none" }}
      >
        <div data-traffic-light className="flex items-center gap-1.5">
          <button
            type="button"
            aria-label="Close window"
            onClick={() => onClose(win.uid)}
            className="group/tl flex size-3 items-center justify-center rounded-full bg-[#ff5f57] ring-1 ring-black/10"
          >
            <svg
              width="6"
              height="6"
              viewBox="0 0 6 6"
              className="opacity-0 group-hover/tl:opacity-100"
            >
              <path
                d="M1 1 L5 5 M5 1 L1 5"
                stroke="#5a0900"
                strokeWidth="1.1"
                strokeLinecap="round"
              />
            </svg>
          </button>
          <span
            className="size-3 rounded-full bg-[#febc2e] ring-1 ring-black/10"
            aria-hidden="true"
          />
          <span
            className="size-3 rounded-full bg-[#28c840] ring-1 ring-black/10"
            aria-hidden="true"
          />
        </div>

        <div className="flex min-w-0 flex-1 items-center justify-center">
          <span className="truncate text-[12px] font-medium tracking-tight text-black/70">
            {title}
          </span>
        </div>

        {/*
          Empty on purpose. The traffic lights sit in the row's left slot, so the
          title only lands optically centred while a same-order counterweight
          holds the right slot open.
        */}
        <div className="shrink-0" style={{ minWidth: 60 }} aria-hidden="true" />
      </div>

      <div className="relative min-h-0 flex-1 overflow-hidden">{children}</div>
    </div>
  );
};

export const PhotoLightbox = ({
  src,
  onClose,
}: {
  src: string;
  onClose: () => void;
}): ReactNode => {
  const overlayRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const el = overlayRef.current;
    if (!el) {
      return;
    }

    const tween = gsap.fromTo(
      el,
      { opacity: 0 },
      { duration: 0.25, ease: "power2.out", opacity: 1 },
    );

    return () => {
      tween.kill();
    };
  }, []);

  return (
    <div
      ref={overlayRef}
      role="presentation"
      onMouseDown={(e) => {
        if (e.target === e.currentTarget) {
          onClose();
        }
      }}
      className="absolute inset-0 z-[60] flex items-center justify-center bg-black/85 p-8 backdrop-blur-sm"
    >
      <img
        src={src}
        alt="Enlarged view"
        draggable={false}
        className="max-h-full max-w-full rounded-[8px] shadow-2xl"
      />
      <button
        type="button"
        onClick={onClose}
        aria-label="Close"
        className="absolute top-5 right-5 flex size-9 items-center justify-center rounded-full bg-white/15 text-white backdrop-blur-md transition-colors hover:bg-white/25"
      >
        <XIcon className="size-5" />
      </button>
    </div>
  );
};
```

### `/window-types.ts`

```ts
/** Shared window-manager vocabulary. Kept dependency-free to avoid import cycles. */

export type WindowKind = "quicklook" | "photos" | "notes" | "finder" | "terminal";

export interface OpenWindow {
  readonly uid: number;
  readonly kind: WindowKind;
  readonly z: number;
  readonly x: number;
  readonly y: number;
  readonly w: number;
  readonly h: number;
  /** Quick Look only — which desktop file is being previewed. */
  readonly fileId?: string;
  /** Finder only — which folder the window is showing. */
  readonly folderName?: string;
}

export const DEFAULT_WINDOW_SIZES = {
  finder: { h: 460, w: 760 },
  notes: { h: 520, w: 720 },
  photos: { h: 600, w: 880 },
  quicklook: { h: 560, w: 760 },
  terminal: { h: 400, w: 640 },
} satisfies Record<WindowKind, { readonly w: number; readonly h: number }>;

export interface WindowCtx {
  readonly isMobile: boolean;
  readonly openQuickLook: (fileId: string) => void;
  readonly openLightbox: (src: string) => void;
}
```
