# Implementation Plan [Overview] Add a Doom emulator that launches in a modal window (“floating over the page”) when the user clicks a new “running on a potato” entry in the existing sidebar menu. The emulator will be loaded on-demand, client-only, and isolated from SSR to protect page performance and stability. Multiple paragraphs outlining the scope, context, and high-level approach. Explain why this implementation is needed and how it fits into the existing system. - This Next.js 15 app uses the App Router, React 19, Tailwind, and several client-side providers (Lenis, Motion). The request is to add a lightweight, non-intrusive Doom experience without altering the primary site navigation. The user wants a single menu item in the sidebar labeled “running on a potato” that, when clicked, opens an overlay modal containing the emulator. - The overlay will be implemented as a client-only modal and mounted at the root layout so it renders above all content, and remains accessible across pages. We will add a React Context Provider to manage the “open/close” state of the modal globally. The sidebar menu will receive an action hook to open the modal. - For the emulator engine, we will use js-dos (DOSBox compiled to WebAssembly) via the npm package “js-dos”. It can boot a packaged .jsdos archive containing DOS Doom and run entirely in the browser. This approach avoids adding complex custom build steps and does not require special COOP/COEP headers. The emulator will be lazy-loaded via dynamic import to keep initial page loads fast. - To provide an immediate out-of-the-box experience, the emulator will load a remotely hosted demo .jsdos archive from the official js-dos CDN (subject to its availability). If remote loading fails (network/CORS), the overlay will show a prompt allowing the user to drag-and-drop or select a local .jsdos archive to run. This keeps the feature functional while avoiding bundling large binary assets into the repository. - Accessibility and usability: the modal will trap focus, provide keyboard controls (ESC to close), restore focus on close, and disable background scroll while open. [Types] Single sentence describing the type system changes. We will add a small set of TypeScript types for the modal provider context and emulator configuration. Detailed type definitions, interfaces, enums, or data structures with complete specifications. Include field names, types, validation rules, and relationships. - app/providers/DoomOverlayProvider.tsx - export type DoomOverlayContextValue = { isOpen: boolean; open: () => void; close: () => void; } - Constraints: - isOpen: true when modal is visible. - open/close: stable callbacks; must be usable from any client component. - app/components/doom/JsDosPlayer.tsx - export type JsDosPlayerProps = { zipUrl?: string; // optional remote .jsdos archive URL className?: string; } - export type JsDosHandle = { stop: () => void; // shutdown emulator and free resources } - Emulator engine choice type: - export enum DoomEngine { JsDos = "js-dos" } - export type DoomConfig = { engine: DoomEngine; jsdos?: { zipUrl?: string } } [Files] Single sentence describing file modifications. We will add a provider, modal overlay, emulator player component, and update the sidebar menu and root layout to integrate the overlay trigger and mount point. Detailed breakdown: - New files to be created (with full paths and purpose) 1) app/providers/DoomOverlayProvider.tsx - Purpose: Client-side React context that exposes open/close state and actions for the Doom modal. Wraps children so any component can open the overlay. 2) app/components/overlay/Modal.tsx - Purpose: Accessible, reusable modal component using a portal, focus trap, ESC-to-close, backdrop click-to-close, and scroll lock. 3) app/components/doom/DoomOverlay.tsx - Purpose: The top-level container rendered inside the modal. It displays loading/error states and hosts the emulator player. Includes a close button and optional instructions if remote loading fails. 4) app/components/doom/JsDosPlayer.tsx - Purpose: Client-only component that lazy-loads “js-dos”, renders the emulator surface, runs a provided .jsdos archive via a URL, and tears down cleanly on unmount. - Existing files to be modified (with specific changes) 1) app/layout.tsx - Wrap current body content with . Mount near the end of (still inside providers) so it renders above app content, but shares styles/providers. 2) app/components/sidebar-menu.tsx - Add a new “running on a potato” menu action above or below current items (About/Writing/Contact). - This should be a button (not a Link) that calls open() from DoomOverlayProvider to display the modal. Maintain current styling conventions via cn() and Tailwind. - Files to be deleted or moved - None. - Configuration file updates - package.json (Dependencies): add "js-dos". - No changes to next.config.ts or tsconfig.json are required for this plan. [Functions] Single sentence describing function modifications. We will add modal state management functions, emulator lifecycle controls, and integrate a new onclick handler in the sidebar menu. Detailed breakdown: - New functions (name, signature, file path, purpose) 1) open ((): void) — app/providers/DoomOverlayProvider.tsx - Purpose: Set modal open state to true. 2) close ((): void) — app/providers/DoomOverlayProvider.tsx - Purpose: Set modal open state to false. 3) DoomOverlay ((): JSX.Element) — app/components/doom/DoomOverlay.tsx - Purpose: Renders Modal and inside it lazy-loads the emulator (JsDosPlayer). Handles errors, shows loading fallback, exposes close control. 4) JsDosPlayer ((props: JsDosPlayerProps) => JSX.Element) — app/components/doom/JsDosPlayer.tsx - Purpose: Initializes js-dos in a container div/canvas and runs a .jsdos ZIP archive via props.zipUrl. Disposes emulator instance on unmount. 5) Modal ((props: { open: boolean; onClose: () => void; children: React.ReactNode; title?: string; className?: string }) => JSX.Element) — app/components/overlay/Modal.tsx - Purpose: Accessible modal with portal, focus trap, backdrop, escape close, body scroll lock. - Modified functions (exact name, current file path, required changes) 1) default export SidebarMenu — app/components/sidebar-menu.tsx - Add one extra item rendered as a button: - Label: "running on a potato" - onClick: calls open() via DoomOverlayProvider context - Style: consistent with other menu items (Tailwind classes), ensure focus-visible styles for accessibility. - Removed functions (name, file path, reason, migration strategy) - None. [Classes] Single sentence describing class modifications. No class-based components are used; all additions are functional React components. Detailed breakdown: - New classes - None. - Modified classes - None. - Removed classes - None. [Dependencies] Single sentence describing dependency modifications. We will add js-dos to support running Doom in-browser via a WASM-powered DOSBox. Details of new packages, version changes, and integration requirements. - Add: "js-dos": "^7.5.0" (or latest v7) - Import usage in client-only component: - import "js-dos/css/js-dos.css" - const { Dos } = await import("js-dos") - Runtime: - Provide a container element; initialize: const dos = Dos(container) - Run: dos.run(zipUrl) - No special COOP/COEP headers required for basic usage. - Asset loading: - Default: remote demo archive (subject to availability), e.g. https://v8.js-dos.com/v7/build/doom.jsdos - Fallback: file input to select a local .jsdos package. We will show instructions in the overlay if remote load fails. - No changes to Next.js, React, Tailwind, or other existing dependencies. [Testing] Single sentence describing testing approach. We will verify overlay behavior, emulator load success and teardown, and ensure no SSR/runtime errors across pages. Test file requirements, existing test modifications, and validation strategies. - Manual QA (dev server): - Open site, click “running on a potato” in sidebar: - Modal opens, background scroll locked, focus lands on modal. - Emulator loads and becomes interactive; performance acceptable. - Close via ESC, backdrop click (if enabled), or close button — focus returns to triggering button. - Network-offline test: - Remote .jsdos load should fail gracefully; overlay shows a message and a file picker to choose a local .jsdos archive. After selecting, emulator runs. - Navigation test: - Navigate between sections while modal closed; ensure global provider doesn’t interfere with Lenis/scroll. - Accessibility: - Confirm appropriate aria attributes, focus management, and keyboard support. - Performance: - Confirm dynamic import defers js-dos until modal opens. - Confirm emulator stops and frees resources when modal closes. [Implementation Order] Single sentence describing the implementation sequence. Implement provider and modal primitives first, then the overlay and emulator, finally wire up the sidebar, and add the dependency. Numbered steps showing the logical order of changes to minimize conflicts and ensure successful integration. 1) Add dependency: js-dos to package.json and install. 2) Create app/providers/DoomOverlayProvider.tsx with context, open/close, and a useDoomOverlay() hook. 3) Create app/components/overlay/Modal.tsx with portal, focus trap, ESC handling, backdrop, and scroll lock. 4) Create app/components/doom/JsDosPlayer.tsx that: - Dynamically imports "js-dos" on mount (client-only). - Imports "js-dos/css/js-dos.css". - Initializes Dos(container) and calls .run(zipUrl). - Cleans up on unmount or when parent closes. 5) Create app/components/doom/DoomOverlay.tsx that: - Reads overlay state from context. - Uses Modal to render content. - Attempts to load remote .jsdos: https://v8.js-dos.com/v7/build/doom.jsdos - If loading fails, renders a file input and drag-and-drop area to run a local .jsdos file. - Provides close control UI. 6) Modify app/layout.tsx to wrap existing providers with and mount near the end of . 7) Modify app/components/sidebar-menu.tsx to add a new button-styled item labeled “running on a potato” that calls open() from the provider. 8) Run dev server and test: - Modal open/close behavior. - Emulator loads, runs, and shuts down cleanly. - Accessibility and performance checks. 9) Optional polish: - Add loading spinner. - Persist last used source (remote vs local) in session storage. - Add instructions link about providing your own .jsdos archive if desired.