Design Archive
中EN

One solver, ten resolutions, no distortion

A data screen had a single 2880×1547 design, and target screens ran from 1366×768 to 4K in different ratios. I wrote the scaling rules as one scale factor, three clamps and one map-placement rule; across ten resolutions the horizontal proportions match 1920, and the side panels never scroll or overflow.

L.12 / 14
CATEGORY
Study
YEAR
2026
STATUS
Note
TOOLS
css / clamp / javascript

entryWrite-up

A data screen has two tabs that share one top bar: the operations page has a 3D map with a glowing halo in the middle, a KPI strip (key performance indicator numbers) along the top, and three chart cards on each side; the facilities page swaps the same map for a version with routes and facility markers, with 9 layer toggles, a set of view actions and a legend in the left panel, and an assistant chat panel on the right. The design was drawn at 2880×1547, 1.5 times a 1920 screen; in use it runs full-screen and covers 16:9, 16:10 and 3:2 screens from 1366 to 1920, plus 2K and 4K, though not 21:9. I was responsible for the design side and had to explain to front-end how this design should be built on every kind of screen. When the design side hands over only one artboard and the word “responsive”, front-end can only guess, so this time I made the scaling rules into a page that runs: every size has a formula, and every formula can be seen working by dragging the browser window.

Fitting the design onto different screens creates three conflicts. The 3D map is cropped by the artboard from a full-bleed bitmap 4704 wide, and the halo at its base passes behind the cards on both sides, so it cannot be treated as the middle column of a grid and stretched; stretching changes its proportions. 12px is the floor for readable text on a big screen, and the left panel’s 9 toggle rows and the right chat panel’s title bar need at least 240px of width at 12px, but giving the side panels a plain minimum width would push them from 35% to 44% of the width on small screens, squeezing the map into a small patch in the middle and looking noticeably worse than 1920. The artboard is 1547 tall, which is not standard 16:9 (1.5 × 1080 would be 1620), so after scaling to width a 16:9 screen has 4.7% less height than the design; the left panel has only 49px of slack in the design, and at 1366×768 it would either scroll or squash its rows, while the requirements ruled out internal scrolling. Scaling the whole screen down uniformly does not work either: small screens would end up with 8px text. The scheme had to satisfy three things at once: horizontal proportions matching 1920, vertical content that fits, and no jump in the map or side panels when switching tabs.

The structure of the operations page went through five versions. The first attempt was a three-column grid with breakpoints, and one version was enough to show that the steps between breakpoints were all forced, and that with more breakpoints front-end could not remember them either. So the picture was split into two layers: the top bar, side panels and KPI strip form the panel layer, whose sizes are solved from the viewport; the map layer is a bitmap that always fills the viewport and is placed and scaled only by the bounding box of the province outlines.

The whole calculation uses a single scale factor, k: of the two ratios by which the current screen is smaller than the design, in width and in height, it takes the smaller one. Every size is held between a floor and a ceiling by the CSS function clamp(min, design value × k, max): in the middle range it scales strictly in proportion, small screens hit the floor and stay readable, and large screens hit the ceiling so they do not look empty. The core of the whole scheme is map placement, which amounts to a hand-written object-fit: contain that looks only at the bounding box of the province outlines and fits it whole inside the container at its own aspect ratio: first find the central visible area between the left and right side panels, fit the bounding box into it in proportion, then put the box’s center at 56.7% of the visible area’s height, reproducing the design’s composition, where it sits at 61.2% of the screen height. The base image needs an extra band of picture added on every side (bleed), or the background shows through on screens with small ratios. The scaling rules are written as continuous functions rather than a breakpoint table, because a table covers only the sizes it lists, while functions cover any width and height and can run directly in the demo page, so design and front-end look at the same thing.

The two-layer model and the core formulas.

The operations page then went through a few trade-offs. In v3.2 the three cards fill the side panel’s usable height rather than aligning to the top; if a filled card ends up taller than it is wide, the side panel widens until the card is square, but the central visible area may not drop below 55% of the width. In v4 type sizes stopped using viewport-width units (vw); JS calculates all of them and writes them into CSS variables, and the chart library reads the same variables, with the rule unified as base × k, a 12px floor and k capped at 1.35, and no separate type scale to maintain for compact screens. v5 lowered the side panel minimum from 300 to 240, which solved the map being squeezed on compact screens. The minimum width of every panel’s content on both pages at 12px was measured one by one, and 240 is the floor that keeps the layout intact; 500k at 1366 comes to 237, only 3px short, so across the ten resolutions the horizontal proportions stay almost entirely proportional, with the two side panels taking 34.7% and the central visible area 62.5% at most of them.

