# Board layout `scripts/compose-board.mjs` lays the approved frames out on one PNG and draws every word with a real font, locally. The image model never draws text on the board, so the shot numbers, captions and prices are always spelled and numbered exactly as in board.json and the price file. ## Sizes | Board | Size | 16:9 frames | 9:16 frames | | -------------- | ----------- | ---------------------------- | ---------------------------- | | 16:9 (default) | 2560 × 1440 | 6 shots: 3 × 2 at 669 × 376 | 6 shots: 6 × 1 at 375 × 666 | | 9:16 | 1440 × 2560 | 6 shots: 2 × 3 at 641 × 360 | 6 shots: 3 × 2 at 415 × 737 | Four shots sit 2 × 2. The script tries every number of columns and keeps the one that gives the largest frames, then centres the grid between the header and the price footer. Frames are cover-fitted to the panel (a frame of the wrong shape is centre-cropped, with a warning). Use the 16:9 board for a laptop, a deck or an email to a client; use the 9:16 board when the board itself will be read on a phone. ## What is on the board - **Header:** the title (`board.title`, one line), the idea (`board.subtitle`, at most 110 characters), and "6 shots · 31 s · 16:9 film · 720p" with the date. - **Each panel:** the frame, then a number badge (01 to 06), the slot label in capitals, the seconds planned, the caption (one or two lines; two are always reserved so the camera notes line up), and "Camera: ..." from `camera`. - **Footer:** "To film it (31 s, 720p, fal prices checked 2026-10-18):" and the cost on each route in the price file's `board_footer` list (Seedance 2.5, Seedance 2.0, Kling 3 pro silent and with audio, Veo 3.1 fast silent and with audio, Wan 2.2), then the whole film on the Meleon film Skill. `--no-prices` leaves the footer off (deliver prices.md beside the board instead). ## Text limits The script refuses text that would not fit, rather than shrinking it to unreadable: | Field | Limit | | --------------- | ------------------------------------------------------- | | `board.title` | One line at the header size (shrinks to 60% before it fails) | | `board.subtitle` | 110 characters; one line on a 16:9 board, two on 9:16 | | `label` | 18 characters, beside the number badge | | `caption` | 60 characters, two lines under the frame | | `camera` | 40 characters, one line | Six vertical frames in one row are narrow (375 px), so captions there wrap to two lines more often; keep them short. ## Fonts - Default: Helvetica (bold for the title and labels), resolved by fontconfig. On Linux a missing Helvetica falls back to DejaVu Sans, which changes the look and the line breaks. The script prints the font it asked for. - Brand type: `--font-file path/to/Brand.ttf --font "Brand Family"`. The family name must match the font's own name, or Pango falls back. Use a font the user owns or one with an open licence (for example from Google Fonts), and do not copy it into the Skill folder. ## Colours A near-black board (#111111) with warm off-white text (#f2ede4), grey notes (#9d978d) and an amber accent (#e8a54b) for the number badges. Frames carry their own colour; a dark board keeps them the brightest thing on the page, like a contact sheet. ## Guides for vertical frames `--guides` draws two dashed amber lines on every 9:16 frame: at 12% from the top (account name, status bar) and at 20% from the bottom (caption, buttons). Nothing essential should sit outside them. Leave guides off the board you send a client unless they asked for it. ## Plan boards `--placeholders` draws a dark panel saying "Frame not made yet" for every shot without a `frame`. It costs nothing and shows the order, the captions and the prices before any frame is paid for. check-board.mjs fails a board with placeholders, so it is never delivered as the finished board. ## Preview and manifest - `--preview board-preview.webp` writes a WebP 2048 px wide (16:9 board) or 1152 px wide (9:16 board), quality 85, so the camera notes stay readable. With real frames it is about 200 KB (the OSTRA sample: 214 KB). - `board.png.json` records the size, the grid, which frame went into each panel, the font and the price snapshot (the date the prices were checked and the totals). check-board.mjs compares it with board.json and the price file, so a board composed from old frames or old prices fails.