Build VHS Slipcover Maker

KINDAreplaces $6/mosaves $72/yrback to the verdict

0%0 of 17 items done

Saved on this device only. Tick prerequisites first, then work the phases in order · do not start one until the checks above it pass.

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 weekendthe files for this build are in the project pack

RuntimeA static single-page application, no server and no accountsGeometryEvery format stored as millimetres in a data file, pixels derived at render timeRenderingOne vector drawing of the whole wrap, shared by the screen and the exportTypeBundled fonts whose licences permit embedding, fitted from real glyph metricsExportPDF at the exact sheet size with fonts embedded and no scalingStateThe design serialised into the URL with defaults omitted

Before step 1

Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.

  1. installfree

    Why Builds the single-page application and runs the test suite.

    Get it nodejs.org, or nvm install 22. open ↗

    Verify node --version prints v22 or higher

  2. have readyfree

    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

  3. installfree, 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

  4. have readyfree

    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

  5. decidefree

    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

Data model

Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.

- 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.

Environment variables

These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.

VariableNeededExampleWhere the value comes from
VITE_DEFAULT_FORMATrequiredvhsThe format file loaded on first visit. Formats are data files in src/formats.
VITE_PRINT_TOLERANCE_MMrequired0.5Your decision. The calibration square must measure within this.
VITE_EXPORT_DPIrequired600PNG export resolution. The PDF is vector and does not use it.
VITE_MAX_ART_PIXELSrequired40000000Above this an image is refused with the limit stated, rather than the tab dying.
VITE_URL_SCHEMA_VERSIONrequired1Bumped when the URL format changes. Every previous version keeps a reader and a fixture.
VITE_FIT_TO_PAGErequiredneverPresent as a constant. Fit to page is how a sleeve comes out five percent small.

The build, in order

  1. A wrap that fits the case in your hand

    Printed at one hundred percent, the wrap folds around a real case.

    1. Panels, spine, flaps, folds and the cut path, measured from the case rather than copied from a forum.

      Files src/formats/vhs.jsonsrc/geometry.ts

    2. A hundred millimetres on paper is the acceptance test for everything after this.

      Files src/calibration.tsx

    3. Paper and a ruler find what a unit test cannot.

      Files docs/fold-order.md

    done when · tick each as it passes
    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.
  2. Type that fits a one-inch spine

    Any title fits the spine and the front, in the font it will print in.

    1. The fallback font has different widths and the export is where you find out.

      Files src/text-fit.tssrc/fonts.ts

    2. A long title has to go somewhere and the cut line is not it.

      Files src/text-fit.tssrc/text-fit.test.ts

    3. An era look is a font, a palette and a layout, not a branch in code.

      Files src/presets.json

    done when · tick each as it passes
    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.
  3. Cover art without a server

    A large photograph is placed, cropped and kept at full resolution on the machine.

    1. Two copies, two jobs. The live drawing never holds thirty megapixels.

      Files src/art.ts

    2. Phone photographs arrive sideways and the fix is one field.

      Files src/art.tssrc/art.test.ts

    3. A refusal with a stated limit beats a tab that dies.

      Files src/art.ts

    done when · tick each as it passes
    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.
  4. The URL is the save file

    A design reopens from its address on another machine, minus the art, and says so.

    1. Short links survive, and old links keep opening.

      Files src/url-state.tssrc/url-state.test.ts

    2. An image in a URL is a truncated URL.

      Files src/url-state.ts

    3. The test that a two year old link still opens.

      Files tests/fixtures/urls-v1.txt

    done when · tick each as it passes
    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.
what this build does not replace
after v1, if you want it

Need the files? The project pack on the verdict page hands your agent the whole brief · more design.