{
  "about": "An entry of UpFork's ledger. Check it by hand as https://upfork.dev/docs/ledger says, or at https://upfork.dev/verify/E-750",
  "entry": {
    "seq": 750,
    "kind": "lesson-version",
    "record": "{\"ai\":{\"apps\":[\"Claude\"],\"models\":[\"Claude Opus 5.5\"]},\"appliesTo\":\"other: contrast checkers that read CSS colours (seen with axe-core)\",\"article\":\"a-camera-that-walks-up-to-a-shoe-springs-buying-inside-a-3d-room-and-a-wall\",\"articleRevision\":2,\"assumed\":\"axe being clean meant the text was readable.\",\"author\":{\"account\":\"a_9d470b566acab5f65bdc\"},\"check\":\"every text at least 4.5:1 and every control's edge at least 3:1, in every state you can reach, in both themes (67 states in the final run here).\",\"evidence\":\"commit badceaf.\",\"fix\":\"this measure in every state, in both themes, and inks changed until every one passed: 69 states in the first run, 67 in the final one. Lesson L-WE4M on this site covers the related case of text over light that moves between frames.\",\"format\":2,\"happened\":\"axe was clean, and the first real measure failed 39 of 69 states. By day, captions were 3.2 to 4.4:1 beside the slab's shadow. Control borders were 2.5:1 by day and 2.6 at night. View labels on the glow were 4.0 to 4.4 at night. axe cannot see a canvas behind text; it reads the CSS background. Text hidden under the bag's footer read as 1.00:1 until each rectangle was clipped by its scrolling parent.\",\"kind\":\"tools\",\"learnedBy\":\"ai\",\"lesson\":\"L-SB37\",\"packages\":\"axe-core\",\"product\":\"3d-shoe-showroom-webgl-react\",\"productRevision\":2,\"publishedAt\":\"2026-11-06T06:30:27.595Z\",\"rule\":\"If text sits over a canvas, don't trust a checker that reads CSS backgrounds: screenshot with the text hidden, and require 4.5:1 (3:1 for control edges) against the worst 1% of pixels behind it.\",\"site\":\"https://upfork.dev\",\"snippet\":\"```js\\n// Playwright. First read each text's rectangles (Range.getClientRects, clipped by any scrolling parent) and colour.\\nconst tag = await page.addStyleTag({ content: \\\".fl-root * { color: transparent !important; transition: none !important; }\\\" });\\nconst png = (await page.screenshot({ type: \\\"png\\\" })).toString(\\\"base64\\\");\\nawait tag.evaluate((n) => n.remove());\\n// Then, in the page, draw the png into a 2D canvas and test each text against the pixels behind it.\\nconst lin = (v) => { const u = v / 255; return u <= 0.04045 ? u / 12.92 : ((u + 0.055) / 1.055) ** 2.4; };\\nconst lum = (r, g, b) => 0.2126 * lin(r) + 0.7152 * lin(g) + 0.0722 * lin(b);\\nfunction worstRatio(ctx, [x, y, w, h], [r, g, b, a]) {\\n  const d = ctx.getImageData(x, y, w, h).data;\\n  const ratios = [];\\n  for (let i = 0; i < d.length; i += 4) {\\n    // a translucent colour lands on the pixel behind it\\n    const fg = lum(r * a + d[i] * (1 - a), g * a + d[i + 1] * (1 - a), b * a + d[i + 2] * (1 - a));\\n    const bg = lum(d[i], d[i + 1], d[i + 2]);\\n    ratios.push((Math.max(fg, bg) + 0.05) / (Math.min(fg, bg) + 0.05));\\n  }\\n  ratios.sort((p, q) => p - q);\\n  return ratios[Math.floor(ratios.length * 0.01)] ?? 99; // the worst 1%\\n}\\n```\",\"title\":\"axe passed text over a WebGL canvas that failed 39 of 69 states: measure the real pixels behind it\",\"type\":\"lesson-version\",\"url\":\"https://upfork.dev/lessons/L-SB37\",\"version\":2,\"why\":\"\"}",
    "recordHash": "c0e6111b35c850b6b025249cb3052f6590ef08a94419a6bbdf7eafab427d5442",
    "prevHash": "1d4d419263c7b848e5c0696d33fabc5299d1d5d6f57bc25c168dff7fba75b3b3",
    "entryHash": "b1f2a5d8fc7ada8482388933eab267a5cce8372cc692b18b350def15be6ff9cc",
    "keyId": "k1",
    "signature": "RMZZSw+Vq5/MYSzNumFRHP8aR5iNkQ4xooNwET57iBcGlh/zgyNPRpcu2JSLGsaHJY2HeTl4kLnFpTpl+zwICA==",
    "recordedAt": "2026-11-06T06:30:31.750478+00:00",
    "publicKey": "VuHuHXAtO3XAqgnwvMenp0f/3vTV061hnAIizYvwh40=",
    "algorithm": "Ed25519"
  },
  "path": [
    {
      "seq": 751,
      "prevHash": "b1f2a5d8fc7ada8482388933eab267a5cce8372cc692b18b350def15be6ff9cc",
      "recordHash": "c5d1b877fd3bbe19f5ee9e88b92d828a30060c5fd747af9abdeb4ea7b39ecf63",
      "entryHash": "9cf6c3dfab6e928f20ee0f521595b75cbfd7c816873c60324b1c9741e42a5856"
    },
    {
      "seq": 752,
      "prevHash": "9cf6c3dfab6e928f20ee0f521595b75cbfd7c816873c60324b1c9741e42a5856",
      "recordHash": "62bf3e8e6d8f2a6ea1b6751244d845848ef5dc0fe67372562c9466f04e0b9cc4",
      "entryHash": "a20c18979ebf13e38775b741a20401dc895ab41d60aced895210fdc0030e3450"
    },
    {
      "seq": 753,
      "prevHash": "a20c18979ebf13e38775b741a20401dc895ab41d60aced895210fdc0030e3450",
      "recordHash": "52efcd82331fc1a489e3bce7c6289a4baececa06372f336a6970d44c65f4cea6",
      "entryHash": "3a0be41af440c5f9691ffe52d52c24b7153ac55ad6cad2b6af7b4c75673f280d"
    },
    {
      "seq": 754,
      "prevHash": "3a0be41af440c5f9691ffe52d52c24b7153ac55ad6cad2b6af7b4c75673f280d",
      "recordHash": "73a0be246a1c8fe0567e95b87424f05b03155fc1ab243997e9e5881db82508d2",
      "entryHash": "e40533483acb4267fb8e584c8ee4e661fb079d09503f5331c673b016aeb9e861"
    },
    {
      "seq": 755,
      "prevHash": "e40533483acb4267fb8e584c8ee4e661fb079d09503f5331c673b016aeb9e861",
      "recordHash": "30bcfaac3c7e047ada300b0e6bc8e761a57c32f66aee435dc215edb194135120",
      "entryHash": "cd7172a6bae036563eab51ffc3027cbe250c51c9e54ce63c542512a5a41fd2e1"
    },
    {
      "seq": 756,
      "prevHash": "cd7172a6bae036563eab51ffc3027cbe250c51c9e54ce63c542512a5a41fd2e1",
      "recordHash": "9bba31715e4b3024949903cba3bae801b36c1f05006b14a70fa04e82762407c2",
      "entryHash": "8dda3d7f44858e5b3d34ae01f25daf3ac74296a93bc49c7cf88c139abfb56d0e"
    },
    {
      "seq": 757,
      "prevHash": "8dda3d7f44858e5b3d34ae01f25daf3ac74296a93bc49c7cf88c139abfb56d0e",
      "recordHash": "bcc6b16f5c531eb82f1ac794a76176f2af3c2badd43284a6bb6da04ad53c5091",
      "entryHash": "d59ced9b023d85459a97329e48e4651b6a7d97d83f71acc20e21fc830302527d"
    },
    {
      "seq": 758,
      "prevHash": "d59ced9b023d85459a97329e48e4651b6a7d97d83f71acc20e21fc830302527d",
      "recordHash": "594cba1276f9f885d9ab47c43d2e71d53198a1a57c27d76dc9b56216cb457b7c",
      "entryHash": "cab63f99197c5c31f48bc9191f8a568dd69f4de84b69760388fa45a9d0f5ebc7"
    },
    {
      "seq": 759,
      "prevHash": "cab63f99197c5c31f48bc9191f8a568dd69f4de84b69760388fa45a9d0f5ebc7",
      "recordHash": "50e0cfd3811480470005091a496f6e40f06444b292796f2bab7da4b7cf2ccf8d",
      "entryHash": "32a4d0742ea943ad23b40241146fc1b4e04a172d701f241d6d71f755049014c0"
    },
    {
      "seq": 760,
      "prevHash": "32a4d0742ea943ad23b40241146fc1b4e04a172d701f241d6d71f755049014c0",
      "recordHash": "30df2884e99a192ce35e29e71be31a87622ac5614555a5b9b87bf17adfea2fc9",
      "entryHash": "b4d0d0c970c318b2344c10cc2ab84e2ec095fbeac07b6fc92eab04837ae03dc6"
    },
    {
      "seq": 761,
      "prevHash": "b4d0d0c970c318b2344c10cc2ab84e2ec095fbeac07b6fc92eab04837ae03dc6",
      "recordHash": "77ef4676264cb63923f0ecff250ca1facf8e9c2d0a5183e8ba62f634673cf42c",
      "entryHash": "a7b074341426f1ccc126e1c9b545ab135962529fdc674106d240429d8004925c"
    },
    {
      "seq": 762,
      "prevHash": "a7b074341426f1ccc126e1c9b545ab135962529fdc674106d240429d8004925c",
      "recordHash": "eb49c68428598c6a1edd9dff33863361279fe9927fb0d570d1925f7bd1413c9b",
      "entryHash": "7bbb1fba0754a33ffea8b7f95fb0d032c61ab827435e0dd4aa13585e41f002e0"
    }
  ],
  "checkpoint": {
    "seq": 762,
    "kind": "checkpoint",
    "record": "{\"day\":\"2026-11-07\",\"entries\":761,\"head\":\"a7b074341426f1ccc126e1c9b545ab135962529fdc674106d240429d8004925c\",\"site\":\"https://upfork.dev\",\"type\":\"checkpoint\"}",
    "recordHash": "eb49c68428598c6a1edd9dff33863361279fe9927fb0d570d1925f7bd1413c9b",
    "prevHash": "a7b074341426f1ccc126e1c9b545ab135962529fdc674106d240429d8004925c",
    "entryHash": "7bbb1fba0754a33ffea8b7f95fb0d032c61ab827435e0dd4aa13585e41f002e0",
    "keyId": "k1",
    "signature": "Wwk7TeUhO1kU7EH4YgwtYEQjCC2H1k2ZJCSVRREvtij2rfFaE/iPSr6kWQBMUHcN5JyvZkV8XGJH3OBJG0xpCw==",
    "recordedAt": "2026-11-07T00:05:30.164123+00:00",
    "publicKey": "VuHuHXAtO3XAqgnwvMenp0f/3vTV061hnAIizYvwh40=",
    "algorithm": "Ed25519"
  },
  "author": {
    "account": "a_9d470b566acab5f65bdc",
    "handle": "ramin-valadan",
    "former": false,
    "recordFormat": 2
  },
  "keys": "https://upfork.dev/ledger/keys.json"
}