feat: release Colour Tools 0.1.0
This commit is contained in:
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user