Lucid Lake Support

Blog#snoot

What a headless browser caught that our eyes missed

Snoot is a plush companion robot, still in design: renders and simulations, no physical prototype yet. Between 20 and 22 October 2026 we built a checker that opens every Snoot page in a headless Chromium and fails the build when a page breaks our written bar. It found things we looked at daily and never saw. It also had a blind spot of its own, fixed on 13 November 2026.

What it checks

Each page opens with its films held on their posters, so runs repeat. Then the checker measures:

  • Layout at 6 widths (320, 360, 390, 720, 1100 and 1440 px) and 4 short windows, down to a phone on its side at 844 × 390. It fails on sideways scroll, a console error, a failed request or overlap in the first view.
  • AA contrast for every visible text element against the first opaque background behind it: 4.5:1, or 3:1 for large text. Text over a picture or a gradient is listed, not judged.
  • Colour-blind contrast. The same text pairs go through protanopia, deuteranopia and tritanopia simulations (Machado, Oliveira and Fernandes 2009, severity 1.0, applied on linear RGB). Each pair must still clear the same line.
  • Keyboard. Tab from the top must reach every link, button and summary once, in reading order. Each stop must show a focus ring, and focus must leave the page with no trap.
  • Gaps between pictures: at most 200 px of text-only space between media rows at 1280 px wide, and 160 px at 390 px.
  • Content said twice: a run of 4 or more words in two blocks, two blocks sharing 60 % of their content words, or a price in two places.
  • Picture loads: every responsive picture loads at least the pixels it draws, at 6 screen sizes up to 2560 × 1440. A stalled picture fails after 30 s.

What it caught that our eyes missed

An accent that only passed for normal vision. On 21 October we moved to a marigold accent, #E08A1E. On our darkest card colour it scores 4.94:1, a pass. In the protanopia view it scores 4.12:1, a fail. Nobody on the team sees that view. We kept #E08A1E for fills and made text and links one step lighter, #E8952B: 5.54:1 normally and 4.66:1 in the protanopia view.

Tall text gaps. On 21 October we turned on the gap and peek rules. The site failed 11 checks on 5 pages. Every page had looked fine in review, yet on a phone the reader scrolled past long runs of text with no picture. We rebuilt the pages as picture-led feeds the same day, and each page joined the judged set as it passed.

Sizes we never open. We review on two or three windows. The checker tests the hero film at 6: 1280 × 720, 1440 × 800, 1512 × 860, 1728 × 1000, 2560 × 1440 and 390 × 844. It must never be cropped and never be wider than the window.

How we prove it can fail

A check that never fails proves nothing. Every new rule ships with a broken build it must catch.

For the film rule we built a copy with the film set to fill and crop at 70 % of the window width. It failed on 3 pages. For the picture rule we shrank one picture's size hint, and in a second copy broke the list of sizes so no choice could match. Both failed. Only then did we trust a green run.

The checker's own miss

On 13 November we found a hole in the contrast check.

The checker read each computed colour with a number regex and treated the numbers as 0–255 RGB. That works for rgb(54, 60, 84). It does not work for color(srgb 0.212 0.234 0.33), which is what the browser computes for a background made with color-mix(). The regex read 0.212, 0.234 and 0.33 as RGB values out of 255: near-black.

Since 22 October the front page mixes its section backgrounds, so the checker judged amber headings against near-black. By our reconstruction it saw about 8.75:1 where the true figure was 4.54:1. The run was green, and the green was wrong.

The fix stops parsing colours by hand. The browser knows every CSS colour format, so the checker now paints the colour into a 1 px canvas and reads the pixel back:

const cv = document.createElement('canvas');
cv.width = cv.height = 1;
const cx = cv.getContext('2d', {willReadFrequently: true});
const rgb = s => {
  if (!s || s === 'transparent') return [0, 0, 0, 0];
  cx.clearRect(0, 0, 1, 1);
  cx.fillStyle = '#000';
  cx.fillStyle = s;
  cx.fillRect(0, 0, 1, 1);
  const d = cx.getImageData(0, 0, 1, 1).data;
  return [d[0], d[1], d[2], d[3] / 255];
};

Black goes in first because the browser keeps the old fillStyle when it rejects a string; a bad input then reads as black, not as the last colour. The same path handles oklch() and lab().

The fixed check found a real fail at once. Two headings on the front page sit on a paler, marble-toned section. Their amber passed AA for normal vision, but only just: 4.54:1. In the protanopia view they fell to 3.85:1, and in the tritanopia view to 4.45:1. Both are under the 4.5:1 line for text that size.

We moved those two headings one step lighter, to #F2A950. They now read 5.47:1 for normal vision and 4.76:1 in the protanopia view, and every simulation passes. That 5.47:1 is now the lowest of the front page's 22 text pairs.

Four swatches of the heading on its section background: before and after, each in normal vision and in a simulated protanopia view
The two headings before and after the fix. Before: 4.54:1, and 3.85:1 in the protanopia view. After: 5.47:1, and 4.76:1.

The lesson is not about colour: a check that parses a format by hand breaks when the format grows. Let the browser answer.

Today's run

We built the site and ran the checker on 13 November 2026. It judged 54 checks on 5 pages, and 53 passed.

PageJudged checksPassedLowest contrastKeyboard stops
Front page12125.47:1 (22 pairs)44 of 44
Collection12126.43:1 (5 pairs)29 of 29
Tech11116.13:1 (10 pairs)68 of 68
Roadmap11105.42:1 (12 pairs)80 of 80
Sources886.43:1 (6 pairs)29 of 29

The one fail is known and still open. The roadmap page is mostly reading. At 1280 px it has text runs of 345 px to 1,116 px between pictures, against the 200 px line. We have not yet chosen between adding pictures and marking those runs allowed, with a reason. Until then the run stays red, on purpose.

The front page at 1440 × 700, one of the short windows the checker tests: the hero film, the headline and the prototype line all in the first view, nothing overlapping
The front page in a short 1440 × 700 window, from the 13 November run.

Reported, not judged yet

These checks print findings but do not fail the run.

  • Text stacks: more than 20 words before a section's first picture. Today the front page has one section at 39 words, and the Tech page has 20 findings across its two widths. Tech is a reference page, and dense text may be right there. We will judge stacks after a copy cut, not before.
  • Content said twice is judged on the Collection page. On the front page it reports 13 findings. Many are a short line repeated across the act cards, or the AI-image labels, which must repeat.
  • Weight: a 390 px phone at 3x loads 8,522 KB in the front page's first view, and 19,167 KB in all. It is a note, not a check. We have not set a budget yet, and a line nobody agreed would only teach us to ignore red.

A judged check must be right every time it fails, or people learn to skip it. So a rule stays a report until we trust it.

This post is about Snoot: A plush bear that performs. It sees you, talks, and makes a show out of your day.

Aptmark is private

Enter the password, or ask me for access.

Ask for access