Vibecode VHS Slipcover Maker
track this build4 phases, 12 steps, beginner friendly0%The core loop, type a title and print a correctly sized VHS slipcover, is a one-session build. But the rich design possibilities, range of 30+ physical media types, 22+ languages supported, and instant generation of covers from a variety of media sources makes this a lengthy project to spin up on your own. That part is not coming out of one prompt.
You are building a lean indie version of VHS Slipcover Maker. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # VHS Slipcover Maker · indie build A browser tool that turns a title and a piece of cover art into a wrap that folds around a real VHS case, whose hard problems are print accuracy on printers that quietly rescale, type that fits a one-inch spine, and full-resolution cover art handled without a server. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | A static single-page application, no server and no accounts | nothing here needs a server and a server would need paying for | | Geometry | Every format stored as millimetres in a data file, pixels derived at render time | the case does not care what your screen calls a pixel | | Rendering | One vector drawing of the whole wrap, shared by the screen and the export | two renderers drift, and the print is the one that drifts | | Type | Bundled fonts whose licences permit embedding, fitted from real glyph metrics | a title measured in a fallback font is a title that overflows the spine | | Export | PDF at the exact sheet size with fonts embedded and no scaling | fit to page is how a perfect design comes out five percent small | | State | The design serialised into the URL with defaults omitted | the URL is the save file and the share link | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Builds the single-page application and runs the test suite. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A real VHS case and a ruler** · free - Why: Every dimension in the format file is measured from this, and a number copied from somewhere else is a number you will find out about after printing. - Get it: Any tape case with its original sleeve, and a steel ruler marked in millimetres. - Verify: Panel, spine and flap measurements are written into the format file - [ ] **A printer that feeds the sheet size, or a print shop** · free, or a few pounds per sheet at a shop - Why: The wrap is wider than the paper most home printers take, and the calibration square only proves the printer that will actually produce the sleeve. - Get it: Check the largest sheet your printer feeds against the format's sheet size; otherwise a print shop that will print at one hundred percent on cardstock. - Verify: A calibration sheet has been printed and measured on it - [ ] **Fonts whose licences permit embedding** · free - Why: The PDF embeds the fonts so it prints identically elsewhere, and a font licence that forbids embedding makes that export unlawful to share. - Get it: Open licence fonts with the licence file kept beside each, recorded in the fonts file. - Verify: Every bundled font has a licence entry with embedding permitted - [ ] **Your print tolerance and the overlong title rule** · free - Why: Both are decisions the tests assert, and choosing them after seeing results means choosing whatever passes. - Get it: Decide how many millimetres of error the calibration square may show, and whether an overlong title wraps or truncates. - Verify: Both are written in the README and encoded in the tests ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Metadata search for films and music. - Accounts and saved designs. - Vendor-aligned sticker sheets. - Interface translation. - 30+ measured dieline templates: cassette J-cards, CD inserts, vinyl jackets, Blu-ray and DVD wraps, MiniDisc - movie and music search that auto-fills cover art and metadata - designs that persist and share as a URL - die-cut label-sheet PDFs aligned to vendor sticker stock - 22 UI languages If one of those is essential to you, that is the reason to keep paying for VHS Slipcover Maker, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · VHS Slipcover Maker The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a printable slipcover maker where the millimetres of the real case are the source of truth to replace VHS Slipcover Maker. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: A static single-page application, no server and no accounts · nothing here needs a server and a server would need paying for - Geometry: Every format stored as millimetres in a data file, pixels derived at render time · the case does not care what your screen calls a pixel - Rendering: One vector drawing of the whole wrap, shared by the screen and the export · two renderers drift, and the print is the one that drifts - Type: Bundled fonts whose licences permit embedding, fitted from real glyph metrics · a title measured in a fallback font is a title that overflows the spine - Export: PDF at the exact sheet size with fonts embedded and no scaling · fit to page is how a perfect design comes out five percent small - State: The design serialised into the URL with defaults omitted · the URL is the save file and the share link ### Data model (create this before Phase 1) - formats(id, name, sheet_w_mm, sheet_h_mm, panels_json, folds_json, cut_path_json, bleed_mm, notes) · one file per physical format, measured from a real case - design(format_id, title, year, runtime, synopsis, palette_json, font_id, preset_id, art_transform_json, schema_version) - presets(id, name, font_id, palette_json, layout_json) · the era looks, as data rather than code - fonts(id, family, file, licence, embedding_permitted) - calibration(printer_label, expected_mm, measured_mm, correction_factor, at) · per printer, from a printed test square - art(handle, original_bytes, width, height, orientation) · in memory and local storage only, never in the URL - Every dimension in a format file is a millimetre and no pixel value appears in data, so a format measured against a real case renders correctly on any screen and prints correctly on any printer that is not rescaling. ### Phase 1 · A wrap that fits the case in your hand Build: Define the VHS wrap as a format file in millimetres: panels, spine, flaps, fold lines and the cut path, measured from the case you own. Render it as one vector drawing. Print a calibration sheet with a hundred millimetre square and a ruler, measure it, and only then print a wrap and fold it around the case. Trap: Designing in pixels at an assumed screen density and then letting the print dialog fit the page produces a sleeve a few percent small, and a few percent is the difference between a slipcover and a piece of paper that will not close. Keep millimetres as the only unit in data, export at the exact sheet size, print at one hundred percent, and prove it with a measured square before trusting anything else. Done when: A printed calibration square measures a hundred millimetres within a stated tolerance, and the tolerance is written in the README. A wrap printed at one hundred percent folds around the real case with the spine text centred, checked by hand. The format file contains no pixel value, asserted by a test that scans it. Changing a panel width in the file changes the render with no code change. Do not build yet: No cover art, presets, URL state or PDF export. ### Phase 2 · Type that fits a one-inch spine Build: Fit the title to the rotated spine and to the front panel from the glyph metrics of the bundled font, with a minimum size and stated wrapping rules. Wait for the font loading promise before measuring or rendering anything. Ship the era presets as data. Trap: Measuring text before the bundled font has loaded measures the fallback font, so the spine title fits on screen and overflows in the export, and it happens on the first load of the day when the cache is cold. Wait on the font loading promise before any measurement, and re-measure if a font arrives late. Done when: A forty character title fits the spine above the minimum size, and a title that cannot fit wraps or truncates by the stated rule rather than crossing the cut line. Text is measured only after the font loading promise resolves, tested by delaying the font. The three presets change font, palette and layout from data alone. Every bundled font's licence permits embedding, recorded in the fonts file. Do not build yet: No cover art, URL state or export yet. ### Phase 3 · Cover art without a server Build: Accept a local image, honour its orientation metadata, and place it in the front panel with zoom and pan. Preview from a downscaled copy and keep the original for export. Bound the size you accept and say what the bound is. Never upload it anywhere. Trap: Putting the full resolution photograph into the live drawing and redrawing it on every drag makes the editor stutter, and exporting from that same drawing runs the tab out of memory on a phone. Preview from a downscaled copy and only touch the original at export time. Done when: Dragging a thirty megapixel image stays smooth, measured. The export uses the original pixels rather than the preview, verified by comparing detail. A sideways photograph from a phone renders upright. The image never leaves the machine, verified by watching network activity while editing. Do not build yet: No URL state or export yet. ### Phase 4 · The URL is the save file Build: Serialise the design into the URL with every default omitted and a schema version. Restore on load. Keep the art out of the URL and tell the person on restore that the image must be chosen again. Keep old links opening when the schema changes. Trap: Encoding the image into the URL produces an address that browsers and servers truncate, so the share link opens to a blank cover with no error. Keep the art out, keep the URL short by omitting defaults, and version the schema so a link from last year still opens. Done when: A design restores from its URL on a second machine with everything except the art, and the missing art is stated rather than silent. Defaults are omitted, tested by checking that a fresh design produces a minimal URL. A link written under a previous schema version opens correctly, tested with a stored fixture. Back and forward move between design states. Do not build yet: No export yet. ### Phase 5 · Export that prints the same everywhere Build: Export a PDF at the exact sheet size with fonts embedded, the cut line drawn and the fold lines omitted, plus a PNG at a stated resolution. Use native vector text rather than HTML embedded in the drawing. Add a second physical format as a data file to prove formats are data. State the sheet size each format needs beside the export button. Trap: A PDF whose page is the wrong size, or whose fonts are not embedded, prints wrong on every machine but yours, and the person will blame their printer. Set the page size from the format, embed the fonts, and open the file in a different viewer on a machine without those fonts before shipping. Done when: The PDF page size equals the format's sheet size exactly, asserted by reading the file. Fonts are embedded, verified by opening it on a machine without them. Fold lines are absent from the export and present on screen. A second format renders and exports with no code change beyond its data file. Do not build yet: Nothing after this. ### Out of scope (and why) - Movie and music metadata search, which needs an API key, a terms review and a cover art rights question you cannot answer on the user's behalf. - Accounts and saved designs, since the URL is the save file and a server would need paying for. - Die-cut sticker sheets aligned to vendor stock, because each vendor's sheet is its own measured template and a misalignment wastes the sheet. - Twenty two interface languages, which is a translation maintenance stream rather than a feature. ### README must contain - That millimetres are the only unit in data and the printed calibration square is the acceptance test - That the export must be printed at one hundred percent and never fit to page, with the tolerance stated - That the wrap needs a sheet larger than many home printers feed, so check the sheet size against your printer or use a print shop - The fold order for assembling the sleeve, with a photograph - That cover art never leaves the machine and is not in the share link - How to add a format as a data file measured from a real case ===== AGENTS.md ===== # Agent instructions · VHS Slipcover Maker indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A static single-page application, no server and no accounts, Every format stored as millimetres in a data file, pixels derived at render time, One vector drawing of the whole wrap, shared by the screen and the export, Bundled fonts whose licences permit embedding, fitted from real glyph metrics, PDF at the exact sheet size with fonts embedded and no scaling, The design serialised into the URL with defaults omitted. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Designing in pixels at an assumed screen density and then letting the print dialog fit the page produces a sleeve a few percent small, and a few percent is the difference between a slipcover and a piece of paper that will not close. Keep millimetres as the only unit in data, export at the exact sheet size, print at one hundred percent, and prove it with a measured square before trusting anything else. - Measuring text before the bundled font has loaded measures the fallback font, so the spine title fits on screen and overflows in the export, and it happens on the first load of the day when the cache is cold. Wait on the font loading promise before any measurement, and re-measure if a font arrives late. - Putting the full resolution photograph into the live drawing and redrawing it on every drag makes the editor stutter, and exporting from that same drawing runs the tab out of memory on a phone. Preview from a downscaled copy and only touch the original at export time. - Encoding the image into the URL produces an address that browsers and servers truncate, so the share link opens to a blank cover with no error. Keep the art out, keep the URL short by omitting defaults, and version the schema so a link from last year still opens. ===== BUILD_PLAN.md ===== # Build plan · VHS Slipcover Maker A browser tool that turns a title and a piece of cover art into a wrap that folds around a real VHS case, whose hard problems are print accuracy on printers that quietly rescale, type that fits a one-inch spine, and full-resolution cover art handled without a server. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A wrap that fits the case in your hand Printed at one hundred percent, the wrap folds around a real case. ### Steps 1. Write the format as millimetres Panels, spine, flaps, folds and the cut path, measured from the case rather than copied from a forum. Files: `src/formats/vhs.json`, `src/geometry.ts` 2. Print and measure a calibration square A hundred millimetres on paper is the acceptance test for everything after this. Files: `src/calibration.tsx` 3. Fold a real wrap around a real case Paper and a ruler find what a unit test cannot. Files: `docs/fold-order.md` ### Done when - [ ] The calibration square measures a hundred millimetres within tolerance - [ ] A wrap printed at one hundred percent fits the case - [ ] No pixel value exists in the format file - [ ] A panel width change renders without code changes ### Watch out - Designing in pixels at an assumed screen density and then letting the print dialog fit the page produces a sleeve a few percent small, and a few percent is the difference between a slipcover and a piece of paper that will not close. Keep millimetres as the only unit in data, export at the exact sheet size, print at one hundred percent, and prove it with a measured square before trusting anything else. ## Phase 2 · Type that fits a one-inch spine Any title fits the spine and the front, in the font it will print in. ### Steps 1. Fit from glyph metrics after fonts load The fallback font has different widths and the export is where you find out. Files: `src/text-fit.ts`, `src/fonts.ts` 2. State the wrapping and truncation rules A long title has to go somewhere and the cut line is not it. Files: `src/text-fit.ts`, `src/text-fit.test.ts` 3. Ship presets as data An era look is a font, a palette and a layout, not a branch in code. Files: `src/presets.json` ### Done when - [ ] A forty character title fits the spine - [ ] Overlong titles wrap or truncate by the stated rule - [ ] Text is measured only after fonts load - [ ] Presets change the look from data alone ### Watch out - Measuring text before the bundled font has loaded measures the fallback font, so the spine title fits on screen and overflows in the export, and it happens on the first load of the day when the cache is cold. Wait on the font loading promise before any measurement, and re-measure if a font arrives late. ## Phase 3 · Cover art without a server A large photograph is placed, cropped and kept at full resolution on the machine. ### Steps 1. Preview downscaled, export from the original Two copies, two jobs. The live drawing never holds thirty megapixels. Files: `src/art.ts` 2. Honour orientation metadata Phone photographs arrive sideways and the fix is one field. Files: `src/art.ts`, `src/art.test.ts` 3. Bound what you accept and say so A refusal with a stated limit beats a tab that dies. Files: `src/art.ts` ### Done when - [ ] A thirty megapixel image drags smoothly - [ ] Export uses original pixels - [ ] Sideways photographs render upright - [ ] No network activity carries the image ### Watch out - Putting the full resolution photograph into the live drawing and redrawing it on every drag makes the editor stutter, and exporting from that same drawing runs the tab out of memory on a phone. Preview from a downscaled copy and only touch the original at export time. ## Phase 4 · The URL is the save file A design reopens from its address on another machine, minus the art, and says so. ### Steps 1. Omit defaults and version the schema Short links survive, and old links keep opening. Files: `src/url-state.ts`, `src/url-state.test.ts` 2. Keep the art out and say so on restore An image in a URL is a truncated URL. Files: `src/url-state.ts` 3. Keep a fixture of every schema version The test that a two year old link still opens. Files: `tests/fixtures/urls-v1.txt` ### Done when - [ ] A design restores on a second machine minus the art - [ ] A fresh design produces a minimal URL - [ ] Old schema versions still open - [ ] Back and forward move between states ### Watch out - Encoding the image into the URL produces an address that browsers and servers truncate, so the share link opens to a blank cover with no error. Keep the art out, keep the URL short by omitting defaults, and version the schema so a link from last year still opens. ## Not in this build - Metadata search for films and music. - Accounts and saved designs. - Vendor-aligned sticker sheets. - Interface translation. ## After v1, if you want it - A print shop preset that exports with bleed and crop marks in the shop's stated format - A barcode generator that produces a scannable code rather than a decorative one - Spine-only labels for a shelf of tapes that already have sleeves ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. The format file loaded on first visit. Formats are data files in src/formats. VITE_DEFAULT_FORMAT=vhs # Required. Your decision. The calibration square must measure within this. VITE_PRINT_TOLERANCE_MM=0.5 # Required. PNG export resolution. The PDF is vector and does not use it. VITE_EXPORT_DPI=600 # Required. Above this an image is refused with the limit stated, rather than the tab dying. VITE_MAX_ART_PIXELS=40000000 # Required. Bumped when the URL format changes. Every previous version keeps a reader and a fixture. VITE_URL_SCHEMA_VERSION=1 # Required. Present as a constant. Fit to page is how a sleeve comes out five percent small. VITE_FIT_TO_PAGE=never
You are building a lean indie version of VHS Slipcover Maker. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== README.md ===== # VHS Slipcover Maker · indie build A browser tool that turns a title and a piece of cover art into a wrap that folds around a real VHS case, whose hard problems are print accuracy on printers that quietly rescale, type that fits a one-inch spine, and full-resolution cover art handled without a server. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | A static single-page application, no server and no accounts | nothing here needs a server and a server would need paying for | | Geometry | Every format stored as millimetres in a data file, pixels derived at render time | the case does not care what your screen calls a pixel | | Rendering | One vector drawing of the whole wrap, shared by the screen and the export | two renderers drift, and the print is the one that drifts | | Type | Bundled fonts whose licences permit embedding, fitted from real glyph metrics | a title measured in a fallback font is a title that overflows the spine | | Export | PDF at the exact sheet size with fonts embedded and no scaling | fit to page is how a perfect design comes out five percent small | | State | The design serialised into the URL with defaults omitted | the URL is the save file and the share link | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Builds the single-page application and runs the test suite. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A real VHS case and a ruler** · free - Why: Every dimension in the format file is measured from this, and a number copied from somewhere else is a number you will find out about after printing. - Get it: Any tape case with its original sleeve, and a steel ruler marked in millimetres. - Verify: Panel, spine and flap measurements are written into the format file - [ ] **A printer that feeds the sheet size, or a print shop** · free, or a few pounds per sheet at a shop - Why: The wrap is wider than the paper most home printers take, and the calibration square only proves the printer that will actually produce the sleeve. - Get it: Check the largest sheet your printer feeds against the format's sheet size; otherwise a print shop that will print at one hundred percent on cardstock. - Verify: A calibration sheet has been printed and measured on it - [ ] **Fonts whose licences permit embedding** · free - Why: The PDF embeds the fonts so it prints identically elsewhere, and a font licence that forbids embedding makes that export unlawful to share. - Get it: Open licence fonts with the licence file kept beside each, recorded in the fonts file. - Verify: Every bundled font has a licence entry with embedding permitted - [ ] **Your print tolerance and the overlong title rule** · free - Why: Both are decisions the tests assert, and choosing them after seeing results means choosing whatever passes. - Get it: Decide how many millimetres of error the calibration square may show, and whether an overlong title wraps or truncates. - Verify: Both are written in the README and encoded in the tests ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Metadata search for films and music. - Accounts and saved designs. - Vendor-aligned sticker sheets. - Interface translation. - 30+ measured dieline templates: cassette J-cards, CD inserts, vinyl jackets, Blu-ray and DVD wraps, MiniDisc - movie and music search that auto-fills cover art and metadata - designs that persist and share as a URL - die-cut label-sheet PDFs aligned to vendor sticker stock - 22 UI languages If one of those is essential to you, that is the reason to keep paying for VHS Slipcover Maker, and the README should say so rather than pretend. ===== BRIEF.md ===== # Build brief · VHS Slipcover Maker The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a printable slipcover maker where the millimetres of the real case are the source of truth to replace VHS Slipcover Maker. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: A static single-page application, no server and no accounts · nothing here needs a server and a server would need paying for - Geometry: Every format stored as millimetres in a data file, pixels derived at render time · the case does not care what your screen calls a pixel - Rendering: One vector drawing of the whole wrap, shared by the screen and the export · two renderers drift, and the print is the one that drifts - Type: Bundled fonts whose licences permit embedding, fitted from real glyph metrics · a title measured in a fallback font is a title that overflows the spine - Export: PDF at the exact sheet size with fonts embedded and no scaling · fit to page is how a perfect design comes out five percent small - State: The design serialised into the URL with defaults omitted · the URL is the save file and the share link ### Data model (create this before Phase 1) - formats(id, name, sheet_w_mm, sheet_h_mm, panels_json, folds_json, cut_path_json, bleed_mm, notes) · one file per physical format, measured from a real case - design(format_id, title, year, runtime, synopsis, palette_json, font_id, preset_id, art_transform_json, schema_version) - presets(id, name, font_id, palette_json, layout_json) · the era looks, as data rather than code - fonts(id, family, file, licence, embedding_permitted) - calibration(printer_label, expected_mm, measured_mm, correction_factor, at) · per printer, from a printed test square - art(handle, original_bytes, width, height, orientation) · in memory and local storage only, never in the URL - Every dimension in a format file is a millimetre and no pixel value appears in data, so a format measured against a real case renders correctly on any screen and prints correctly on any printer that is not rescaling. ### Phase 1 · A wrap that fits the case in your hand Build: Define the VHS wrap as a format file in millimetres: panels, spine, flaps, fold lines and the cut path, measured from the case you own. Render it as one vector drawing. Print a calibration sheet with a hundred millimetre square and a ruler, measure it, and only then print a wrap and fold it around the case. Trap: Designing in pixels at an assumed screen density and then letting the print dialog fit the page produces a sleeve a few percent small, and a few percent is the difference between a slipcover and a piece of paper that will not close. Keep millimetres as the only unit in data, export at the exact sheet size, print at one hundred percent, and prove it with a measured square before trusting anything else. Done when: A printed calibration square measures a hundred millimetres within a stated tolerance, and the tolerance is written in the README. A wrap printed at one hundred percent folds around the real case with the spine text centred, checked by hand. The format file contains no pixel value, asserted by a test that scans it. Changing a panel width in the file changes the render with no code change. Do not build yet: No cover art, presets, URL state or PDF export. ### Phase 2 · Type that fits a one-inch spine Build: Fit the title to the rotated spine and to the front panel from the glyph metrics of the bundled font, with a minimum size and stated wrapping rules. Wait for the font loading promise before measuring or rendering anything. Ship the era presets as data. Trap: Measuring text before the bundled font has loaded measures the fallback font, so the spine title fits on screen and overflows in the export, and it happens on the first load of the day when the cache is cold. Wait on the font loading promise before any measurement, and re-measure if a font arrives late. Done when: A forty character title fits the spine above the minimum size, and a title that cannot fit wraps or truncates by the stated rule rather than crossing the cut line. Text is measured only after the font loading promise resolves, tested by delaying the font. The three presets change font, palette and layout from data alone. Every bundled font's licence permits embedding, recorded in the fonts file. Do not build yet: No cover art, URL state or export yet. ### Phase 3 · Cover art without a server Build: Accept a local image, honour its orientation metadata, and place it in the front panel with zoom and pan. Preview from a downscaled copy and keep the original for export. Bound the size you accept and say what the bound is. Never upload it anywhere. Trap: Putting the full resolution photograph into the live drawing and redrawing it on every drag makes the editor stutter, and exporting from that same drawing runs the tab out of memory on a phone. Preview from a downscaled copy and only touch the original at export time. Done when: Dragging a thirty megapixel image stays smooth, measured. The export uses the original pixels rather than the preview, verified by comparing detail. A sideways photograph from a phone renders upright. The image never leaves the machine, verified by watching network activity while editing. Do not build yet: No URL state or export yet. ### Phase 4 · The URL is the save file Build: Serialise the design into the URL with every default omitted and a schema version. Restore on load. Keep the art out of the URL and tell the person on restore that the image must be chosen again. Keep old links opening when the schema changes. Trap: Encoding the image into the URL produces an address that browsers and servers truncate, so the share link opens to a blank cover with no error. Keep the art out, keep the URL short by omitting defaults, and version the schema so a link from last year still opens. Done when: A design restores from its URL on a second machine with everything except the art, and the missing art is stated rather than silent. Defaults are omitted, tested by checking that a fresh design produces a minimal URL. A link written under a previous schema version opens correctly, tested with a stored fixture. Back and forward move between design states. Do not build yet: No export yet. ### Phase 5 · Export that prints the same everywhere Build: Export a PDF at the exact sheet size with fonts embedded, the cut line drawn and the fold lines omitted, plus a PNG at a stated resolution. Use native vector text rather than HTML embedded in the drawing. Add a second physical format as a data file to prove formats are data. State the sheet size each format needs beside the export button. Trap: A PDF whose page is the wrong size, or whose fonts are not embedded, prints wrong on every machine but yours, and the person will blame their printer. Set the page size from the format, embed the fonts, and open the file in a different viewer on a machine without those fonts before shipping. Done when: The PDF page size equals the format's sheet size exactly, asserted by reading the file. Fonts are embedded, verified by opening it on a machine without them. Fold lines are absent from the export and present on screen. A second format renders and exports with no code change beyond its data file. Do not build yet: Nothing after this. ### Out of scope (and why) - Movie and music metadata search, which needs an API key, a terms review and a cover art rights question you cannot answer on the user's behalf. - Accounts and saved designs, since the URL is the save file and a server would need paying for. - Die-cut sticker sheets aligned to vendor stock, because each vendor's sheet is its own measured template and a misalignment wastes the sheet. - Twenty two interface languages, which is a translation maintenance stream rather than a feature. ### README must contain - That millimetres are the only unit in data and the printed calibration square is the acceptance test - That the export must be printed at one hundred percent and never fit to page, with the tolerance stated - That the wrap needs a sheet larger than many home printers feed, so check the sheet size against your printer or use a print shop - The fold order for assembling the sleeve, with a photograph - That cover art never leaves the machine and is not in the share link - How to add a format as a data file measured from a real case ===== AGENTS.md ===== # Agent instructions · VHS Slipcover Maker indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A static single-page application, no server and no accounts, Every format stored as millimetres in a data file, pixels derived at render time, One vector drawing of the whole wrap, shared by the screen and the export, Bundled fonts whose licences permit embedding, fitted from real glyph metrics, PDF at the exact sheet size with fonts embedded and no scaling, The design serialised into the URL with defaults omitted. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Designing in pixels at an assumed screen density and then letting the print dialog fit the page produces a sleeve a few percent small, and a few percent is the difference between a slipcover and a piece of paper that will not close. Keep millimetres as the only unit in data, export at the exact sheet size, print at one hundred percent, and prove it with a measured square before trusting anything else. - Measuring text before the bundled font has loaded measures the fallback font, so the spine title fits on screen and overflows in the export, and it happens on the first load of the day when the cache is cold. Wait on the font loading promise before any measurement, and re-measure if a font arrives late. - Putting the full resolution photograph into the live drawing and redrawing it on every drag makes the editor stutter, and exporting from that same drawing runs the tab out of memory on a phone. Preview from a downscaled copy and only touch the original at export time. - Encoding the image into the URL produces an address that browsers and servers truncate, so the share link opens to a blank cover with no error. Keep the art out, keep the URL short by omitting defaults, and version the schema so a link from last year still opens. ===== BUILD_PLAN.md ===== # Build plan · VHS Slipcover Maker A browser tool that turns a title and a piece of cover art into a wrap that folds around a real VHS case, whose hard problems are print accuracy on printers that quietly rescale, type that fits a one-inch spine, and full-resolution cover art handled without a server. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A wrap that fits the case in your hand Printed at one hundred percent, the wrap folds around a real case. ### Steps 1. Write the format as millimetres Panels, spine, flaps, folds and the cut path, measured from the case rather than copied from a forum. Files: `src/formats/vhs.json`, `src/geometry.ts` 2. Print and measure a calibration square A hundred millimetres on paper is the acceptance test for everything after this. Files: `src/calibration.tsx` 3. Fold a real wrap around a real case Paper and a ruler find what a unit test cannot. Files: `docs/fold-order.md` ### Done when - [ ] The calibration square measures a hundred millimetres within tolerance - [ ] A wrap printed at one hundred percent fits the case - [ ] No pixel value exists in the format file - [ ] A panel width change renders without code changes ### Watch out - Designing in pixels at an assumed screen density and then letting the print dialog fit the page produces a sleeve a few percent small, and a few percent is the difference between a slipcover and a piece of paper that will not close. Keep millimetres as the only unit in data, export at the exact sheet size, print at one hundred percent, and prove it with a measured square before trusting anything else. ## Phase 2 · Type that fits a one-inch spine Any title fits the spine and the front, in the font it will print in. ### Steps 1. Fit from glyph metrics after fonts load The fallback font has different widths and the export is where you find out. Files: `src/text-fit.ts`, `src/fonts.ts` 2. State the wrapping and truncation rules A long title has to go somewhere and the cut line is not it. Files: `src/text-fit.ts`, `src/text-fit.test.ts` 3. Ship presets as data An era look is a font, a palette and a layout, not a branch in code. Files: `src/presets.json` ### Done when - [ ] A forty character title fits the spine - [ ] Overlong titles wrap or truncate by the stated rule - [ ] Text is measured only after fonts load - [ ] Presets change the look from data alone ### Watch out - Measuring text before the bundled font has loaded measures the fallback font, so the spine title fits on screen and overflows in the export, and it happens on the first load of the day when the cache is cold. Wait on the font loading promise before any measurement, and re-measure if a font arrives late. ## Phase 3 · Cover art without a server A large photograph is placed, cropped and kept at full resolution on the machine. ### Steps 1. Preview downscaled, export from the original Two copies, two jobs. The live drawing never holds thirty megapixels. Files: `src/art.ts` 2. Honour orientation metadata Phone photographs arrive sideways and the fix is one field. Files: `src/art.ts`, `src/art.test.ts` 3. Bound what you accept and say so A refusal with a stated limit beats a tab that dies. Files: `src/art.ts` ### Done when - [ ] A thirty megapixel image drags smoothly - [ ] Export uses original pixels - [ ] Sideways photographs render upright - [ ] No network activity carries the image ### Watch out - Putting the full resolution photograph into the live drawing and redrawing it on every drag makes the editor stutter, and exporting from that same drawing runs the tab out of memory on a phone. Preview from a downscaled copy and only touch the original at export time. ## Phase 4 · The URL is the save file A design reopens from its address on another machine, minus the art, and says so. ### Steps 1. Omit defaults and version the schema Short links survive, and old links keep opening. Files: `src/url-state.ts`, `src/url-state.test.ts` 2. Keep the art out and say so on restore An image in a URL is a truncated URL. Files: `src/url-state.ts` 3. Keep a fixture of every schema version The test that a two year old link still opens. Files: `tests/fixtures/urls-v1.txt` ### Done when - [ ] A design restores on a second machine minus the art - [ ] A fresh design produces a minimal URL - [ ] Old schema versions still open - [ ] Back and forward move between states ### Watch out - Encoding the image into the URL produces an address that browsers and servers truncate, so the share link opens to a blank cover with no error. Keep the art out, keep the URL short by omitting defaults, and version the schema so a link from last year still opens. ## Not in this build - Metadata search for films and music. - Accounts and saved designs. - Vendor-aligned sticker sheets. - Interface translation. ## After v1, if you want it - A print shop preset that exports with bleed and crop marks in the shop's stated format - A barcode generator that produces a scannable code rather than a decorative one - Spine-only labels for a shelf of tapes that already have sleeves ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. The format file loaded on first visit. Formats are data files in src/formats. VITE_DEFAULT_FORMAT=vhs # Required. Your decision. The calibration square must measure within this. VITE_PRINT_TOLERANCE_MM=0.5 # Required. PNG export resolution. The PDF is vector and does not use it. VITE_EXPORT_DPI=600 # Required. Above this an image is refused with the limit stated, rather than the tab dying. VITE_MAX_ART_PIXELS=40000000 # Required. Bumped when the URL format changes. Every previous version keeps a reader and a fixture. VITE_URL_SCHEMA_VERSION=1 # Required. Present as a constant. Fit to page is how a sleeve comes out five percent small. VITE_FIT_TO_PAGE=never
You are building a production product version of VHS Slipcover Maker. Create the following project files first, then implement the application by following them. Keep the files updated as decisions change. Do not collapse this into a single README or prompt. ===== PRODUCT.md ===== # VHS Slipcover Maker · product brief ## Problem The core loop, type a title and print a correctly sized VHS slipcover, is a one-session build. But the rich design possibilities, range of 30+ physical media types, 22+ languages supported, and instant generation of covers from a variety of media sources makes this a lengthy project to spin up on your own. That part is not coming out of one prompt. ## Product outcome A slipcover maker whose output fits the case in your hand, whose share link reopens the design a year later, and whose PDF prints the same on a printer you have never seen. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - a printer that takes 11 x 17 cardstock, or a print shop - TMDB API key only if you add movie-poster search ## Explicit non-goals for v1 - Metadata search for films and music. - Accounts and saved designs. - Vendor-aligned sticker sheets. - Interface translation. - 30+ measured dieline templates: cassette J-cards, CD inserts, vinyl jackets, Blu-ray and DVD wraps, MiniDisc - movie and music search that auto-fills cover art and metadata - designs that persist and share as a URL - die-cut label-sheet PDFs aligned to vendor sticker stock - 22 UI languages ## Success criteria - The calibration square is confirmed to measure a hundred millimetres within tolerance - A printed wrap is confirmed to fold around a real case - Text is confirmed measured only after fonts load - Old URL schema versions are confirmed to open - The PDF page size is confirmed to equal the sheet size with fonts embedded ===== BRIEF.md ===== # Build brief · VHS Slipcover Maker The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a printable slipcover maker where the millimetres of the real case are the source of truth to replace VHS Slipcover Maker. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: A static single-page application, no server and no accounts · nothing here needs a server and a server would need paying for - Geometry: Every format stored as millimetres in a data file, pixels derived at render time · the case does not care what your screen calls a pixel - Rendering: One vector drawing of the whole wrap, shared by the screen and the export · two renderers drift, and the print is the one that drifts - Type: Bundled fonts whose licences permit embedding, fitted from real glyph metrics · a title measured in a fallback font is a title that overflows the spine - Export: PDF at the exact sheet size with fonts embedded and no scaling · fit to page is how a perfect design comes out five percent small - State: The design serialised into the URL with defaults omitted · the URL is the save file and the share link ### Data model (create this before Phase 1) - formats(id, name, sheet_w_mm, sheet_h_mm, panels_json, folds_json, cut_path_json, bleed_mm, notes) · one file per physical format, measured from a real case - design(format_id, title, year, runtime, synopsis, palette_json, font_id, preset_id, art_transform_json, schema_version) - presets(id, name, font_id, palette_json, layout_json) · the era looks, as data rather than code - fonts(id, family, file, licence, embedding_permitted) - calibration(printer_label, expected_mm, measured_mm, correction_factor, at) · per printer, from a printed test square - art(handle, original_bytes, width, height, orientation) · in memory and local storage only, never in the URL - Every dimension in a format file is a millimetre and no pixel value appears in data, so a format measured against a real case renders correctly on any screen and prints correctly on any printer that is not rescaling. ### Phase 1 · A wrap that fits the case in your hand Build: Define the VHS wrap as a format file in millimetres: panels, spine, flaps, fold lines and the cut path, measured from the case you own. Render it as one vector drawing. Print a calibration sheet with a hundred millimetre square and a ruler, measure it, and only then print a wrap and fold it around the case. Trap: Designing in pixels at an assumed screen density and then letting the print dialog fit the page produces a sleeve a few percent small, and a few percent is the difference between a slipcover and a piece of paper that will not close. Keep millimetres as the only unit in data, export at the exact sheet size, print at one hundred percent, and prove it with a measured square before trusting anything else. Done when: A printed calibration square measures a hundred millimetres within a stated tolerance, and the tolerance is written in the README. A wrap printed at one hundred percent folds around the real case with the spine text centred, checked by hand. The format file contains no pixel value, asserted by a test that scans it. Changing a panel width in the file changes the render with no code change. Do not build yet: No cover art, presets, URL state or PDF export. ### Phase 2 · Type that fits a one-inch spine Build: Fit the title to the rotated spine and to the front panel from the glyph metrics of the bundled font, with a minimum size and stated wrapping rules. Wait for the font loading promise before measuring or rendering anything. Ship the era presets as data. Trap: Measuring text before the bundled font has loaded measures the fallback font, so the spine title fits on screen and overflows in the export, and it happens on the first load of the day when the cache is cold. Wait on the font loading promise before any measurement, and re-measure if a font arrives late. Done when: A forty character title fits the spine above the minimum size, and a title that cannot fit wraps or truncates by the stated rule rather than crossing the cut line. Text is measured only after the font loading promise resolves, tested by delaying the font. The three presets change font, palette and layout from data alone. Every bundled font's licence permits embedding, recorded in the fonts file. Do not build yet: No cover art, URL state or export yet. ### Phase 3 · Cover art without a server Build: Accept a local image, honour its orientation metadata, and place it in the front panel with zoom and pan. Preview from a downscaled copy and keep the original for export. Bound the size you accept and say what the bound is. Never upload it anywhere. Trap: Putting the full resolution photograph into the live drawing and redrawing it on every drag makes the editor stutter, and exporting from that same drawing runs the tab out of memory on a phone. Preview from a downscaled copy and only touch the original at export time. Done when: Dragging a thirty megapixel image stays smooth, measured. The export uses the original pixels rather than the preview, verified by comparing detail. A sideways photograph from a phone renders upright. The image never leaves the machine, verified by watching network activity while editing. Do not build yet: No URL state or export yet. ### Phase 4 · The URL is the save file Build: Serialise the design into the URL with every default omitted and a schema version. Restore on load. Keep the art out of the URL and tell the person on restore that the image must be chosen again. Keep old links opening when the schema changes. Trap: Encoding the image into the URL produces an address that browsers and servers truncate, so the share link opens to a blank cover with no error. Keep the art out, keep the URL short by omitting defaults, and version the schema so a link from last year still opens. Done when: A design restores from its URL on a second machine with everything except the art, and the missing art is stated rather than silent. Defaults are omitted, tested by checking that a fresh design produces a minimal URL. A link written under a previous schema version opens correctly, tested with a stored fixture. Back and forward move between design states. Do not build yet: No export yet. ### Phase 5 · Export that prints the same everywhere Build: Export a PDF at the exact sheet size with fonts embedded, the cut line drawn and the fold lines omitted, plus a PNG at a stated resolution. Use native vector text rather than HTML embedded in the drawing. Add a second physical format as a data file to prove formats are data. State the sheet size each format needs beside the export button. Trap: A PDF whose page is the wrong size, or whose fonts are not embedded, prints wrong on every machine but yours, and the person will blame their printer. Set the page size from the format, embed the fonts, and open the file in a different viewer on a machine without those fonts before shipping. Done when: The PDF page size equals the format's sheet size exactly, asserted by reading the file. Fonts are embedded, verified by opening it on a machine without them. Fold lines are absent from the export and present on screen. A second format renders and exports with no code change beyond its data file. Do not build yet: Nothing after this. ### Out of scope (and why) - Movie and music metadata search, which needs an API key, a terms review and a cover art rights question you cannot answer on the user's behalf. - Accounts and saved designs, since the URL is the save file and a server would need paying for. - Die-cut sticker sheets aligned to vendor stock, because each vendor's sheet is its own measured template and a misalignment wastes the sheet. - Twenty two interface languages, which is a translation maintenance stream rather than a feature. ### README must contain - That millimetres are the only unit in data and the printed calibration square is the acceptance test - That the export must be printed at one hundred percent and never fit to page, with the tolerance stated - That the wrap needs a sheet larger than many home printers feed, so check the sheet size against your printer or use a print shop - The fold order for assembling the sleeve, with a photograph - That cover art never leaves the machine and is not in the share link - How to add a format as a data file measured from a real case ===== ARCHITECTURE.md ===== # Architecture · VHS Slipcover Maker ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | A static single-page application, no server and no accounts | nothing here needs a server and a server would need paying for | | Geometry | Every format stored as millimetres in a data file, pixels derived at render time | the case does not care what your screen calls a pixel | | Rendering | One vector drawing of the whole wrap, shared by the screen and the export | two renderers drift, and the print is the one that drifts | | Type | Bundled fonts whose licences permit embedding, fitted from real glyph metrics | a title measured in a fallback font is a title that overflows the spine | | Export | PDF at the exact sheet size with fonts embedded and no scaling | fit to page is how a perfect design comes out five percent small | | State | The design serialised into the URL with defaults omitted | the URL is the save file and the share link | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Geometry | Formats as millimetres, pixels derived. | Any renderer, provided no pixel value enters a format file. | | Type | Fitting from glyph metrics after fonts load. | Any text engine, provided measurement waits for the font. | | Art | Local images, previewed downscaled, exported from the original. | Any image pipeline, provided nothing is uploaded. | | State | Versioned URL serialisation with defaults omitted. | Any encoding, provided old links keep opening. | | Export | Exact page size, embedded fonts, cut line only. | Not swappable for fit to page, which is the failure this exists to prevent. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `VITE_DEFAULT_FORMAT` · required · The format file loaded on first visit. Formats are data files in src/formats. - `VITE_PRINT_TOLERANCE_MM` · required · Your decision. The calibration square must measure within this. - `VITE_EXPORT_DPI` · required · PNG export resolution. The PDF is vector and does not use it. - `VITE_MAX_ART_PIXELS` · required · Above this an image is refused with the limit stated, rather than the tab dying. - `VITE_URL_SCHEMA_VERSION` · required · Bumped when the URL format changes. Every previous version keeps a reader and a fixture. - `VITE_FIT_TO_PAGE` · required · Present as a constant. Fit to page is how a sleeve comes out five percent small. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it. ===== AGENTS.md ===== # Agent instructions · VHS Slipcover Maker product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: A static single-page application, no server and no accounts, Every format stored as millimetres in a data file, pixels derived at render time, One vector drawing of the whole wrap, shared by the screen and the export, Bundled fonts whose licences permit embedding, fitted from real glyph metrics, PDF at the exact sheet size with fonts embedded and no scaling, The design serialised into the URL with defaults omitted. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Designing in pixels at an assumed screen density and then letting the print dialog fit the page produces a sleeve a few percent small, and a few percent is the difference between a slipcover and a piece of paper that will not close. Keep millimetres as the only unit in data, export at the exact sheet size, print at one hundred percent, and prove it with a measured square before trusting anything else. - Measuring text before the bundled font has loaded measures the fallback font, so the spine title fits on screen and overflows in the export, and it happens on the first load of the day when the cache is cold. Wait on the font loading promise before any measurement, and re-measure if a font arrives late. - Putting the full resolution photograph into the live drawing and redrawing it on every drag makes the editor stutter, and exporting from that same drawing runs the tab out of memory on a phone. Preview from a downscaled copy and only touch the original at export time. - Encoding the image into the URL produces an address that browsers and servers truncate, so the share link opens to a blank cover with no error. Keep the art out, keep the URL short by omitting defaults, and version the schema so a link from last year still opens. - A PDF whose page is the wrong size, or whose fonts are not embedded, prints wrong on every machine but yours, and the person will blame their printer. Set the page size from the format, embed the fonts, and open the file in a different viewer on a machine without those fonts before shipping. ===== MILESTONES.md ===== # Delivery milestones · VHS Slipcover Maker Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A wrap that fits the case in your hand Printed at one hundred percent, the wrap folds around a real case. ### Steps 1. Write the format as millimetres Panels, spine, flaps, folds and the cut path, measured from the case rather than copied from a forum. Files: `src/formats/vhs.json`, `src/geometry.ts` 2. Print and measure a calibration square A hundred millimetres on paper is the acceptance test for everything after this. Files: `src/calibration.tsx` 3. Fold a real wrap around a real case Paper and a ruler find what a unit test cannot. Files: `docs/fold-order.md` ### Done when - [ ] The calibration square measures a hundred millimetres within tolerance - [ ] A wrap printed at one hundred percent fits the case - [ ] No pixel value exists in the format file - [ ] A panel width change renders without code changes ### Watch out - Designing in pixels at an assumed screen density and then letting the print dialog fit the page produces a sleeve a few percent small, and a few percent is the difference between a slipcover and a piece of paper that will not close. Keep millimetres as the only unit in data, export at the exact sheet size, print at one hundred percent, and prove it with a measured square before trusting anything else. ## M2 · Type that fits a one-inch spine Any title fits the spine and the front, in the font it will print in. ### Steps 1. Fit from glyph metrics after fonts load The fallback font has different widths and the export is where you find out. Files: `src/text-fit.ts`, `src/fonts.ts` 2. State the wrapping and truncation rules A long title has to go somewhere and the cut line is not it. Files: `src/text-fit.ts`, `src/text-fit.test.ts` 3. Ship presets as data An era look is a font, a palette and a layout, not a branch in code. Files: `src/presets.json` ### Done when - [ ] A forty character title fits the spine - [ ] Overlong titles wrap or truncate by the stated rule - [ ] Text is measured only after fonts load - [ ] Presets change the look from data alone ### Watch out - Measuring text before the bundled font has loaded measures the fallback font, so the spine title fits on screen and overflows in the export, and it happens on the first load of the day when the cache is cold. Wait on the font loading promise before any measurement, and re-measure if a font arrives late. ## M3 · Cover art without a server A large photograph is placed, cropped and kept at full resolution on the machine. ### Steps 1. Preview downscaled, export from the original Two copies, two jobs. The live drawing never holds thirty megapixels. Files: `src/art.ts` 2. Honour orientation metadata Phone photographs arrive sideways and the fix is one field. Files: `src/art.ts`, `src/art.test.ts` 3. Bound what you accept and say so A refusal with a stated limit beats a tab that dies. Files: `src/art.ts` ### Done when - [ ] A thirty megapixel image drags smoothly - [ ] Export uses original pixels - [ ] Sideways photographs render upright - [ ] No network activity carries the image ### Watch out - Putting the full resolution photograph into the live drawing and redrawing it on every drag makes the editor stutter, and exporting from that same drawing runs the tab out of memory on a phone. Preview from a downscaled copy and only touch the original at export time. ## M4 · The URL is the save file A design reopens from its address on another machine, minus the art, and says so. ### Steps 1. Omit defaults and version the schema Short links survive, and old links keep opening. Files: `src/url-state.ts`, `src/url-state.test.ts` 2. Keep the art out and say so on restore An image in a URL is a truncated URL. Files: `src/url-state.ts` 3. Keep a fixture of every schema version The test that a two year old link still opens. Files: `tests/fixtures/urls-v1.txt` ### Done when - [ ] A design restores on a second machine minus the art - [ ] A fresh design produces a minimal URL - [ ] Old schema versions still open - [ ] Back and forward move between states ### Watch out - Encoding the image into the URL produces an address that browsers and servers truncate, so the share link opens to a blank cover with no error. Keep the art out, keep the URL short by omitting defaults, and version the schema so a link from last year still opens. ## M5 · Export that prints the same everywhere (production only) The PDF prints identically on a machine without your fonts, and a second format proves the system. ### Steps 1. Set the page size from the format and embed fonts Then open it somewhere that has neither your fonts nor your browser. Files: `src/export-pdf.ts`, `tests/pdf-page-size.test.ts` 2. Draw the cut line only Fold lines on screen, never on paper. Files: `src/export-pdf.ts` 3. Add a second format as data If it needs code, formats are not data yet. Files: `src/formats/cassette-jcard.json` ### Done when - [ ] The PDF page size equals the sheet size - [ ] Fonts are embedded and render elsewhere - [ ] Fold lines are absent from exports - [ ] A second format needs only a data file ### Watch out - A PDF whose page is the wrong size, or whose fonts are not embedded, prints wrong on every machine but yours, and the person will blame their printer. Set the page size from the format, embed the fonts, and open the file in a different viewer on a machine without those fonts before shipping. ===== OPERATIONS.md ===== # Operations · VHS Slipcover Maker ## Backup There is no server and nothing to back up on one. The designs live in URLs people keep, the art is their own file, and the format files are the asset worth protecting, which is what version control is for. ## Restore Restore means opening an old link, so every URL schema version keeps its reader and a fixture in the test suite. A link that no longer opens is a lost design, and the person cannot tell you which version wrote it. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring For a static tool the only measurement that matters is whether printed sleeves fit, so the README asks people to measure the calibration square and report the printer and the figure, which is how a format error and a printer scaling error are told apart. ## Incident checklist A sleeve that does not fit is the incident, and it is either a printer that scaled or a format measured wrong. The calibration square separates the two in one print, which is why it exists. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] The calibration square is confirmed to measure a hundred millimetres within tolerance - [ ] A printed wrap is confirmed to fold around a real case - [ ] Text is confirmed measured only after fonts load - [ ] Old URL schema versions are confirmed to open - [ ] The PDF page size is confirmed to equal the sheet size with fonts embedded ## Launch constraint Do not market omitted VHS Slipcover Maker capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered. ===== .env.example ===== # Copy to .env and fill in. Never commit .env; this file documents it. # Required. The format file loaded on first visit. Formats are data files in src/formats. VITE_DEFAULT_FORMAT=vhs # Required. Your decision. The calibration square must measure within this. VITE_PRINT_TOLERANCE_MM=0.5 # Required. PNG export resolution. The PDF is vector and does not use it. VITE_EXPORT_DPI=600 # Required. Above this an image is refused with the limit stated, rather than the tab dying. VITE_MAX_ART_PIXELS=40000000 # Required. Bumped when the URL format changes. Every previous version keeps a reader and a fixture. VITE_URL_SCHEMA_VERSION=1 # Required. Present as a constant. Fit to page is how a sleeve comes out five percent small. VITE_FIT_TO_PAGE=never
# VHS Slipcover Maker · indie build A browser tool that turns a title and a piece of cover art into a wrap that folds around a real VHS case, whose hard problems are print accuracy on printers that quietly rescale, type that fits a one-inch spine, and full-resolution cover art handled without a server. Estimated effort: **weekend**. Work `BUILD_PLAN.md` top to bottom · every phase ends in a check that has to pass before the next one starts. ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | A static single-page application, no server and no accounts | nothing here needs a server and a server would need paying for | | Geometry | Every format stored as millimetres in a data file, pixels derived at render time | the case does not care what your screen calls a pixel | | Rendering | One vector drawing of the whole wrap, shared by the screen and the export | two renderers drift, and the print is the one that drifts | | Type | Bundled fonts whose licences permit embedding, fitted from real glyph metrics | a title measured in a fallback font is a title that overflows the spine | | Export | PDF at the exact sheet size with fonts embedded and no scaling | fit to page is how a perfect design comes out five percent small | | State | The design serialised into the URL with defaults omitted | the URL is the save file and the share link | ## Before you start Have every one of these ready. The plan assumes them from step one. - [ ] **Node.js 22 or newer** · free - Why: Builds the single-page application and runs the test suite. - Get it: nodejs.org, or nvm install 22. - Verify: node --version prints v22 or higher - [ ] **A real VHS case and a ruler** · free - Why: Every dimension in the format file is measured from this, and a number copied from somewhere else is a number you will find out about after printing. - Get it: Any tape case with its original sleeve, and a steel ruler marked in millimetres. - Verify: Panel, spine and flap measurements are written into the format file - [ ] **A printer that feeds the sheet size, or a print shop** · free, or a few pounds per sheet at a shop - Why: The wrap is wider than the paper most home printers take, and the calibration square only proves the printer that will actually produce the sleeve. - Get it: Check the largest sheet your printer feeds against the format's sheet size; otherwise a print shop that will print at one hundred percent on cardstock. - Verify: A calibration sheet has been printed and measured on it - [ ] **Fonts whose licences permit embedding** · free - Why: The PDF embeds the fonts so it prints identically elsewhere, and a font licence that forbids embedding makes that export unlawful to share. - Get it: Open licence fonts with the licence file kept beside each, recorded in the fonts file. - Verify: Every bundled font has a licence entry with embedding permitted - [ ] **Your print tolerance and the overlong title rule** · free - Why: Both are decisions the tests assert, and choosing them after seeing results means choosing whatever passes. - Get it: Decide how many millimetres of error the calibration square may show, and whether an overlong title wraps or truncates. - Verify: Both are written in the README and encoded in the tests ## Quick start Follow Phase 1 in `BUILD_PLAN.md`; it creates the project. Then copy `.env.example` to `.env` and fill in the values it documents. ## Honest limits This build deliberately does not replace: - Metadata search for films and music. - Accounts and saved designs. - Vendor-aligned sticker sheets. - Interface translation. - 30+ measured dieline templates: cassette J-cards, CD inserts, vinyl jackets, Blu-ray and DVD wraps, MiniDisc - movie and music search that auto-fills cover art and metadata - designs that persist and share as a URL - die-cut label-sheet PDFs aligned to vendor sticker stock - 22 UI languages If one of those is essential to you, that is the reason to keep paying for VHS Slipcover Maker, and the README should say so rather than pretend.
# Build brief · VHS Slipcover Maker The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a printable slipcover maker where the millimetres of the real case are the source of truth to replace VHS Slipcover Maker. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: A static single-page application, no server and no accounts · nothing here needs a server and a server would need paying for - Geometry: Every format stored as millimetres in a data file, pixels derived at render time · the case does not care what your screen calls a pixel - Rendering: One vector drawing of the whole wrap, shared by the screen and the export · two renderers drift, and the print is the one that drifts - Type: Bundled fonts whose licences permit embedding, fitted from real glyph metrics · a title measured in a fallback font is a title that overflows the spine - Export: PDF at the exact sheet size with fonts embedded and no scaling · fit to page is how a perfect design comes out five percent small - State: The design serialised into the URL with defaults omitted · the URL is the save file and the share link ### Data model (create this before Phase 1) - formats(id, name, sheet_w_mm, sheet_h_mm, panels_json, folds_json, cut_path_json, bleed_mm, notes) · one file per physical format, measured from a real case - design(format_id, title, year, runtime, synopsis, palette_json, font_id, preset_id, art_transform_json, schema_version) - presets(id, name, font_id, palette_json, layout_json) · the era looks, as data rather than code - fonts(id, family, file, licence, embedding_permitted) - calibration(printer_label, expected_mm, measured_mm, correction_factor, at) · per printer, from a printed test square - art(handle, original_bytes, width, height, orientation) · in memory and local storage only, never in the URL - Every dimension in a format file is a millimetre and no pixel value appears in data, so a format measured against a real case renders correctly on any screen and prints correctly on any printer that is not rescaling. ### Phase 1 · A wrap that fits the case in your hand Build: Define the VHS wrap as a format file in millimetres: panels, spine, flaps, fold lines and the cut path, measured from the case you own. Render it as one vector drawing. Print a calibration sheet with a hundred millimetre square and a ruler, measure it, and only then print a wrap and fold it around the case. Trap: Designing in pixels at an assumed screen density and then letting the print dialog fit the page produces a sleeve a few percent small, and a few percent is the difference between a slipcover and a piece of paper that will not close. Keep millimetres as the only unit in data, export at the exact sheet size, print at one hundred percent, and prove it with a measured square before trusting anything else. Done when: A printed calibration square measures a hundred millimetres within a stated tolerance, and the tolerance is written in the README. A wrap printed at one hundred percent folds around the real case with the spine text centred, checked by hand. The format file contains no pixel value, asserted by a test that scans it. Changing a panel width in the file changes the render with no code change. Do not build yet: No cover art, presets, URL state or PDF export. ### Phase 2 · Type that fits a one-inch spine Build: Fit the title to the rotated spine and to the front panel from the glyph metrics of the bundled font, with a minimum size and stated wrapping rules. Wait for the font loading promise before measuring or rendering anything. Ship the era presets as data. Trap: Measuring text before the bundled font has loaded measures the fallback font, so the spine title fits on screen and overflows in the export, and it happens on the first load of the day when the cache is cold. Wait on the font loading promise before any measurement, and re-measure if a font arrives late. Done when: A forty character title fits the spine above the minimum size, and a title that cannot fit wraps or truncates by the stated rule rather than crossing the cut line. Text is measured only after the font loading promise resolves, tested by delaying the font. The three presets change font, palette and layout from data alone. Every bundled font's licence permits embedding, recorded in the fonts file. Do not build yet: No cover art, URL state or export yet. ### Phase 3 · Cover art without a server Build: Accept a local image, honour its orientation metadata, and place it in the front panel with zoom and pan. Preview from a downscaled copy and keep the original for export. Bound the size you accept and say what the bound is. Never upload it anywhere. Trap: Putting the full resolution photograph into the live drawing and redrawing it on every drag makes the editor stutter, and exporting from that same drawing runs the tab out of memory on a phone. Preview from a downscaled copy and only touch the original at export time. Done when: Dragging a thirty megapixel image stays smooth, measured. The export uses the original pixels rather than the preview, verified by comparing detail. A sideways photograph from a phone renders upright. The image never leaves the machine, verified by watching network activity while editing. Do not build yet: No URL state or export yet. ### Phase 4 · The URL is the save file Build: Serialise the design into the URL with every default omitted and a schema version. Restore on load. Keep the art out of the URL and tell the person on restore that the image must be chosen again. Keep old links opening when the schema changes. Trap: Encoding the image into the URL produces an address that browsers and servers truncate, so the share link opens to a blank cover with no error. Keep the art out, keep the URL short by omitting defaults, and version the schema so a link from last year still opens. Done when: A design restores from its URL on a second machine with everything except the art, and the missing art is stated rather than silent. Defaults are omitted, tested by checking that a fresh design produces a minimal URL. A link written under a previous schema version opens correctly, tested with a stored fixture. Back and forward move between design states. Do not build yet: No export yet. ### Phase 5 · Export that prints the same everywhere Build: Export a PDF at the exact sheet size with fonts embedded, the cut line drawn and the fold lines omitted, plus a PNG at a stated resolution. Use native vector text rather than HTML embedded in the drawing. Add a second physical format as a data file to prove formats are data. State the sheet size each format needs beside the export button. Trap: A PDF whose page is the wrong size, or whose fonts are not embedded, prints wrong on every machine but yours, and the person will blame their printer. Set the page size from the format, embed the fonts, and open the file in a different viewer on a machine without those fonts before shipping. Done when: The PDF page size equals the format's sheet size exactly, asserted by reading the file. Fonts are embedded, verified by opening it on a machine without them. Fold lines are absent from the export and present on screen. A second format renders and exports with no code change beyond its data file. Do not build yet: Nothing after this. ### Out of scope (and why) - Movie and music metadata search, which needs an API key, a terms review and a cover art rights question you cannot answer on the user's behalf. - Accounts and saved designs, since the URL is the save file and a server would need paying for. - Die-cut sticker sheets aligned to vendor stock, because each vendor's sheet is its own measured template and a misalignment wastes the sheet. - Twenty two interface languages, which is a translation maintenance stream rather than a feature. ### README must contain - That millimetres are the only unit in data and the printed calibration square is the acceptance test - That the export must be printed at one hundred percent and never fit to page, with the tolerance stated - That the wrap needs a sheet larger than many home printers feed, so check the sheet size against your printer or use a print shop - The fold order for assembling the sleeve, with a photograph - That cover art never leaves the machine and is not in the share link - How to add a format as a data file measured from a real case
# Agent instructions · VHS Slipcover Maker indie build - Read `README.md` and `BUILD_PLAN.md` before writing code. The stack is fixed: A static single-page application, no server and no accounts, Every format stored as millimetres in a data file, pixels derived at render time, One vector drawing of the whole wrap, shared by the screen and the export, Bundled fonts whose licences permit embedding, fitted from real glyph metrics, PDF at the exact sheet size with fonts embedded and no scaling, The design serialised into the URL with defaults omitted. Do not substitute. - Work one phase at a time, in order. Do not start a phase until every "Done when" item of the previous one passes. - Prefer the fewest moving parts that satisfy the step. No frameworks, services or dependencies the plan does not name. - Secrets live in `.env`, never in source or logs. Keep `.env.example` current when a variable is introduced. - Do not invent cryptography, security guarantees, APIs or compliance claims. - Add a focused test for every destructive, security-sensitive or data-loss path the plan names. - Run the project checks before declaring a phase complete, and record any deliberate shortcut in the README under "Tradeoffs". ## Known traps - Designing in pixels at an assumed screen density and then letting the print dialog fit the page produces a sleeve a few percent small, and a few percent is the difference between a slipcover and a piece of paper that will not close. Keep millimetres as the only unit in data, export at the exact sheet size, print at one hundred percent, and prove it with a measured square before trusting anything else. - Measuring text before the bundled font has loaded measures the fallback font, so the spine title fits on screen and overflows in the export, and it happens on the first load of the day when the cache is cold. Wait on the font loading promise before any measurement, and re-measure if a font arrives late. - Putting the full resolution photograph into the live drawing and redrawing it on every drag makes the editor stutter, and exporting from that same drawing runs the tab out of memory on a phone. Preview from a downscaled copy and only touch the original at export time. - Encoding the image into the URL produces an address that browsers and servers truncate, so the share link opens to a blank cover with no error. Keep the art out, keep the URL short by omitting defaults, and version the schema so a link from last year still opens.
# Build plan · VHS Slipcover Maker A browser tool that turns a title and a piece of cover art into a wrap that folds around a real VHS case, whose hard problems are print accuracy on printers that quietly rescale, type that fits a one-inch spine, and full-resolution cover art handled without a server. Phases are in dependency order. Each ends in a "Done when" list; treat an unticked item as a blocker, not a note. ## Phase 1 · A wrap that fits the case in your hand Printed at one hundred percent, the wrap folds around a real case. ### Steps 1. Write the format as millimetres Panels, spine, flaps, folds and the cut path, measured from the case rather than copied from a forum. Files: `src/formats/vhs.json`, `src/geometry.ts` 2. Print and measure a calibration square A hundred millimetres on paper is the acceptance test for everything after this. Files: `src/calibration.tsx` 3. Fold a real wrap around a real case Paper and a ruler find what a unit test cannot. Files: `docs/fold-order.md` ### Done when - [ ] The calibration square measures a hundred millimetres within tolerance - [ ] A wrap printed at one hundred percent fits the case - [ ] No pixel value exists in the format file - [ ] A panel width change renders without code changes ### Watch out - Designing in pixels at an assumed screen density and then letting the print dialog fit the page produces a sleeve a few percent small, and a few percent is the difference between a slipcover and a piece of paper that will not close. Keep millimetres as the only unit in data, export at the exact sheet size, print at one hundred percent, and prove it with a measured square before trusting anything else. ## Phase 2 · Type that fits a one-inch spine Any title fits the spine and the front, in the font it will print in. ### Steps 1. Fit from glyph metrics after fonts load The fallback font has different widths and the export is where you find out. Files: `src/text-fit.ts`, `src/fonts.ts` 2. State the wrapping and truncation rules A long title has to go somewhere and the cut line is not it. Files: `src/text-fit.ts`, `src/text-fit.test.ts` 3. Ship presets as data An era look is a font, a palette and a layout, not a branch in code. Files: `src/presets.json` ### Done when - [ ] A forty character title fits the spine - [ ] Overlong titles wrap or truncate by the stated rule - [ ] Text is measured only after fonts load - [ ] Presets change the look from data alone ### Watch out - Measuring text before the bundled font has loaded measures the fallback font, so the spine title fits on screen and overflows in the export, and it happens on the first load of the day when the cache is cold. Wait on the font loading promise before any measurement, and re-measure if a font arrives late. ## Phase 3 · Cover art without a server A large photograph is placed, cropped and kept at full resolution on the machine. ### Steps 1. Preview downscaled, export from the original Two copies, two jobs. The live drawing never holds thirty megapixels. Files: `src/art.ts` 2. Honour orientation metadata Phone photographs arrive sideways and the fix is one field. Files: `src/art.ts`, `src/art.test.ts` 3. Bound what you accept and say so A refusal with a stated limit beats a tab that dies. Files: `src/art.ts` ### Done when - [ ] A thirty megapixel image drags smoothly - [ ] Export uses original pixels - [ ] Sideways photographs render upright - [ ] No network activity carries the image ### Watch out - Putting the full resolution photograph into the live drawing and redrawing it on every drag makes the editor stutter, and exporting from that same drawing runs the tab out of memory on a phone. Preview from a downscaled copy and only touch the original at export time. ## Phase 4 · The URL is the save file A design reopens from its address on another machine, minus the art, and says so. ### Steps 1. Omit defaults and version the schema Short links survive, and old links keep opening. Files: `src/url-state.ts`, `src/url-state.test.ts` 2. Keep the art out and say so on restore An image in a URL is a truncated URL. Files: `src/url-state.ts` 3. Keep a fixture of every schema version The test that a two year old link still opens. Files: `tests/fixtures/urls-v1.txt` ### Done when - [ ] A design restores on a second machine minus the art - [ ] A fresh design produces a minimal URL - [ ] Old schema versions still open - [ ] Back and forward move between states ### Watch out - Encoding the image into the URL produces an address that browsers and servers truncate, so the share link opens to a blank cover with no error. Keep the art out, keep the URL short by omitting defaults, and version the schema so a link from last year still opens. ## Not in this build - Metadata search for films and music. - Accounts and saved designs. - Vendor-aligned sticker sheets. - Interface translation. ## After v1, if you want it - A print shop preset that exports with bleed and crop marks in the shop's stated format - A barcode generator that produces a scannable code rather than a decorative one - Spine-only labels for a shelf of tapes that already have sleeves
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. The format file loaded on first visit. Formats are data files in src/formats. VITE_DEFAULT_FORMAT=vhs # Required. Your decision. The calibration square must measure within this. VITE_PRINT_TOLERANCE_MM=0.5 # Required. PNG export resolution. The PDF is vector and does not use it. VITE_EXPORT_DPI=600 # Required. Above this an image is refused with the limit stated, rather than the tab dying. VITE_MAX_ART_PIXELS=40000000 # Required. Bumped when the URL format changes. Every previous version keeps a reader and a fixture. VITE_URL_SCHEMA_VERSION=1 # Required. Present as a constant. Fit to page is how a sleeve comes out five percent small. VITE_FIT_TO_PAGE=never
# VHS Slipcover Maker · product brief ## Problem The core loop, type a title and print a correctly sized VHS slipcover, is a one-session build. But the rich design possibilities, range of 30+ physical media types, 22+ languages supported, and instant generation of covers from a variety of media sources makes this a lengthy project to spin up on your own. That part is not coming out of one prompt. ## Product outcome A slipcover maker whose output fits the case in your hand, whose share link reopens the design a year later, and whose PDF prints the same on a printer you have never seen. ## Target user A builder who needs a maintainable product foundation, not a one-off demo. ## Required capabilities - a printer that takes 11 x 17 cardstock, or a print shop - TMDB API key only if you add movie-poster search ## Explicit non-goals for v1 - Metadata search for films and music. - Accounts and saved designs. - Vendor-aligned sticker sheets. - Interface translation. - 30+ measured dieline templates: cassette J-cards, CD inserts, vinyl jackets, Blu-ray and DVD wraps, MiniDisc - movie and music search that auto-fills cover art and metadata - designs that persist and share as a URL - die-cut label-sheet PDFs aligned to vendor sticker stock - 22 UI languages ## Success criteria - The calibration square is confirmed to measure a hundred millimetres within tolerance - A printed wrap is confirmed to fold around a real case - Text is confirmed measured only after fonts load - Old URL schema versions are confirmed to open - The PDF page size is confirmed to equal the sheet size with fonts embedded
# Build brief · VHS Slipcover Maker The one-shot brief this plan expands. `BUILD_PLAN.md` (or `MILESTONES.md`) is the same sequence broken into steps and checks; where the two disagree, the plan wins. Build me a printable slipcover maker where the millimetres of the real case are the source of truth to replace VHS Slipcover Maker. Build it in phases, in the order below. Do not write the whole thing in one pass. Finish a phase, run its "Done when" check, fix what fails, and only then start the next phase. ### Stack (fixed, do not substitute) - Runtime: A static single-page application, no server and no accounts · nothing here needs a server and a server would need paying for - Geometry: Every format stored as millimetres in a data file, pixels derived at render time · the case does not care what your screen calls a pixel - Rendering: One vector drawing of the whole wrap, shared by the screen and the export · two renderers drift, and the print is the one that drifts - Type: Bundled fonts whose licences permit embedding, fitted from real glyph metrics · a title measured in a fallback font is a title that overflows the spine - Export: PDF at the exact sheet size with fonts embedded and no scaling · fit to page is how a perfect design comes out five percent small - State: The design serialised into the URL with defaults omitted · the URL is the save file and the share link ### Data model (create this before Phase 1) - formats(id, name, sheet_w_mm, sheet_h_mm, panels_json, folds_json, cut_path_json, bleed_mm, notes) · one file per physical format, measured from a real case - design(format_id, title, year, runtime, synopsis, palette_json, font_id, preset_id, art_transform_json, schema_version) - presets(id, name, font_id, palette_json, layout_json) · the era looks, as data rather than code - fonts(id, family, file, licence, embedding_permitted) - calibration(printer_label, expected_mm, measured_mm, correction_factor, at) · per printer, from a printed test square - art(handle, original_bytes, width, height, orientation) · in memory and local storage only, never in the URL - Every dimension in a format file is a millimetre and no pixel value appears in data, so a format measured against a real case renders correctly on any screen and prints correctly on any printer that is not rescaling. ### Phase 1 · A wrap that fits the case in your hand Build: Define the VHS wrap as a format file in millimetres: panels, spine, flaps, fold lines and the cut path, measured from the case you own. Render it as one vector drawing. Print a calibration sheet with a hundred millimetre square and a ruler, measure it, and only then print a wrap and fold it around the case. Trap: Designing in pixels at an assumed screen density and then letting the print dialog fit the page produces a sleeve a few percent small, and a few percent is the difference between a slipcover and a piece of paper that will not close. Keep millimetres as the only unit in data, export at the exact sheet size, print at one hundred percent, and prove it with a measured square before trusting anything else. Done when: A printed calibration square measures a hundred millimetres within a stated tolerance, and the tolerance is written in the README. A wrap printed at one hundred percent folds around the real case with the spine text centred, checked by hand. The format file contains no pixel value, asserted by a test that scans it. Changing a panel width in the file changes the render with no code change. Do not build yet: No cover art, presets, URL state or PDF export. ### Phase 2 · Type that fits a one-inch spine Build: Fit the title to the rotated spine and to the front panel from the glyph metrics of the bundled font, with a minimum size and stated wrapping rules. Wait for the font loading promise before measuring or rendering anything. Ship the era presets as data. Trap: Measuring text before the bundled font has loaded measures the fallback font, so the spine title fits on screen and overflows in the export, and it happens on the first load of the day when the cache is cold. Wait on the font loading promise before any measurement, and re-measure if a font arrives late. Done when: A forty character title fits the spine above the minimum size, and a title that cannot fit wraps or truncates by the stated rule rather than crossing the cut line. Text is measured only after the font loading promise resolves, tested by delaying the font. The three presets change font, palette and layout from data alone. Every bundled font's licence permits embedding, recorded in the fonts file. Do not build yet: No cover art, URL state or export yet. ### Phase 3 · Cover art without a server Build: Accept a local image, honour its orientation metadata, and place it in the front panel with zoom and pan. Preview from a downscaled copy and keep the original for export. Bound the size you accept and say what the bound is. Never upload it anywhere. Trap: Putting the full resolution photograph into the live drawing and redrawing it on every drag makes the editor stutter, and exporting from that same drawing runs the tab out of memory on a phone. Preview from a downscaled copy and only touch the original at export time. Done when: Dragging a thirty megapixel image stays smooth, measured. The export uses the original pixels rather than the preview, verified by comparing detail. A sideways photograph from a phone renders upright. The image never leaves the machine, verified by watching network activity while editing. Do not build yet: No URL state or export yet. ### Phase 4 · The URL is the save file Build: Serialise the design into the URL with every default omitted and a schema version. Restore on load. Keep the art out of the URL and tell the person on restore that the image must be chosen again. Keep old links opening when the schema changes. Trap: Encoding the image into the URL produces an address that browsers and servers truncate, so the share link opens to a blank cover with no error. Keep the art out, keep the URL short by omitting defaults, and version the schema so a link from last year still opens. Done when: A design restores from its URL on a second machine with everything except the art, and the missing art is stated rather than silent. Defaults are omitted, tested by checking that a fresh design produces a minimal URL. A link written under a previous schema version opens correctly, tested with a stored fixture. Back and forward move between design states. Do not build yet: No export yet. ### Phase 5 · Export that prints the same everywhere Build: Export a PDF at the exact sheet size with fonts embedded, the cut line drawn and the fold lines omitted, plus a PNG at a stated resolution. Use native vector text rather than HTML embedded in the drawing. Add a second physical format as a data file to prove formats are data. State the sheet size each format needs beside the export button. Trap: A PDF whose page is the wrong size, or whose fonts are not embedded, prints wrong on every machine but yours, and the person will blame their printer. Set the page size from the format, embed the fonts, and open the file in a different viewer on a machine without those fonts before shipping. Done when: The PDF page size equals the format's sheet size exactly, asserted by reading the file. Fonts are embedded, verified by opening it on a machine without them. Fold lines are absent from the export and present on screen. A second format renders and exports with no code change beyond its data file. Do not build yet: Nothing after this. ### Out of scope (and why) - Movie and music metadata search, which needs an API key, a terms review and a cover art rights question you cannot answer on the user's behalf. - Accounts and saved designs, since the URL is the save file and a server would need paying for. - Die-cut sticker sheets aligned to vendor stock, because each vendor's sheet is its own measured template and a misalignment wastes the sheet. - Twenty two interface languages, which is a translation maintenance stream rather than a feature. ### README must contain - That millimetres are the only unit in data and the printed calibration square is the acceptance test - That the export must be printed at one hundred percent and never fit to page, with the tolerance stated - That the wrap needs a sheet larger than many home printers feed, so check the sheet size against your printer or use a print shop - The fold order for assembling the sleeve, with a photograph - That cover art never leaves the machine and is not in the share link - How to add a format as a data file measured from a real case
# Architecture · VHS Slipcover Maker ## Stack | Part | Choice | Why | | --- | --- | --- | | Runtime | A static single-page application, no server and no accounts | nothing here needs a server and a server would need paying for | | Geometry | Every format stored as millimetres in a data file, pixels derived at render time | the case does not care what your screen calls a pixel | | Rendering | One vector drawing of the whole wrap, shared by the screen and the export | two renderers drift, and the print is the one that drifts | | Type | Bundled fonts whose licences permit embedding, fitted from real glyph metrics | a title measured in a fallback font is a title that overflows the spine | | Export | PDF at the exact sheet size with fonts embedded and no scaling | fit to page is how a perfect design comes out five percent small | | State | The design serialised into the URL with defaults omitted | the URL is the save file and the share link | ## Modules Each module has one owner concern and a documented way to replace it. | Module | Owns | How to replace it | | --- | --- | --- | | Geometry | Formats as millimetres, pixels derived. | Any renderer, provided no pixel value enters a format file. | | Type | Fitting from glyph metrics after fonts load. | Any text engine, provided measurement waits for the font. | | Art | Local images, previewed downscaled, exported from the original. | Any image pipeline, provided nothing is uploaded. | | State | Versioned URL serialisation with defaults omitted. | Any encoding, provided old links keep opening. | | Export | Exact page size, embedded fonts, cut line only. | Not swappable for fit to page, which is the failure this exists to prevent. | ## Configuration Every runtime setting is an environment variable documented in `.env.example`, validated at startup, with a safe local default wherever one exists. - `VITE_DEFAULT_FORMAT` · required · The format file loaded on first visit. Formats are data files in src/formats. - `VITE_PRINT_TOLERANCE_MM` · required · Your decision. The calibration square must measure within this. - `VITE_EXPORT_DPI` · required · PNG export resolution. The PDF is vector and does not use it. - `VITE_MAX_ART_PIXELS` · required · Above this an image is refused with the limit stated, rather than the tab dying. - `VITE_URL_SCHEMA_VERSION` · required · Bumped when the URL format changes. Every previous version keeps a reader and a fixture. - `VITE_FIT_TO_PAGE` · required · Present as a constant. Fit to page is how a sleeve comes out five percent small. ## Production baseline - Security: least privilege, input validation at every boundary, secret redaction in logs, rate limits on abuse-prone paths, no invented security primitives. - Data: explicit schema and migrations, transactional writes where integrity matters, backup and restore procedures that have been exercised. - Integrations: adapters around third-party providers, idempotent webhook or job processing, bounded retries, timeouts. - Observability: structured logs with request or operation ids, an error-tracking hook, and health and readiness checks where a server exists. - Quality: unit tests for domain rules, integration tests at module boundaries, one end-to-end test of the critical path. ## Decision records For each dependency in the stack table, keep a short note: why it was chosen, its failure mode, and how it is replaced. Do not add infrastructure until a requirement in `PRODUCT.md` justifies it.
# Agent instructions · VHS Slipcover Maker product build - Read `PRODUCT.md` and `ARCHITECTURE.md` before changing code. The stack is fixed: A static single-page application, no server and no accounts, Every format stored as millimetres in a data file, pixels derived at render time, One vector drawing of the whole wrap, shared by the screen and the export, Bundled fonts whose licences permit embedding, fitted from real glyph metrics, PDF at the exact sheet size with fonts embedded and no scaling, The design serialised into the URL with defaults omitted. - Implement milestone by milestone from `MILESTONES.md`; keep each change reviewable and leave the application runnable at every commit. - Treat authentication, payments, encryption, imports, webhooks and destructive actions as high-risk boundaries when present. - Never invent cryptography or silently weaken a requirement to make a check pass. - Put every external service behind an interface with a deterministic fake for tests. - Add migrations and rollback or recovery notes for every persistent data change. - Log useful operational context without credentials, tokens, passwords or personal data. - Update documentation and run every check before completing a milestone. ## Known traps - Designing in pixels at an assumed screen density and then letting the print dialog fit the page produces a sleeve a few percent small, and a few percent is the difference between a slipcover and a piece of paper that will not close. Keep millimetres as the only unit in data, export at the exact sheet size, print at one hundred percent, and prove it with a measured square before trusting anything else. - Measuring text before the bundled font has loaded measures the fallback font, so the spine title fits on screen and overflows in the export, and it happens on the first load of the day when the cache is cold. Wait on the font loading promise before any measurement, and re-measure if a font arrives late. - Putting the full resolution photograph into the live drawing and redrawing it on every drag makes the editor stutter, and exporting from that same drawing runs the tab out of memory on a phone. Preview from a downscaled copy and only touch the original at export time. - Encoding the image into the URL produces an address that browsers and servers truncate, so the share link opens to a blank cover with no error. Keep the art out, keep the URL short by omitting defaults, and version the schema so a link from last year still opens. - A PDF whose page is the wrong size, or whose fonts are not embedded, prints wrong on every machine but yours, and the person will blame their printer. Set the page size from the format, embed the fonts, and open the file in a different viewer on a machine without those fonts before shipping.
# Delivery milestones · VHS Slipcover Maker Estimated effort: **weekend** for the indie phases; the production-only milestones add the trust and operability layer. ## M1 · A wrap that fits the case in your hand Printed at one hundred percent, the wrap folds around a real case. ### Steps 1. Write the format as millimetres Panels, spine, flaps, folds and the cut path, measured from the case rather than copied from a forum. Files: `src/formats/vhs.json`, `src/geometry.ts` 2. Print and measure a calibration square A hundred millimetres on paper is the acceptance test for everything after this. Files: `src/calibration.tsx` 3. Fold a real wrap around a real case Paper and a ruler find what a unit test cannot. Files: `docs/fold-order.md` ### Done when - [ ] The calibration square measures a hundred millimetres within tolerance - [ ] A wrap printed at one hundred percent fits the case - [ ] No pixel value exists in the format file - [ ] A panel width change renders without code changes ### Watch out - Designing in pixels at an assumed screen density and then letting the print dialog fit the page produces a sleeve a few percent small, and a few percent is the difference between a slipcover and a piece of paper that will not close. Keep millimetres as the only unit in data, export at the exact sheet size, print at one hundred percent, and prove it with a measured square before trusting anything else. ## M2 · Type that fits a one-inch spine Any title fits the spine and the front, in the font it will print in. ### Steps 1. Fit from glyph metrics after fonts load The fallback font has different widths and the export is where you find out. Files: `src/text-fit.ts`, `src/fonts.ts` 2. State the wrapping and truncation rules A long title has to go somewhere and the cut line is not it. Files: `src/text-fit.ts`, `src/text-fit.test.ts` 3. Ship presets as data An era look is a font, a palette and a layout, not a branch in code. Files: `src/presets.json` ### Done when - [ ] A forty character title fits the spine - [ ] Overlong titles wrap or truncate by the stated rule - [ ] Text is measured only after fonts load - [ ] Presets change the look from data alone ### Watch out - Measuring text before the bundled font has loaded measures the fallback font, so the spine title fits on screen and overflows in the export, and it happens on the first load of the day when the cache is cold. Wait on the font loading promise before any measurement, and re-measure if a font arrives late. ## M3 · Cover art without a server A large photograph is placed, cropped and kept at full resolution on the machine. ### Steps 1. Preview downscaled, export from the original Two copies, two jobs. The live drawing never holds thirty megapixels. Files: `src/art.ts` 2. Honour orientation metadata Phone photographs arrive sideways and the fix is one field. Files: `src/art.ts`, `src/art.test.ts` 3. Bound what you accept and say so A refusal with a stated limit beats a tab that dies. Files: `src/art.ts` ### Done when - [ ] A thirty megapixel image drags smoothly - [ ] Export uses original pixels - [ ] Sideways photographs render upright - [ ] No network activity carries the image ### Watch out - Putting the full resolution photograph into the live drawing and redrawing it on every drag makes the editor stutter, and exporting from that same drawing runs the tab out of memory on a phone. Preview from a downscaled copy and only touch the original at export time. ## M4 · The URL is the save file A design reopens from its address on another machine, minus the art, and says so. ### Steps 1. Omit defaults and version the schema Short links survive, and old links keep opening. Files: `src/url-state.ts`, `src/url-state.test.ts` 2. Keep the art out and say so on restore An image in a URL is a truncated URL. Files: `src/url-state.ts` 3. Keep a fixture of every schema version The test that a two year old link still opens. Files: `tests/fixtures/urls-v1.txt` ### Done when - [ ] A design restores on a second machine minus the art - [ ] A fresh design produces a minimal URL - [ ] Old schema versions still open - [ ] Back and forward move between states ### Watch out - Encoding the image into the URL produces an address that browsers and servers truncate, so the share link opens to a blank cover with no error. Keep the art out, keep the URL short by omitting defaults, and version the schema so a link from last year still opens. ## M5 · Export that prints the same everywhere (production only) The PDF prints identically on a machine without your fonts, and a second format proves the system. ### Steps 1. Set the page size from the format and embed fonts Then open it somewhere that has neither your fonts nor your browser. Files: `src/export-pdf.ts`, `tests/pdf-page-size.test.ts` 2. Draw the cut line only Fold lines on screen, never on paper. Files: `src/export-pdf.ts` 3. Add a second format as data If it needs code, formats are not data yet. Files: `src/formats/cassette-jcard.json` ### Done when - [ ] The PDF page size equals the sheet size - [ ] Fonts are embedded and render elsewhere - [ ] Fold lines are absent from exports - [ ] A second format needs only a data file ### Watch out - A PDF whose page is the wrong size, or whose fonts are not embedded, prints wrong on every machine but yours, and the person will blame their printer. Set the page size from the format, embed the fonts, and open the file in a different viewer on a machine without those fonts before shipping.
# Operations · VHS Slipcover Maker ## Backup There is no server and nothing to back up on one. The designs live in URLs people keep, the art is their own file, and the format files are the asset worth protecting, which is what version control is for. ## Restore Restore means opening an old link, so every URL schema version keeps its reader and a fixture in the test suite. A link that no longer opens is a lost design, and the person cannot tell you which version wrote it. Do a restore drill before the first real user, and write the date here when it passes. ## Monitoring For a static tool the only measurement that matters is whether printed sleeves fit, so the README asks people to measure the calibration square and report the printer and the figure, which is how a format error and a printer scaling error are told apart. ## Incident checklist A sleeve that does not fit is the incident, and it is either a printer that scaled or a format measured wrong. The calibration square separates the two in one print, which is why it exists. 1. Contain the issue without destroying evidence or user data. 2. Record the timeline and affected scope. 3. Rotate exposed secrets and revoke compromised sessions or credentials. 4. Restore from a verified backup when needed. 5. Document the root cause, the remediation and the regression test. ## Release gate - [ ] The calibration square is confirmed to measure a hundred millimetres within tolerance - [ ] A printed wrap is confirmed to fold around a real case - [ ] Text is confirmed measured only after fonts load - [ ] Old URL schema versions are confirmed to open - [ ] The PDF page size is confirmed to equal the sheet size with fonts embedded ## Launch constraint Do not market omitted VHS Slipcover Maker capabilities as implemented. The non-goals in `PRODUCT.md` remain user-visible limitations until they are deliberately delivered.
# Copy to .env and fill in. Never commit .env; this file documents it. # Required. The format file loaded on first visit. Formats are data files in src/formats. VITE_DEFAULT_FORMAT=vhs # Required. Your decision. The calibration square must measure within this. VITE_PRINT_TOLERANCE_MM=0.5 # Required. PNG export resolution. The PDF is vector and does not use it. VITE_EXPORT_DPI=600 # Required. Above this an image is refused with the limit stated, rather than the tab dying. VITE_MAX_ART_PIXELS=40000000 # Required. Bumped when the URL format changes. Every previous version keeps a reader and a fixture. VITE_URL_SCHEMA_VERSION=1 # Required. Present as a constant. Fit to page is how a sleeve comes out five percent small. VITE_FIT_TO_PAGE=never
$ choose a build depth, inspect the files, then open the complete pack in your agent
One cover is a fun afternoon project; people pay when the twelfth format still measures right against the real case in their hand and a whole shelf of tapes needs matching spines.
x30+ measured dieline templates: cassette J-cards, CD inserts, vinyl jackets, Blu-ray and DVD wraps, MiniDisc
xmovie and music search that auto-fills cover art and metadata
xdesigns that persist and share as a URL
xdie-cut label-sheet PDFs aligned to vendor sticker stock
x22 UI languages
Nothing worth pointing at. That's why the prompt exists.
Vibecode VHS Slipcover Maker
Kinda. The core of VHS Slipcover Maker is buildable in a weekend with the prompt on this page, but there are real gaps: 30+ measured dieline templates: cassette J-cards, CD inserts, vinyl jackets, Blu-ray and DVD wraps, MiniDisc, movie and music search that auto-fills cover art and metadata. Read the honest list above before committing.
How much does VHS Slipcover Maker cost?
VHS Slipcover Maker costs about $6/month (Basic, checked 2026-08-04), which is $72 per year.
What do I lose by replacing VHS Slipcover Maker?
Honestly: 30+ measured dieline templates: cassette J-cards, CD inserts, vinyl jackets, Blu-ray and DVD wraps, MiniDisc; movie and music search that auto-fills cover art and metadata; designs that persist and share as a URL; die-cut label-sheet PDFs aligned to vendor sticker stock; 22 UI languages. If any of those are load-bearing for you, keep paying.
Is there an open-source alternative to VHS Slipcover Maker?
No mature open-source alternative worth pointing at, which is exactly why the one-shot prompt on this page exists.