The facilities page uses the same solver, with four alignment requirements: a shared top bar; the same map placement rule, with the position taken from the first page; equal side panel widths on both pages; and a single side panel on the second page as tall as the first page’s three cards plus two gaps. As a result, the two pages’ solver functions are identical word for word except for two outline constants, and switching tabs causes no horizontal jump at all. The two directions are handled separately: horizontal proportion is the easiest thing to notice and must match the 1920 reference screen, while vertical differences are absorbed by filling, tightening gaps and lowering type size, which brings the complexity down.

The facilities page’s left panel has to hold 9 toggle rows, view actions and a legend, without scrolling. It uses three row modes, stepping down one mode at a time according to the remaining height: mode A is full, with name and description on two lines; mode B is condensed to one line; mode C uses two columns. Review added three constraints: compact screens may drop to B’s single line, but the description is the only element on the page allowed below 12px, down to 10px; small widgets such as toggles, counts, color swatches and stars cannot scale with k, or they look large at 1366 and small at 4K, so they follow the row height instead, with type sizes as their limits; and the elements in the panel reproduce only the design’s visuals, without being built into interactive components.

After the first version was delivered, the design side revised the page so that the name and description keep their two lines on compact screens too, with no single-line mode as in the first version. The revision moved the legend out of the left panel into a strip directly below the map; the left panel’s row height went from 78 to 88, and the name and description sizes up to 20 and 16; the view-action buttons gained icons; the right panel was made as tall as the left; and the whole map moved up.

This time the first version was not overwritten; a separate v2 was started. First came the arithmetic: two lines were possible, but 1366×768 would need every available concession; removing the legend saved only about 95px on compact screens, while the legend strip at the bottom took back 44px plus margins and spacing, leaving the side panel only 6px more net height. The initial v2.0 concession ladder had two steps that reduced buttons to icons only, and I rejected them: view actions must show icon and text at every resolution, a hard requirement from the product side. The remaining concessions came in three steps: first drop the description size from 12 to 10; then reduce row padding and row gap from 8 to 6; finally remove the view-actions title and lower button height from 32 to 30. With all three steps used, 1366×768 just fits two lines at a row height of 45.7; 1536 and 1600 use only the first step, and 1440, 1680 and above need none. Then v2.2 removed the word “legend”, which had taken its own line in the legend strip, and v2.3 rewrote the delivery page as a condensed version for front-end. The legend strip and the raised map are changes to the layout itself, which the solver cannot smooth out, and the scheme states this cost: switching tabs makes the side panel bottom and the map each jump vertically by about 60 to 140px, and the message area loses 60 to 100px. The 12px type floor, text on buttons and no internal scrolling were fixed first as the limits that cannot give, and the remaining concessions could then be put in order.

The left panel's concession ladder: two lines first, conceding in a fixed order.

The first version of the facilities page took the size of the design’s “outer outline” layer as the bounding box, so the provinces came out 8% smaller than on the first page, and at 1920 the right edge was off by 78px; it only showed up when the two pages’ guides were overlaid while reviewing the demo page. It was changed to measure the exported bitmap by pixel threshold, which gave 1770×1172. The first page was the opposite case: its image carries a glowing halo, so a pixel threshold counts the halo too, and the node constants from the design were the accurate ones. Which value to use therefore cannot be written as a fixed rule; it has to be checked against guides that overlay both pages at the same resolution.

Two ways to measure the bounding box: the layer frame comes out too small, and pixels come out too large on the page with the halo.

In v2.1 the compact concession was originally written as max(6, 16c), where c is the side panel width divided by 500 and sizes inside the side panel scale with it. At 1366 this formula came to 7.7 rather than 6; the extra 1.7px across 18 gaps was just enough to push 1366 back into the single-line mode. It became an absolute 6, since only the compact tier ever reaches this step and there is no need to multiply by a ratio.

When v1.2 lowered the description’s floor to 10px, the rule was at first written to apply globally, so the 12px that fit at 1920 was squeezed to 10 as well. It was later changed to try 12 first and drop to 10 only when mode A does not fit.

The left panel’s height model left out the two 1px borders at the top and bottom of the panel, so the last row touched the bottom; this only came to light when automated screenshots were measured pixel by pixel, row by row.

The document once said the two-column mode C triggers at 1280×720; in testing it does not, and the fallback layout uses mode B, so the document was changed to “a safety mode, never triggered within the supported range”. Discrepancies like this were caught by checking the document line by line against the ten-resolution table during review; the acceptance standard was that every number in the document must be visible in the demo page.

