Foundations
Grid
The layout is a Mondrian: CSS Grid on a black page, and the gaps between the panels are the black lines. Nothing draws a line; the grid leaves it.
This frame:
How a block is made
- The block is a grid on
--black, withgap: var(--frame)and the same width of black around it (padding: 0 var(--frame) var(--frame): the block above provides the top edge). - Its layout is named, in
grid-template-areas; each tier changes the areas and tracks, never the markup. - Each cell is a panel coloured by one
bg-*class and named bydata-cell: the block’s prefix plus the area it fills. - Side columns are
14cqi; decorative rows stay short (108–130px on desktop, 56–76px on mobile) so the content panel dominates; every block is at least one screen tall. - Many blocks then move their tracks with the scroll (a
view()timeline animatinggrid-template-rowsor-columns), so the lines shift as you read; under reduced motion they hold still.
<section class="sec sec-a">
<div class="p bg-blue" data-cell="sec01-panel">…</div>
<div class="s1 bg-paper" data-cell="sec01-s1"></div>
<div class="s2 bg-paper" data-cell="sec01-s2"></div>
<div class="s3 bg-yellow" data-cell="sec01-s3"></div>
</section>
.sec {
display: grid;
gap: var(--frame);
padding: 0 var(--frame) var(--frame);
background: var(--black);
min-height: var(--screen);
}
.sec-a {
grid-template-columns: 1fr 14cqi;
grid-template-rows: 1fr 1fr 108px;
grid-template-areas: "panel s1" "panel s2" "panel s3";
}Cell maps
Every block’s desktop grid, drawn from its areas and named by its cells (in proportion, not to scale). Mobile layouts and each block’s scroll motion are in the cell map of DESIGN.md.
.hero: columns 1fr 14cqi, rows --bar 1fr 130pxhero-yellow hero-yellow
hero-title hero-light
hero-sub hero-blue.about: columns 14cqi 1fr 14cqi, rows 108px 1fr 92pxabout-s1 about-text about-text
about-s2 about-text about-text
about-base about-base about-base.sec-a: columns 1fr 14cqi, rows 1fr 1fr 108pxsec01-panel sec01-s1
sec01-panel sec01-s2
sec01-panel sec01-s3.sec-b: columns 14cqi 1fr, rows 108px 1fr 1frsec02-s1 sec02-panel
sec02-s2 sec02-panel
sec02-s3 sec02-panel.sec-c: columns 1fr 1fr 14cqi, rows 1fr 120pxsec03-panel sec03-panel sec03-panel
sec03-s1 sec03-s2 sec03-s3.sec-d: columns 1fr, rows 1frsec04-panel.sec-e: columns 2fr 1fr 1fr 14cqi, rows auto 1fr 1frsec05-panel sec05-panel sec05-panel sec05-s1
sec05-b1 sec05-b2 sec05-b3 sec05-s2
sec05-b1 sec05-b4 sec05-b4 sec05-s3.foot: columns 1fr 14cqi, rows 1fr 56pxfoot-main foot-r
foot-main foot-b.ap-head: columns 14cqi 1fr 9cqi 5cqi, rows 18% 1fr 1fr 18% of a screenap-head-s1 ap-head-s2 ap-head-s3 ap-head-s3
ap-head-s4 ap-head-main ap-head-s5 ap-head-s6
ap-head-s4 ap-head-main ap-head-s7 ap-head-s7
ap-head-s8 ap-head-s9 ap-head-s9 ap-head-s10.as: columns 14cqi 1fr 1fr 6cqi, rows 60%, 40%, 40% of a screen (at least)as-s1 as-head as-head as-s4
as-s2 as-commit as-testing as-s5
as-s3 as-issues as-feedback as-s5Breakpoints
Every breakpoint is a container query on the page (container-type: inline-size on <body class="page">), so widths are the page’s, not the window’s. The one viewport media query is short, for phones in landscape.
| Tier | Width | Reference | Grids |
|---|---|---|---|
mobile | ≤ 760 | iPhone 13, 390 × 844 | mobile |
tablet | 760–1024 | iPad portrait, 820 × 1180 | mobile, for now (the hero, i1 and i2 desktop) |
tablet-l | 1024–1280 | iPad landscape, 1180 × 820 | desktop |
desktop | 1280–1440 | Laptops, 1280 × 800 | desktop |
desktop-l | 1440–1920 | 1440 × 900, the design reference | desktop |
desktop-xl | ≥ 1920 | 1920 × 1080 and up | desktop, larger type |
short | height ≤ 500, landscape | iPhone 17 landscape, 874 × 402 | its width tier’s, sized to the height |
@container (max-width: 760px) { … } /* mobile */
@container (760px < width < 1024px) { … } /* tablet only */
@container (width >= 1024px) { … } /* tablet-l and up */
@container (width >= 1280px) { … } /* desktop and up */Bound a rule on both ends when it must stop at a tier: an open-ended query leaks into every tier beyond it.