Privacy choices

Hue Codex uses local settings and privacy-aware measurement.

Required storage keeps tools and your local workspace working. Analytics helps improve popular tools; stricter consent regions stay opt-in unless you allow it. Marketing and personalized ads stay off until you choose them.

Abstract palette generation methodology workspace with a seed swatch, harmony wheel, role-based palette cards, scale ladders, locked colors, and accessibility checks.

Methodology

How Hue Codex generates palettes and scales

Palette tools turn one or more colors into candidate systems. Hue Codex keeps those systems deterministic and role-aware, accepts exact user-defined colors in every role, and perceptually fits the remaining unlocked pairings to explicit contrast targets.

Hue Codex heuristicStandardDraft standardApproximation

Short answer

Hue Codex palette generation combines documented HSL style formulas and hue anchors with a lock-aware OKLCH correction pass. Editing any role creates an exact custom anchor and locks it automatically; Rebuild unlocked roles then fits the rest of the palette around every locked color. Unlocked body text is fitted to 7:1 on the surface; meaningful action, support, boundary, and status colors are fitted to 3:1; and black or white primary-action text is selected at 4.5:1 or better. Those are pair-specific guarantees, not a claim that every possible component state is accessible.

  • Harmony tools rotate hue angles around a color wheel and then apply the listed saturation/lightness tone formulas.
  • Role palettes assign jobs such as primary, secondary, accent, surface, text, border, success, warning, and danger.
  • Tint, shade, tone, and ladder tools can use OKLCH, HSL, or simple RGB-style approaches depending on the selected mode.
  • Random seed selection is not itself reproducible, but regeneration is deterministic once the resulting HEX seed, style, locks, and previous locked colors are known.
  • An exact role edit locks that color immediately and puts the palette into a pending state until the unlocked roles are rebuilt and all nine contracts are evaluated again.
  • Unlocked named role pairings pass their documented targets; incompatible locked colors remain unchanged and are reported as exact conflicts.

Standards status

These badges identify which parts of this methodology are standards-backed, draft-track, source-origin references, Hue Codex-specific, approximate, or dependent on browser behavior.

Hue Codex heuristicStandardDraft standardApproximation
Hue Codex heuristicHue Codex ranking, role hints, bands, labels, or workflow guidance rather than an external standard.
StandardStable standard or standards-backed behavior used as authority, such as WCAG 2.2 and broadly implemented CSS Color 4 behavior.
Draft standardDraft, candidate, or preview specification material, such as CSS Color 5 features; not treated as final authority.
ApproximationModel or estimate with known limits, including CMYK, color-vision simulation, image palette extraction, or CSS duotone output.

Formulas, choices, heuristics, and limits

This separates standards-based formulas from Hue Codex implementation decisions, product heuristics, and known limitations for this methodology.

Standards-based formulas

Formula, threshold, syntax, or data behavior taken from a cited standard. Primary and model sources can inform implementation, but they are not automatically standards.

  • WCAG contrast formulas are used for palette readability metadata.
  • CSS Color 4 color-model behavior supports the conversions behind HSL, Lab/LCH, and OKLab/OKLCH views.

Implementation choices

How Hue Codex chooses to parse, normalize, round, export, or sequence calculations.

  • Seeds and pasted colors are normalized before Hue Codex builds roles, harmonies, scales, and exports.
  • Locked roles keep their previous values during regeneration.
  • The unlocked surface searches for the nearest OKLCH lightness that satisfies locked foreground contracts when a shared solution exists.
  • Unlocked contracted roles keep hue while lightness moves toward the higher-contrast endpoint; chroma is reduced only when needed for sRGB output.
  • Scale mode selection determines whether Hue Codex uses OKLCH, HSL, or simple RGB-style mixing.
  • Style preset formulas, role hue anchors, semantic hue pulls, saturation/lightness clamps, and scale weights are fixed constants listed in the pseudocode.
  • The Random seed action uses the browser JavaScript Math.random() source to choose a new HSL seed, then deterministic palette formulas run from that resulting HEX value.
  • Palette share URLs use versioned fragment state to serialize the concrete seed, selected style, lock flags, resolved role colors, and supported export settings. They do not serialize an internal random generator state.
  • Palette accessibility metadata is recomputed after fitting from the resolved surface and text roles; the dashboard, specimen, role cards, and exports use the same pair values.

Hue Codex heuristics

Product rankings, bands, labels, suggestions, or role hints that are useful guidance but not external standards.

  • Role assignment, semantic state color shifts, tone presets, style presets, and role hints are Hue Codex product heuristics with deterministic constants.
  • Harmony tuning applies explicit saturation and lightness formulas that favor practical swatches over purely geometric hue rotations.
  • The 7:1 text contract, 3:1 functional-role contract, 0.05 solver margin, and decision to preserve OKLCH hue before reducing chroma are Hue Codex product policies built on WCAG contrast math.
  • Accessible pair counts and best text suggestions outside the declared role pairings remain screening aids, not design-system approval.

Known limitations

Caveats, edge cases, browser dependencies, approximations, or contexts the method does not prove.

  • Passing named role contracts do not guarantee a complete brand system or every component, state, overlay, gradient, opacity, or adjacent-color pairing.
  • High-chroma or very neutral seeds can produce constrained, clipped, or low-chroma companions.
  • Final UI roles still need component-level contrast, color-vision, and state review.
  • A random seed cannot be regenerated from an internal PRNG seed because Hue Codex does not expose or store a seeded PRNG state.

Representative automated fixture coverage

Hue Codex imports versioned JSON fixtures and tests the corresponding pure production functions directly. The groups below are the representative regression evidence most relevant to this methodology.

  • Palette fixtures: Deterministic role output, locked-color preservation, readable-pair metadata, and export consistency. View the JSON fixtures.
  • Contrast fixtures: Known ratios, threshold boundaries, symmetry, and unrounded pass/fail behavior. View the JSON fixtures.
  • Export fixtures: Displayed-versus-exported values, parseable CSS and JSON, and safe allowlisted SVG output. View the JSON fixtures.

What a pass means. A passing group confirms the named vectors and tested properties still match the documented implementation. It does not prove every possible input, final browser rendering, full accessibility conformance, profile-aware print output, or every downstream export consumer.

Inputs and role assignment

Hue Codex heuristicStandard

Palette generation starts with a seed color or pasted color list. The seed is normalized to HEX, converted to HSL and OKLCH when needed, and used to build predictable relationships.

