import type { Config } from "tailwindcss"; const config: Config = { darkMode: "class", content: ["./src/**/*.{ts,tsx}"], prefix: "", theme: { extend: { colors: { border: "hsl(var(--border))", input: "hsl(var(--input))", ring: "hsl(var(--ring))", background: "hsl(var(--background))", foreground: "hsl(var(--foreground))", primary: { DEFAULT: "hsl(var(--primary))", foreground: "hsl(var(--primary-foreground))", }, // One class per brand accent, on purpose. These were objects with // `solid` and `text` variants tuned for contrast in specific spots; the // palette is now one hue each, so `bg-coral`/`text-coral` and // `bg-teal`/`text-teal` are the whole vocabulary. `destructive` below // keeps its `solid` variant - errors are the deliberate exception. // // The `foreground` on each is not a second coral or a second teal. It is // the label that sits *on* that fill, the pairing every other filled token // here already has, and the collapse was about differently-tuned brand // hues rather than about the object. coral: { DEFAULT: "hsl(var(--coral))", foreground: "hsl(var(--coral-foreground))", }, teal: { DEFAULT: "hsl(var(--teal))", foreground: "hsl(var(--teal-foreground))", }, pressure: "hsl(var(--pressure))", ceiling: "hsl(var(--ceiling))", // The channels under the depth plot, one accent each - see the blocks on // these in `globals.css` for the hues and the contrast they were measured // at. "heart-rate": "hsl(var(--heart-rate))", ndl: "hsl(var(--ndl))", tts: "hsl(var(--tts))", ppo2: "hsl(var(--ppo2))", cns: "hsl(var(--cns))", "gradient-factor": "hsl(var(--gradient-factor))", "surface-gradient-factor": "hsl(var(--surface-gradient-factor))", "gas-oxygen": "hsl(var(--gas-oxygen))", "gas-helium": "hsl(var(--gas-helium))", // Whole colours, not HSL triplets - see `--map-water` in `globals.css`. "map-water": { DEFAULT: "var(--map-water)", foreground: "var(--map-water-foreground)", }, tooltip: { DEFAULT: "hsl(var(--tooltip))", foreground: "hsl(var(--tooltip-foreground))", }, secondary: { DEFAULT: "hsl(var(--secondary))", foreground: "hsl(var(--secondary-foreground))", }, destructive: { DEFAULT: "hsl(var(--destructive))", solid: "hsl(var(--destructive-solid))", foreground: "hsl(var(--destructive-foreground))", }, success: { DEFAULT: "hsl(var(--success))", foreground: "hsl(var(--success-foreground))", }, warning: { DEFAULT: "hsl(var(--warning))", foreground: "hsl(var(--warning-foreground))", }, muted: { DEFAULT: "hsl(var(--muted))", foreground: "hsl(var(--muted-foreground))", }, accent: { DEFAULT: "hsl(var(--accent))", foreground: "hsl(var(--accent-foreground))", }, popover: { DEFAULT: "hsl(var(--popover))", foreground: "hsl(var(--popover-foreground))", }, card: { DEFAULT: "hsl(var(--card))", foreground: "hsl(var(--card-foreground))", }, }, fontFamily: { wordmark: ["var(--font-outfit)", "sans-serif"], }, // Not `tracking-tight` (-0.025em), which crowds a geometric face. letterSpacing: { wordmark: "-0.01em", }, borderRadius: { lg: "var(--radius)", md: "calc(var(--radius) - 2px)", sm: "calc(var(--radius) - 4px)", }, keyframes: { "accordion-down": { from: { height: "0" }, to: { height: "var(--radix-accordion-content-height)" }, }, "accordion-up": { from: { height: "var(--radix-accordion-content-height)" }, to: { height: "0" }, }, "skeleton-in": { from: { opacity: "0" }, to: { opacity: "1" }, }, "skeleton-pulse": { "0%, 100%": { opacity: "1" }, "50%": { opacity: "0.5" }, }, }, animation: { "accordion-down": "accordion-down 0.2s ease-out", "accordion-up": "accordion-up 0.2s ease-out", // Two animations, and the delay on the first is the point: a skeleton // takes up its space immediately but stays *invisible* for 150ms, so a // response that beats it (most of them, against a local API) swaps // straight from the old page to the new one with no grey flash in // between - while still reserving the layout, so nothing jumps when it // lands. Only a load slow enough to be worth reporting is ever seen, // and once seen it breathes so it reads as pending rather than broken. // `both` is what holds opacity at 0 through the delay. // // One flat delay, counted from the placeholder's own insertion, which is // also the click: a route draws its own frame as it mounts, and there is // no second renderer of that frame to carry a delay across. skeleton: "skeleton-in 200ms ease-out 150ms both, skeleton-pulse 1.8s ease-in-out 350ms infinite", // The reveal half on its own, for the *chrome* a placeholder draws - the // card outlines and row borders. Those are real `Card`s and `TableRow`s, // so without this they paint instantly and a fast response still flashes // a grid of empty bordered boxes, which is most of what the delay exists // to prevent. Deliberately not the full `skeleton` shorthand: a pulsing // container multiplied by a pulsing bar would dip the bars to a quarter // opacity instead of half. "skeleton-reveal": "skeleton-in 200ms ease-out 150ms both", }, }, }, plugins: [require("tailwindcss-animate")], }; export default config;