The transparent top bar image would not export at first: the PNG exported from the design tool was flattened onto white by a blend-mode layer. The fix was to export an SVG with the text turned into shapes (converted to outlines), delete the canvas background rectangle, and then screenshot it with a transparent background in a headless browser (one that runs without a visible window and is controlled by a script).

In the final scheme, the two pages share k, margins, the top bar, side panel width, visible area width and province outline width; the facilities page v2 solves its left panel by the ladder, with buttons keeping icon and text at every step, and the right panel’s message area is the only region on the page allowed to scroll. Each scheme is packaged as a single-file web page for front-end: the top half has the formulas, the ten-resolution table and an acceptance checklist; the bottom half is an interactive demo where you can pick a preset resolution, type any size, or drag the window to re-lay out in real time, with guides overlaid for the map outline, the visible area, the legend strip and the other page’s side panel frame at the same resolution, plus a live readout panel that front-end can check their own code against. All ten common resolutions were opened in a headless browser at their real viewport sizes and screenshotted, checking one by one that the left panel list does not overflow, the 9 descriptions each stay on their own line, button icons and text are not truncated, the legend strip fits on one line, and the console shows no errors.

The demo page's readout panel at 1920×1080: standard tier, left panel at step 0.
The same panel at 1366×768: compact tier, the left panel uses step 3 and still shows two lines.

processBefore & after

Shared by both pages

k
min(W/2880, H/1547) · 1366: 0.474 · 1920: 0.667 · 3840: 1.333
Side panel S
clamp(240, 500k, 560), wider on tall screens · 240 / 333 / 578
Top bar T
clamp(64, 136k, 200) · 65 / 91 / 181
Visible area width
W − 2(M+S) · 62.1% / 62.5% / 67.1%
Province outline width
result of contain · 59.6% / 58.8% / 62–63%
Tiers
k < 0.60 compact · < 1.0 standard · ≥ 1.0 roomy
Type
base × min(k, 1.35) · hard 12px floor · KPI numbers floor 34
Fallback
below 1280×720, lay out at 1280×720 and scale the whole screen down, centered
On resize
write CSS variables → force reflow → debounce 200ms → tell charts to redraw

Formulas

text
k  = min(W / 2880, H / 1547)
M  = clamp(16,  40k,  56)       safe margin
T  = clamp(64, 136k, 200)       top bar height
S  = clamp(240, 500k, 560)      side panel width

channel = W − 2(M + S)          central visible width
s  = min(channel·(1−padX)/OUT_W, Hc·(1−padY)/OUT_H)
cx = W/2,  cy = top + 0.567·Hc  where the bounding box center lands

Operations page

Cards
split the side panel into thirds · above an aspect ratio of 1.45, shrink gaps to gain height · if gaps at 8 are still not enough, give up the ratio; never overflow
Compact charts
every other axis label · two-character category abbreviations · bars no wider than 16 · top 6 when there are more than 6 categories · legend merged into the unit line

Facilities page v2

c
side panel width ÷ 500; sizes inside the side panels scale with it
Side panel top
T + clamp(24, 65k, 100)
Legend strip height
2·max(12, 25c) + max(20, 30c), centered directly below the map
Side panel bottom
15k above the legend strip; left and right panels equal height
Right panel title bar
max(64, 92c)
Concession ladder
description 12 → 10 · padding and row gap 8 → 6 · drop title, buttons 32 → 30

The ten resolutions verified

1366×768, 1440×900, 1536×864, 1600×900, 1680×1050, 1920×1080, 1920×1200, 2256×1504, 2560×1440, 3840×2160.

resultTakeaways

Finding
Across ten common resolutions the horizontal proportions match 1920×1080, with side panels at 34.7% and the visible area at 62.5%, and 1366 off by only 3px; side panels never scroll or overflow, switching tabs causes no horizontal jump, and at 1366×768 the left panel still shows name and description on two lines.
Parameters
k = min(W/2880, H/1547) · side panel clamp(240, 500k, 560) · top bar clamp(64, 136k, 200) · type base × min(k, 1.35), 12px floor
Open
The rules cover only 16:9, 16:10, 3:2 and 2K/4K, not 21:9; below 1280×720 the whole screen can only be scaled down and centered with black bars; switching between the facilities page v2 and the operations page makes the side panel bottom and the map jump vertically by about 60–140px, and avoiding that would need the two layouts brought in line.

logChangelog

2026-10
First entry

Related experiments

note
L.08(STUDY)2026

A hero image, from full-screen to a spec

responsivecssobject-fit+1
note
L.13(STUDY)2026

Translating design constraints into CSS front-end can use as is

responsivecssflexbox+1
note
L.01(STUDY)2026

(1) Choosing a base model

chest-xraypytorchrad-dino+1
← LabL.12 / 14
1 / 1

100%