Role palettes first compute an expressive candidate from the seed HSL value and the selected style. Balanced keeps the seed as the initial primary candidate; other styles rebuild primary from the seed hue plus style saturation and lightness. Hue Codex then fits unlocked functional roles in OKLCH, moving lightness first and reducing chroma only when needed for an sRGB-safe result.

Generation-time validation pipeline

Hue Codex heuristicStandardApproximation

The Palette Generator validates during generation rather than emitting an unchecked draft and a repair list. For an unlocked palette, the result is not presented until all nine built-in pair checks pass. The same resolved values drive the Role Board, live specimen, validation details, share state, and exports.

Locks are absolute. A locked role keeps its exact HEX. If the locked values make a contract impossible, Hue Codex does not silently change them; it preserves the requested palette and reports the exact affected roles, current raw ratio, target, and number of checks blocked by locks.

Ordered Palette Generator pipeline.
StepOperationDeterministic rule
1Normalize the seedParse the input, reduce it to opaque sRGB HEX for palette math, and retain the normalized seed for fidelity reporting.
2Build the style candidateApply the selected one of 17 documented HSL role formulas and semantic hue anchors.
3Restore locksCopy every locked role HEX from the previous or shared palette before any contrast correction.
4Resolve Surface around locksWhen Surface is unlocked, scan sRGB-safe OKLCH lightness candidates and choose the least-distorted one that satisfies all compatible locked role-on-Surface contracts. Equal scores keep the first stable scan result.
5Fit unlocked contracted rolesIn role order, keep OKLCH hue stable, move lightness toward the higher-contrast black or white endpoint, and reduce chroma only when required for a clean sRGB round trip.
6Derive filled-role foregroundsFor Primary, Secondary, Accent, Success, Warning, and Danger, choose whichever of #000000 or #FFFFFF has the higher unrounded contrast.
7Evaluate all nine checksEvaluate Text/Surface at 7:1; Primary, Secondary, Accent, Border, Success, Warning, and Danger on Surface at 3:1; and On Primary/Primary at 4.5:1. Threshold decisions use raw ratios.
8Attach validation metadataRecord pass and blocked counts, seed-to-Primary OKLab distance, exact before/after adjustments, readable on-colors, lock state, style, and sRGB output gamut.

Custom role editing and explicit rebuild

Hue Codex heuristicStandardBrowser-dependent

Every named role exposes a text input and native color picker. A supported opaque CSS color is normalized to uppercase sRGB HEX, applied to the current palette, and locked automatically as a user-defined anchor. Editing Primary also updates the normalized seed so the next rebuild grows from that exact primary color.

A direct edit updates the visible role, specimen, and exports immediately, but Hue Codex deliberately labels the palette as awaiting rebuild. Rebuild unlocked roles starts from the selected seed and style, restores every locked HEX without modification, resolves a compatible unlocked Surface when possible, fits the other unlocked roles, and reruns the nine built-in checks. An incompatible set of locked colors is preserved and reported instead of being silently altered. Unlocking a role after that conflict creates a new pending regeneration state and re-enables Rebuild unlocked roles, so the released value can be replaced without changing the anchors that remain locked.

Exact color editing and rebuild behavior.
InteractionDocumented behavior
Role text input or color pickerAccept a supported opaque CSS color, normalize it to sRGB HEX, replace that exact role, mark it user-defined, and lock it automatically.
Edit PrimaryUse the exact new Primary as both the locked role value and the seed for subsequent unlocked-role generation.
Pending stateShow the custom values immediately, identify which anchors await rebuilding, enable Rebuild unlocked roles, and label JSON validation as customization-pending rather than approved.
Rebuild unlocked rolesPreserve every locked HEX exactly, regenerate only unlocked roles from the current seed/style candidate, fit the named contrast contracts, and recompute all metadata and exports.
Unlock before or after rebuildingThe role is no longer an anchor, is marked as ready to regenerate, and immediately re-enables Rebuild unlocked roles. The next rebuild or seed/style regeneration replaces that released value while preserving roles that remain locked.
Incompatible locksPreserve the requested locked colors and report the exact failed pair, raw ratio, target, and lock-blocked count.

Deterministic regeneration and random seeds

Hue Codex heuristicBrowser-dependent

Hue Codex does not use a seeded, replayable palette PRNG. The Palette Generator Random seed action calls the browser JavaScript Math.random() source three times to choose h = floor(random * 360), s = 62 + floor(random * 24), and l = 42 + floor(random * 20), then converts that HSL color to a normalized HEX seed.

After a concrete HEX seed exists, palette regeneration is deterministic. The same normalized seed, selected style, lock vector, and previous locked role values produce the same role palette; without locks, the same seed and style produce the same nine generated role colors.

Random seed, URL, share, and lock behavior.
AreaDocumented behavior
PRNG sourceBrowser JavaScript Math.random(); Hue Codex does not expose, store, or serialize an internal PRNG seed.
Seed sourceThe generator reads a validated fragment state when present; otherwise it starts from #4169E1. Legacy route and state-query inputs permanently redirect to fragment state. The Random seed button replaces the seed input with the new random HEX without rewriting the current URL.
Deterministic regenerationRegeneration calls the role formulas using normalize_hex(seed), selected style, current locks, and the previous palette for locked roles. No random value is used after the seed HEX is chosen.
Share URLCopy Link creates /palette-generator#s={versioned-state}. The fragment preserves the current seed, style, lock flags, resolved role palette, and supported export setting; it does not preserve internal Math.random() draws or custom-marker and pending-rebuild provenance. Rebuild before handoff when a local edit is still pending.
Lock behaviorA locked role keeps its previous HEX when seed or style changes or an explicit rebuild runs. Multiple locked roles are treated as simultaneous anchors. The unlocked surface can move to satisfy their compatible role contracts; other unlocked roles are then fitted around the resolved surface. Current lock flags are encoded in an explicit Share URL.

Visual example with expected output

Hue Codex heuristicStandard

This example uses one seed so visual output can be compared against the formulas and test vectors on this page. Inputs are seed #336699, Balanced style, no locked roles, and the default OKLCH scale mode with 3 generated steps. Border and warning show the post-generation contrast fit.

Seed #336699, Balanced style

Balanced keeps the seed as Primary, generates the remaining role candidates, and then fits functional roles. The scale rows use OKLCH weights 0.25, 0.5, and 0.75.

Expected role palette

