EnergyShop · Storefront PLP · MR !673

Density & de-bulk — before / after

One day of work on feat/plp-dynamic-facets-discovery: the category banner merged into the title band (it used to spend a whole row of vertical space), a measured density pass against the Figma PLP frame, a full px→rem codemod, a global scale knob, and a denser card grid. Drag the sliders to compare — the first one shows the complete journey.

8,308
px values converted to rem (21 sheets + 15 Dart files)
−6.25%
global scale (root 16 → 15px), one tunable number
2→4
cards per row at 1280px (3→4 at 1440px)
−32%
list-row height via the 3-column anatomy fix

The whole journey at 1280px — original layout vs today

The true starting point: the category banner sat on its own row under the title, spending a full band of vertical space before any product appeared, with 2 cards per row below it. After the band merge (title + tiles on one row), the density pass, the px→rem codemod, the global scale knob, and the 14rem grid: products start a full banner-height sooner and the row fits 4 cards.

The whole journey at 1280px — original layout vs today — after
The whole journey at 1280px — original layout vs today — before
Before After

Full de-bulk at 1440px

Global 93.75% root scale + 14rem grid base + back-chevron removed. The row goes from 3 cards to 4; the rail and toolbar tighten with the page instead of drifting apart.

Full de-bulk at 1440px — after
Full de-bulk at 1440px — before
Before After

Full de-bulk at 1280px

The width most laptops actually browse at. Cards per row go from 2 to 4 (212px cards; the price row got a wrap valve so the currency never clips).

Full de-bulk at 1280px — after
Full de-bulk at 1280px — before
Before After

Density pass — filter rail

Facet block rhythm halved (96 → 48px), section titles 16 → 14px, rail width matched to the Figma content column (x = 348). Three facet sections above the fold instead of one and a half.

Density pass — filter rail — after
Density pass — filter rail — before
Before After

Toolbar — sort dropdown, open menu

The "Sorted by" control already matched the frame’s fixed 21rem — the wide-pill feel is baked into the frame itself. Made intrinsic instead: 315 → 213px, menu right-anchored at max-content width, option rows grown from 20.6 to 30px hit areas; grid/list toggle aligned to the row height.

Toolbar — sort dropdown, open menu — after
Toolbar — sort dropdown, open menu — before
Before After
Dense list view after the anatomy fix
List view after the anatomy fix: the price/lead-time block was silently stacking under the text (a flex-column wrapper made its right-pinning a no-op, ~46px wasted per row). Now a true 3-column row — 175.6 → 119.7px per row.
Density pass — measured, not eyeballed (at 1440px, pre-knob values)
ElementBeforeFigma frameAfter
Filter rail width340268 (+40 gap)317 — content x=348, frame-exact
Facet block spacing964848
Facet title → chips / title font16 / 168 / ~148 / 14
List row height175.696119.7 (−32%)
List thumbnail88~64–7272
Page title446444 — kept (frame's is bigger; growing it adds bulk)
"Sorted by" control width315 (fixed 21rem)336 (same 21rem!)213 — intrinsic, 12rem floor
Sort menu option rows20.6~38 pacing30 — ≥2rem hit areas, right-anchored
Grid/list toggle height37.548 (row-uniform)41.3 — matches the sort control row
Cards per row (Helmer grid, filter rail visible)
ViewportBeforeAfterCard width after
144034248px
128024212px (wrap valve added for the price row)
102422321px
7603
px → rem conversion by sheet (top sheets; kept-as-px = hairlines, breakpoints, clamp/vw, sentinels)
SheetConvertedSheetConverted
main.css3,831orders.css350
arrester-configurator.css1,389dashboard.css269
cart.css546order_detail.css225
my-assets.css482Dart inline styles (15 files)334

Value-exact by proof, not promise

The codemod is pure ÷16 arithmetic. Before adding the scale knob, a desktop screenshot diff at 1280px measured 32 of ~2.3M pixels changed (0.0014%) — antialiasing noise. That's why the conversion ships unflagged.

Two knowingly-visible flag-off changes

The root knob can't be flag-gated — rem resolves against <html>, not a page class. Flag-off pages also shrink 6.25% (and pack 3 cards/row at 1280). Accepted by design for this branch; stated in the MR.

Merge coordination with mr/theme-consolidation

Both branches carry the identical codemod script. Whichever merges second resolves the mechanical conflicts by re-running scripts/px-to-rem-codemod.mjs on the merge result. Order agreed: !673 first, theme-consolidation rebases after.

Separate issue: drawing-first card images

The A4 engineering drawings on some Helmer cards are a data bug, not layout: all 110 membership products have their gallery at rank 0, and the drawing URL wins the tie alphabetically. Fix = re-rank photo-first + set thumbnails + reindex Meili (demo DB and prod). Pending go-ahead.