No description
- TypeScript 82.4%
- CSS 12.1%
- HTML 5.5%
Default swatches were stored as rounded channel values (e.g. 0.231/0.51/0.965) that display as #3b82f6 but aren't exactly that color, so OKLCH and other formats showed slightly different numbers than oklch.com. Define defaults as hex and snap random colors to 8-bit channels so they round-trip exactly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|---|---|---|
| .forgejo/workflows | ||
| public | ||
| src | ||
| .gitignore | ||
| .oxlintrc.json | ||
| bun.lock | ||
| index.html | ||
| package.json | ||
| README.md | ||
| tsconfig.app.json | ||
| tsconfig.json | ||
| tsconfig.node.json | ||
| vite.config.ts | ||
ccolors
A side-by-side color converter. Each column holds one color. Edit any format in it and every other format updates live.
Features
- 11 formats: HEX, RGB, HSL, HSV/HSB, HWB, OKLCH, OKLab, LCH, Lab, Display P3, CMYK
- Paste anything: the "Paste any" field accepts any supported notation
- Gamut warnings: colors outside sRGB (e.g. from OKLCH or P3) are clipped and flagged
- Alpha: a slider, plus alpha in every format that supports it
- Native color picker and EyeDropper (on browsers that support the EyeDropper API)
- Copy buttons: one per format
- Multiple columns: add, duplicate and remove colors; they're saved to
localStorage
Conversions follow CSS Color Module Level 4.
Development
bun install
bun run dev # start the dev server
bun run build # type-check and build to dist/
bun run preview # serve the production build
bun run lint # oxlint
bun run typecheck
CI / Deploy
.forgejo/workflows/ci.yml runs lint, typecheck and build on every push and pull request. When a push to main passes, it deploys to Vercel using the deploy environment. The workflow needs these secrets: VERCEL_TOKEN, VERCEL_ORG_ID and VERCEL_PROJECT_ID.
Built with React 19 (React Compiler enabled), TypeScript and Vite.
Project structure
src/
├── App.tsx # layout: toolbar + color columns
├── main.tsx # entry point
├── index.css # styles
├── components/
│ ├── Toolbar.tsx
│ ├── ColorColumn.tsx # one color: header, picker, alpha, fields
│ ├── ColumnHeader.tsx # hex label, duplicate/remove buttons
│ ├── ColorPicker.tsx # native picker + EyeDropper
│ ├── AlphaSlider.tsx
│ ├── ColorField.tsx # editable, parse-as-you-type input
│ └── FormatField.tsx # ColorField bound to a format
├── hooks/
│ ├── useSwatches.ts # column list, persisted to localStorage
│ └── useCopy.ts # clipboard with "copied" feedback
├── color/
│ ├── index.ts # public API
│ ├── types.ts # RGBA, Parsed, Format
│ ├── math.ts # vector/matrix helpers, number formatting
│ ├── matrices.ts # CSS Color 4 conversion matrices
│ ├── spaces.ts # sRGB ↔ OKLab, Lab, HSL, HSV, HWB, …
│ ├── parse.ts # tokenizer shared by the parsers
│ ├── formats.ts # format registry (format + parse per notation)
│ └── utils.ts # contrast text, CSS output, random colors
└── types/
└── eyedropper.d.ts # EyeDropper API typing
Adding a format
Add an entry to FORMATS in src/color/formats.ts with id, label, placeholder, format and parse. The UI renders a field for it automatically, and "Paste any" picks it up.