a colour effect kept apart from what it colours: solid, aurora, or your own
npx shadcn add @ag/shadeAlready installed? Preview an update
npx shadcn add @ag/shade --dry-runYour installed source stays unchanged until you update it. How updates work
the demo above, verbatim
"use client"
import type * as React from "react"
import { Sample } from "@/app/samples"
import { aurora } from "@/registry/base-nova/lib/aurora"
import { type Shade, shadeTable, solid } from "@/registry/base-nova/lib/shade"
// #region shade
/** One base colour, one travel (x 0 to 1, left to right), two shades: solid keeps the
* base all the way, aurora travels its palette. A drawing that takes a shade wears
* whichever it is handed. The strip is one gradient through the travel's colours,
* blended in OKLCH, the way the light reads once drawn, not a row of samples. */
const BASE = { css: "oklch(0.87 0.19 150)", hue: 150 }
const STEPS = 24
export function Travel({
shade,
base = BASE,
}: {
shade: Shade
base?: typeof BASE
}) {
const stops = shadeTable(shade, base.css, base.hue, STEPS).join(", ")
return (
<div
className="h-6 rounded-md bg-(image:--travel)"
style={
{
"--travel": `linear-gradient(to right in oklch, ${stops})`,
} as React.CSSProperties
}
/>
)
}
// #endregion
export default function ShadeDemo() {
return (
<Sample
name="shade"
glance
title="one travel, two shades"
description="solid above, aurora below"
with="shade"
>
<div className="dark space-y-2 rounded-xl bg-black p-5 text-foreground">
<Travel shade={solid} />
<Travel shade={aurora()} />
</div>
</Sample>
)
}the source, every file an install writes
// SHADE: a colour effect, apart from what is drawn with it. A drawing knows how far
// along a thing is (`x`, 0..1: a peak's energy, the age of a trail, the top of a glow)
// and has one base colour (a person's, the text's); a shade says what colour that is
// there. The same drawing in `solid` is one colour, pure light; in lib/aurora's
// `aurora` it travels an aurora borealis's palette. Any other effect is one more
// Shade.
//
// A shade's colours are CSS (relative colours welcome: `oklch(from <base> …)`), the
// same in a stylesheet, an inline style or a canvas; `shadeTable` makes a travel's
// steps once, for a canvas to resolve through `getComputedStyle`.
export interface Shade {
/** Its identity: two shades with one name colour the same (a drawing redoes its
* colours only when this changes). */
name: string
/** The colour `x` (0..1) along the travel from `base` (any CSS colour), whose OKLCH
* hue is `baseHue`. */
color(base: string, baseHue: number, x: number): string
}
/** One colour all the way: the base itself, light without a palette. */
export const solid: Shade = { name: "solid", color: (base) => base }
/** `steps` colours along the travel, from x = 0 to x = 1. */
export function shadeTable(
shade: Shade,
base: string,
baseHue: number,
steps: number,
): string[] {
if (!(Number.isInteger(steps) && steps >= 2))
throw new Error(
`shade ${shade.name}: ${steps} steps; a travel needs at least 2`,
)
return Array.from({ length: steps }, (_, k) =>
shade.color(base, baseHue, k / (steps - 1)),
)
}