# Color & Vision Lab: learning and design notes

Research checked 20 October 2026. These notes distinguish source-backed facts from product decisions and predictions. The app is an educational and design-exploration tool.

## Source-backed facts

### Light and filters

- [Exploratorium: Colored Shadows](https://www.exploratorium.edu/snacks/colored-shadows) demonstrates additive mixing: appropriately balanced red, green, and blue lights appear white together. Red plus green appears yellow, green plus blue cyan, and red plus blue magenta. This supports experiments that let a learner switch one source off and inspect the changed overlap.
- [UW–Madison, L. R. Ingersoll Wonders of Physics Museum: Subtractive Color Mixing](https://www.physics.wisc.edu/ingersollmuseum/exhibits/opticscolor/subcolormix/) explains complementary absorption: ideal cyan removes red, magenta removes green, and yellow removes blue. Combining cyan and yellow leaves green; all three ideal filters remove the three channels. This supports a filter demonstration, not a promise that real paint mixtures will match the display.
- The interface should call the CMY result an **ideal filter** model. The software uses three channels and cannot capture real pigment spectra, scattering, paper, ink thickness, illumination, or a printer profile. Percentages represent model controls rather than paint volumes or physical lamp power.

### Context and color appearance

- [Exploratorium: Sliding Gray Step](https://annex.exploratorium.edu/exhibits/gray_step/) lets visitors change the surroundings of identical gray stripes and compare their appearance. Its exact striped geometry differs from this app's two-square demonstration. It supports the broader lesson that surrounding visual context affects appearance; it does not establish an exact predicted perceptual difference for these squares.
- Keep the center RGB values identical while changing only their surroundings. Reveal a connecting region of the same RGB color so learners can directly compare the values and appearance. Avoid declaring that every viewer must experience the same illusion strength.

### Contrast and accessible color

- [W3C: Understanding WCAG 2.2 SC 1.4.3, Contrast Minimum](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html) specifies AA contrast of at least **4.5:1** for normal text and **3:1** for large text. Large text is at least 18 pt regular or 14 pt bold, equivalent to 24 CSS px regular or approximately 18.67 CSS px bold. Threshold decisions must use the unrounded ratio: 4.499:1 does not meet 4.5:1.
- [W3C: SC 1.4.6, Contrast Enhanced](https://www.w3.org/WAI/WCAG22/Understanding/contrast-enhanced.html) specifies **7:1** for normal text and **4.5:1** for large text at AAA. A numeric pass is limited to the tested color pair; thin fonts, anti-aliasing, and other presentation details can still affect practical readability.
- [W3C: SC 1.4.1, Use of Color](https://www.w3.org/WAI/WCAG22/Understanding/use-of-color.html) requires that color not be the only visual means of conveying meaningful information. Text, patterns, or shapes can supply another visible clue. A color-vision preview alone does not establish compliance.
- [W3C: SC 1.4.11, Non-text Contrast](https://www.w3.org/WAI/WCAG22/Understanding/non-text-contrast.html) addresses required visual information for interface components, states, and graphical objects, generally at a 3:1 contrast threshold against adjacent colors with specified exceptions. Do not present the text checker as a complete interface audit.

### Color-vision simulation provenance

- The numerical-model implementation was checked against the authors' paper, [Machado, Oliveira & Fernandes (2009), *A Physiologically-based Model for Simulation of Color Vision Deficiency*, corrected PDF](https://profs.ic.uff.br/~laffernandes/content/publications/journal/2009_tvcg_15(6)/machado_oliveira_fernandes-tvcg-15(6)-2009-corrected.pdf).
- The [authors' matrix supplement](https://www.inf.ufrgs.br/~oliveira/pubs_files/CVD_Simulation/CVD_Simulation.html) is the cited coefficient origin. That page timed out during this work, so these notes do **not** claim its table was directly inspected. Coefficients were independently cross-checked against [QGIS's official preview-effect implementation](https://api.qgis.org/api/qgsprevieweffect_8cpp_source.html).
- The app applies the authors' matrices for protanopia/protanomaly, deuteranopia/deuteranomaly and tritanopia/tritanomaly to linear RGB, then encodes the result back to sRGB. Out-of-gamut channels are clipped, and the interface says so for the test color. *(Updated 21 October 2026: a severity slider now exposes the published 0.0–1.0 matrices; see below.)*
- The separate grayscale option uses relative luminance and is explicitly a design check, not an achromatopsia model. All previews are approximations; they are not eye tests or exact depictions of an individual's experience.

## Design decisions and predictions

These are design judgments to validate with users, not findings established by the sources above:

1. Give every experiment a question, an action to try, a short explanation, a real-world use, and a concise model limitation. Prediction: learners will be more likely to connect a control change with a visible cause.
2. Offer six launchable experiments: white RGB light, yellow from red/green, full CMY absorption, readable text, identical center squares, and color-plus-shape signals. Prediction: a clear starting challenge will be easier to approach than an empty collection of sliders.
3. Put the visible result and its controls together, retain the brand orange for interaction accents, and reserve experimental colors for the actual color models. Prediction: people can distinguish app controls from the colors being measured.
4. Keep labels, numerical values, and pass/fail words alongside colors. A learner should not need to distinguish hue to locate a control or read a result.
5. Include quick prediction questions with explanations that also say how to reproduce the result. The goal is to support exploration, not grade or diagnose a person.
6. Use honest labels for approximate models. Screen RGB output is useful for learning and basic design checks; it is not a replacement for calibrated displays, print proofs, or research-grade spectral measurements.

## Learning module

`learning.js` exports UMD `ColorLearning` with:

- `modes`: `light`, `pigment`, `contrast`, `illusion`, and `vision`; each includes `title`, `kicker`, `question`, `tryThis`, `explanation`, `realWorld`, and `limits`.
- `experiments`: six preset cards with stable IDs and control values. Channel amounts are percentages from 0 to 100.
- `challenges`: six questions with answer IDs, choices, explanations, and a repeatable action.
- `sources`: primary-source links suitable for a learning references area.

All learning text is original paraphrase. No external image or interactive exhibit code is copied.

## Validation status

The learning module was loaded in Node and checked for all seven required fields across five modes, six unique experiment IDs with valid modes, six questions with valid answer choices, and six HTTPS source links. Those checks passed.

`node --test apps/color-vision-lab/tests/learning.test.cjs` passed all nine learning-integration checks: the white, yellow, and black presets produce their taught colors at the actual default probe; the cyan/yellow filter challenge leaves green; the readable preset exceeds normal-text AA and AAA; the illusion uses a single shared center color; the vision preset uses a supported full-severity model; answers and mode references are valid; and preset values survive saving and restoration.

A source review confirmed that the sample-size classification uses 24 CSS px regular or 14 × 96/72 CSS px bold, and that the result table uses unrounded contrast thresholds. The illusion bridge's position matches the center squares in the desktop, tablet, and phone CSS layouts; this is a geometry review, not a visual browser-test claim.

The integrating task records final numerical-model, interaction, and desktop/mobile browser validation. No claim of full WCAG conformance or user-study results is made here.

### Integrated verification — 20 October 2026

- All **70 tests pass** across the numerical model (13), scene (13), saved-state validation (14), learning/presets (9), and actual controller (21). Tests exercise the production model, including local probe coverage, exact contrast boundaries, linear-light midtones, vision matrix references, input recovery, preserved old palettes, mode-specific reset, setup restoration and CSV/SVG/CSS contents.
- Browser review used the running local app at desktop 1440 × 1100/1150, phone 390 × 844, tablet 768 × 1024 and landscape 844 × 390. All five modes were checked for horizontal overflow at tablet and landscape widths; phone light, contrast and vision views were inspected visually in light/dark themes. The phone's hidden source label initially widened the page; a scoped CSS fix removed the overflow.
- Actual browser dragging moved the red source away from the probe: the sampled result changed from white to cyan while the separate all-source-overlap swatch stayed white. Exact probe X = 0 reported black, and full CMY overlap reported black. Keyboard source motion and live numeric coordinate edits worked.
- In the browser, `#647b7d` on white displayed a rounded 4.50 but correctly failed normal-text AA. The interface explains that rounding is not a pass. Suggesting black produced 21:1 without changing the white background; larger/bold sample text updated its applicable category.
- The context reveal joined the identical center colors without changing either value. Vision selection and the labels/shapes toggle updated both comparison panels; original saved color `#777777` remained available.
- No browser warnings or errors were reported during the review. Back/forward cache lifecycle, storage failure, corrupted inputs and export payloads were verified by controller tests rather than a claim of end-to-end browser downloads.

## 3D light studio, palettes and grading — 20 October 2026

### Evidence and scope

- [Three.js color management](https://threejs.org/manual/pages/color-management.html) supports doing light calculations in linear RGB and encoding display output once. This checkout bundles Three r128, so the scene uses its `texture.encoding` and `renderer.outputEncoding` APIs. The receiver is an unlit texture from the same sampler as the probe, with tone mapping disabled.
- [Adobe: color correction techniques](https://www.adobe.com/learn/premiere-pro/web/color-correction-techniques) informs the distinction between corrective exposure/color adjustments and a creative look. The five controls here use our explicitly defined educational transform, not an Adobe grading algorithm.
- [W3C CSS Color conversion code](https://www.w3.org/TR/css-color-4/#color-conversion-code) supports the sRGB transfer functions. Exposure multiplies linear channels by 2^stops; contrast scales around linear 0.18; saturation blends with relative luminance. Warmth/tint are creative channel gains, not calibrated Kelvin white balance.
- [GOLDEN: color mixing guide](https://goldenartistcolors.com/resources/color-mixing-guide) supports the paint lesson: absorption/scattering, transparency, layer thickness and unequal pigment tinting strengths affect mixtures. This lab offers a digital value study and ideal CMY filters, not a pigment recipe or print proof.
- The existing W3C contrast sources support all 25 palette pair calculations. Results use unrounded ratios; pass/fail is written in text as well as shown with color.

### Implemented experiments and predictions

1. Move three lamps on a 10-unit receiver, change their height (1–6 units) and cone half-angle (10–50 degrees), and sample the floor. Perspective and top cameras share one model. Filters retain independent positions. Predict: moving red away from a white overlap leaves cyan.
2. The default ideal mode holds brightness constant inside each beam, with a smooth outer edge. Optional distance dimming applies an inverse-square term referenced to 3 units and the incidence cosine. Predict: directly below one lamp, doubling its height gives one quarter of its linear intensity. Beam spread changes coverage, not emitted power. These relative units are not watts, lux, or a spectral lighting calculation. Beam cones are illustrative guides.
3. Check five editable palette colors against every background choice, select AA/AAA/large-text targets, apply a pair to the readability preview, and export the exact matrix. Importing notebook colors makes a copy and leaves discoveries intact.
4. Grade an original procedurally drawn still life or a local JPEG/PNG/WebP. Compare original and graded pixels with a divider, sample a pixel, inspect RGB histograms and clipping, and export the graded working PNG. Working images are capped at 1 million pixels, 1600 × 1000; uploads up to 15 MB. Saved setups contain adjustments, not image files. The original is preserved for comparison; PNG export uses the resized working resolution.
5. Digital-media and paint/art lessons link attention, communication, context and light/dark values to practice. Interactive poster palettes and a grayscale art study let learners compare a controlled change. Color associations are presented as context-dependent. No external artwork was copied.

### Verification

- **151 Color & Vision tests plus the shared catalog check pass (152 total).** Coverage includes linear-light reference cases, beam geometry and falloff, real Three geometry/raycast interactions with a mocked renderer, receiver/probe agreement, camera framing at tall-lamp and narrow-view extremes, grade identity and clipping, histograms, local-image decode races and size limits, matrix boundary decisions, controller integration, save migration, exports and disposal.
- The catalog check validates **192 local resource/link references** across the existing 11-app catalog.
- Browser verification used the running local app at desktop 1440 × 1000, phone 390 × 844, tablet 768 × 1024 and landscape 844 × 390. New light/readability/grading modes showed no horizontal page overflow; the palette matrix scrolls within its own panel. Both themes were inspected.
- A real browser drag moved red to x19%, y21.2%; the center sample changed from white to cyan. Switching to 3D retained that position; enabling falloff and raising red to 6 units changed the measured RGB result. Keyboard lamp motion, camera orbit and zoom were exercised.
- The grading preset changed displayed pixel values and clipping; zero saturation produced equal RGB channels. The art grayscale control and story tabs updated the visible study. Choosing white text on brand orange applied those exact colors and correctly failed normal-text AA at the displayed 2.36:1.
- No browser warnings or errors were reported. Upload race/error handling, download payloads and storage failure were verified in controller tests; these notes do not claim end-to-end browser file-picker or download checks.
## Paint studio and selectable light scenes — 20 October 2026

### Evidence and model boundaries

- [Curtis and colleagues, Computer-Generated Watercolor, section 5](https://grail.cs.washington.edu/wp-content/uploads/2015/08/curtis-1997-cgw.pdf) describes weighted absorption and scattering coefficients for pigment mixtures. This lab uses a deliberately reduced three-channel, opaque Kubelka–Munk-style approximation. Its starting swatches and relative scattering strengths are synthetic, not measured spectral data.
- The palette converts sRGB swatches to linear reflectance-like channels, derives `K/S = (1 − R)² / (2R)`, combines K and S by relative parts, then recovers reflectance and encodes sRGB. White has a stronger illustrative scattering coefficient. The empty palette has no color; scaling every ingredient equally preserves its color.
- [GOLDEN's Color Mixing Guide](https://goldenartistcolors.com/resources/color-mixing-guide) and [Strong and Weak Colors](https://goldenartistcolors.com/resources/strong-and-weak-colors) inform the explanations of pigment choice, tinting strength and unequal contributions. The displayed recipe teaches relationships; it cannot specify a matching mixture of branded paints.
- Canvas strokes use ordinary digital alpha compositing over original line art. They do not simulate wet paint, drying, glazing, spectral illumination or pigment transport. Palette mixing and painting a transparent layer are explicitly distinguished.
- The 3D objects use the bundled [Three r128 SpotLight](https://github.com/mrdoob/three.js/blob/r128/src/lights/SpotLight.js) and its [lighting shader](https://github.com/mrdoob/three.js/blob/r128/src/renderers/shaders/ShaderChunk/lights_pars_begin.glsl.js). Lamp position, intensity, height and spread update real scene lights. Object shading is illustrative; the probe continues to measure the exact receiver model without object shadows. Top view hides props to expose that receiver.

### Implemented interactions and design predictions

1. Eight named pigments, numeric relative amounts and six starter recipes create a visible mixture with an explanation tied to its ingredients. Prediction: adding white to the initial green makes the taught tint visibly lighter without changing existing brush strokes.
2. Blank paper, a still life, a garden and a city each keep separate applied strokes and undo/redo histories. Brush width, opacity, pen pressure, eraser, eyedropper, keyboard dots and PNG export support actual making. The scenes use original procedural line art; no external artwork is copied.
3. Save setup includes all four paintings, with strict validation before replacing current artwork. Older setups without paint data preserve current paintings. Reset experiment resets paint controls only; Clear canvas is undoable. New sketch has an explicit confirmation and clears only the current scene. Boundaries are 240 operations per scene, 300 points per stroke and 64,000 points overall; reaching a limit preserves existing work and directs the user to export or start a fresh sketch.
4. The 3D picker offers the open studio, a ceramic still life, a sculpture gallery and a toy workshop. More detailed lamp housings, cooling fins, controls and tripod feet accompany the new props. Switching scenes preserves lamps, probe and camera. Prediction: neutral curved and faceted objects make the effect of surface direction easier to compare than a flat receiver alone.

### Verified results

- **196 tests pass**: 195 Color & Vision tests and the shared catalog check, which validates 195 local references across 11 apps. New coverage includes pigment identity, tint/shade behavior, ratio invariance, empty mixtures, real paint event handling, per-scene history, undoable clear, new-sketch confirmation, strict save validation, legacy preservation, scene geometry, light updates, mode isolation, exports and cleanup.
- Browser checks covered desktop 1440 × 1100, phone 390 × 844, tablet 768 × 1024 and landscape 844 × 390, in light and dark themes. The added interfaces had no horizontal page overflow at these sizes.
- Actual drawing produced counted strokes; adding white changed the brush from `#509040` to `#68a956` and updated the explanation. Undo/redo, scene switching and undo after clearing restored the visible artwork. A pointer drag on the phone-size canvas also produced a stroke.
- Browser scene selection showed the new props while keeping the edited light intensity. Turning off red changed the probe from white to cyan. Top view displayed its explicit hidden-object explanation. Save migration, malformed saves, PNG creation and limits were verified in automated tests; these notes do not claim an end-to-end browser download test.


## Severity, audit and fixes — 21 October 2026

### Evidence
- Machado, Oliveira & Fernandes (2009) publish simulation matrices for severities 0.0–1.0 in 0.1 steps and recommend linear interpolation between them. Severity 1.0 models dichromacy; lower values model anomalous trichromacy (protanomaly, deuteranomaly, tritanomaly), which is more common than dichromacy. All 33 matrices were copied programmatically from colour-science's `CVD_MATRICES_MACHADO2010` dataset; its 1.0 rows match the previously verified full-severity matrices digit for digit.
- WCAG 2.2 relative luminance (0.04045 threshold, 0.2126/0.7152/0.0722 weights) and the (L1+0.05)/(L2+0.05) ratio were re-checked against an independent Python calculation: #777777 on white = 4.478089…:1 (fails 4.5 AA even though it can look close), #ff8a1f on #17242b = 6.729173…:1.

### Prediction
- Lowering severity moves a saturated red less than full protanopia; 0% is the identity; neutral grays are unchanged at every severity because each matrix row sums to 1 (±2e-6 from six-digit rounding).

### Changes
- Severity slider (0–100%, 10% steps, keyboard accessible) for the three CVD types; disabled with an explanation for grayscale/original. Labels switch between “-opia” (100%) and “-omaly”. Saved setups store `visionSeverity`; older saves restore 100%. The guided preset's `severity: 100` now sets the slider instead of being ignored. CSV export adds severity and a clipping flag.
- Clipping detection: `simulateVisionDetail` reports when the linear result leaves [0, 1]; the test color note says so.
- Fixed the studio-scene lesson text (“overlap with P.” → “with the Probe tool.”), gave the palette strips `role="img"` (axe aria-prohibited-attr), and enlarged the lamp/label checkbox rows to 44 px.

### Verified
- `node --test apps/color-vision-lab/tests/*.cjs`: 204 pass (8 new severity/clipping/state tests and 1 app test). Browser: phone 390×844 and desktop 1440×900, both themes — no horizontal overflow, no page errors, axe WCAG A/AA clean except the intentional failing #777777 sample in Readability. Notebook SVG/CSS and readability CSV downloads checked for content.
