Read 2026-10-21 (2026-10-22 UTC) from https://burnstudio.co/, https://burnstudio.co/work and https://burnstudio.co/work/antropico, plus the site's own CSS and JS. Serves inspiration rows 13 (the landing) and 14 (the work grid, the hover pill, the draggable menu, the phone bar) in ../../INSPIRATION.md; deepens "The name as the grid" and "The grid, and a bar that speaks for the canvas" in ../../bar-stage-drawers.md.
Checked by a verifier on 2026-10-22: VERIFY.md lists what was checked, what was corrected in place below and what stays unconfirmed.
Short version: a hand-written jQuery site on Kirby. One number, the pitch of a cross-marked grid, is computed from the viewport width; the logo, every thumbnail and every gap are whole multiples of it, so things that look scattered are on the grid to the pixel. The menu is a row of grey pills that grows a title pill on hover, can be dragged anywhere, and becomes the video player's bar on a project page. Everything on the landing, /work and the bar moves with plain CSS transitions of 300 ms ease, except the menu's 1 s glide; the only scripted motion is the homepage slideshows (Swiper, 800 ms slides on a desktop) and the project page's credit ticker (Marquee3000).
Quoted from INSPIRATION.md (fuller versions of the row 14 quotes are in bar-stage-drawers.md).
bar-stage-drawers.md (its notes on rows 13 and 14) calls the mode switch and the live clock "the only other furniture" and does not mention the list toggle or the project page. Captured because the brief asked for them.| Layer | Finding | Evidence |
|---|---|---|
| CMS | Kirby (PHP flat-file CMS) | data-kirby-url="https://burnstudio.co" on menu items; media under /media/pages/<page>/<hash>-<timestamp>/ (Kirby's media folder pattern: landing stills under /media/pages/home/…, thumbnails under /media/pages/work/<slug>/…) with Kirby thumb names (-300x.jpg, -640x.webp to -1920x.webp); x-powered-by: PHP/8.1.34 |
| Server, hosting | Apache, PHP 8.1.34, hosted by GoDaddy (its Host Europe network, Germany); no CDN headers on the HTML; DNS on GoDaddy | response headers (server: Apache, x-powered-by: PHP/8.1.34, no cf-ray, x-cache or age); NS ns17/ns18.domaincontrol.com; A 92.205.148.223 with no reverse name; RIPE: netname DE-GODADDY-20071017, org Host Europe GmbH, route 92.205.144.0/21, origin AS21499 |
| Page routing | Custom AJAX page swap, no router library | assets/js/ajax.js: a[data-ajax] links fetch /ajax/null/main/<slug> (JSON {status, content, dataPage}), fade <main>, replace its HTML, history.pushState with the whole main HTML in the state |
| Framework | None; no bundler; six plain scripts | assets/js/helpers.js, observerprojects.js, clock.js, main.js, ajax.js, player.js (globals, no modules); probe globals: jQuery, $ only; no generator meta |
| DOM and drag | jQuery 3.6.0, jQuery UI 1.14.1 (used for draggable only) |
https://code.jquery.com/jquery-3.6.0.min.js, https://code.jquery.com/ui/1.14.1/jquery-ui.min.js (SRI on the jQuery UI tag only); runtime $.fn.jquery 3.6.0, $.ui.version 1.14.1 |
| Sliders | Swiper 11 (floating major; jsDelivr served 11.2.10 on 2026-10-22), loaded on every page, used on the homepage only: two vertical slideshows set to loop: true, though Swiper warns that there are too few slides for loop mode |
https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js (banner "Swiper 11.2.10"); console "Swiper Loop Warning"; initHomepageSwipers() in helpers.js: #landscape-swiper 14 slides, #portrait-swiper 9 |
| Ticker | Marquee3000 1.1.1 for the project page's credit line | https://cdn.jsdelivr.net/npm/marquee3000@1.1.1/marquee3k.min.js; Marquee3k.init({selector: 'marquee-project'}); data-speed=".25" |
| Animation | CSS transitions only: 23 style rules declare a transition (a grep of the source counted 28 lines), no @keyframes, no prefers-reduced-motion rule, no animation library |
CSSOM walk through the 14 imported stylesheets (verifier) and a grep of them; tokens --transition-time: .3s, --transition-effect: ease; probe found no GSAP, Lenis, Motion, Lottie or canvas |
| Scroll | Native scrolling; scroll bars hidden (Blink and WebKit only) | ::-webkit-scrollbar { display: none; width: 0 } in commons/globals.css; probe scrollBehavior: auto, no smooth-scroll global |
| 3D | None | probe: no canvas, no WebGL |
| Fonts | "Camera Plain" regular and medium (ABC Camera Plain, served from the foundry's trial files; the Medium file is declared as font-weight: bold) for text; "Politics" book for the small caps-style labels |
commons/fonts.css: ABCCameraPlain-Regular-Trial.woff2, ABCCameraPlain-Medium-Trial.woff2, Politics-Book.woff2 under /assets/fonts/; probe fonts list |
| Video | Native <video>: thumbnails lazy-played by an IntersectionObserver, hover previews preload="none", project films with a custom player bar |
lazyVideos() in helpers.js; player.js; 15 videos on home, 30 on /work, 1 on a project |
| Analytics | Google Analytics 4 through gtag | https://www.googletagmanager.com/gtag/js?id=G-QE723JP8VM; cookies _ga, _ga_QE723JP8VM |
| Stored state | One cookie, color-mode, 30 days; nothing in localStorage or sessionStorage |
setCookie() in helpers.js; eval after drag and mode switch |
Note on the probe: its counts of keyframes, transition rules and scrollbar rules read 0 because every rule arrives through @import from styles.css, which the probe does not follow. The source greps above are the evidence for those.
Measurements are computed values at 1440 × 900 unless a phone (390 × 844) is named. Source excerpts are in sources/; per-run outcomes are in steps/*.meta.json.
What it is. A white layer over the whole viewport (#landing-container, #fff, fixed, z-index 110) above the site's #F8F8F8. A field of small grey crosses on a 59.25 px pitch. In the centre, a 473 × 176.75 px logo made of eleven grey rounded blocks (#afafaf80, which reads #d7d7d7 on white; radius 5 px) on an 8 × 3 grid of 58.25 px cells with 1 px gaps, so the logo's pitch equals the crosses' pitch. A small bold "TM" sits at the right end of row 1, just above the n; "A Multi-Platform Creative Company" (bold, 16 px, #AFAFAF80) sits one cell below. Nothing else on the page.
The letter map (column, row of the 8 × 3 grid; the counters of the letters are the 1 px gaps and one empty cell):
| Block | Cells | Letter part |
|---|---|---|
| 1 | col 1, rows 1 to 2 | b, ascender |
| 2 | col 1, row 3 | b, foot |
| 3 | col 2, rows 2 to 3 | b, bowl |
| 4, 5 | cols 3 and 4, row 2 | u, two stems |
| 6 | cols 3 to 4, row 3 | u, base |
| 7 | col 5, rows 2 to 3 | r, stem |
| 8 | col 6, row 2 | r, arm (col 6 row 3 stays empty) |
| 9 | cols 7 to 8, row 2 | n, shoulder |
| 10, 11 | cols 7 and 8, row 3 | n, two legs |
| text | cols 2 to 8, row 1 | holds the TM |
Measured rects: block 1 at (483.5, 361.6) 58.25 × 117.5; block 6 at (602, 480.1) 117.5 × 58.25; block 9 at (839, 420.9) 117.5 × 58.25 (all twelve in steps/01-landing-rest-hover-click.meta.json, block-rects).
DOM.
<div id="landing-container">
<div id="background-container"><svg width="100%" height="100%"><defs>
<pattern id="cross-pattern" class="pattern-script" width="50" height="50" patternUnits="userSpaceOnUse">
<polygon class="crosses-svg" points="4,1.8 2.2,1.8 2.2,0 1.8,0 1.8,1.8 0,1.8 0,2.2 1.8,2.2 1.8,4 2.2,4 2.2,2.2 4,2.2"/>
</pattern></defs><rect width="100%" height="100%" fill="url(#cross-pattern)"/></svg></div>
<div id="logo-landing">
<div class="logo">
<div class="logo-block logo-text"><p class="logo-tm bold">TM</p></div>
<div class="logo-block" data-index="1"><figure class="figure-media"><picture class="media-container">
<img src="…/burn_hoodie_2-300x.jpg" alt="" class="media portrait"></picture></figure></div>
<!-- data-index 2 to 11, same markup, each with its own 300 px still -->
</div>
<p class="bold small">A Multi-Platform Creative Company</p>
</div>
</div>
Styles that matter (commons/logo.css, pages/home.css): .logo { display: grid; grid-template-columns: repeat(8, 1fr); gap: 1px; width: calc(var(--gap) * 8 + 7px); height: calc(var(--gap) * 3 + 2px) }; each block places itself with grid-row and grid-column by data-index; .logo-block figure { opacity: 0; transition: opacity .3s ease }, shown by :hover or .active; #logo-landing { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%) }; html.hide-landing #landing-container { opacity: 0; transform: translateY(5px); pointer-events: none } with transition: opacity .3s ease, transform .3s ease.
How the crosses meet the logo. The cross pattern's tile is set to the pitch by grid(); its container has 7 px padding, so cross centres fall at 9 px + k × pitch from the left. On the landing the SVG is made pitch × 901 + 4 px tall (--landing-grid-height, 53,388.25 px) and flex-centred, so an odd number of tiles straddles the viewport's middle and a row of crosses falls half a pitch above and below centre. Measured: every block edge sits 0.5 px from a cross centre on both axes.
Behaviour (main.js, inside $(window).on('load')):
- Idle: startRandomHighlight() runs setInterval(…, 2000): pick a random block other than the current one, remove .active from all, add it to the new one. Both opacity transitions run at once, so it is a 300 ms cross-fade. Observed in the rest burst (no input): nothing lit for the first frame, then block 6, then 8 (block 6 still fading in the same frame), then 3 (landing-rest-logo-strip.png); block 7 was lit by the time of the eval right after the burst (active-after-burst).
- First hover on any block: clears the interval for good and removes .active; from then on only :hover lights a block (landing-hover-block6.png, landing-hover-block1.png; active-after-hover-2500ms stayed empty).
- Click on any block: html.hide-landing, the layer fades out and drops 5 px over 300 ms, and the homepage that was already rendered behind it is revealed (landing-after-click.png). The blocks hold no links: the click does not open the block's project (a correction to row 13's "opens on a click"; bar-stage-drawers.md has it right).
- Behind the landing: the black BURN logo top left, the two-line claim, and two vertical Swiper slideshows (landscape stills on the left two thirds, portrait stills on the right) whose slides are .project-hover links, so the menu's title pill reacts to them too (landing-after-click.png shows "55. C Bluff Bounce | Bluff Bounce | Matheo Fernandes" because the pointer landed on a slide).
Assets. Eleven 300 px JPEG stills named after talent and campaigns (burn_kevin-durant_kd-300x.jpg, burn_coi-leray-300x.jpg, burn_brady_gun-control-300x.jpg and so on); the cross is a 4 × 4 unit plus drawn as one polygon with 0.4-unit arms, fill: currentColor (#AFAFAF, dark #505050).
Phone. The landing is display: none at 820 px and narrower (@media only screen and (max-width: 820px)). The phone home is one full-bleed vertical Swiper (below 821 px initHomepageSwipers() moves the landscape slides into the portrait slider, so it holds both sets), with the headline in large type and the logo across the full width at the foot, both white under mix-blend-mode: difference (the "translucent blocks" in ../../burnstudio/mobile-top.png are this blend), and the foot bar naming the active slide ("64. C.S Bored Room Copier | Circle | Giraffe Sisters", m-home.png).
Accessibility and motion. Blocks are divs with click handlers: not focusable, no role, no keyboard path past the landing; all stills alt="". The idle cycle never checks prefers-reduced-motion (there is no such query anywhere in the CSS); it is only an opacity cross-fade every 2 s, but it runs until the first hover, including in a background tab.
Rebuild for ETS27 (library-agnostic):
1. Compute the stage pitch once (see 3.2) and draw the cells as a CSS grid whose track size is the pitch minus 1 px with a 1 px gap, so cells sit on the background marks.
2. Describe the word as data: each cell gets {row, col, rowSpan, colSpan, item} where item is a speaker, session or theme. "27", "ETS27" or "free your mind" are just different maps. A small script can turn a pixel font into the map.
3. Each cell is a <button> with an accessible name (the speaker's name) holding a hidden layer (the marker sketch or photo) at opacity 0.
4. Idle: a 2 s timer that moves an is-lit class to a random other cell; stop it on the first pointerenter, on focus, on visibilitychange to hidden, and never start it under prefers-reduced-motion: reduce.
5. Reveal: opacity 0 to 1 over 300 ms ease on hover, focus and is-lit.
6. Click: unlike Burn, open the cell's drawer (the ETS27 drawer model), or hand the stage to the bar.
- Libraries that make it easy: none needed (CSS grid plus a timer). GSAP or Motion only if the reveal becomes a drawn stroke (marker look: SVG stroke-dashoffset).
Gotchas.
- The landing ignores dark mode for its own background: #landing-container is hard-coded #fff, so a dark-mode visitor gets a white page with darker blocks (landing-dark.png), then a black site behind it (home-behind-landing-dark.png).
- The mode cookie is applied in the load handler, after every image has loaded, so a dark-mode visitor sees the light theme first.
- grid() assigns an undeclared gap, which leaks as window.gap.
The math (grid() in helpers.js, run on load and on every resize):
var divisor = windowWidth < 820 ? 8 : 24;
var cellSize = divisor + 1;
gap = ((windowWidth - (4 * cellSize) - 14) / divisor) + 4; // the pitch
$(".pattern-script").attr("width", gap).attr("height", gap); // cross tile = pitch
body.style.setProperty("--gap", gap - 1 + "px"); // cell = pitch - 1
Answer: exact grid math. The expression simplifies to pitch = (W − 18) / d, with d = 24 cells at 820 px and wider and d = 8 below. --gap = pitch − 1. At 1440: pitch 59.25, --gap 58.25 px (measured; grid-vars). At 390: pitch 46.5, --gap 45.5 px (measured). Other widths: 1280 gives 52.58; 1920 gives 79.25; 820 gives 33.42; 819 jumps to 100.13 (the 8-cell phone grid, single column).
The projects grid (pages/work.css): display: grid; grid-template-columns: repeat(8, 1fr); column-gap: 2px; row-gap: calc(var(--gap) + 3px); align-items: center, inside main { padding: 1em 1em var(--gap) } (1em = 10 px, root font 62.5 %). Measured: eight 175.75 px columns, 2 px gutters, 61.25 px row gaps. The column pitch, 175.75 + 2 = 177.75, is exactly three cross pitches at any width: (W − 34)/8 + 2 = (W − 18)/8 = 3 × (W − 18)/24. With the 10 px inset and the crosses centred at 9 px, every 2 px gutter has a cross in its middle.
Answer: thumbnail size classes. Every thumbnail is one project column wide (three cells, 175.75 px at 1440). Only the height varies, by the image's class:
Class (on .media) |
Height rule | At 1440 | Cells tall | Count on /work |
|---|---|---|---|---|
landscape (still) |
calc(var(--gap) * 2) |
116.5 px | 2 (minus 2 px) | 59 |
| none (video, the "C.S" social cuts) | calc(var(--gap) * 4 + 2px) |
235 px | 4 (minus 2 px) | 5 |
portrait (still) |
calc(var(--gap) * 6 + 4px) |
353.5 px | 6 (minus 2 px) | 4 |
(This corrects "one, two or three cells wide, sometimes tall" in bar-stage-drawers.md.) Every height is an even number of pitches minus 2 px, so when a row holds a taller item, align-items: center offsets the shorter ones by whole pitches and they stay on the grid. Row pitches come out as 3, 5 or 7 pitches; the measured row tracks were 116.5, 353.5, 235, 116.5, 353.5, … px.
Captions. .project-code-container hangs under the image (position: absolute; bottom: -1px; transform: translate(-50%, 100%); height: calc(var(--gap) - 1px)), so the caption is one cell tall (--gap − 1, 57.25 px at 1440) and starts 1 px under the image, inside the 61.25 px row gap: "N 58 TYPE C" with the labels in Politics 9 px uppercase and the values in Camera Plain 14 px, colour #00000080. On the phone the caption adds "DIRECTOR …".
The "random spots". Not random: a fixed 23-item cycle of seven column pins, on top of CSS grid's default sparse auto-placement:
.project.work-project:nth-child(23n + 1), .project.work-project:nth-child(23n + 13) { grid-column: 2; }
.project.work-project:nth-child(23n + 5) { grid-column: 7; }
.project.work-project:nth-child(23n + 8) { grid-column: 3; }
.project.work-project:nth-child(23n + 12) { grid-column: 8; }
.project.work-project:nth-child(23n + 15), .project.work-project:nth-child(23n + 22) { grid-column: 6; }
When a pinned column lies to the right of the placement cursor, the cells in between stay empty; when it lies to the left, the item wraps to the next row and leaves the rest of the row empty. Measured rows (item-rects): row 1 holds items 1 to 6 in columns 2, 3, 4, 5, 7, 8; row 2 items 7 to 12 in 1, 3, 4, 5, 6, 8; row 3 items 13 to 17 in 2, 3, 6, 7, 8; row 4 items 18 to 23 in 1, 2, 3, 4, 6, 7. The whole page is twelve rows, 3,088 px tall (work-full.png). Filtering (All, Commercial, Cinematic Social) hides items with display: none and sorts hidden ones to the end, so the pins apply to the visible set and the scatter re-forms.
Phone. One column (grid-template-columns: 1fr, every item grid-column: 1 !important): landscape stills 370 × 230.5 (5 pitches minus 2 px), portraits 276 × 414.5 centred, videos 370 × 184 (the desktop :not(.landscape):not(.portrait) rule outranks the phone rule). The cross pitch is 46.5 px and the crosses read as small "+" marks (m-work-00-top.png).
Rebuild for ETS27.
1. One stage variable: --pitch: calc((100vw - 18px) / var(--cells)) with --cells: 24 and 8 under 820 px. This is pure CSS once scroll bars are hidden, which ETS27 wants anyway; otherwise compute it from document.documentElement.clientWidth. Recompute on resize.
2. Background marks: an SVG pattern (Burn's polygon), or background-image of one inline-SVG cross with background-size: var(--pitch) var(--pitch) and background-position: 7px 7px. With the marker look, draw the cross as a tiny marker tick.
3. Blocks the mind places declare a size from a short list in pitches: for example 3 × 2 (a speaker card), 3 × 4 (a session), 3 × 6 (a portrait), 6 × 4 (a map), so heights are calc(var(--pitch) * k - 2px) with k even, inside a CSS grid of 8 tracks with 2 px gaps and 10 px insets.
4. Placement: let the model choose order and size; let the layout choose the column from a pin cycle like Burn's or a seeded random, never a free x/y. That keeps "scattered but never off-grid" a property of the layout, not something the model has to get right.
5. Libraries: none. CSS grid does the packing; grid-auto-flow: row (sparse) is what makes the holes.
Gotchas. The pitch depends on viewport width only, so a tall phone in landscape gets the 24-cell desktop grid at 820 px and wider. The CSS breakpoints use max-width: 820px while grid() switches at < 820, so at exactly 820 px the one-column phone layout runs on the 24-cell pitch (33.42 px). Thumbnails sit 1 px off the crosses by design; anything that adds a border or outline breaks the alignment. Full-page screenshots here show no video thumbnails (see "Not confirmed").
What it is. The menu is a row of pills 31.4 px tall, radius 3 px, fill #7c7c7c (dark mode #5b5b5b), text #afafaf, 1 px apart: BURN 76.0 px wide at x 35, "∷ Work" 71.5, "∷ All" 55.5, "List" 61.9 (y 156.4). The fifth pill, #nav-title-container, rests at width 0 just right of "List" (x 303.9). On hover it grows to the width of its text, 441.7 × 31.4 px for "06. C Ban Assault Weapons | Brady | Greg Hardes & Aaron Chapman" (work-hover.png), with the code in Politics 9 px uppercase and the title in Camera Plain 14 px.
DOM.
<div id="nav-title-container" class="nav-container-animate">
<div class="nav-element" id="project-name-container">
<span class="mono uppercase" id="nav-project-code"></span>
<p class="small" id="nav-project-name"></p>
</div>
</div>
<!-- each thumbnail: -->
<a href="…/work/ban-assault-weapons" data-code="06. C" data-title="Ban Assault Weapons | Brady | Greg Hardes & Aaron Chapman"
data-ajax="work/ban-assault-weapons" class="project work-project project-hover">…
<div class="hover-preview-container hide-on-mobile"><video playsinline muted loop preload="none" class="media">…</video></div>
</a>
Mechanism (main.js, "HOVER PROJECT"): a delegated mouseenter on .project-hover copies data-code and data-title into the pill (and into the player bar's title, used later on the project page), measures the inner #project-name-container, which is position: absolute and so keeps its natural width while the container is 0 wide, writes that width (and the height as max-height) inline on #nav-title-container, adds header.show-title (text opacity 0 to 1) and calls play() on the card's preview video. mouseleave sets the width back to 0, removes show-title and pauses the video. The CSS: .nav-container-animate { width: 0; max-height: 31.4px; overflow: hidden; transition: background-color .3s ease, width .3s ease, max-height .3s ease, margin-right .3s ease, backdrop-filter 1s ease .3s } and .nav-container-animate .nav-element { opacity: 0; transition: opacity .3s ease }. Thumbnails only accept the pointer once the header has shown (html:has(header.show) .project-hover { pointer-events: auto }).
Answer: pill timing and easing. Width and text opacity move together over 300 ms with CSS ease (cubic-bezier(.25, .1, .25, 1)), starting in the frame the pointer enters. Measured with an in-page requestAnimationFrame sampler (pill-width-samples-enter): 0 px at 69 ms, 11.4 at 78, 101.9 at 139, 218.3 at 203 (74 %), 262.9 at 261, 292.5 at 349, final 294.5 px at 394 ms, the text opacity tracking the same fraction (0.04, 0.35, 0.74, 0.89, 0.99, 1). Leaving runs the same curve backwards (241 px at 168 ms to 0 at 460 ms). There is no delay and no debounce. The preview video's container switches to opacity 1 instantly under :hover (no transition) inside @media (hover: hover); 25 of the 68 projects have a preview.
The pill re-targets as the pointer travels. Crossing other cards on the way out re-extends the pill with their titles before it retracts: the unhover burst catches it retracting with "Bored Room C…", the portrait the pointer crossed (work-unhover-pill-header-strip.png, frame 00), and the leave samples show the pill start to close (283.3 px, text 0.96), re-open to the crossed card's narrower title (271.6 px, text back to 1), then close.
Bursts. work-hover-pill-header-strip.png: frame 00 (+370 ms) catches the pill as an empty grey stub, frame 01 (+620 ms) fully out with text. work-unhover-pill-header-strip.png: frame 00 mid-retraction, frame 01 gone.
Phone. No hover and no desktop menu (#header is opacity 0 on non-project pages at 820 px and narrower); the foot bar (3.5) does the job.
Accessibility. Pointer only: no focus handler, no aria-live, so keyboard and screen-reader users never get the title. The pill text is #afafaf on #7c7c7c, about 1.9:1 contrast. Clicked buttons do show Chromium's focus ring (list-open-light.png, work-dark.png) despite outline: … auto 0px.
Rebuild for ETS27 ("the bar speaks for the canvas"):
1. Give the bar a detail slot after its fixed pills.
2. On pointerenter and on focus of any stage block, write the block's label into the slot and open it; on pointerleave and blur, close it after a short grace period (about 80 ms) so crossing the 2 px gutters between blocks does not make it flicker, which Burn's does.
3. Animate the width and opacity together over 300 ms ease. Modern CSS can do it without measuring: interpolate-size: allow-keywords with a width: 0 to width: auto transition (Chromium), or a grid track transition from 0fr to 1fr (all evergreen browsers). Burn's measuring approach works everywhere.
4. Put aria-live="polite" on the slot, and keep the text out of the accessibility tree while closed.
5. Libraries: none needed. Motion's layout animations or GSAP Flip make the width change trivial if the bar is a React or vanilla component.
Gotchas.
- The pill state leaks across page swaps: the AJAX swap removes the hovered card without a mouseleave, so after a project and Back the pill is still out with the old title (pt-02-back-settled.png). Clear the slot on every route change.
- The pill always grows to the right; with the menu dragged near the right edge it runs off-screen (drag-05-moved-menu-hover-pill.png, "…Jacob Pro").
- The open pill covers the captions under it (work-hover.png).
What it is. A 12.5 × 12.5 px four-arrow glyph (an inline SVG with fill: currentColor), 6 px left of the first pill and vertically centred on it (#menu-drag, position: absolute; top: 50%; left: -6px; transform: translate(-100%, -50%); cursor: grab). It is always visible; the whole header appears 600 ms after the load event (a 300 ms fade), which is the "appears" the owner saw. Dragging it moves the entire header (clock line, pills, copyright lines), 303.9 × 130.7 px on /work.
Mechanism. jQuery UI: $("#header").draggable({ handle: "#menu-drag", containment: "window" }), so jQuery UI's defaults apply too (scroll: true, scrollSensitivity: 20, scrollSpeed: 20). The header is position: fixed and jQuery UI writes top and left inline on every move. CSS: #header:not(.ui-draggable-dragging) { transition: top 1s ease, left 1s ease, opacity .3s ease, color .3s ease }, so while dragging there is no transition (1:1 follow, cursor grabbing); after the drop nothing animates until a script sets top/left again.
Measured (steps/03-work-drag-menu.meta.json): start (10, 126.5); pointer held at (640, 520) puts the header at (628, 474.5); released at (900, 700): (888, 654.5), the exact pointer delta. Shots: drag-01 to drag-07, drag-sheet.png.
What persists. The inline position lasts as long as the document. AJAX navigation keeps it, because the header lives outside <main>. Only the project route moves it (to the bottom centre, gliding over 1 s), and returning to /work keeps that bottom position. Nothing is stored: cookies hold only GA and color-mode, local and session storage are empty, and a reload puts the menu back at (10, 126.5) (drag-07-after-reload.png). The owner's "the menu appears on the bottom" probably comes from this: on /work the menu starts top left, and it sits at the bottom after a visit to a project.
Phone. #menu-drag is display: none and the header is pinned to the bottom (top: auto !important; bottom: 0 !important), so there is no dragging on the phone.
Accessibility. The handle is a div: no keyboard move, no label, no snap back.
Rebuild for ETS27 ("a bar you can move"):
1. Use pointer events on a real handle (<button aria-label="Move the bar">) with setPointerCapture.
2. Move the bar with transform: translate3d() rather than top and left.
3. Clamp to the viewport on every move using the bar's current size, and again whenever the bar changes width (the detail slot) or the window resizes.
4. Remember the position in sessionStorage for the visit.
5. Snap back to the dock: double-click the handle, or drop near the bottom centre, with a 300 ms ease glide (Burn's 1 s glide feels slow).
6. Keyboard: arrows move the bar by one grid pitch while the handle has focus; Escape docks it.
7. Libraries: Motion (drag, dragConstraints, dragMomentum={false}, dragSnapToOrigin), GSAP Draggable (bounds: window), interact.js or @use-gesture; any of them makes the clamp and the snap a few lines.
Gotchas.
- Containment is measured once, at drag start, from the header's size at that moment. With the title pill still open the header was wider, so a drag to the bottom-right corner stopped at x 852.9 instead of 1136 (header-corner, drag-06-contained-corner.png).
- Dragging near an edge scrolls the page (jQuery UI's scroll: true): drag-06 shows the grid scrolled 60 px.
- The header blocks clicks only on its pills and handle (pointer-events: none on the rest), but its text still overlays captions wherever it is dropped.
What it is. A fixed bar at the bottom of the phone viewport: container 390 × 51.4 px with 10 px padding, bar 370 × 31.4 px, #7c7c7c, radius 3 px, text #afafaf: the code in Politics ("45. C"), the title in Camera Plain 14 px ("Ciel Azur | Jaymie | Caraz"). It carries a backdrop-filter: blur(45px) that the opaque fill hides.
DOM.
<div id="mobile-title-container" class="hide-on-desktop">
<div id="mobile-project-title-home" class="nav-element">
<span class="mono uppercase" id="mobile-project-code-home">00.XX</span>
<p class="small" id="mobile-project-name-home">Project title goes here | Client Name</p>
</div>
</div>
Mechanism (observerprojects.js, started by an inline script on /work and by ajax.js after a swap): activateCenteredProject('.projects-grid .project', 'active', changeMobileProject) binds scroll and resize on window; every event measures every project's getBoundingClientRect(), keeps those on screen, picks the one whose centre is nearest innerHeight / 2, moves an active class to it and writes its data-code and data-title into the bar. The text swaps instantly, with no transition and no throttle. On the phone home the same bar is driven by Swiper's slideChangeTransitionEnd.
Measured (steps/07-phone-work-bar.meta.json):
- At the top: "24. C Ball Was Out | Yony | Coming of Age". This is the second project; the first sits too high to be nearest the middle.
- At 1500 px: "55. C Bluff Bounce | Bluff Bounce | Matheo Fernandes"; the active card's centre is at 392 against a middle of 422.
- At 4200 px: "45. C Ciel Azur | Jaymie | Caraz".
- Shots: m-work-00-top.png, m-work-01-scroll-1500.png, m-work-02-scroll-4200.png.
The phone menu. A full-width "∷ Menu" pill at the top under a one-line header ("BURN: A MULTI-PLATFORM CREATIVE COMPANY", "MODE LIGHT DARK"). Tapping it shows "∷ Close", two grey panels (Work, Talent, About, Contact, Shop | All, Commercial, Cinematic Social) and the BURN logo as eleven grey blocks across the width (m-work-03-menu-open.png). The panels rise 5 px and fade over 300 ms.
Accessibility. The bar is not a live region and cannot be tapped (it is information only).
Rebuild for ETS27:
1. Use an IntersectionObserver with rootMargin: '-50% 0px -50% 0px': a zero-height line across the middle of the viewport, so whichever block crosses it is "current", with no scroll handler and no layout reads.
2. Write the block's label into the bar's detail slot, the same slot as the desktop hover (3.3), so one bar speaks for the canvas on both.
3. Cross-fade the text over 150 to 200 ms.
4. Make the bar tappable to open the current block's drawer.
5. aria-live="polite", rate-limited.
6. Libraries: none; GSAP ScrollTrigger (start: 'center center', onToggle) if scroll effects are already using it.
Gotchas. Burn's version reads layout for all 68 cards on every scroll event. The top of the page names the second item, not the first.
Mode switch. Two text buttons, "Light Dark", after "MODE:" in Politics 9 px uppercase above the pills (or in the phone header); the active one is underlined (offset 2 px). applyMode() swaps html.light for html.dark, marks the button and writes the cookie color-mode for 30 days. Dark: page #070707, text #FFFFFF80, pills #5b5b5b, crosses #505050; the page colours transition over 300 ms ease (work-dark.png, mode-dark-sheet.png; the burst's first frame landed after the transition had finished).
Live clock. clock.js runs every second: new Intl.DateTimeFormat('en-GB', {timeZone: 'Europe/Berlin', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false}), printed as 'GMT+1 ' + time into #time-nav (50 px wide). Measured "GMT+1 05:07:47", then "05:07:49" two seconds later, at 03:07:48 UTC, which is Berlin summer time (CEST, UTC+2): the label is static and is wrong during summer time (late March to late October, about seven months a year).
List view. #list-trigger toggles html.show-list. .projects-list is a fixed, full-viewport sheet, rgba(255, 255, 255, .95) (dark rgba(0, 0, 0, .95)), z-index 10 under the menu, fading in over 300 ms ease while rising 5 px. It is a four-column grid (1fr 1fr 2fr 6fr: number, type, director, name) of tiny Politics uppercase rows, 5 px apart, each row a data-ajax link to the project; the four-dot selector by "List" turns on (list-open-light.png, list-open-dark.png). The sheet holds 204 rows: the 68 projects listed three times over, numbered 01 to 204 (3,088 px of content), so it runs far past a 900 px viewport (row 57 is the last fully visible). Its overflow is visible, so it is not a scroll container, and html.show-list sets the root to overflow: hidden, so rows 58 to 204 cannot be scrolled into view (computed styles; a real wheel or touch scroll was not tried).
Rebuild for ETS27. The clock idea maps to "time to the summit" or Austin time in the bar's quiet line; label it from the zone (timeZoneName: 'short'), not by hand. Apply the mode before first paint (a class from the cookie in an inline script in <head>). If a list view exists (the agenda as a list), give it overflow: auto and keep the bar above it.
All durations are CSS values confirmed by the captures. fsit2 was heavily loaded during the runs (load average about 25), so burst frames landed 220 to 1,070 ms apart (single shots took 110 to 1,200 ms); the bursts resolve the 1 s glide but not the 300 ms fades. Burst timestamps are when each frame finished.
Landing to home (click a block). Only the landing moves: opacity 1 to 0 and translateY 0 to 5 px over 300 ms ease, pointer-events: none at once. The homepage was rendered behind it all along. landing-click-sheet.png: frame 00 (+224 ms) still shows the landing, frame 01 (+904 ms) the homepage.
Grid to project (click a thumbnail, AJAX).
1. Click: preventDefault, body.loading (the wait cursor everywhere), GET /ajax/null/main/work/<slug>.
2. Response: one transitionend listener on #main, then body.hide-content: <main> fades to opacity 0 over 300 ms.
3. On transitionend: <main>'s HTML replaced, scroll to 0, videos and links re-wired, hide-content removed (fade in over 300 ms), body[data-page="project"]; CSS collapses the menu pills (#nav-container to max-height: 0, width 100 px) and opens the player bar (Back, code and title, Play with a progress line and time, fullscreen, Mute); history.pushState({slug, elementHref, lang, mainContent, dataPage, previousState}).
4. +400 ms: the header's top/left are set to the bottom centre (its bottom edge 50 px above the viewport's; a direct visit uses 20 px), and it glides there over 1 s ease.
5. +2000 ms: the script clicks Play: the film plays, the credit ticker fades out over 300 ms and Marquee3k pauses.
Observed (pt-in-sheet.png):
- 00 (+841 ms): still the grid, title pill out.
- 01 (+1910): the poster in the 1420 × 880 video box (10 px inset, 8 px radius), drawn letterboxed at about 1376 × 880 with #F8F8F8 showing 22 px either side (pixel edges in pt-in-01.png), the credit ticker across the middle ("N 58 DIRECTOR Matheo Fernandes PRODUCED Burn …"), the player bar still at the menu's old top-left spot (pt-in-01.png).
- 02 (+2648): the bar mid-glide (pt-in-02.png).
- 03 (+3524): settled at the bottom centre (top 768.7, left 362.2, 714.9 × 81.3).
- 05 (+4938): the film playing from its black opening, ticker gone; 06 to 15 show the film's opening card and first shot.
- pt-01-project-settled.png: the film playing with the bar at the bottom.
Answer: what a project page looks like and how it returns. A project page is the film and nothing else: a video element fitted to the viewport with 10 px insets and 8 px corners (the poster draws letterboxed inside it, about 1376 × 880 at 1440 × 900; the playing film fills 1420 × 861), a one-line credit ticker (number, director, producer, running time) scrolling slowly across the middle until play, and the menu turned into the player bar at the bottom centre. It returns by the bar's "Back": when the history state has a previous entry it calls history.back(); popstate re-renders the stored main HTML from the history state (no network) with the same 300 ms fade out and in (pt-back-sheet.png: frame 00 at +849 ms shows the film washed out mid-fade, frame 01 the grid). The grid comes back at the top (scroll position is not restored), the menu keeps the player bar's top and left (768.7, 362.2), so the taller work header now ends at the viewport's bottom edge, about 48 px left of centre, and the last title pill is still open (pt-02-back-settled.png, verify/verify-back.png). Without history (a direct visit) Back loads /work by AJAX.
Direct visit to a project URL. Server-rendered with data-page="project": the bar placed bottom: 20px and centred, no autoplay, the ticker with the running time ("TIME 14:53", project-direct.png; project-direct-later.png 2.5 s on shows the ticker moved and the poster unchanged). The bar's code reads "23.CS" here, a placeholder hard-coded in the template on every page; reached from the grid, the bar shows the hovered card's "58. C" and full title instead.
On the phone the project bar is two rows at the foot (progress on top, then Back and title, 370 × 65 px; m-project.png).
Within a page. Hover pill: 300 ms ease, width and text together (3.3). Thumbnail preview: instant. Landing idle: 300 ms cross-fade every 2 s. List sheet and phone menu: 300 ms fade with a 5 px rise. Mode: 300 ms on the page colours. Menu glide: 1 s ease whenever a script moves it.
Rebuild note for ETS27. Burn's page transition is a fade of the content plus the persistent bar moving to its new job, which is exactly the bar-stage-drawers model: keep the bar outside the swapped region, let the route set the bar's mode and position, and fade or slide only the stage. The View Transitions API (document.startViewTransition, with the bar given its own view-transition-name) does this natively, and Back gets the same treatment when the popstate re-render runs inside it; Barba or Swup do it for multi-page sites.
Contact sheets: landing-rest-sheet.png and landing-rest-logo-strip.png, landing-click-sheet.png, work-hover-pill-sheet.png and work-hover-pill-header-strip.png, work-unhover-pill-sheet.png and work-unhover-pill-header-strip.png, drag-sheet.png, list-open-sheet.png, mode-dark-sheet.png, mode-list-states-sheet.png, pt-in-sheet.png, pt-back-sheet.png, phone-sheet.png.
All in shots/. Desktop is 1440 × 900 at 1x; phone is 390 × 844 at 2x (780 × 1688 files).
| File | What it shows | Device |
|---|---|---|
landing-rest-00.png to -09.png, landing-rest-sheet.png, landing-rest-logo-strip.png |
Landing with no input, a frame about every 700 ms: blocks 6, 8, 3 light in turn | desktop |
landing-hover-block6.png, landing-hover-block1.png |
Hover lights only the hovered block; the idle cycle has stopped | desktop |
landing-click-00.png to -07.png, landing-click-sheet.png |
Click on a block: landing gone by frame 01 | desktop |
landing-after-click.png |
Homepage behind the landing: black logo, claim, two vertical slideshows, title pill out for the slide under the pointer | desktop |
work-rest.png |
/work at rest: pills, drag handle, clock, scatter on the cross grid | desktop |
work-full.png |
The whole /work page (12 rows, 3,088 px); the five video cards show only captions | desktop |
work-hover.png |
Title pill out for "06. C Ban Assault Weapons …" | desktop |
work-hover-pill-00.png to -07.png, -sheet.png, -header-strip.png |
Pill growing (frame 00 an empty stub) then full | desktop |
work-unhover-pill-00.png to -05.png, -sheet.png, -header-strip.png |
Pill retracting with "Bored Room C…" picked up on the way out | desktop |
drag-01-handle-hover.png |
Pointer on the four-arrow handle | desktop |
drag-02-mid-held.png, drag-03-mid-held-2.png |
Menu following the held pointer | desktop |
drag-04-released.png |
Menu left where it was dropped | desktop |
drag-05-moved-menu-hover-pill.png |
Title pill growing from the moved menu, clipped at the right edge | desktop |
drag-06-contained-corner.png |
Corner drag: page scrolled 60 px, menu stopped short of the right edge | desktop |
drag-07-after-reload.png |
After reload the menu is back top left | desktop |
drag-sheet.png |
The seven drag states together | desktop |
list-open-00.png to -05.png, list-open-sheet.png, list-open-light.png |
List sheet open (light) | desktop |
mode-dark-00.png to -05.png, mode-dark-sheet.png, work-dark.png |
/work in dark mode | desktop |
list-open-dark.png |
List sheet in dark mode | desktop |
landing-dark.png |
Landing in dark mode: still white | desktop |
home-behind-landing-dark.png |
Homepage in dark mode: white logo on black | desktop |
mode-list-states-sheet.png |
The five mode and list states together | desktop |
pt-00-work.png |
/work before clicking a card | desktop |
pt-in-00.png to -15.png, pt-in-sheet.png |
Card to project: fade, poster with ticker, bar gliding down, film plays | desktop |
pt-01-project-settled.png |
Project page, film playing, bar at the bottom centre | desktop |
pt-back-00.png to -11.png, pt-back-sheet.png |
Back: film fading out, grid returning | desktop |
pt-02-back-settled.png |
Grid after Back: menu still at the bottom, stale title pill | desktop |
project-direct.png, project-direct-later.png |
Project URL loaded directly: poster, ticker with running time, bar 20 px off the bottom, "23.CS" placeholder | desktop |
project-back-hover.png |
Pointer on Back: no hover style | desktop |
m-work-00-top.png |
Phone /work top: Menu pill, stacked cards, bar "24. C Ball Was Out …" | phone |
m-work-01-scroll-1500.png |
Bar "55. C Bluff Bounce …" | phone |
m-work-02-scroll-4200.png |
Bar "45. C Ciel Azur …" | phone |
m-work-03-menu-open.png |
Phone menu open: Close, two panels, logo blocks | phone |
m-project.png |
Phone project page: two-row player bar at the foot | phone |
m-home.png |
Phone home: film still, difference-blend headline and logo, foot bar | phone |
phone-sheet.png |
The six phone states together | phone |
verify/verify-back.png |
Verifier: /work after a project and Back; the menu sits where the player bar was, with the stale title pill | desktop |
verify/verify-dark.png |
Verifier: the same state in dark mode (#070707 page, #505050 crosses) |
desktop |
Earlier screenshots of the site, reused for reference and not duplicated here: ../../burnstudio/desktop-top.png, ../../burnstudio/mobile-top.png, ../../burnstudio/work-desktop-top.png, ../../burnstudio/work-desktop-s1.png, ../../burnstudio/work-mobile-top.png.
commons/fonts.css, variables.css, globals.css, main.css, nav.css, logo.css; elements/player.css; pages/about.css, contact.css, work.css, talents.css, home.css; blocks/columns.css, list.css).observerprojects.js, clock.js, main.js, ajax.js, player.js.dig for NS, A and PTR of burnstudio.co.sources/js-excerpts.js, sources/css-excerpts.css, sources/markup-excerpts.html.steps/01-*.json to steps/08-*.json (run with design/tools/capture-remote.sh on fsit2) and their outcome records steps/*.meta.json (computed CSS, rects, evals, probe; DOM dumps trimmed to 3 KB).steps/verify-01-desktop.json and steps/verify-02-desktop.json run on fsit2, outcome records shots/verify/verify-01-desktop.meta.json and shots/verify/verify-02-desktop.meta.json; dig from fsit2 and a RIPE WHOIS lookup for 92.205.148.223. Details in VERIFY.md.../../INSPIRATION.md (rows 13, 14), ../../bar-stage-drawers.md, ../../components-to-copy.md, ../_examples/*.json./ajax/<lang>/<id>/<slug> returning JSON). The site's composer lock or the panel would show the version; neither is public.video-playing, paused: false, readyState: 4) but painted nothing in headless Chromium, while the project film painted fine. The verifier narrowed it for the thumbnails: currentTime advances while videoWidth stays 0 and no frame is decoded, and this Chromium answers canPlayType "probably" for H.264 (avc1) and "" for HEVC (hvc1). So the video track is missing or in a codec this build cannot decode, and HEVC fits; the codec itself is still unconfirmed (no media download). A browser that decodes HEVC (Safari, or Chrome on macOS) would show them; a headed run of the same Chromium build would not help, since it reports no HEVC support.swiper@11, so a later 11.x can arrive unannounced. Which of the two sliders triggers the loop warning was not isolated.Verifier pass on 2026-10-22 UTC (the evening of 2026-10-21 local). The aim was to refute the teardown's claims, not to restate them. Result: 69 claims held, 17 statements were wrong or overstated and are corrected in place in README.md and component.json (with 6 additions from new evidence), and 10 points stay unconfirmed.
README.md and component.json and listed every stack, mechanism, timing and measurement claim (96, grouped below).steps/*.meta.json records (evals, css steps, probes, burst times, DOM dumps), sources/js-excerpts.js, sources/css-excerpts.css, sources/markup-excerpts.html and the steps files. Arithmetic redone: the pitch formula, the cell and column pitches, the cross offsets, the logo cells, rows 1 to 4 of the grid by CSS grid's sparse auto-placement, the ease curve at 134 ms, the contrast ratio, the drag deltas and the containment stop.landing-rest-logo-strip, landing-click-sheet, landing-after-click, work-hover-pill-header-strip, work-unhover-pill-header-strip, drag-05-moved-menu-hover-pill, drag-06-contained-corner, list-open-light, mode-list-states-sheet, pt-in-sheet, pt-back-sheet, pt-02-back-settled, project-direct, m-work-03-menu-open, m-home, m-project, and the verifier's verify/verify-back and verify/verify-dark.project-direct.png, pt-in-01.png, pt-in-03.png and pt-01-project-settled.png with a throwaway script kept outside the project.design/tools/capture-remote.sh: steps/verify-01-desktop.json (32 steps, 0 step errors, 0 page errors, 30.4 s; results shots/verify/verify-back.png, shots/verify/verify-dark.png, shots/verify/verify-01-desktop.meta.json) and steps/verify-02-desktop.json (14 steps, 0 step errors, 0 page errors, 12.6 s; shots/verify/verify-02-desktop.meta.json). Requests to the site: two loads of /, four of /work, the one /ajax/ fetch the page makes when a card is clicked, and one HEAD of /work for the headers. The Swiper banner was read once from jsDelivr with cache: 'force-cache', and the /work HTML once from the browser cache with only-if-cached (no network). Everything else came from the live DOM, the CSSOM (walked through the @import rules) and the site's own global functions read with String(fn) in the page, plus the load handler jQuery stores.dig for NS, A and PTR from fsit2, and one RIPE WHOIS query for 92.205.148.223.Stack
data-kirby-url on the 5 nav items; every image URL uses Kirby's media folders and thumb names (the folder name is corrected in section 3)./work returned content-type, date, server: Apache, x-content-type-options: nosniff, x-powered-by: PHP/8.1.34 and nothing else (no cf-ray, x-cache, age or via).ns17 and ns18.domaincontrol.com, A 92.205.148.223, no PTR.a[data-ajax] handler, /ajax/null/main/<slug> (the run logged /ajax/null/main/work/antropico), JSON status, content, dataPage, the transitionend swap and pushState with slug, elementHref, lang, mainContent, dataPage, previousState.jQuery and $; no generator meta.$.fn.jquery, $.ui.version); draggable is used once, on #header with handle #menu-drag and containment window; defaults scroll: true, scrollSensitivity: 20, scrollSpeed: 20.#landscape-swiper (vertical, slidesPerView: 'auto', 14 slides, 945 px wide on the left) and #portrait-swiper (vertical, 9 slides, 473 px wide on the right), every slide a .project-hover link.initProjectMarquee() is Marquee3k.init({selector: 'marquee-project'}) plus a show class; data-speed=".25" in the markup.@keyframes and 0 prefers-reduced-motion rules through all 14 imports; tokens .3s and ease; the probes found no GSAP, Lenis, Motion, Lottie or canvas and 0 running animations.::-webkit-scrollbar { display: none; appearance: none; width: 0 !important } in commons/globals.css, the only scrollbar rule; scroll-behavior: auto.@font-face rules, Camera Plain normal (ABCCameraPlain-Regular-Trial.woff2), Camera Plain bold (ABCCameraPlain-Medium-Trial.woff2), Politics normal (Politics-Book.woff2)./, 30 on /work (25 hover previews and 5 thumbnails), 1 on the project; lazyVideos() is an IntersectionObserver on .autoplay that calls play() and adds video-playing.G-QE723JP8VM (inline config); cookies _ga and _ga_QE723JP8VM.setCookie writes color-mode for days * 864e5 ms with 30 days; local and session storage empty after a drag.@import; the verifier's walk followed them (16 sheets, the one cross-origin Swiper sheet unreadable).Landing
#fff, z-index 110, 1440 × 900, opacity .3s ease, transform .3s ease; hide-landing gives opacity 0, translateY(5px), pointer-events: none.--gap 58.25, logo 473 × 176.75 (8 × 58.25 + 7 by 3 × 58.25 + 2); blocks rgba(175, 175, 175, .5), radius 5 px, which is #d7d7d7 on white.block-rects sit on the 8 × 3 cells exactly as the table says.setInterval(…, 2000) with a loop that picks a different random block; the strip shows nothing, then 6, then 8 with 6 still fading, then 3.hasHovered flag; active-after-hover-2500ms empty).hide-landing for any .logo-block (the TM text block included); the URL stays /; the block markup holds no link.landing-after-click.png): the black BURN logo, the two-line claim, the two slideshows and the pill "55. C Bluff Bounce | Bluff Bounce | Matheo Fernandes" for the slide under the pointer.burn_hoodie_2, burn_kevin-durant_kd, burn_coi-leray, burn_house-of-leon, burn_storm-reid, burn_anthony-edwards, burn_lamelo-ball, burn_brady_gun-control, burn_deloitte_senegal, burn_mvmi, burn_hoodie_1.fill: currentColor from #background-container { color: #AFAFAF }; dark mode rgb(80, 80, 80).m-home.png), #landscape-container with mix-blend-mode: difference, the logo 370 px wide at the foot.mode-list-states-sheet.png).getCookie('color-mode') and calls applyMode; with the dark cookie set, <html> had no class at readyState "interactive", and the cached /work HTML starts <html lang="en">, so the first paint is light.window.gap leaks (59.25 on both pages).Grid
align-items: center, flow row; main padding 10 10 58.25 px; root font 10 px.item-rects match both the README and the sparse auto-placement worked by hand.work-full.png is 1440 × 3088; html computes 3,088 px).handleProjectsFilters toggles .hidden (display: none) and, for a category, sorts hidden items last and re-appends them; "all" sorts by data-order.Title pill
#7c7c7c (dark #5b5b5b), text #afafaf; BURN 75.97 px at x 35, Work 71.47, List 61.94, the title pill at x 303.875 with width 0 (All is 55.5 by subtraction); open 441.7 × 31.4 px.mouseenter and mouseleave, show-title, play() and pause() on the preview, as quoted.ease (cubic-bezier(.25, .1, .25, 1) gives 74.4 % at 134 of 300 ms; measured 74.1 %) with the text opacity tracking the width; leaving runs 241 px to 0 between 168 and 460 ms; the re-target dips to 283.3 px and opens again to 271.6 px with the text back at 1.(hover: hover).#afafaf on #7c7c7c: 1.90:1.pt-02-back-settled.png, verify-back.png).drag-05: "…Jacob Pro").list-open-light.png, Dark in verify-dark.png).Draggable menu
grab.top: calc((var(--gap) * 2) + 10px), set 600 ms after load, faded in over 300 ms.ui-draggable-dragging, a computed transition of only opacity and colour, cursor grabbing; inline top and left move by exactly the pointer deltas (618, 348) and (260, 180).drag-06 than in drag-05 (y 155 to 95), which is three move events inside the 20 px band times scrollSpeed 20.#controls-nav, #menu-drag, #copyright-nav { display: none !important } and the header pinned to the bottom.Phone bar
#7c7c7c, radius 3 px, backdrop-filter: blur(45px) opacity(1).$(window).on('load', …) on /work and again by handleNavActiveLink after an AJAX swap to /work.m-work-03-menu-open.png): Close, the Work, Talent, About, Contact, Shop panel and the All, Commercial, Cinematic Social panel, eleven blocks; the foot bar stays.slideChangeTransitionEnd on the portrait slider calls changeMobileProject.Furniture
applyMode swaps the html class and writes the cookie; "MODE: Light Dark" with the active one underlined, text-underline-offset: 2px; dark page rgb(7, 7, 7), text rgba(255, 255, 255, .5), pills rgb(91, 91, 91), crosses rgb(80, 80, 80); html transitions colour and background over .3 s.'Europe/Berlin', the static 'GMT+1 ' prefix, 50 px wide; the agent's own eval read "05:07:48 CEST" at 03:07:48 UTC.html.show-list; fixed 1440 × 900, rgba(255, 255, 255, .95) (dark .95 black), z-index 10, 300 ms opacity and transform; 1fr 1fr 2fr 6fr; 5 px gaps; rows are a.project.list-project with data-ajax; the selector dot at opacity 1; row 57 is the last fully visible ("57. Caraz The Life Cycle of a Grey Ant | Trailer").Flow and shots
landing-click-sheet.png): frame 00 the landing with block 1 lit under the pointer, frame 01 the homepage./ajax/null/main/work/antropico request; handleNavActiveLink runs inside the transitionend callback, sets the header to calc(100vh - <height>px - 50px) after 400 ms and clicks Play after 2000 ms; the bar settles at (362.2, 768.7), 714.9 × 81.3, centred, 50 px above the bottom; the pt-in-sheet.png frames are as described.checkBackTrigger is link.classList.contains('back-trigger') && history.state?.previousState; getInitialState gives the first entry a full state on load, so Back after a card calls history.back(); no new /ajax/ request after Back; scroll 0; a direct visit's Back loads /work by AJAX.bottom: 20px, centred (423.8 + 592.4 / 2 = 720), paused, ticker is-init show, "TIME 14:53" and "23.CS" (project-direct.png); the phone bar 370 × 65 px in two rows (m-project.png).work-full.png 1440 × 3088.Each item was wrong or overstated; the text was corrected in place where named.
/media/site/<hash>-<timestamp>/. True: every image URL seen is under /media/pages/<page>/<hash>-<timestamp>/ (landing stills /media/pages/home/…, thumbnails /media/pages/work/<slug>/…) with Kirby thumb names (-300x.jpg, -640x.webp to -1920x.webp); nothing in the folder or the runs shows /media/site/. Corrected in README section 2 and stack.evidence.integrity; the jQuery 3.6.0 tag has none. Corrected in README section 2 and stack.evidence.transition declarations. The live CSSOM, walked through all 14 imports, has 23 style rules with a transition; the 28 was a line count of the source that the verifier did not reproduce (it can include overridden duplicates or comments). Both figures are now given, in README section 2 and stack.evidence.loop: true, but every homepage load logs Swiper's warning that there are too few slides for loop mode ("it will be disabled or not function properly"); the script loads on every page and is used on the homepage only. Corrected in README section 2 and stack.animation./work and the bar; the homepage slideshows are Swiper-driven (800 ms slides at 820 px and wider) and the credit ticker is Marquee3000. Corrected in the README's short version.INSPIRATION.md call the mode switch, clock, list toggle and project transition "the only other furniture". True: the phrase is in bar-stage-drawers.md and names only the mode switch and the live clock. Corrected in README section 1 and the owner_words of the mode switch and the clock.active-after-burst). Corrected in README 3.1.@media only screen and (max-width: 820px), so they apply at 820 px too, while grid() keeps the 24-cell pitch at 820 px; at exactly 820 px the one-column phone layout runs on a 33.42 px pitch (now a sentence in the 3.2 gotchas). Corrected in README 3.1, 3.2 and 3.3 and the landing's phone state.initHomepageSwipers() moves the landscape slides into the portrait slider, so it holds both sets (the screenshot happens to show a portrait). Corrected in README 3.1.--gap − 1) and starts 1 px under the image, inside a 61.25 px gap (1 px above it, 3 px below). Corrected in README 3.2 and the grid's mechanism.width and max-height. Corrected in README 3.3 and the pill's mechanism.ul holds a header and 204 rows: the 68 projects listed three times over, numbered 01 to 204 (row 69 starts again at Antrópico), 3,088 px of content. Row 57 is the last fully visible, as said, and the overflow point now rests on the computed styles (overflow: visible on the sheet, overflow: hidden on the root while it is open). Corrected in README 3.6 and section 7, the list's mechanism and not_confirmed.mechanism.not_confirmed.pt-in-01.png and project-direct.png); the playing film fills 1420 × 861 (x 10 to 1429, y 20 to 879 in pt-01-project-settled.png; css 1420 × 860.875). Corrected in README section 4 and the project page's mechanism.top and left (768.7, 362.2), but the work header is 620.5 × 130.7 px, so its bottom edge meets the viewport's (0.5 px off) and its centre sits about 48 px left of the middle (header-after-back, after-back, verify-back.png). Corrected in README section 4 and the back transition's layers.canPlayType "probably") and reports no HEVC support (""), so a headed run of it would not help. Corrected in README section 7 and not_confirmed.Additions from the verifier's evidence, also in place:
92.205.128.0 - 92.205.151.255, netname DE-GODADDY-20071017, org Host Europe GmbH (descr GoDaddy), route 92.205.144.0/21, origin AS21499. README sections 2 and 7, stack.evidence, not_confirmed.stack.animation.font-weight: bold (the probe's "Camera Plain bold"). README section 2, stack.fonts.not_confirmed.currentTime advances (1.55 s, 2.29 s) while videoWidth is 0 and no frame is decoded. README section 7, not_confirmed.stack.evidence (runtime versions, headers, CSSOM walk, history state); rows for verify/verify-back.png and verify/verify-dark.png in the README shots table, a sources line for the verifier runs and a pointer to this file under the README's opening paragraph./ajax/<lang>/<id>/<slug>.#nav-container collapsing to max-height: 0 and 100 px in project mode, Marquee3k pausing on play, the outline: … auto 0px rule, no hover style on Back (project-back-hover.png not viewed), and "23.CS" being in the template on every page (seen on the direct project page, desktop and phone, only).handleNavActiveLink calls activateCenteredProject on every AJAX arrival at /work, and each call binds one more window scroll and resize handler without removing the last (source; the handler count was 1 after a fresh load and was not measured after swaps).a.project.list-project with data-categories, so handleProjectsFilters, which walks $('.project'), filters the list as well as the grid.tmp/ folder. The verifier's scratch (two steps generators, a pixel-edge script, backup copies) lived in the session scratchpad outside the project and was deleted.shots/phone-sheet.png (6.3 MB), shots/pt-in-sheet.png (3.4 MB), shots/work-full.png (2.8 MB) and shots/mode-list-states-sheet.png (2.5 MB): contact sheets and a full-page screenshot, which the rule allows.shots/verify/ (two PNGs and two meta files), steps/verify-01-desktop.json, steps/verify-02-desktop.json and this file.