Primary#336699Seed color
Secondary#353078Support role
Accent#B8732EContrast accent
Surface#F3F4F6Panel/page surface
Text#262517Readable ink
Border#8A8B973.07:1 control boundary
Success#27906APositive state
Warning#958F003.07:1 warning indicator
Danger#E13D79Error state

Expected OKLCH tints, 3 steps

Tint 1#598ABDweight 0.25
Tint 2#7FAFE2weight 0.5
Tint 3#A7D6FFweight 0.75

Expected OKLCH shades, 3 steps

Shade 1#154978weight 0.25
Shade 2#002D59weight 0.5
Shade 3#00123Bweight 0.75

Expected OKLCH tones, 3 steps

Tone 1#416992weight 0.25
Tone 2#4E6B8Aweight 0.5
Tone 3#5A6D83weight 0.75

Role generation rules

Hue Codex heuristic

Role generation starts with base = rgb_to_hsl(seed) and settings = palette_style_settings(base, style). Hue Codex converts the formulas below to HEX after normalizing hue and clamping HSL channels. These values are candidates: unlocked contracted roles may receive a minimal OKLCH lightness/chroma correction, while locked roles keep their previous HEX.

role_hue(key, fallback_offset) uses an absolute style anchor when settings[key + Hue] exists: harmonized_hue(base.h, anchor, settings.roleShift or 0.2, settings.roleMaxShift or 36). Otherwise it uses normalize_degrees(base.h + fallback_offset).

Role formulas before final HSL-to-HEX conversion.
RoleHue formulaSaturation formulaLightness formula
primarybase.h; balanced style keeps the original seed HEX instead of regenerating HSLclamp(settings.s)clamp(settings.l, 4, 97)
secondaryrole_hue(secondary, settings.secondary)settings.s * 0.86settings.l - 7
accentrole_hue(accent, settings.accent)min(94, settings.s + 10)settings.l + 5
surfacerole_hue(surface, settings.surface)settings.surfaceS ?? max(12, settings.s * 0.28)settings.surfaceL ?? 96
textrole_hue(text, settings.text)settings.textS ?? max(24, settings.s * 0.42)settings.textL ?? 12
borderrole_hue(surface, settings.surface)settings.borderS ?? max(10, settings.s * 0.24)settings.borderL ?? 78
successsemantic_hue(145, 0.20, 18)semantic_s(48, 34, 88)semantic_l(36, 28, 52)
warningsemantic_hue(44, 0.16, 14)semantic_s(68, 44, 94)semantic_l(62, 48, 74)
dangersemantic_hue(350, 0.14, 12)semantic_s(64, 42, 92)semantic_l(56, 42, 68)

Semantic hue targets

Hue Codex heuristic

Success, warning, and danger are nudged toward familiar status-color hue regions, then pulled toward the seed hue. The pull is shortestHueDistance(anchor, seed) * strength * settings.semanticShift, clamped to plus or minus max_shift. Monochrome style sets semanticMode to seed, so semantic hues use base.h instead of the status anchors.

Semantic hue anchors, ranges, and weighting.
RoleAnchorTarget range after capSeed weightingS and L formulas
success145deg127..163degshortestHueDistance(145, base.h) * 0.20 * settings.semanticShift, capped to +/-18degs = clamp((48 + base.s * 0.18) * settings.semanticS, 34, 88); l = clamp(36 + settings.semanticL, 28, 52)
warning44deg30..58degshortestHueDistance(44, base.h) * 0.16 * settings.semanticShift, capped to +/-14degs = clamp((68 + base.s * 0.18) * settings.semanticS, 44, 94); l = clamp(62 + settings.semanticL, 48, 74)
danger350deg338..360deg or 0..2degshortestHueDistance(350, base.h) * 0.14 * settings.semanticShift, capped to +/-12degs = clamp((64 + base.s * 0.18) * settings.semanticS, 42, 92); l = clamp(56 + settings.semanticL, 42, 68)

Harmony and scale logic

Hue Codex heuristicStandardApproximation

Harmony generation uses conventional hue offsets such as complementary, analogous, triadic, tetradic, split-complementary, and monochromatic relationships. Preserve, balanced, vivid, and muted tone presets use the exact saturation/lightness equations in the pseudocode.

Scale generation clamps requested steps to 3 through 11, then uses weights (index + 1) / (count + 1). The seed is the anchor color for each formula and is not inserted as one of the generated tint, shade, or tone rows. OKLCH, HSL, and simple RGB modes apply different formulas, so the chosen mode changes the resulting HEX values.

Scale formula specifications

Hue Codex heuristicApproximation

Scale formulas are Hue Codex generation heuristics, not standards-defined palettes. All modes derive generated values from the normalized seed HEX, clamp requested step count to count = min(11, max(3, requested_steps)), and assign weight_i = (i + 1) / (count + 1) for i = 0..count-1.

The generated scale groups contain only derived colors. The original seed is shown or exported separately when the tool presents the base color, which keeps the seed anchored without duplicating it inside every tint, shade, or tone list.

