Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Hang — a wall arrangement planner

Build Live demo Licence: MIT Dependencies: none Works offline

Measure the wall. Arrange the art. Print the plan. Bang the nails in the right place.


Hang is a wall arrangement planner that runs entirely in your browser. Describe a real wall — its size, its colour, the radiator and the light switch you can't cover — then drag your framed pieces around until the arrangement looks right. When it does, print three A4 sheets and take them to the wall.

No server, no accounts, and your photographs never leave the machine.

▸ Open the planner

The workflow

  1. Describe the wall. Width, height and colour in millimetres, drawn to scale.
  2. Block out what you can't cover. Doors, radiators, sockets, a sloped ceiling — dragged into place, so an awkward wall can be described properly.
  3. Add the artwork. Artwork size, mount and frame (each with its own colour), an optional photograph with a pan-and-zoom crop, and a hook position measured down from the top of the frame.
  4. Arrange it. Drag pieces onto the wall with snapping to edges, centres and matching gaps. Nothing may overlap another piece, a blocked area or the wall edge — the geometry won't let you plan something you can't hang.
  5. Print it. Three A4 landscape sheets: the measured hanging plan, the hook positions, and a colour preview. Print to PDF and take it to the wall.

What's in it

  • Real arranging tools. Box-select or shift-click for multi-select, then align and distribute PowerPoint-style, drag the set together, or nudge it 1 mm at a time with the arrow keys. Full undo/redo.
  • A reserve strip. Drag a piece off the wall and it waits below instead of vanishing.
  • Hook positions, not guesses. The plan sheet gives ordinate dimensions from a single datum, so one mis-measurement doesn't cascade down the wall.
  • Save and load. One .json file carrying the whole plan, image thumbnails included, so a plan travels between machines.
  • Autosave. The wall is written to localStorage as you work.
  • An offline single file. dist/hang-standalone.html is the entire planner — stylesheet, modules, thumbnails and web fonts inlined. Download it once and it works forever, which is handy when the wall you are measuring is nowhere near the wifi.

Running it

The site is plain HTML, CSS and ES modules — there is nothing to install and no framework to learn. It does need to be served over HTTP (ES modules do not load from file://):

npm run serve            # http://localhost:8080, serves src/ directly

Edit anything under src/ and reload. That is the whole development loop.

npm run build            # writes dist/
npm run preview          # build, then serve dist/
npm test                 # drives the built site in Chromium (needs Playwright)

npm test needs Playwright available (npm i -D playwright && npx playwright install chromium); without it the script says so and exits cleanly.

The build

There is no bundler. scripts/build.mjs copies src/ to dist/ and adds one extra artefact: the standalone HTML file described above. (Font embedding is best-effort: if Google Fonts can't be reached at build time the file falls back to the system stack and everything else still works.)

Deploying to GitHub Pages

.github/workflows/deploy.yml builds and publishes on every push to main. One-time setup in the repository: Settings → Pages → Build and deployment → Source: GitHub Actions. No branch to create and no gh-pages to maintain.

All asset paths are relative, so the site works at a user site (you.github.io) or a project site (you.github.io/repo/) without configuration.

Pull requests run the same build and smoke test without deploying.

Layout

src/
  index.html            the shell
  css/organic.css       the Organic design system — tokens and components
  css/app.css           app chrome, scrollbars, and the print rules for A4
  js/dom.js             ~200-line keyed renderer: h(), createRenderer()
  js/geometry.js        rectangles, overlap and fit tests, in wall millimetres
  js/labels.js          label wrapping and shrink-to-fit
  js/plan.js            measurement model and the three printable sheets
  js/view.js            the markup
  js/app.js             state, history, drag, snapping, selection, file I/O
  js/example-prints.js  baked thumbnails for the Example wall  (PLACEHOLDER — see below)
scripts/                build, dev server, smoke test — all dependency-free
docs/                   the README hero
.github/workflows/      build, test, deploy to Pages

Two conventions worth knowing before editing:

  1. Millimetres everywhere. A piece's x/y is the top-left corner of its frame; w/h are the artwork alone, so the framed rectangle grows by the mount plus the frame on each side (geometry.js: outer()). Only the render layer multiplies by scale() to reach pixels.
  2. Every document change goes through commit(). That is what captures undo history and writes localStorage; setState() alone is for view state (selection, menus, toasts) that history should ignore. A drag opens a transaction so the whole gesture folds into one undo step.

Where it came from

  • project/Wall Planner.dc.html — the prototype, and the visual reference this implementation matches.
  • project/_ds/organic-*/ — the Organic design system it is built on; src/css/organic.css is a copy of its styles.css.

The prototype ran on a React-backed template runtime. This implementation is plain modules with its own small renderer, but the geometry, layout rules and markup are ported faithfully — the label-fitting engine, snapping maths and plan drawing are the prototype's, line for line.

The Example wall uses Banksy prints at their real published sheet sizes. Those images are in copyright and are here for the sake of a demonstration layout.

One missing asset

src/js/example-prints.js is a placeholder. This repository was rebuilt from the session transcript after the original working copy was lost, and that file — 132 KB of base64 thumbnails — was the one thing too large to recover.

To restore it, copy example-prints.js from the Claude Design handoff bundle over src/js/example-prints.js. Nothing needs to change in the code.

Until then the Example wall still loads with the correct sizes, positions, frames and labels; the seven frames are simply empty. Two smoke-test checks skip themselves and say so, and turn back into real assertions once the file is in place.

Licence

MIT © 2026 Andy Roberts.

About

Picture wall planning made easy - Plan how art hangs on a real wall, then print the measurements you need to put it up.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages