Messenger was the specification layer.
Orders came in as Messenger threads. Which frame, how many photographs, what colour, what the text should say, whether the text was even possible at that width — all of it negotiated in messages, then transcribed by hand. The thread was doing the job of a product page, an order form and a spec sheet at once, and it was the only place the order existed.
A configurator is the obvious answer. The part that decides whether it works is less obvious: the customer is not buying a picture of a frame, they are buying a physical object with a fixed pocket size and a fixed skirt. If the screen and the object disagree about proportion, the thread comes back — as a complaint instead of an order.
One rule, encoded in both systems.
The printed frame is generated from one parametric model: a fixed square pocket, one gap that repeats between pockets, a consistent edge on three sides, and a deeper skirt along the bottom. Those proportions are the product. The configurator does not approximate them in CSS percentages — it encodes the same constants the manufacturing model does and derives every frame in the catalogue from them.
The honest limit of that claim: the constants are carried across by hand, not generated from the model at build time. Two systems agreeing because someone transcribed them is a rule with a drift risk, and the fix — one generated source consumed by both — is the version I would build next.
There is a concrete reason it cannot be percentages. In CSS, a percentage padding resolves against the box's own width, and a percentage grid row-gap resolves against an indefinite height as zero. Both are correct behaviour and both break the moment a frame is not square — which is seven of the nine. So the preview measures its own rendered width, calibrates one scale factor against the derived outer width, and converts every constant through it.
The derivation
Expressed in pocket units: the pocket is 1, everything else a rounded ratio of it — no more than the product photographs already show. The model's actual dimensions and tolerances stay unpublished. The rule is the portfolio piece; the tooling is the business.
Caption length is not a style choice either: ten characters per column, recomputed whenever the frame or its orientation changes. A wider frame earns more words.
Each step changes the object in front of you, or it does not exist.
Four steps do the making — frame, photographs, colour, personalisation — and the test for every one of them was the same: does this step visibly change the thing being bought? A step that only collects a value belongs after the craft, not inside it. That is why colour is its own step and quantity is not. The reveal follows the fourth step and the product counts it as the fifth, which is why the progress label reads “of 5”.
Photographs are the only hard gate among the four craft steps. You cannot leave that step until every pocket is filled, and the continue button counts what is outstanding rather than going grey and explaining nothing. Everything else in the craft is optional by design — text, stamp and layout are all free, and skipping them is a valid keepsake, not an incomplete one. Submission is gated separately, at checkout, on valid contact details.
Five colours, because five survived the photographs.
The interface does not have its own frame palette. The five colours a customer picks from are the five matte colours that survived elimination against real photographs in part one. Garcia chose that production palette; what I did here was encode those five into the configurator and design the ink behaviour around them, so a swatch on screen and a printed frame are one decision rather than two.
Which creates a problem the interface has to solve: text and stamps sit on the frame, and the frame is a colour the customer chose. So the ink is not a token. It is computed: relative luminance by the WCAG formula, then a threshold selects the dark or the light ink. That is a threshold, not a search — it does not compare both candidates and take the better one. For the five production colours the two approaches agree and every result clears AA once the rendered opacity is composited in, which is the check worth stating rather than the mechanism worth overselling. The placeholder hint scales its own opacity by the same measurement, because a faint dark hint on a pale frame is the case that fails.
Contrast is computed in this page from the same relative-luminance formula the product uses, then composited at the opacity the caption and stamp actually render at. The lowest of the five is Blush at 4.80:1. The ink decision is the product's; the arithmetic is reproducible here.
Two frames hold four photographs. They are not the same price.
Wide Shot and Classic both take four photographs. Wide Shot costs more. Read as a spreadsheet that is an error, and it is exactly the kind of thing a later contributor tidies up on a quiet afternoon — because four equals four.
It is not an error. A four-across frame is a longer object with a longer skirt and a different footprint; it prints and ships differently from a compact square. The catalogue is the single source of truth for pricing, so the exception is recorded in it as an instruction not to normalise it. A rule that only exists in someone's memory is a rule with an expiry date.
| Frame | Grid | Photographs | Price |
|---|---|---|---|
| Solo | 1 × 1 | 1 | ₱388 |
| Double Take | 2 × 1 | 2 | ₱588 |
| Triple Treat | 3 × 1 | 3 | ₱788 |
| Classic | 2 × 2 | 4 | ₱888 |
| Wide Shot | 4 × 1 | 4 | ₱988 |
| Six Pack | 3 × 2 | 6 | ₱1,288 |
| Eight Tiles | 4 × 2 | 8 | ₱1,688 |
| Gridstagram | 3 × 3 | 9 | ₱1,788 |
| Darling Dozen | 4 × 3 | 12 | ₱2,488 |
Nine frames, one to twelve photographs. Prices are the published storefront ones. Personalisation is itemised at zero rather than hidden, so a price can be attached later without restructuring the order.
A first version earns its scope by naming what it left out.
There is no payment processor. Payment is arranged directly, and the order is recorded rather than transacted. For a studio filling orders by hand, payment integration would have added implementation and support cost before order volume justified it — and it would have replaced a working manual step with a fragile automatic one.
The same reasoning shaped the handoff from the marketing site. A visitor can assemble a keepsake on the landing page and carry it into the configurator through a single encoded value in the URL. It restores the frame, the colour, the text, the stamp and the orientation — and every field is validated on its own, so an unknown value is skipped rather than fatal. Malformed fields fall back independently, leaving the configurator in a usable default state rather than a broken one.
What I would change: the arrangement sandbox at the photograph step lets a customer reorder freely, but that arrangement is not what gets manufactured — photographs are matched to pockets by slot. It reads as a promise the order does not keep, and it is the highest-priority fix on my own list. The cheap correction is to state the constraint at the point of interaction — label the step as choosing which photographs go in, not which pocket each one lands in. The complete one is to carry arrangement through into the order record. I would ship the label first, because it stops the interface implying something the order cannot honour.
Part one is the object. This is the part that had to agree with it.