feat: release Colour Tools 0.1.0

This commit is contained in:
2026-08-31 21:21:35 +02:00
commit 0c1fc94b58
100 changed files with 17406 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
# Accessibility notes
Colour Tools treats colour as data as well as appearance. Swatches are paired
with textual values, gamut/contrast status uses text and symbols in addition to
hue, and generated palettes remain usable without visually distinguishing
every swatch.
Workspace navigation uses tabs with selected state and arrow/Home/End keyboard
navigation. Native inputs and labelled controls are preferred. The large picker
supports arrow-key adjustment and announces its saturation and brightness;
image sampling supports numeric coordinates and keyboard movement in addition
to pointer selection. Calculation and file statuses use live regions without
clearing the last valid result while a user is typing.
The shared Toolbox shell provides system, light and dark themes. Focus rings,
touch targets, reflow and text contrast should be checked in all themes and at
400% zoom. A WCAG contrast result is not itself proof that the complete
interface conforms: test keyboard order, names/roles/values, error recovery,
high contrast modes, reduced motion and screen-reader output in context.
+69
View File
@@ -0,0 +1,69 @@
# Architecture
Colour Tools is a client-only React application built as relocatable static
files. The UI and calculation engine are intentionally separated so colour
operations can be tested without a browser and UI state can keep showing the
last valid result while an input is incomplete.
## Layers
1. `src/colour/` is the serialisable colour domain. It parses and formats
colours, converts gamuts, composites layers, interpolates stops, evaluates
contrast and Delta E, simulates colour-vision deficiencies, and builds or
exports palettes. Its public API is re-exported from `src/colour/index.ts`.
2. `src/palette/` contains browser-independent raster bounds, sampling and
deterministic OKLab clustering. `src/workers/palette.worker.ts` exposes the
expensive extraction path to a dedicated worker.
3. `src/components/` contains the seven workspaces and shared accessible
controls. Workspace selection is represented by a URL fragment, so a view
can be bookmarked without router or server support.
4. `src/hooks/` owns bounded device-local UI persistence. Image bytes are not
stored there.
5. `src/toolbox/` contains the checked manifest identity. The shared Toolbox
shell supplies navigation, contextual actions and theme behaviour without
changing the standalone application domain.
## Colour values
The engine's `ColourValue` is a plain object: colour-space identifier, three
numeric coordinates and alpha. It deliberately carries coordinates that may
sit outside a display gamut. Color.js 0.7.1 supplies standards-aligned parsing
and conversion primitives; Colour Tools wraps these in typed, bounded domain
operations and stable UI formats.
Browser preview is a separate operation. It maps a value to sRGB and reports
whether mapping occurred. This keeps display limitations from mutating the
source colour used by later conversions or comparisons.
## Raster pipeline
The image picker validates the selected file before decode, rejects excessive
bytes/dimensions/pixels, then draws the decoded image to a same-origin in-memory
Canvas 2D surface. Sampling reads a bounded neighbourhood. Palette extraction
subsamples deterministically, converts points to OKLab and clusters them with a
fixed initialization and iteration bound. The worker client can fall back to
the same pure function when workers are unavailable.
Temporary object URLs are revoked and outstanding extraction is aborted when an
image is replaced or removed. No image or extracted palette is transmitted.
## Build and deployment
Vite builds with a relative base. `scripts/generate-toolbox-manifest.mjs`
checks package, application and Toolbox versions plus immutable source
identity. `scripts/prepare-release-files.mjs` places project documentation and
the exact installed runtime licence inventory beside the executable app.
The service worker is relative-scope and deployment-subpath aware. It is
progressive enhancement: the app remains usable when registration is rejected.
The deterministic packager rejects symbolic links, source maps, secret-like
filenames and root-absolute asset references before creating the release ZIP
and SHA-256 sidecar.
## Testing boundaries
Unit tests cover parsing, numerical operations, invalid inputs, raster bounds,
sampling and palette determinism. Browser smoke tests run the production build
under a restrictive policy at `/deep/nested/colour/`, exercise all major
workspaces and fail on uncaught errors or external network requests. The
Toolbox contract checker validates the static artifact independently.
+94
View File
@@ -0,0 +1,94 @@
# Colour mathematics and terminology
This document records the choices that materially change a result. It is not a
replacement for CSS Color, Compositing and Blending, or WCAG.
## Parsing and conversion
The parser accepts CSS colour syntax supported by the installed Color.js
version, including hex and named colours; `rgb()`, `hsl()`, `hwb()`, Lab/LCH
and OKLab/OKLCH functions; and registered `color()` spaces. Conversion keeps
finite coordinates outside a target gamut. Hex and ordinary browser sRGB
previews require mapping; the UI labels when this happened.
The converter exposes device-oriented and mathematical representations, but a
numeric tuple is meaningful only with its named colour space, white point,
transfer curve and alpha convention. CMYK output is a convenient mathematical
conversion, not a printer ICC separation or proof.
## Alpha compositing
For a source colour over a backdrop, with straight alpha `αs` and `αb`, output
alpha is:
```text
αo = αs + αb × (1 αs)
```
For each premultiplied channel under normal source-over:
```text
Co = Cs × αs + Cb × αb × (1 αs)
co = Co / αo when αo > 0
```
Layers are accumulated from the bottom upward even though the editor displays
the top layer first. Layer opacity multiplies the colour's own alpha. Blend
modes are applied before source-over using the model in CSS Compositing and
Blending.
“Encoded sRGB” applies the operation directly to sRGB-encoded channel values,
matching many quick web calculators. “Linear-light sRGB” first removes the
sRGB transfer curve and is generally the physically meaningful choice for
mixing emitted light. The two results are expected to differ.
## Interpolation
A ramp samples its first and last stop and distributes missing positions like a
CSS gradient. Each segment is interpolated in the selected space with the
chosen easing. Cylindrical spaces need an explicit hue route: shorter, longer,
increasing, decreasing or raw. The default premultiplies colour components by
alpha before mixing and unpremultiplies afterwards, preventing hidden channels
inside a transparent endpoint from tinting the ramp. A straight-alpha toggle
is available for intentionally reproducing that model. OKLCH is the UI default
because equal numeric progress tends to look more even than encoded RGB; it
does not guarantee that every intermediate colour lies in a display gamut.
## Gamut mapping
An in-gamut check does not modify the colour. Display mapping uses an OKLCH
chroma-reduction method by default; channel clipping is available at the engine
boundary where explicitly selected. The result includes Delta E OK from the
source so callers can quantify the change. A browser preview is always mapped
to sRGB because CSS support and physical displays differ.
## Contrast and transparency
Relative luminance and WCAG 2 contrast are evaluated after translucent
foreground/background colours are composited over the stated canvas (white by
default). The ratio is:
```text
(Llighter + 0.05) / (Ldarker + 0.05)
```
The UI reports 3:1, 4.5:1 and 7:1 thresholds and searches for a nearby lighter
or darker passing foreground. A mathematical pass is only one part of an
accessibility review; font size/weight, focus states, disabled controls,
non-colour cues and actual display conditions remain relevant.
## Perceptual difference and simulations
Delta E 76, CMC, CIEDE2000, Delta E OK, ITP and Jz answer different historical
or application needs; values from different formulas are not interchangeable.
The colour-vision previews apply bounded matrices in linear sRGB and expose a
severity control. They are approximations for interface review, not a model of
every observer and not medical advice.
## Palette extraction
The raster extractor ignores sufficiently transparent pixels, converts bounded
samples to OKLab and clusters them deterministically. Reported coverage is the
share of accepted samples assigned to a cluster, not an exact segmentation of
every source pixel. Small antialiased regions and colour-managed decoding can
therefore influence the result.
+60
View File
@@ -0,0 +1,60 @@
# Privacy, security and deployment
## Data flow
Colour input, saved palette entries, selected image bytes, decoded pixels and
generated exports stay in the browser. The application does not issue API
requests, load remote fonts, submit analytics or resolve remote colour
references. A saved palette belongs to the current browser origin and is
stored until the user clears it or browser storage is removed. Images are
in-memory only.
The static host still receives ordinary requests for HTML, JavaScript, CSS,
icons and worker files on initial load and revalidation. Host logs, reverse
proxy logs and browser extension behaviour are outside the application's local
processing boundary.
## Recommended headers
Serve the static artifact over HTTPS and apply a policy equivalent to:
```text
Content-Security-Policy: default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self'; worker-src 'self' blob:; manifest-src 'self'
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-origin
Permissions-Policy: camera=(), microphone=(), geolocation=(), usb=(), payment=()
Referrer-Policy: no-referrer
X-Content-Type-Options: nosniff
```
The shared Toolbox shell uses inline style properties for colour previews, so
`style-src 'unsafe-inline'` is currently required. Do not add
`script-src 'unsafe-inline'` or third-party script origins. `blob:` is required
for local image object URLs and may be used for a worker fallback.
Serve `.js` as `text/javascript`, `.json` as `application/json`, `.webmanifest`
as `application/manifest+json`, and `.svg` as `image/svg+xml`. Hashed assets may
use a long immutable cache lifetime; keep `index.html`, `sw.js` and
`toolbox-app.json` on revalidation/no-cache so releases update predictably.
## Reverse-proxy example
For an nginx deployment rooted at `/apps/colour/`, use `try_files` only to
resolve actual static files and the directory index; do not rewrite missing
asset paths to HTML. The application itself uses relative paths and needs no
server-side router. Keep the `sw.js` scope at the application directory.
If the app is assembled into toolbox-portal, use the portal's release lock and
assembly process instead of unpacking files into an existing live directory.
Verify the ZIP against its SHA-256 sidecar before assembly.
## File limits and cleanup
The image picker enforces encoded-byte, dimension and decoded-pixel limits
before expensive processing. Sampling radius and palette colour counts are
bounded. Object URLs, decoded buffers and worker requests are released or
cancelled when replaced. Browsers and image decoders remain security-sensitive
dependencies, so deploy current supported browser versions and publish patched
application releases promptly.
See [SECURITY.md](../SECURITY.md) for private reporting instructions.