Colour
Read when: you pick any colour: a token for a page, a scale step, a gradient, a contrast check, a code or
diagram palette.
Owns: the Iris and Azure scales, the semantic light and dark tokens, gradients, where colour may go, the 95/5
rule, contrast ratios, the code-highlighting and diagram palettes.
Values match the site's main.css and shared/code-theme.ts (checked 2026-11-03). Every value here is also in
../assets/haythamasalama-brand-tokens.css. Hex is the source of truth;
the OKLCH values are computed from it and rounded.
TL;DR
- Ink first: black, white and space carry the page. Iris and Azure stay under 5% of any surface.
- Use the semantic tokens (
bg, fg, muted, line...), never raw hex, in anything that has a theme.
- Hover, the current page and selected filters are neutral (
fg, surface), never coloured.
- On white, coloured text and buttons with white text use step 600 or darker. Why: 500 is 3.5:1 or less on Paper.
- Gradients:
--gradient for one word, badges and progress; --gradient-signature for hairlines and artwork.
The two colours
| Name |
Hex |
OKLCH |
Meaning |
| Iris 500 |
#7F7CF2 |
oklch(64.5% 0.172 281.6) |
the creative: the designer and video editor before the code |
| Azure 500 |
#4F9BFF |
oklch(68.8% 0.166 256.2) |
the engineer: clear, calm, trusted with money |
The 95/5 rule: about 70% Ink, 25% white, greys and the signature, never more than 5% Iris + Azure.
Semantic tokens
The site's theme tokens. Dark is the default (:root and .dark); .light on <html> switches.
| Token |
Dark (default) |
Light |
Use |
bg |
#0B0B0C Ink |
#FFFFFF Paper |
page |
surface |
#131316 |
#F4F4F5 |
tiles, hover fill, selected chip |
raised |
#0F0F11 |
#FAFAFA |
figures, code blocks, hero frames |
line |
#1F1F23 |
#E4E4E7 |
borders, dividers, cards |
edge |
#232328 |
#E4E4E7 |
logo tile and copy button borders |
chip |
#2A2A30 |
#D4D4D8 |
pill borders, hover border, list bullets |
fg |
#EDEDEF |
#0B0B0C |
primary text, every hover target |
soft |
#C4C4CC |
#3F3F46 |
lead and long-form body text |
muted |
#A1A1AA |
#52525B |
secondary text, idle links |
faint |
#85858F |
#6B6B74 |
labels, dates, eyebrows, footer |
mark |
#D4D4D8 |
#27272A |
icons and one-colour logos |
underline |
#3F3F46 |
#D4D4D8 |
link underline |
accent |
Azure 500 |
Azure 600 |
details |
accent-2 |
Iris 500 |
Iris 600 |
focus ring, quote border, "copied" tick |
- The light neutrals are Tailwind's
zinc scale. The dark neutrals are custom, slightly blue-violet (hue about 286°).
- Favicon and app icon tiles have a
#26262B inner border.
- Specimen tiles that must look the same in both themes (a signature on Ink, artwork previews) use raw hex.
OKLCH of the neutrals
| Hex |
OKLCH |
Hex |
OKLCH |
#0B0B0C |
oklch(15.0% 0.002 286.1) |
#FFFFFF |
oklch(100% 0 0) |
#0F0F11 |
oklch(16.9% 0.004 286.0) |
#FAFAFA |
oklch(98.5% 0 0) |
#131316 |
oklch(18.8% 0.006 285.8) |
#F4F4F5 |
oklch(96.7% 0.001 286.4) |
#1F1F23 |
oklch(24.1% 0.008 285.8) |
#E4E4E7 |
oklch(92.0% 0.004 286.3) |
#232328 |
oklch(25.8% 0.009 285.7) |
#D4D4D8 |
oklch(87.1% 0.005 286.3) |
#2A2A30 |
oklch(28.7% 0.011 285.7) |
#6B6B74 |
oklch(53.1% 0.014 285.9) |
#EDEDEF |
oklch(94.7% 0.003 286.3) |
#52525B |
oklch(44.2% 0.015 285.8) |
#C4C4CC |
oklch(82.3% 0.011 286.2) |
#3F3F46 |
oklch(37.0% 0.012 285.8) |
#A1A1AA |
oklch(71.2% 0.013 286.1) |
#27272A |
oklch(27.4% 0.005 286.0) |
#85858F |
oklch(62.0% 0.015 286.0) |
#26262B |
oklch(27.1% 0.009 285.8) |
Iris scale
| Step |
Hex |
OKLCH |
| 50 |
#F5F6FE |
oklch(97.5% 0.011 280.5) |
| 100 |
#E9EBFE |
oklch(94.4% 0.026 281.1) |
| 200 |
#D4D7FE |
oklch(88.9% 0.054 281.8) |
| 300 |
#BABEFE |
oklch(82.1% 0.090 281.4) |
| 400 |
#9D9FFE |
oklch(74.0% 0.136 281.6) |
| 500 |
#7F7CF2 |
oklch(64.5% 0.172 281.6) |
| 600 |
#6762D3 |
oklch(56.0% 0.169 281.4) |
| 700 |
#534DAF |
oklch(48.0% 0.151 281.8) |
| 800 |
#3F3B87 |
oklch(40.0% 0.123 281.6) |
| 900 |
#2C2A62 |
oklch(32.1% 0.096 281.1) |
| 950 |
#1D1B45 |
oklch(25.1% 0.077 281.5) |
Azure scale
| Step |
Hex |
OKLCH |
| 50 |
#F2F7FE |
oklch(97.4% 0.011 256.7) |
| 100 |
#E2EEFF |
oklch(94.5% 0.026 257.0) |
| 200 |
#C5DDFE |
oklch(89.0% 0.052 256.4) |
| 300 |
#9EC7FE |
oklch(82.0% 0.090 255.9) |
| 400 |
#70ADFF |
oklch(74.1% 0.135 256.2) |
| 500 |
#4F9BFF |
oklch(68.8% 0.166 256.2) |
| 600 |
#2873D1 |
oklch(56.0% 0.162 256.2) |
| 700 |
#195CAE |
oklch(48.1% 0.146 256.2) |
| 800 |
#134686 |
oklch(39.9% 0.120 256.4) |
| 900 |
#0D3261 |
oklch(32.0% 0.093 256.4) |
| 950 |
#052144 |
oklch(25.0% 0.074 255.9) |
- 300–500: details on dark. 600–700: details on white, and buttons with white text.
- The 950 steps need a hairline border to read against Ink:
#2A2A3A for Iris, #1A2A44 for Azure.
Gradients
| Name |
Dark |
Light |
Use |
--gradient |
linear-gradient(90deg, Iris 500, Azure 500) |
Iris 600 → Azure 600 |
.text-gradient, .border-gradient, progress fill |
--gradient-signature |
linear-gradient(90deg, #7F7CF2, #6C8CF9, #4F9BFF) |
#6762D3, #4F6AD2, #2873D1 |
hairlines, artwork, the gradient signature file |
| Hero hairline |
linear-gradient(90deg, rgb(127 124 242 / 0), #7F7CF2, #4F9BFF, rgb(79 155 255 / 0)) |
same |
1 px line on the top edge of a hero frame |
| Status dot |
linear-gradient(135deg, Iris 500, Azure 500) |
same |
the 8 px live dot |
- The middle stops:
#6C8CF9 is oklch(66.7% 0.165 269.2), #4F6AD2 is oklch(55.8% 0.163 269.6).
- No gradient is ever a hover state. Why: hover stays neutral across the site, the signature included.
Where the colour goes
| Place |
Treatment |
| Badge (PRO, LIVE, NEW, a version) |
Geist Mono 10.5–12 px, tracking 0.1em, .border-gradient, .text-gradient text, radius 6 px |
| Status dot |
8 px circle, the status-dot gradient, .animate-live pulse |
| One word per page |
.text-gradient, often in Instrument Serif italic |
| Focus ring |
2 px outline in accent-2, 2 px offset, 4 px radius |
| Progress |
4 px track in line, filled with --gradient; never a solid block |
| Numbered cards |
the number in Geist Mono 12 px, Iris 400 on dark, Iris 600 on light |
| Copied confirmation |
the tick in accent-2, the word "Copied" in .text-gradient, for 1.6 s |
| Quote border in long-form text |
accent-2 |
| Text selection |
rgb(127 124 242 / 0.35) |
| Code |
Iris keywords, Azure strings and values, grey punctuation (below) |
Everything else is neutral: the current nav item is fg, a selected filter chip is fg text with an fg border
on surface, and links hover to fg or gain a 1 px underline in underline.
Contrast (computed, WCAG 2)
| Pair |
Ratio |
fg #EDEDEF on Ink |
16.8:1 |
soft #C4C4CC on Ink |
11.4:1 |
muted #A1A1AA on Ink |
7.7:1 |
faint #85858F on Ink |
5.4:1 |
| Iris 400 / Azure 400 on Ink |
8.3:1 / 8.5:1 |
| Iris 500 / Azure 500 on Ink |
5.7:1 / 7.0:1 |
| Ink on Paper |
19.7:1 |
#3F3F46 / #52525B / #6B6B74 on Paper |
10.4:1 / 7.7:1 / 5.3:1 |
| White on Iris 600 / Azure 600 |
4.9:1 / 4.7:1 |
| White on Iris 700 / Azure 700 |
6.9:1 / 6.6:1 |
| Iris 500 / Azure 500 on Paper |
3.5:1 / 2.8:1 (not for text) |
Code highlighting
The site's two Shiki themes, built from the scales. Background raised: #0F0F11 dark, #FAFAFA light.
| Role |
Dark |
Light |
| Text |
#C4C4CC |
#3F3F46 |
| Punctuation, operators |
#85858F |
#71717A |
| Comments (italic) |
#6B6B74 |
#8B8B94 |
| Keywords, tags, links, deleted lines |
#9D9FFE Iris 400 |
#6762D3 Iris 600 |
| Strings, attributes, inserted lines |
#9EC7FE Azure 300 |
#2873D1 Azure 600 |
| Numbers, constants |
#70ADFF Azure 400 |
#195CAE Azure 700 |
| Types, classes, namespaces |
#D4D7FE Iris 200 |
#3F3B87 Iris 800 |
| Properties, keys |
#C5DDFE Azure 200 |
#534DAF Iris 700 |
| Functions, headings (headings bold) |
#EDEDEF |
#0B0B0C |
Adjust these palettes when a language needs it; don't switch to a stock theme.
Diagrams
Diagrams are drawn in Excalidraw with a fixed palette, then each colour is mapped to a theme token so the figure
follows light and dark.
| Pick in Excalidraw |
Becomes |
stroke #1e1e1e or #000000 |
fg |
stroke #495057 / #868e96 |
soft / muted |
stroke #ced4da, fill #e9ecef |
chip |
stroke #7f7cf2 / #4f9bff |
accent-2 / accent |
stroke #ffffff (text on a coloured fill) |
#ffffff |
fill #ffffff / #f8f9fa |
bg / surface |
fill #e9ebfe / #e2eeff |
color-mix(in oklab, Iris 500 16%, transparent) / color-mix(in oklab, Azure 500 14%, transparent) |
- Any other colour is drawn as it is, so stay inside this palette.
- Labels use Excalifont (see typography-layout.md).
- The figure sits in a
raised box with a line border, radius 12 px, padding 16 × 12 px (24 px from 768 px up).
It keeps a 600 px minimum width and scrolls sideways inside its box on small screens, so labels never shrink.