| | @@ -0,0 +1,115 @@ |
| 1 | +/* Steid's theme. |
| 2 | + Started from Topcoat's `neutral` theme and retuned; see plans/ui.md. |
| 3 | + |
| 4 | + The theme is a small set of design tokens. Components refer to tokens only, |
| 5 | + never to raw colors, so the whole product restyles by editing `:root` and |
| 6 | + `.dark` below. A component that hardcodes a color is a bug — it will not |
| 7 | + follow a palette change and it will not adapt to the color scheme. |
| 8 | + |
| 9 | + --background the page background |
| 10 | + --surface raised panels: cards, menus, flash messages |
| 11 | + --foreground text and iconography on the background |
| 12 | + --muted-foreground secondary text: captions, descriptions, placeholders |
| 13 | + --primary the emphasis color: primary actions, selection, links |
| 14 | + --primary-foreground text on a primary-filled surface |
| 15 | + --success confirmations, and git additions |
| 16 | + --success-foreground text on a success-filled surface |
| 17 | + --warning caution, and git modifications |
| 18 | + --warning-foreground text on a warning-filled surface |
| 19 | + --destructive destructive actions, errors, and git deletions |
| 20 | + --destructive-foreground text on a destructive-filled surface |
| 21 | + --border hairline borders and dividers |
| 22 | + --ring the focus ring |
| 23 | + --shadow-xs a subtle shadow for controls: buttons, inputs |
| 24 | + --shadow-sm a shadow for raised surfaces: cards, menus |
| 25 | + |
| 26 | + Interactive states (hover, press) have no tokens of their own: components |
| 27 | + derive them by applying the fill or foreground color at reduced opacity, |
| 28 | + which adapts to both color schemes automatically. |
| 29 | + |
| 30 | + Steid is dark-first — the layout sets `class="dark"` on <html>. Light mode |
| 31 | + is supported and secondary. */ |
| 32 | + |
| 33 | +@import "tailwindcss"; |
| 34 | + |
| 35 | +/* Scan the project's Rust sources for the Tailwind classes they use. This |
| 36 | + file is installed at the project root, so `./src` is the crate's source. */ |
| 37 | +@source "./src/**/*.rs"; |
| 38 | + |
| 39 | +/* Dark mode is opted into by putting the `dark` class on an ancestor, |
| 40 | + typically <html>. */ |
| 41 | +@custom-variant dark (&:is(.dark *)); |
| 42 | + |
| 43 | +/* Expose the tokens as Tailwind utilities (`bg-background`, `text-primary`, |
| 44 | + `border-border`, ...). The indirection through plain variables on `:root` |
| 45 | + and `.dark` is what lets the values swap when the color scheme changes. */ |
| 46 | +@theme inline { |
| 47 | + --font-sans: "Geist", ui-sans-serif, system-ui, sans-serif; |
| 48 | + /* Paths, commit hashes, branch names, and code. */ |
| 49 | + --font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", monospace; |
| 50 | + --color-background: var(--background); |
| 51 | + --color-surface: var(--surface); |
| 52 | + --color-foreground: var(--foreground); |
| 53 | + --color-muted-foreground: var(--muted-foreground); |
| 54 | + --color-primary: var(--primary); |
| 55 | + --color-primary-foreground: var(--primary-foreground); |
| 56 | + --color-success: var(--success); |
| 57 | + --color-success-foreground: var(--success-foreground); |
| 58 | + --color-warning: var(--warning); |
| 59 | + --color-warning-foreground: var(--warning-foreground); |
| 60 | + --color-destructive: var(--destructive); |
| 61 | + --color-destructive-foreground: var(--destructive-foreground); |
| 62 | + --color-border: var(--border); |
| 63 | + --color-ring: var(--ring); |
| 64 | + --shadow-xs: var(--shadow-xs); |
| 65 | + --shadow-sm: var(--shadow-sm); |
| 66 | +} |
| 67 | + |
| 68 | +/* Light mode. Secondary, but it should not look like an afterthought. */ |
| 69 | +:root { |
| 70 | + --background: oklch(0.99 0.003 260); |
| 71 | + --surface: oklch(1 0 0); |
| 72 | + --foreground: oklch(0.24 0.012 260); |
| 73 | + --muted-foreground: oklch(0.51 0.014 260); |
| 74 | + --primary: oklch(0.5 0.18 250); |
| 75 | + --primary-foreground: oklch(0.99 0.003 260); |
| 76 | + --success: oklch(0.52 0.15 145); |
| 77 | + --success-foreground: oklch(0.99 0.01 145); |
| 78 | + --warning: oklch(0.62 0.14 85); |
| 79 | + --warning-foreground: oklch(0.99 0.01 85); |
| 80 | + --destructive: oklch(0.55 0.2 27); |
| 81 | + --destructive-foreground: oklch(0.99 0.012 27); |
| 82 | + --border: oklch(0.9 0.008 260); |
| 83 | + --ring: oklch(0.5 0.18 250); |
| 84 | + --shadow-xs: 0 1px 2px oklch(0.24 0.02 260 / 12%); |
| 85 | + --shadow-sm: |
| 86 | + 0 1px 2px oklch(0.24 0.02 260 / 10%), |
| 87 | + 0 2px 8px -2px oklch(0.24 0.02 260 / 9%); |
| 88 | +} |
| 89 | + |
| 90 | +/* Dark mode. The primary surface for this product. */ |
| 91 | +.dark { |
| 92 | + --background: oklch(0.16 0.02 260); |
| 93 | + --surface: oklch(0.2 0.02 260); |
| 94 | + --foreground: oklch(0.9 0.01 260); |
| 95 | + --muted-foreground: oklch(0.66 0.014 260); |
| 96 | + --primary: oklch(0.65 0.15 250); |
| 97 | + --primary-foreground: oklch(0.16 0.02 260); |
| 98 | + --success: oklch(0.7 0.17 145); |
| 99 | + --success-foreground: oklch(0.16 0.02 145); |
| 100 | + --warning: oklch(0.8 0.15 85); |
| 101 | + --warning-foreground: oklch(0.16 0.02 85); |
| 102 | + --destructive: oklch(0.65 0.2 25); |
| 103 | + --destructive-foreground: oklch(0.14 0.02 25); |
| 104 | + --border: oklch(0.3 0.012 260); |
| 105 | + --ring: oklch(0.65 0.15 250); |
| 106 | + /* Light-mode shadows disappear on a dark background, so these are deeper. */ |
| 107 | + --shadow-xs: 0 1px 2px oklch(0 0 0 / 35%); |
| 108 | + --shadow-sm: 0 1px 2px oklch(0 0 0 / 35%), 0 2px 8px -2px oklch(0 0 0 / 30%); |
| 109 | +} |
| 110 | + |
| 111 | +@layer base { |
| 112 | + body { |
| 113 | + @apply bg-background font-sans text-foreground; |
| 114 | + } |
| 115 | +} |