Exact tint, shade, and tone formulas by scale mode.
ModeSeed anchorTint trajectoryShade trajectoryTone trajectoryClamping and output
OKLCHConvert seed to L0, C0, H0 with rgb_to_oklch(seed). Keep H0 for all generated swatches.L = L0 + (0.98 - L0) * w; C = C0 * (1 - 0.18w)L = L0 * (1 - 0.82w); C = C0 * (1 - 0.08w)L = L0 + (0.62 - L0) * 0.32w; C = C0 * (1 - 0.78w)Clamp L to 0..1 and C to >= 0, convert OKLCH to sRGB, then clamp and round encoded RGB channels to 0..255 before HEX output.
HSLConvert seed to H0, S0, L0 with rgb_to_hsl(seed). Keep H0 for all generated swatches.S = max(0, S0 - 10w); L = L0 + (98 - L0) * wS = S0; L = max(4, L0 * (1 - 0.82w))S = max(0, S0 * (1 - 0.75w)); L = L0 + (50 - L0) * 0.45wH is normalized to 0..360, S/L are interpreted as percentages, and final RGB channels are clamped and rounded to 0..255.
Simple RGBUse the seed encoded sRGB 8-bit channels as R0, G0, B0. No linear-light conversion is applied.mix_srgb(seed, #FFFFFF, w)mix_srgb(seed, #000000, w)mix_srgb(seed, #808080, 0.82w)mix_srgb(a,b,w) clamps w to 0..1 and returns round(a_channel + (b_channel - a_channel) * w) for each encoded sRGB channel.

Harmony specifications

Hue Codex heuristic

Harmony swatches are generated in HSL. Hue is calculated as normalize_degrees(base.h + offset), where normalize_degrees returns a value in [0, 360). Non-monochromatic harmonies use the selected tone preset for saturation and lightness; monochromatic harmonies keep the same hue and use the listed saturation-scale and lightness-delta arrays.

Hue Codex does not adjust OKLCH chroma during harmony generation. OKLCH chroma adjustments belong to the separate tint, shade, and tone scale formulas.

Harmony swatch counts, offsets, and adjustment rules.
HarmonySwatchesOffsetsSaturation/lightness/chroma adjustmentNeutral handling
Complementary2[0, 180]Base swatch remains seed. Complement uses tone_values(base, tone, index 1). No OKLCH chroma adjustment.Preserve tone keeps neutral support gray; balanced/vivid/muted can introduce saturation from the fallback hue.
Analogous3[0, -30, 30]Base remains seed. Neighbors use tone_values at indexes 1 and 2. Negative offset wraps through normalize_degrees.Achromatic seed uses fallback hue 0; -30 wraps to 330 and +30 to 30 if tone clamps add saturation.
Triadic3[0, 120, 240]Base remains seed. Triad swatches use tone_values at indexes 1 and 2. No OKLCH chroma adjustment.Neutral seed behavior follows selected tone: preserve stays gray; other tones may create low/medium-saturation fallback-hue swatches.
Tetradic4[0, 90, 180, 270]Base remains seed. Square/complement swatches use tone_values at indexes 1, 2, and 3. No OKLCH chroma adjustment.Neutral seed can generate fallback-hue square colors when tone saturation minimums apply.
Split complementary3[0, 150, 210]Base remains seed. Split swatches use tone_values at indexes 1 and 2. No OKLCH chroma adjustment.Neutral seed uses fallback hue 0, so split hues are 150 and 210 only if the tone preset adds saturation.
Monochromatic5[0, 0, 0, 0, 0]Base remains seed. Other swatches use saturationScale [0.72, 0.82, 0.62, 0.38] with lightnessDelta [-28, -14, 16, 32], clamped to s 18..92 and l 10..94. No OKLCH chroma adjustment.Neutral base remains the seed; non-base swatches use the fallback hue and minimum saturation 18 because of the monochromatic clamp.

Accessibility metadata

StandardHue Codex heuristic

Palette accessibility metadata uses the WCAG contrast formula, then Hue Codex applies role-specific product checks. The formula and thresholds are standards-based; the palette recommendation title, readiness status, best-text suggestions, and next-step copy are Hue Codex hints.

Counts ordered non-identical palette color pairs whose raw contrast ratio is at least 4.5:1. Display values may round to two decimals, but the count uses the unrounded ratio.

The generator also records seed fidelity as Euclidean distance in OKLab multiplied by 100, plus every role adjustment as before HEX, after HEX, OKLab distance, and reason. This is provenance metadata, not a perceptual acceptability standard.

Per-role metadata calculated for every palette color.
FieldExact pair or candidate poolThreshold or use
contrastOnSurfacecurrent role color on the surface role, falling back to #FFFFFF if no surface role existsRounded ratio for display/export metadata
wcagOnSurfacecurrent role color on the surface roleRaw-ratio booleans: AA normal >= 4.5, AAA normal >= 7, AA large/UI >= 3, AAA large >= 4.5
contrastWithTextcurrent role color against the text role, falling back to #10212B if no text role existsRounded ratio used as metadata; it is not a universal approval for using that role as text
bestTexthighest raw contrast on the current role color from [text role, #FFFFFF, #000000]A swatch-label and copy hint only; final text choices must be checked in the actual foreground/background context

Palette contrast contracts

StandardHue Codex heuristic

The Palette Generator uses named role contracts both to fit unlocked colors and to report locked conflicts. The dashboard, live specimen, handoff summary, and contrast report all read the same resolved role values. These contracts do not test every possible UI state.

The nine named role pairs tested by the Palette Generator.
PairWhere it appearsThreshold interpretation
text on surfaceFit pass, recommendation, specimen, role cards, exportsGenerated target >= 7.0 for AAA normal text
primary on surfaceFit pass, specimen, recommendation, decision grid, reportGenerated target >= 3.0 as a meaningful action/UI color
black or white on primarySpecimen, recommendation, readiness, handoff, report, and all developer exportsHigher raw-contrast candidate; AA normal text target >= 4.5
secondary and accent on surfaceFit pass, specimen, recommendation, decision grid, reportGenerated target >= 3.0 as meaningful support/UI or graphical colors
border on surfaceFit pass, specimen, recommendation, role cards, exportsGenerated target >= 3.0 for a meaningful control boundary; decorative dividers are a separate use
success, warning, danger on surfaceFit pass, specimen, recommendation, role cards, exportsGenerated target >= 3.0 for status icons or indicators; status meaning still needs text, an icon, shape, or another non-color cue
every contracted role on surfaceRole cards and the Role on surface block in the contrast reportEach raw ratio is evaluated before display rounding
all ordered color pairsShared palette readable-pairs countCounts ordered non-identical palette color pairs whose raw WCAG contrast ratio is at least 4.5:1. Display values may round to two decimals, but the count uses the unrounded ratio.

Pair guarantees and limits

Hue Codex heuristic

An unlocked generated palette reports all contracts passing only when body text reaches 7:1 on surface, primary/secondary/accent/border/status roles reach 3:1 on surface, and the selected black-or-white primary foreground reaches 4.5:1. The generator uses a 0.05 internal safety margin when moving unlocked colors, but conformance booleans use the exact unrounded thresholds.

The contract does not account for arbitrary role combinations, font rendering, disabled states, focus appearance size, multiple adjacent colors, imagery, gradients, opacity, or whether color is the only status cue. The live specimen demonstrates the intended pairings; it is not a universal UI certification.

The live specimen uses only checked relationships: Text on Surface; Primary on Surface; On Primary on Primary; Secondary, Accent, Border, Success, Warning, and Danger on Surface. Its optional role-mapping inspector exposes exact foreground and background HEX values, the ratio, target, and pass status for the selected component.

Locked colors and edge cases

Hue Codex heuristicBrowser-dependent
  • Locked colors keep their previous HEX value when a palette is regenerated. If the surface is flexible, it searches for the nearest lightness that satisfies every locked role contract before other unlocked roles are fitted.
  • A custom role edit accepts a supported opaque CSS color, normalizes it to uppercase sRGB HEX, and locks the role. Editing Primary also changes the seed used for the next rebuild.
  • If a custom role is unlocked before rebuild, the next rebuild or seed/style regeneration may replace it; only locked colors are exact generation constraints.
  • When a locked surface and locked foreground conflict, or several locked foregrounds have no shared surface solution, Hue Codex preserves the locks and reports the exact failed contract.
  • Highly neutral seeds can produce low-chroma companion roles; Hue Codex clamps role saturation to 0..100 and role lightness to 4..97 before converting HSL roles to HEX.
  • During the contrast-fit pass, OKLCH hue is held stable, lightness moves first, and chroma is reduced only when the candidate would not round-trip cleanly through sRGB.
  • Semantic colors use anchors 145deg, 44deg, and 350deg, then pull toward the seed hue by shortestHueDistance(anchor, seed) * strength * semanticShift, capped by maxShift.

Validation checks

Hue Codex heuristic
Palette behavior checks used for QA.
CheckExpected behavior
#336699 Balanced fitted role fixtureprimary #336699; secondary #353078; accent #B8732E; surface #F3F4F6; text #262517; border #8A8B97; success #27906A; warning #958F00; danger #E13D79
#336699 Balanced role checksumSerialize the preceding fixture as role:#HEX pairs in the listed order, joined by | with no spaces. Expected SHA-256, grouped for readability: a5b6fdfb 1fd9edc8 340946b0 eafaeb8a a83b34a2 07ef73c0 3fdee079 9a16130e.
Seed #0EA5E9 with Calm stylePrimary #2B96C3, surface #F3F4F7, text #272116, border #868C97, and warning #958F00 after constraint fitting
Locked roleLocked HEX value remains unchanged after regeneration
Exact two-anchor brand paletteStarting from #ED1C24 Balanced, editing Secondary to #FFF200 locks Primary and Secondary exactly; rebuilding preserves both values and all nine declared contracts pass.
Pending custom validationBefore explicit rebuild, the dashboard warns that custom anchors are pending and JSON exports validation.status = customization-pending with the affected role names.
Editable role controlsEvery role text input and native color picker has an accessible label; Enter applies a text value, Escape restores the current value, and a successful edit locks the role.
Locked accent during regenerationIf the accent role is locked, its previous HEX remains unchanged while unlocked roles recalculate from the new seed/style
Contrast metadataBest text and pair counts update after every palette change
Text role changed after generationcontrastWithText, best-text suggestions, and accessible pair counts are recalculated from the new text role
Scale step countRequested steps are clamped to a practical range for readable output
Random seed actionThe chosen HEX is random because it comes from Math.random(); after that HEX is set, regeneration follows deterministic formulas
Share palette URLThe copied /palette-generator#s={versioned-state} URL restores the supported current seed, style, locks, resolved roles, and export setting, but not an internal PRNG state
Dynamic role inputs and sharingPer-role editor fields are excluded from generic form serialization so exact palette roles are carried once in the resolved palette state and SVG/share actions do not reject them as unsupported fields.
Locked role plus new random seedLocked roles keep their prior HEX while unlocked roles regenerate from the new random seed and selected style
Unlocked style/seed invariantFor every supported style and tested boundary or arbitrary sRGB seed, all declared role contracts pass after fitting
All 17 styles stay distinctFor representative chromatic and neutral seeds, each supported style has a stable role signature and the intended style direction remains visible after fitting
Palette text/surface contracttext on surface is fitted to AAA normal 7:1 using the raw contrast ratio
Palette action/border/status contractsprimary, secondary, accent, border, success, warning, and danger are fitted to UI/non-text 3:1 on surface
Primary action textHigher raw contrast from [#FFFFFF, #000000] is used and must reach AA normal 4.5:1
Readable filled-role foregroundson-primary, on-secondary, on-accent, on-success, on-warning, and on-danger each use the higher-contrast black-or-white candidate and appear in CSS, JSON, token, role-map, Tailwind, Sass, contrast, and handoff exports
Validation metadataPass count, blocked count, seed/Primary OKLab distance, exact adjustment history, locks, style, on-colors, and sRGB output are recalculated after generation
Live role mappingThe optional inspector is off by default, makes mapped specimen elements keyboard-focusable only while active, and reports the exact pair and target used by the generator
Locked conflictIncompatible locked HEX values remain unchanged and the failed role contract is returned as a locked failure
Shared readable pairs countCounts ordered non-identical palette color pairs whose raw WCAG contrast ratio is at least 4.5:1; a pair that only rounds up to 4.50 is not counted

Palette generation pseudocode

StandardHue Codex heuristicApproximation

Harmony math and role palettes are deterministic Hue Codex heuristics. WCAG contrast math and thresholds are standards-based; the style formulas, role contracts, OKLCH repair strategy, and safety margin are documented product choices.

Harmony hue offsets and tone clamps
scheme_offsets:
  complementary:       count = 2, offsets = [0, 180]
  analogous:           count = 3, offsets = [0, -30, 30]
  triadic:             count = 3, offsets = [0, 120, 240]
  tetradic:            count = 4, offsets = [0, 90, 180, 270]
  split_complementary: count = 3, offsets = [0, 150, 210]
  monochromatic:       count = 5, offsets = [0, 0, 0, 0, 0]

monochromatic_variants:
  index 0 Base:      lightnessDelta = 0,   saturationScale = 1
  index 1 Deep tone: lightnessDelta = -28, saturationScale = 0.72
  index 2 Shade:     lightnessDelta = -14, saturationScale = 0.82
  index 3 Tint:      lightnessDelta = 16,  saturationScale = 0.62
  index 4 Pale tint: lightnessDelta = 32,  saturationScale = 0.38

tone_values(base_hsl, tone, index):
  if tone == preserve:
    return s = base.s, l = base.l
  if tone == vivid:
    return s = clamp(base.s + 16, 64, 94),
           l = clamp(base.l + (index even ? -2 : 4), 42, 62)
  if tone == muted:
    return s = clamp(base.s * 0.52, 22, 56),
           l = clamp(base.l + (index even ? 6 : 10), 46, 74)
  # balanced
  return s = clamp(base.s * 0.86, 40, 78),
         l = clamp(base.l + (index even ? -4 : 6), 40, 68)

generate_harmony(seed, scheme, tone):
  base = rgb_to_hsl(hex_to_rgb(normalize_hex(seed)))
  # Hue wrapping uses normalize_degrees(h), returning [0, 360).
  # Example: base.h + -30 becomes 330 when base.h is 0.
  for each offset at index:
    h = normalize_degrees(base.h + offset)
    if scheme == monochromatic:
      s = clamp(base.s * saturationScale[index], 18, 92)
      l = clamp(base.l + lightnessDelta[index], 10, 94)
    else:
      s, l = tone_values(base, tone, index)
    hex = index == 0 ? seed : rgb_to_hex(hsl_to_rgb(h, s, l))
    attach WCAG best-text and pair metadata

neutral_seed_policy:
  # Achromatic HSL seeds have base.s = 0 and a fallback hue of 0.
  # Preserve tone keeps generated non-base hues achromatic because s = 0.
  # Balanced, vivid, muted, and monochromatic modes can introduce saturation
  # through their minimum s clamps; that hue is a Hue Codex fallback policy,
  # not a recovered meaningful source hue.
  # Harmony generation does not adjust OKLCH chroma; OKLCH chroma changes
  # are only part of the tint/shade/tone scale formulas.
Role palette style preset constants
clamp(value, low, high):
  return max(low, min(high, value))

palette_style_settings(base_hsl, style):
  balanced = {
    s: base.s, l: base.l,
    secondary: 34, accent: 180, surface: 24, text: 205,
    semanticS: 1, semanticL: 0, semanticShift: 1
  }

  settings = {
    balanced: balanced,
    vivid: {
      s: min(94, base.s + 18), l: min(62, base.l + 3),
      secondary: 42, accent: 174, surface: 28, text: 212,
      semanticS: 1.12, semanticL: -2, semanticShift: 1.2
    },
    calm: {
      s: max(34, base.s - 22), l: min(68, base.l + 8),
      secondary: 24, accent: 156, surface: 18, text: 200,
      semanticS: 0.84, semanticL: 4, semanticShift: 0.75
    },
    neutral: {
      s: max(18, base.s - 38), l: min(64, base.l + 4),
      secondary: 18, accent: 144, surface: 12, text: 196,
      semanticS: 0.64, semanticL: 8, semanticShift: 0.55,
      surfaceS: 12, borderS: 10
    },
    warm: {
      s: max(46, min(86, base.s * 0.82 + 14)), l: clamp(base.l + 2, 38, 62),
      secondaryHue: 28, accentHue: 54, surfaceHue: 36, textHue: 24,
      roleShift: 0.18, roleMaxShift: 34,
      semanticS: 0.96, semanticL: 2, semanticShift: 0.85
    },
    cool: {
      s: max(38, min(82, base.s * 0.82 + 8)), l: clamp(base.l + 1, 38, 62),
      secondaryHue: 205, accentHue: 268, surfaceHue: 206, textHue: 220,
      roleShift: 0.2, roleMaxShift: 38,
      semanticS: 0.86, semanticL: 2, semanticShift: 0.7
    },
    muted: {
      s: max(20, base.s - 38), l: clamp(base.l + 2, 34, 62),
      secondary: 28, accent: 160, surface: 20, text: 205,
      semanticS: 0.58, semanticL: 6, semanticShift: 0.5
    },
    pastel: {
      s: max(30, min(58, base.s * 0.48 + 18)), l: clamp(base.l + 22, 68, 82),
      secondary: 26, accent: 156, surface: 18, text: 205,
      semanticS: 0.48, semanticL: 14, semanticShift: 0.42,
      surfaceL: 97, borderL: 86
    },
    earthy: {
      s: max(26, min(58, base.s * 0.55 + 12)), l: clamp(base.l - 3, 34, 54),
      secondaryHue: 36, accentHue: 96, surfaceHue: 42, textHue: 30,
      roleShift: 0.16, roleMaxShift: 30,
      semanticS: 0.62, semanticL: 4, semanticShift: 0.45,
      surfaceS: 24, borderS: 18
    },
    monochrome: {
      s: max(18, base.s * 0.68), l: clamp(base.l, 34, 62),
      secondary: 0, accent: 0, surface: 0, text: 0,
      semanticS: 0.5, semanticL: 8, semanticShift: 0,
      semanticMode: seed,
      surfaceS: max(8, base.s * 0.16),
      textS: max(12, base.s * 0.32),
      borderS: max(8, base.s * 0.2)
    },
    high-contrast: {
      s: min(96, max(58, base.s + 12)), l: base.l >= 56 ? 44 : 58,
      secondary: 180, accent: 60, surface: 0, text: 205,
      semanticS: 1.16, semanticL: -6, semanticShift: 1.15,
      surfaceL: 98, textL: 7, borderL: 18
    },
    dark: {
      s: max(36, min(72, base.s * 0.72 + 10)), l: clamp(base.l - 22, 18, 38),
      secondary: 28, accent: 180, surface: 210, text: 42,
      semanticS: 0.92, semanticL: 4, semanticShift: 0.8,
      surfaceL: 10, textL: 92, borderL: 30,
      surfaceS: 18, textS: 18, borderS: 22
    },
    light: {
      s: max(24, min(58, base.s * 0.52 + 12)), l: clamp(base.l + 24, 68, 84),
      secondary: 22, accent: 150, surface: 18, text: 205,
      semanticS: 0.62, semanticL: 12, semanticShift: 0.55,
      surfaceL: 98, textL: 16, borderL: 88
    },
    playful: {
      s: min(96, max(62, base.s + 16)), l: clamp(base.l + 9, 54, 72),
      secondary: 54, accent: 205, surface: 42, text: 224,
      semanticS: 1.05, semanticL: 5, semanticShift: 1.05
    },
    elegant: {
      s: max(18, min(48, base.s * 0.42 + 10)), l: clamp(base.l - 8, 28, 50),
      secondary: 28, accent: 132, surface: 18, text: 210,
      semanticS: 0.5, semanticL: 1, semanticShift: 0.38,
      surfaceS: 10, textS: 20, borderS: 12, borderL: 70
    },
    retro: {
      s: max(42, min(78, base.s * 0.6 + 22)), l: clamp(base.l + 1, 42, 62),
      secondaryHue: 32, accentHue: 176, surfaceHue: 46, textHue: 24,
      roleShift: 0.12, roleMaxShift: 28,
      semanticS: 0.82, semanticL: 5, semanticShift: 0.62,
      surfaceS: 28, borderS: 22, borderL: 72
    },
    futuristic: {
      s: min(98, max(54, base.s + 20)), l: clamp(base.l + 2, 42, 64),
      secondaryHue: 210, accentHue: 292, surfaceHue: 222, textHue: 188,
      roleShift: 0.24, roleMaxShift: 44,
      semanticS: 1.08, semanticL: -1, semanticShift: 0.92,
      surfaceL: 8, textL: 90, borderL: 34,
      surfaceS: 22, borderS: 34
    }
  }

  return settings[style] or balanced
Role palette assignment and clamps
harmonized_hue(seed_hue, anchor_hue, strength, max_shift):
  shortest = ((seed_hue - anchor_hue + 540) % 360) - 180
  shift = clamp(shortest * strength, -max_shift, max_shift)
  return normalize_degrees(anchor_hue + shift)

build_role_palette(seed, style, locks, previous_palette):
  base = rgb_to_hsl(hex_to_rgb(seed))
  settings = palette_style_settings(base, style)

  role_hue(key, fallback_offset):
    if settings has key + "Hue":
      return harmonized_hue(base.h, settings[key + "Hue"],
                            settings.roleShift or 0.2,
                            settings.roleMaxShift or 36)
    return normalize_degrees(base.h + fallback_offset)

  semantic_hue(anchor, strength, max_shift):
    if settings.semanticMode == seed:
      return base.h
    return harmonized_hue(base.h, anchor,
                          strength * settings.semanticShift,
                          max_shift)

  semantic_targets:
    success: anchor = 145deg, strength = 0.20, max_shift = 18deg, range = 127..163deg
    warning: anchor = 44deg,  strength = 0.16, max_shift = 14deg, range = 30..58deg
    danger:  anchor = 350deg, strength = 0.14, max_shift = 12deg, range = 338..360deg or 0..2deg

  semantic_s(base_amount, min_s, max_s):
    return clamp((base_amount + base.s * 0.18) * settings.semanticS, min_s, max_s)

  semantic_l(base_amount, min_l, max_l):
    return clamp(base_amount + settings.semanticL, min_l, max_l)

  roles = [
    primary:   style == balanced ? seed : hsl(base.h, clamp(settings.s), clamp(settings.l, 4, 97)),
    secondary: hsl(role_hue(secondary, settings.secondary), settings.s * 0.86, settings.l - 7),
    accent:    hsl(role_hue(accent, settings.accent), min(94, settings.s + 10), settings.l + 5),
    surface:   hsl(role_hue(surface, settings.surface), settings.surfaceS ?? max(12, settings.s * 0.28), settings.surfaceL ?? 96),
    text:      hsl(role_hue(text, settings.text), settings.textS ?? max(24, settings.s * 0.42), settings.textL ?? 12),
    border:    hsl(role_hue(surface, settings.surface), settings.borderS ?? max(10, settings.s * 0.24), settings.borderL ?? 78),
    success:   hsl(semantic_hue(145, 0.2, 18), semantic_s(48,34,88), semantic_l(36,28,52)),
    warning:   hsl(semantic_hue(44, 0.16, 14), semantic_s(68,44,94), semantic_l(62,48,74)),
    danger:    hsl(semantic_hue(350, 0.14, 12), semantic_s(64,42,92), semantic_l(56,42,68))
  ]

  for each role at index:
    if locks[index] and previous_palette has color at index:
      keep previous hex
    else:
      convert HSL role to HEX with h normalized, s clamped 0..100, l clamped 4..97

  role_contracts:
    text on surface: minimum 7.0
    primary, secondary, accent, border, success, warning, danger on surface: minimum 3.0

  if surface is unlocked and a locked role misses its contract:
    search the surface OKLCH lightness range for the nearest surface that fits every locked role

  for each unlocked contracted role:
    if raw WCAG contrast is below target + 0.05:
      keep OKLCH hue, move lightness toward the higher-contrast black/white endpoint,
      binary-search the smallest passing move, and reduce chroma only when needed for sRGB

  locked roles never move; if locked colors make the system unsatisfiable, report the exact failed pair
Random seed, regeneration, and share flow
random_seed():
  # This is not a seedable Hue Codex PRNG.
  # It uses the browser JavaScript Math.random() source.
  h = floor(Math.random() * 360)
  s = 62 + floor(Math.random() * 24)  # 62..85 percent
  l = 42 + floor(Math.random() * 20)  # 42..61 percent
  return rgb_to_hex(hsl_to_rgb(h, s, l))

initial_palette(location):
  state = decode_versioned_fragment_state(location.hash)
  seed = normalize_hex(state.seed or #4169E1)
  style = state.style or balanced
  locks = state.locks or [false, false, false, false, false, false, false, false, false]

  if state contains resolved role colors:
    palette = create_palette_from_role_colors(state.palette)
    return palette, seed, style, locks

  return build_role_palette(seed, style, locks, previous = null)

regenerate(seed, style, locks, previous_palette):
  # Deterministic after seed, style, locks, and previous locked values are known.
  seed = normalize_hex(seed)
  return build_role_palette(seed, style, locks, previous_palette)

share_palette(seed, style, locks, palette, export_mode):
  state = canonical_versioned_state(seed, style, locks, palette, export_mode)
  return "/palette-generator#s=" + base64url_encode(state)

shared_palette_fragment_behavior:
  # The fragment stores the supported current settings and resolved role colors.
  # It does not store an internal PRNG state or the original Math.random() draws.
  validate version, tool, fields, locks, roles, names, colors, and size bounds
Accessibility metadata and role checks
wcag_score(foreground, background):
  raw = wcag_contrast_ratio(foreground, background)
  return {
    ratio: round(raw, 2),
    rawRatio: raw,
    normalAA: raw >= 4.5,
    normalAAA: raw >= 7,
    largeAA: raw >= 3,
    largeAAA: raw >= 4.5,
    uiAA: raw >= 3
  }

with_accessibility_metadata(color, palette):
  surface = palette.role(surface) or #FFFFFF
  text = palette.role(text) or #10212B
  return {
    contrastOnSurface: round(wcag_contrast_ratio(color.hex, surface), 2),
    contrastWithText: round(wcag_contrast_ratio(color.hex, text), 2),
    bestText: highest_raw_contrast(color.hex, [text, #FFFFFF, #000000]),
    wcagOnSurface: wcag_score(color.hex, surface)
  }

palette_primary_checks(palette):
  primary = palette.role(primary)
  surface = palette.role(surface)
  text = palette.role(text)
  border = palette.role(border)
  success = palette.role(success)
  warning = palette.role(warning)
  danger = palette.role(danger)
  secondary = palette.role(secondary)
  accent = palette.role(accent)
  bestOnPrimary = highest_raw_contrast(primary.hex, [#FFFFFF, #000000])

  return {
    textOnSurface: wcag_score(text.hex, surface.hex),
    primaryOnSurface: wcag_score(primary.hex, surface.hex),
    secondaryOnSurface: wcag_score(secondary.hex, surface.hex),
    accentOnSurface: wcag_score(accent.hex, surface.hex),
    whiteOnPrimary: wcag_score(#FFFFFF, primary.hex),
    blackOnPrimary: wcag_score(#000000, primary.hex),
    bestOnPrimaryScore: wcag_score(bestOnPrimary.hex, primary.hex),
    borderOnSurface: wcag_score(border.hex, surface.hex),
    successOnSurface: wcag_score(success.hex, surface.hex),
    warningOnSurface: wcag_score(warning.hex, surface.hex),
    dangerOnSurface: wcag_score(danger.hex, surface.hex)
  }

readable_pair_count(palette):
  count = 0
  for each foreground in palette.colors:
    for each background in palette.colors:
      if foreground.hex != background.hex:
        raw = wcag_contrast_ratio(foreground.hex, background.hex)
        if raw >= 4.5:
          count += 1
  return count
Tint, shade, and tone scale formulas
build_scales(seed, requested_steps, mode):
  count = clamp(requested_steps, 3, 11)
  weights = [(index + 1) / (count + 1) for index in 0..count-1]

  if mode == simple:
    Tints  = [mix_srgb(seed, #FFFFFF, weight) for weight in weights]
    Shades = [mix_srgb(seed, #000000, weight) for weight in weights]
    Tones  = [mix_srgb(seed, #808080, weight * 0.82) for weight in weights]
    return Tints, Shades, Tones

  if mode == hsl:
    base = rgb_to_hsl(hex_to_rgb(seed))
    Tints = [
      hsl(base.h, max(0, base.s - weight * 10),
          base.l + (98 - base.l) * weight)
      for weight in weights
    ]
    Shades = [
      hsl(base.h, base.s,
          max(4, base.l * (1 - weight * 0.82)))
      for weight in weights
    ]
    Tones = [
      hsl(base.h, max(0, base.s * (1 - weight * 0.75)),
          base.l + (50 - base.l) * weight * 0.45)
      for weight in weights
    ]
    return Tints, Shades, Tones

  # default mode: OKLCH perceptual
  base = rgb_to_oklch(hex_to_rgb(seed))
  oklch_hex(l, c, h = base.h):
    return rgb_to_hex(oklch_to_rgb(
      l = clamp(l, 0, 1),
      c = max(0, c),
      h = h
    ))

  Tints = [
    oklch_hex(base.l + (0.98 - base.l) * weight,
              base.c * (1 - weight * 0.18))
    for weight in weights
  ]
  Shades = [
    oklch_hex(base.l * (1 - weight * 0.82),
              base.c * (1 - weight * 0.08))
    for weight in weights
  ]
  Tones = [
    oklch_hex(base.l + (0.62 - base.l) * weight * 0.32,
              base.c * (1 - weight * 0.78))
    for weight in weights
  ]
  return Tints, Shades, Tones

Reproducible test vectors

StandardHue Codex heuristicApproximation

These vectors isolate deterministic palette math from product heuristics such as role labels or palette ranking.

Harmony and RGB scale vectors.
InputExpected outputNotes
Seed #336699 to HSLh=210, s=50%, l=40%Normalized seed values used for hue rotations
Balanced fitted role palette from #336699primary #336699; secondary #353078; accent #B8732E; surface #F3F4F6; text #262517; border #8A8B97; success #27906A; warning #958F00; danger #E13D79Uses balanced style candidates followed by the OKLCH role-contract fit
Calm fitted role palette from #0EA5E9primary #2B96C3; surface #F3F4F7; text #272116; border #868C97; warning #958F00Uses Calm style candidates followed by action, boundary, and status fitting against the surface
#336699 complementary hue +180deg#996633Same saturation and lightness, hue normalized to 30deg
#336699 analogous hue -30deg#339999Same saturation and lightness, hue 180deg
#336699 analogous hue +30deg#333399Same saturation and lightness, hue 240deg
Base hue 0 analogous offset -30deg330degHue wrapping uses normalize_degrees(base.h + offset) into [0, 360)
#336699 mixed 50% toward #FFFFFF in sRGB#99B3CCRGB tint vector
#336699 mixed 50% toward #000000 in sRGB#1A334DRGB shade vector
#336699 OKLCH scale, 3 stepsTints #598ABD #7FAFE2 #A7D6FF; Shades #154978 #002D59 #00123B; Tones #416992 #4E6B8A #5A6D83Weights are 0.25, 0.5, and 0.75 with the default OKLCH formulas
#336699 HSL scale, 3 stepsTints #548BC2 #8CB0D4 #C3D5E7; Shades #29517A #1E3C5A #14273B; Tones #3E6993 #4A6C8D #566F87Weights are 0.25, 0.5, and 0.75 with HSL hue preserved at 210deg
#336699 simple RGB scale, 3 stepsTints #668CB3 #99B3CC #CCD9E6; Shades #264D73 #1A334D #0D1A26; Tones #436B94 #53718F #62768AWeights are 0.25, 0.5, and 0.75; tone mix uses 0.82 times the weight toward #808080
Neutral #808080 complementary, balanced tone#808080 and #63BCBCBase remains seed; support swatch uses fallback hue 180 with balanced tone s=40 and l=56.196078
Neutral #808080 monochromatic#808080 #432E2E #6D4C4C #B89999 #DAC9C9Non-base swatches use fallback hue 0 and minimum saturation 18 after monochromatic clamps

Sources and standards

These references anchor the public standards, model origins, source records, and formats used by Hue Codex. Status badges distinguish stable standards, drafts, primary sources, model sources, Hue Codex heuristics, approximations, and browser-dependent behavior.

Tools using this methodology

These Hue Codex tools link to this methodology because they depend on the formulas, assumptions, limits, or data policy described here.

Report a calculation issue

If a result does not match the documented formula or fixture, send the exact input, tool settings, displayed result, expected result, precision mode, and browser details. Please do not include private images, personal information, or secrets.