@tasker-systems/temper-design 0.0.0-stage → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +185 -2
- package/brand/mark.svg +4 -0
- package/brand/wordmark.svg +5 -0
- package/contract/base.css +221 -0
- package/contract/shadcn-bridge.css +86 -0
- package/contract/tailwind.css +70 -0
- package/contract/theme.schema.json +444 -0
- package/package.json +30 -4
- package/quiet-instrument/theme.css +66 -0
- package/quiet-instrument/theme.json +85 -0
- package/quiet-instrument-paper/theme.css +66 -0
- package/quiet-instrument-paper/theme.json +85 -0
- package/themes.json +14 -0
package/README.md
CHANGED
|
@@ -1,3 +1,186 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @tasker-systems/temper-design
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
temper's theme contract and its themes — the one source every temper surface paints from.
|
|
4
|
+
The web UI (`packages/temper-ui`) and the desktop (`tasker-systems/temper-contrib`) both
|
|
5
|
+
consume it, so the same role, the same term and the same state read alike on each.
|
|
6
|
+
|
|
7
|
+
A theme is **data**: a `theme.json` that gives a value to every role in the contract.
|
|
8
|
+
Components consume roles, never values, so any theme that satisfies the contract restyles
|
|
9
|
+
everything a surface paints — its chrome, shadcn-svelte primitives, and anything rendered
|
|
10
|
+
from a json-render spec — with no per-theme code.
|
|
11
|
+
|
|
12
|
+
A closed JSON Schema is the source of truth, the files are validated by a self-check, and a
|
|
13
|
+
hand-broken fixture must be refused for the check to count.
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
packages/temper-design/
|
|
17
|
+
├── themes.json # the theme index — name, title, appearance, counterpart (generated)
|
|
18
|
+
├── contract/
|
|
19
|
+
│ ├── theme.schema.json # the contract — every role, closed (draft 2020-12)
|
|
20
|
+
│ ├── tailwind.css # roles → Tailwind v4 utilities (generated from the schema)
|
|
21
|
+
│ ├── shadcn-bridge.css # shadcn-svelte's variable names answered from roles
|
|
22
|
+
│ └── base.css # base styles + t-*/ed-* recipes, roles only
|
|
23
|
+
├── quiet-instrument/ # dark — temper's house theme
|
|
24
|
+
│ ├── theme.json # source of truth
|
|
25
|
+
│ └── theme.css # generated — never edit
|
|
26
|
+
├── quiet-instrument-paper/ # light counterpart
|
|
27
|
+
├── brand/ # the threaded-t mark and wordmark, in currentColor
|
|
28
|
+
├── preview/index.html # every role and the foundational components, both themes
|
|
29
|
+
├── scripts/themes.sh # build · check · contrast · --self-check
|
|
30
|
+
└── tests/fixtures/broken/ # themes the check must refuse
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Installing
|
|
34
|
+
|
|
35
|
+
Published to npm in lockstep with temper's releases. Inside this repository, consume it as a
|
|
36
|
+
`file:` dependency, as `packages/temper-ui` does with `clients/temper-telemetry-ts`; outside
|
|
37
|
+
it, depend on the published version:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{ "dependencies": { "@tasker-systems/temper-design": "<temper version>" } }
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
bun **copies** a `file:` dependency into the consumer's install; it does not link it. An edit
|
|
44
|
+
here reaches a consumer only after that consumer runs `bun install` again — until then it paints
|
|
45
|
+
from the copy it took, and stale styling will look like a bug in your change. Reinstall after
|
|
46
|
+
every edit you want to see in a consuming surface.
|
|
47
|
+
|
|
48
|
+
What a consumer imports:
|
|
49
|
+
|
|
50
|
+
| Path | What it is |
|
|
51
|
+
|---|---|
|
|
52
|
+
| `@tasker-systems/temper-design/contract/tailwind.css` | the roles as `tp-` Tailwind v4 utilities; resets the default palette |
|
|
53
|
+
| `@tasker-systems/temper-design/contract/shadcn-bridge.css` | optional: shadcn-svelte's variable names answered from roles |
|
|
54
|
+
| `@tasker-systems/temper-design/contract/base.css` | base styles and the `t-*`/`ed-*` recipes |
|
|
55
|
+
| `@tasker-systems/temper-design/<theme>/theme.css` | one theme's values, under `[data-theme="<theme>"]` |
|
|
56
|
+
| `@tasker-systems/temper-design/<theme>/theme.json` | one theme as data, for applying it without a build |
|
|
57
|
+
| `@tasker-systems/temper-design/themes.json` | every theme's name, title, appearance and counterpart — list and pair themes from this, never by globbing the package's directories |
|
|
58
|
+
| `@tasker-systems/temper-design/contract/theme.schema.json` | the contract itself |
|
|
59
|
+
| `@tasker-systems/temper-design/brand/*.svg` | the mark and wordmark, in `currentColor` |
|
|
60
|
+
|
|
61
|
+
The checker (`scripts/`, `tests/`, `pyproject.toml`) is not published: it runs in this
|
|
62
|
+
repository's CI, and the package carries only what it proves.
|
|
63
|
+
|
|
64
|
+
## The contract
|
|
65
|
+
|
|
66
|
+
`contract/theme.schema.json` names every role a component may read. A theme must supply
|
|
67
|
+
all of them and may not invent more (`additionalProperties: false`): a component that
|
|
68
|
+
needs a colour the contract lacks is a change to the contract, reviewed as one, not a
|
|
69
|
+
theme quietly growing a private name.
|
|
70
|
+
|
|
71
|
+
| Group | Roles | Used for |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| Ground | `ground` `surface` `surface-raised` `overlay` | window, panels, popovers, modal scrim |
|
|
74
|
+
| Text | `text` `text-muted` `text-subtle` `text-faint` | reading, secondary, labels, disabled-only |
|
|
75
|
+
| Rule | `rule` `rule-strong` | hairlines, input outlines |
|
|
76
|
+
| Accent | `accent` `accent-muted` `accent-line` `accent-line-soft` `accent-wash` `accent-hover` `on-accent` `focus` | the single accent and its tints; the rail; keyboard focus |
|
|
77
|
+
| Conditions | `notice` `success` `danger` `pending` (+ `-wash` each) | attention, confirmation, destructive, awaiting-you |
|
|
78
|
+
| Region states | `region-arriving` `region-empty` `region-gave-up` `region-failed` (+ `-wash` each) | the four states a read can present as |
|
|
79
|
+
| Authorship | `author-human` `author-agent` | gutter and attribution marks |
|
|
80
|
+
| Categorical | `cat-1` … `cat-8` `cat-neutral` | bound vocabularies — core's doc types and plugins' (see below) |
|
|
81
|
+
| Code | `code-key` `code-string` `code-number` `code-comment` | syntax highlighting |
|
|
82
|
+
| Font | `reading` `doing` `ui` | serif prose, mono technical, dense UI |
|
|
83
|
+
| Radius, motion, tracking | `chip` `panel` · `quick` `settle` `easing` · `label` `strip` `eyebrow` `ui` `mark` | shape and rhythm |
|
|
84
|
+
|
|
85
|
+
Every role becomes a CSS custom property `--tp-<role>` (`--tp-text-muted`,
|
|
86
|
+
`--tp-font-reading`, `--tp-motion-quick`) and a Tailwind utility with a `tp-` prefix
|
|
87
|
+
(`bg-tp-ground`, `text-tp-text-muted`, `border-tp-rule`, `font-tp-doing`). The prefix keeps
|
|
88
|
+
temper's roles apart from shadcn's names, where `accent` and `muted` mean other things.
|
|
89
|
+
|
|
90
|
+
### What the check enforces
|
|
91
|
+
|
|
92
|
+
`scripts/themes.sh --self-check` runs in CI. For every theme it checks:
|
|
93
|
+
|
|
94
|
+
- **The contract.** Every role present, none invented, every value `#rrggbb` or `rgba()`.
|
|
95
|
+
- **Legibility floors.** Contrast is measured on the colours as they paint — alpha
|
|
96
|
+
composited over `ground`, washes composited before the text on them is measured.
|
|
97
|
+
`text` 7:1; secondary and label text, the accent, the categorical slots, conditions,
|
|
98
|
+
region states and code 4.5:1; slots 3:1 on surface; focus rings, authorship marks
|
|
99
|
+
3:1; `text-faint` 2.5:1, which is why it is reserved for disabled and decorative
|
|
100
|
+
marks and never carries information. `themes.sh contrast <theme.json>` prints every pair.
|
|
101
|
+
- **Distinctness.** The four region states, the nine categorical marks (eight slots and
|
|
102
|
+
the neutral), the two authorship marks and the conditions must stay apart; and no
|
|
103
|
+
slot may read as a chrome or state role (`accent`, `author-*`, `notice`, `region-*`)
|
|
104
|
+
— a vocabulary bound to a slot that paints like the accent would dress its terms in
|
|
105
|
+
meaning they do not carry (CIE76 ΔE on the painted colour).
|
|
106
|
+
`themes.sh slots <theme.json>` prints the slot canvas. Colour is only one of the
|
|
107
|
+
channels that separates a failed read from a refused one, but a theme that collapses
|
|
108
|
+
two of them has removed a channel, and that is checkable.
|
|
109
|
+
- **Generated files agree.** `theme.css` matches its `theme.json`; `contract/tailwind.css`
|
|
110
|
+
covers every role in the schema.
|
|
111
|
+
- **Counterparts pair.** A theme's `counterpart` exists, points back, and has the
|
|
112
|
+
opposite `appearance`.
|
|
113
|
+
|
|
114
|
+
And across the tree: the contract CSS carries no literal colour and references no role the
|
|
115
|
+
contract lacks; the brand marks paint in `currentColor`; every fixture in
|
|
116
|
+
`tests/fixtures/broken/` is refused.
|
|
117
|
+
|
|
118
|
+
## Using a theme
|
|
119
|
+
|
|
120
|
+
```css
|
|
121
|
+
@import "tailwindcss";
|
|
122
|
+
@import "<themes>/contract/tailwind.css"; /* roles as utilities; default palette reset */
|
|
123
|
+
@import "<themes>/contract/shadcn-bridge.css"; /* optional: shadcn-svelte components in-theme */
|
|
124
|
+
@import "<themes>/quiet-instrument/theme.css";
|
|
125
|
+
@import "<themes>/quiet-instrument-paper/theme.css";
|
|
126
|
+
@import "<themes>/contract/base.css";
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Then choose the theme with one attribute: `<html data-theme="quiet-instrument">`.
|
|
130
|
+
|
|
131
|
+
`contract/tailwind.css` resets Tailwind's default palette. A component cannot reach for
|
|
132
|
+
`zinc-800` or `white`; if it needs a colour, it names a role.
|
|
133
|
+
|
|
134
|
+
A theme can also be applied without a build: a surface can read a `theme.json` and set
|
|
135
|
+
each `--tp-*` property on the root element. That is how a theme installed after the app
|
|
136
|
+
was built — or one shipped by a plugin — takes effect.
|
|
137
|
+
|
|
138
|
+
## Bound vocabularies and categorical slots
|
|
139
|
+
|
|
140
|
+
Colour is bound, never named. The contract owns eight categorical slots and a neutral;
|
|
141
|
+
**vocabularies own the bindings to them** — core's doc types through core's declared
|
|
142
|
+
default binding, a plugin's doc types through its own definition. A doc type bound
|
|
143
|
+
nowhere renders in the neutral role. Many doc types may share a slot; a binding to
|
|
144
|
+
anything but a slot is refused.
|
|
145
|
+
|
|
146
|
+
```json
|
|
147
|
+
{ "doctype": "story", "tint": "cat-1" }
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The binding is part of the vocabulary that declares the doc type — core's default for
|
|
151
|
+
core's kinds, the package's entry for a plugin's — so the same term keeps the same slot
|
|
152
|
+
in every theme and on every surface that honours it. Nothing in any app names a
|
|
153
|
+
`doctype-*` role, because no such role exists: a view tints by **one vocabulary per
|
|
154
|
+
colour channel**, so one slot never means two words in the same view. Where a hue marks
|
|
155
|
+
a doc type, the doc type's word is shown beside it — hue never carries identity alone.
|
|
156
|
+
|
|
157
|
+
## Adding a theme
|
|
158
|
+
|
|
159
|
+
1. Copy a theme directory and rename it; set `name` to the directory name.
|
|
160
|
+
2. Change values in `theme.json` — only values; the roles are fixed.
|
|
161
|
+
3. `scripts/themes.sh build` to regenerate `theme.css`.
|
|
162
|
+
4. `scripts/themes.sh check <name>/theme.json`, and `contrast` to see where you stand.
|
|
163
|
+
5. If it has a light/dark partner, set `counterpart` on both.
|
|
164
|
+
|
|
165
|
+
## The house theme
|
|
166
|
+
|
|
167
|
+
**Quiet Instrument** carries temper's visual voice to every surface: obsidian ground,
|
|
168
|
+
parchment text, one steel-blue accent; Source Serif 4 for reading, JetBrains Mono for
|
|
169
|
+
doing, Inter only for dense UI. Semantic colour is a whisper — the conditions and the
|
|
170
|
+
categorical slots exist for meaning, not decoration. Hover shifts colour and nothing
|
|
171
|
+
else; there is no bounce, no shadow, no gradient. The rail — a 2px accent-soft left
|
|
172
|
+
border — is the markdown blockquote marker made structural.
|
|
173
|
+
|
|
174
|
+
**Quiet Instrument — Paper** keeps the same restraint on a warm paper ground with ink
|
|
175
|
+
text and a deepened accent; every colour was re-chosen to clear the floors, not inverted.
|
|
176
|
+
|
|
177
|
+
Fonts are bundled by each surface (`@fontsource-variable/*`), never fetched by the contract:
|
|
178
|
+
a surface must be able to render offline. `preview/index.html` loads them from Google Fonts for convenience only.
|
|
179
|
+
|
|
180
|
+
## Setup
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
cd packages/temper-design
|
|
184
|
+
uv sync
|
|
185
|
+
scripts/themes.sh --self-check
|
|
186
|
+
```
|
package/brand/mark.svg
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" role="img" aria-label="temper">
|
|
2
|
+
<path d="M 12 7 L 12 25" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" fill="none"/>
|
|
3
|
+
<path d="M 6 13 L 18 13 Q 23 13 25 16.5 Q 27 20 25 24" stroke="currentColor" stroke-width="2.8" stroke-linecap="round" fill="none"/>
|
|
4
|
+
</svg>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 40" role="img" aria-label="temper">
|
|
2
|
+
<path d="M 12 6 L 12 34" stroke="currentColor" stroke-width="3.5" stroke-linecap="round" fill="none"/>
|
|
3
|
+
<path d="M 4 16 L 20 16 Q 27 16 30 21 Q 33 26 31 32" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" fill="none"/>
|
|
4
|
+
<text x="46" y="27" font-family="'JetBrains Mono', 'Fira Code', monospace" font-size="18" fill="currentColor" letter-spacing="0.12em">temper</text>
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/* Theme-independent base and recipes. Consumes contract roles only — no literal
|
|
2
|
+
colour appears in this file, so every recipe restyles with the active theme.
|
|
3
|
+
|
|
4
|
+
The `t-*` type recipes and `ed-*` editorial classes are carried over from temper's
|
|
5
|
+
web UI by name, so a component ported from there keeps its class list. They are plain
|
|
6
|
+
CSS rather than Tailwind utilities: they are recipes a component composes, and they
|
|
7
|
+
must read the same whether or not the consumer uses Tailwind.
|
|
8
|
+
|
|
9
|
+
Import after tailwindcss (when used), contract/tailwind.css, and a theme.css. */
|
|
10
|
+
|
|
11
|
+
/* ─── Base ──────────────────────────────────────────────────────────────── */
|
|
12
|
+
|
|
13
|
+
html {
|
|
14
|
+
background: var(--tp-ground);
|
|
15
|
+
color: var(--tp-text);
|
|
16
|
+
font-family: var(--tp-font-ui);
|
|
17
|
+
-webkit-font-smoothing: antialiased;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
::selection {
|
|
21
|
+
background: var(--tp-accent-hover);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
:focus-visible {
|
|
25
|
+
outline: 2px solid var(--tp-focus);
|
|
26
|
+
outline-offset: 2px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
@media (prefers-reduced-motion: reduce) {
|
|
30
|
+
*,
|
|
31
|
+
*::before,
|
|
32
|
+
*::after {
|
|
33
|
+
animation-duration: 0.01ms !important;
|
|
34
|
+
animation-iteration-count: 1 !important;
|
|
35
|
+
transition-duration: 0.01ms !important;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* ─── Type recipes ──────────────────────────────────────────────────────── */
|
|
40
|
+
|
|
41
|
+
.t-hero-title {
|
|
42
|
+
font-family: var(--tp-font-reading);
|
|
43
|
+
font-size: clamp(2.4rem, 5vw, 3.8rem);
|
|
44
|
+
font-weight: 300;
|
|
45
|
+
line-height: 1.15;
|
|
46
|
+
letter-spacing: 0.01em;
|
|
47
|
+
color: var(--tp-text);
|
|
48
|
+
}
|
|
49
|
+
.t-hero-title em { color: var(--tp-accent); font-style: italic; }
|
|
50
|
+
|
|
51
|
+
.t-ed-hero-name {
|
|
52
|
+
font-family: var(--tp-font-reading);
|
|
53
|
+
font-size: clamp(2.5rem, 6vw, 4.5rem);
|
|
54
|
+
font-weight: 400;
|
|
55
|
+
letter-spacing: -0.025em;
|
|
56
|
+
line-height: 0.95;
|
|
57
|
+
color: var(--tp-text);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.t-h2 {
|
|
61
|
+
font-family: var(--tp-font-reading);
|
|
62
|
+
font-size: 1.6rem;
|
|
63
|
+
font-weight: 300;
|
|
64
|
+
line-height: 1.3;
|
|
65
|
+
color: var(--tp-text);
|
|
66
|
+
}
|
|
67
|
+
.t-h2 em { color: var(--tp-accent); font-style: italic; }
|
|
68
|
+
|
|
69
|
+
.t-label {
|
|
70
|
+
font-family: var(--tp-font-doing);
|
|
71
|
+
font-size: 0.65rem;
|
|
72
|
+
letter-spacing: var(--tp-tracking-label);
|
|
73
|
+
text-transform: uppercase;
|
|
74
|
+
color: var(--tp-accent);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.t-body {
|
|
78
|
+
font-family: var(--tp-font-reading);
|
|
79
|
+
font-size: 1rem;
|
|
80
|
+
line-height: 1.8;
|
|
81
|
+
color: var(--tp-text-muted);
|
|
82
|
+
}
|
|
83
|
+
.t-body strong { color: var(--tp-text); font-weight: 400; }
|
|
84
|
+
|
|
85
|
+
.t-tagline {
|
|
86
|
+
font-family: var(--tp-font-reading);
|
|
87
|
+
font-size: 1.1rem;
|
|
88
|
+
font-style: italic;
|
|
89
|
+
line-height: 1.7;
|
|
90
|
+
color: var(--tp-text-subtle);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.t-ed-deck {
|
|
94
|
+
font-family: var(--tp-font-reading);
|
|
95
|
+
font-size: 1.05rem;
|
|
96
|
+
font-style: italic;
|
|
97
|
+
line-height: 1.55;
|
|
98
|
+
color: var(--tp-text-muted);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.t-code {
|
|
102
|
+
font-family: var(--tp-font-doing);
|
|
103
|
+
font-size: 0.8rem;
|
|
104
|
+
color: var(--tp-text-muted);
|
|
105
|
+
}
|
|
106
|
+
.t-code .cmd { color: var(--tp-accent); }
|
|
107
|
+
.t-code .flag { color: var(--tp-text-subtle); }
|
|
108
|
+
|
|
109
|
+
.t-wordmark {
|
|
110
|
+
font-family: var(--tp-font-doing);
|
|
111
|
+
font-weight: 500;
|
|
112
|
+
letter-spacing: var(--tp-tracking-mark);
|
|
113
|
+
color: var(--tp-accent);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.t-strip {
|
|
117
|
+
font-family: var(--tp-font-doing);
|
|
118
|
+
font-size: 0.62rem;
|
|
119
|
+
letter-spacing: var(--tp-tracking-strip);
|
|
120
|
+
text-transform: uppercase;
|
|
121
|
+
color: var(--tp-text-subtle);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.t-action {
|
|
125
|
+
font-family: var(--tp-font-doing);
|
|
126
|
+
font-size: 0.62rem;
|
|
127
|
+
letter-spacing: 0.18em;
|
|
128
|
+
text-transform: uppercase;
|
|
129
|
+
color: var(--tp-accent);
|
|
130
|
+
background: none;
|
|
131
|
+
border: none;
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
transition: color var(--tp-motion-quick) var(--tp-motion-easing);
|
|
134
|
+
}
|
|
135
|
+
.t-action:hover { color: var(--tp-text); }
|
|
136
|
+
|
|
137
|
+
/* ─── Editorial structure ───────────────────────────────────────────────── */
|
|
138
|
+
|
|
139
|
+
/* The rail: the markdown blockquote marker made structural. */
|
|
140
|
+
.ed-rail {
|
|
141
|
+
padding-left: 1.8rem;
|
|
142
|
+
border-left: 2px solid var(--tp-accent-line-soft);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.ed-strip {
|
|
146
|
+
display: flex;
|
|
147
|
+
align-items: baseline;
|
|
148
|
+
gap: 0.6rem;
|
|
149
|
+
padding-bottom: 1rem;
|
|
150
|
+
border-bottom: 1px solid var(--tp-rule);
|
|
151
|
+
font-family: var(--tp-font-doing);
|
|
152
|
+
font-size: 0.62rem;
|
|
153
|
+
letter-spacing: var(--tp-tracking-strip);
|
|
154
|
+
text-transform: uppercase;
|
|
155
|
+
color: var(--tp-text-subtle);
|
|
156
|
+
}
|
|
157
|
+
.ed-strip-em { color: var(--tp-text-muted); }
|
|
158
|
+
.ed-strip-accent { color: var(--tp-accent); }
|
|
159
|
+
.ed-strip-sep { opacity: 0.5; }
|
|
160
|
+
.ed-strip-spacer { flex: 1; }
|
|
161
|
+
|
|
162
|
+
.ed-notice {
|
|
163
|
+
border-left: 2px solid var(--tp-notice);
|
|
164
|
+
padding: 0.7rem 1.2rem;
|
|
165
|
+
background: var(--tp-notice-wash);
|
|
166
|
+
color: var(--tp-notice);
|
|
167
|
+
font-family: var(--tp-font-reading);
|
|
168
|
+
font-style: italic;
|
|
169
|
+
font-size: 0.9rem;
|
|
170
|
+
line-height: 1.55;
|
|
171
|
+
}
|
|
172
|
+
.ed-notice--success {
|
|
173
|
+
border-left-color: var(--tp-success);
|
|
174
|
+
background: var(--tp-success-wash);
|
|
175
|
+
color: var(--tp-success);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.ed-action {
|
|
179
|
+
font-family: var(--tp-font-doing);
|
|
180
|
+
font-size: 0.62rem;
|
|
181
|
+
text-transform: uppercase;
|
|
182
|
+
letter-spacing: 0.18em;
|
|
183
|
+
background: none;
|
|
184
|
+
border: none;
|
|
185
|
+
cursor: pointer;
|
|
186
|
+
padding: 0.4rem 0;
|
|
187
|
+
transition: color var(--tp-motion-quick) var(--tp-motion-easing);
|
|
188
|
+
}
|
|
189
|
+
.ed-action--primary { color: var(--tp-accent); }
|
|
190
|
+
.ed-action--primary:hover { color: var(--tp-text); }
|
|
191
|
+
.ed-action--ghost { color: var(--tp-text-subtle); }
|
|
192
|
+
.ed-action--ghost:hover { color: var(--tp-text-muted); }
|
|
193
|
+
.ed-action--danger { color: var(--tp-danger); }
|
|
194
|
+
.ed-action:disabled { color: var(--tp-text-faint); cursor: not-allowed; }
|
|
195
|
+
.ed-action-sep { display: inline-block; margin: 0 0.6rem; color: var(--tp-text-faint); }
|
|
196
|
+
|
|
197
|
+
/* ─── Code ──────────────────────────────────────────────────────────────── */
|
|
198
|
+
|
|
199
|
+
.hljs .hljs-comment,
|
|
200
|
+
.hljs .hljs-quote { color: var(--tp-code-comment); font-style: italic; }
|
|
201
|
+
.hljs .hljs-attr,
|
|
202
|
+
.hljs .hljs-attribute,
|
|
203
|
+
.hljs .hljs-property,
|
|
204
|
+
.hljs .hljs-selector-tag,
|
|
205
|
+
.hljs .hljs-name,
|
|
206
|
+
.hljs .hljs-meta,
|
|
207
|
+
.hljs .hljs-symbol,
|
|
208
|
+
.hljs .hljs-bullet,
|
|
209
|
+
.hljs .hljs-addition { color: var(--tp-code-key); }
|
|
210
|
+
.hljs .hljs-string,
|
|
211
|
+
.hljs .hljs-regexp { color: var(--tp-code-string); }
|
|
212
|
+
.hljs .hljs-number,
|
|
213
|
+
.hljs .hljs-literal,
|
|
214
|
+
.hljs .hljs-keyword,
|
|
215
|
+
.hljs .hljs-built_in,
|
|
216
|
+
.hljs .hljs-type { color: var(--tp-code-number); }
|
|
217
|
+
.hljs .hljs-title,
|
|
218
|
+
.hljs .hljs-title.function_ { color: var(--tp-text); }
|
|
219
|
+
.hljs .hljs-deletion { color: var(--tp-danger); }
|
|
220
|
+
.hljs .hljs-emphasis { font-style: italic; }
|
|
221
|
+
.hljs .hljs-strong { font-weight: 600; }
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/* shadcn-svelte's variable names, answered from contract roles.
|
|
2
|
+
|
|
3
|
+
shadcn-svelte components (and json-render's shadcn-svelte catalog, which is built on
|
|
4
|
+
them) read `--background`, `--primary`, `--muted-foreground` and the rest. Pointing
|
|
5
|
+
those names at `--tp-*` roles means any active theme restyles them with no per-theme
|
|
6
|
+
work: a theme supplies roles once, and this file is the only place the two
|
|
7
|
+
vocabularies meet.
|
|
8
|
+
|
|
9
|
+
The bridge makes shadcn components legible in any theme. It does not make them speak
|
|
10
|
+
in temper's voice — a shadcn default Button is still a filled button, where temper's
|
|
11
|
+
primary action is a mono text action. Voice lives in temper's own components.
|
|
12
|
+
|
|
13
|
+
Import after tailwindcss and contract/tailwind.css. */
|
|
14
|
+
|
|
15
|
+
:root {
|
|
16
|
+
--background: var(--tp-ground);
|
|
17
|
+
--foreground: var(--tp-text);
|
|
18
|
+
--card: var(--tp-surface);
|
|
19
|
+
--card-foreground: var(--tp-text);
|
|
20
|
+
--popover: var(--tp-surface-raised);
|
|
21
|
+
--popover-foreground: var(--tp-text);
|
|
22
|
+
--primary: var(--tp-accent);
|
|
23
|
+
--primary-foreground: var(--tp-on-accent);
|
|
24
|
+
--secondary: var(--tp-surface-raised);
|
|
25
|
+
--secondary-foreground: var(--tp-text);
|
|
26
|
+
--muted: var(--tp-surface);
|
|
27
|
+
--muted-foreground: var(--tp-text-subtle);
|
|
28
|
+
/* shadcn's `accent` is a hover fill, not an accent colour. */
|
|
29
|
+
--accent: var(--tp-accent-hover);
|
|
30
|
+
--accent-foreground: var(--tp-text);
|
|
31
|
+
--destructive: var(--tp-danger);
|
|
32
|
+
--border: var(--tp-rule-strong);
|
|
33
|
+
--input: var(--tp-rule-strong);
|
|
34
|
+
--ring: var(--tp-focus);
|
|
35
|
+
--radius: var(--tp-radius-chip);
|
|
36
|
+
--chart-1: var(--tp-cat-1);
|
|
37
|
+
--chart-2: var(--tp-cat-2);
|
|
38
|
+
--chart-3: var(--tp-cat-3);
|
|
39
|
+
--chart-4: var(--tp-cat-4);
|
|
40
|
+
--chart-5: var(--tp-cat-5);
|
|
41
|
+
--sidebar: var(--tp-surface);
|
|
42
|
+
--sidebar-foreground: var(--tp-text-muted);
|
|
43
|
+
--sidebar-primary: var(--tp-accent);
|
|
44
|
+
--sidebar-primary-foreground: var(--tp-on-accent);
|
|
45
|
+
--sidebar-accent: var(--tp-accent-hover);
|
|
46
|
+
--sidebar-accent-foreground: var(--tp-text);
|
|
47
|
+
--sidebar-border: var(--tp-rule);
|
|
48
|
+
--sidebar-ring: var(--tp-focus);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@theme inline {
|
|
52
|
+
--color-background: var(--background);
|
|
53
|
+
--color-foreground: var(--foreground);
|
|
54
|
+
--color-card: var(--card);
|
|
55
|
+
--color-card-foreground: var(--card-foreground);
|
|
56
|
+
--color-popover: var(--popover);
|
|
57
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
58
|
+
--color-primary: var(--primary);
|
|
59
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
60
|
+
--color-secondary: var(--secondary);
|
|
61
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
62
|
+
--color-muted: var(--muted);
|
|
63
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
64
|
+
--color-accent: var(--accent);
|
|
65
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
66
|
+
--color-destructive: var(--destructive);
|
|
67
|
+
--color-border: var(--border);
|
|
68
|
+
--color-input: var(--input);
|
|
69
|
+
--color-ring: var(--ring);
|
|
70
|
+
--color-chart-1: var(--chart-1);
|
|
71
|
+
--color-chart-2: var(--chart-2);
|
|
72
|
+
--color-chart-3: var(--chart-3);
|
|
73
|
+
--color-chart-4: var(--chart-4);
|
|
74
|
+
--color-chart-5: var(--chart-5);
|
|
75
|
+
--color-sidebar: var(--sidebar);
|
|
76
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
77
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
78
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
79
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
80
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
81
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
82
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
83
|
+
--radius-sm: calc(var(--radius) - 1px);
|
|
84
|
+
--radius-md: var(--radius);
|
|
85
|
+
--radius-lg: calc(var(--radius) + 1px);
|
|
86
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/* Generated from contract/theme.schema.json by themes/scripts/themes.sh build. Do not edit.
|
|
2
|
+
Import after tailwindcss and before any theme.css. */
|
|
3
|
+
|
|
4
|
+
@theme {
|
|
5
|
+
--color-*: initial;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
@theme inline {
|
|
9
|
+
--color-tp-ground: var(--tp-ground);
|
|
10
|
+
--color-tp-surface: var(--tp-surface);
|
|
11
|
+
--color-tp-surface-raised: var(--tp-surface-raised);
|
|
12
|
+
--color-tp-overlay: var(--tp-overlay);
|
|
13
|
+
--color-tp-text: var(--tp-text);
|
|
14
|
+
--color-tp-text-muted: var(--tp-text-muted);
|
|
15
|
+
--color-tp-text-subtle: var(--tp-text-subtle);
|
|
16
|
+
--color-tp-text-faint: var(--tp-text-faint);
|
|
17
|
+
--color-tp-rule: var(--tp-rule);
|
|
18
|
+
--color-tp-rule-strong: var(--tp-rule-strong);
|
|
19
|
+
--color-tp-accent: var(--tp-accent);
|
|
20
|
+
--color-tp-accent-muted: var(--tp-accent-muted);
|
|
21
|
+
--color-tp-accent-line: var(--tp-accent-line);
|
|
22
|
+
--color-tp-accent-line-soft: var(--tp-accent-line-soft);
|
|
23
|
+
--color-tp-accent-wash: var(--tp-accent-wash);
|
|
24
|
+
--color-tp-accent-hover: var(--tp-accent-hover);
|
|
25
|
+
--color-tp-on-accent: var(--tp-on-accent);
|
|
26
|
+
--color-tp-focus: var(--tp-focus);
|
|
27
|
+
--color-tp-notice: var(--tp-notice);
|
|
28
|
+
--color-tp-notice-wash: var(--tp-notice-wash);
|
|
29
|
+
--color-tp-success: var(--tp-success);
|
|
30
|
+
--color-tp-success-wash: var(--tp-success-wash);
|
|
31
|
+
--color-tp-danger: var(--tp-danger);
|
|
32
|
+
--color-tp-danger-wash: var(--tp-danger-wash);
|
|
33
|
+
--color-tp-pending: var(--tp-pending);
|
|
34
|
+
--color-tp-pending-wash: var(--tp-pending-wash);
|
|
35
|
+
--color-tp-region-arriving: var(--tp-region-arriving);
|
|
36
|
+
--color-tp-region-arriving-wash: var(--tp-region-arriving-wash);
|
|
37
|
+
--color-tp-region-empty: var(--tp-region-empty);
|
|
38
|
+
--color-tp-region-empty-wash: var(--tp-region-empty-wash);
|
|
39
|
+
--color-tp-region-gave-up: var(--tp-region-gave-up);
|
|
40
|
+
--color-tp-region-gave-up-wash: var(--tp-region-gave-up-wash);
|
|
41
|
+
--color-tp-region-failed: var(--tp-region-failed);
|
|
42
|
+
--color-tp-region-failed-wash: var(--tp-region-failed-wash);
|
|
43
|
+
--color-tp-author-human: var(--tp-author-human);
|
|
44
|
+
--color-tp-author-agent: var(--tp-author-agent);
|
|
45
|
+
--color-tp-cat-1: var(--tp-cat-1);
|
|
46
|
+
--color-tp-cat-2: var(--tp-cat-2);
|
|
47
|
+
--color-tp-cat-3: var(--tp-cat-3);
|
|
48
|
+
--color-tp-cat-4: var(--tp-cat-4);
|
|
49
|
+
--color-tp-cat-5: var(--tp-cat-5);
|
|
50
|
+
--color-tp-cat-6: var(--tp-cat-6);
|
|
51
|
+
--color-tp-cat-7: var(--tp-cat-7);
|
|
52
|
+
--color-tp-cat-8: var(--tp-cat-8);
|
|
53
|
+
--color-tp-cat-neutral: var(--tp-cat-neutral);
|
|
54
|
+
--color-tp-code-key: var(--tp-code-key);
|
|
55
|
+
--color-tp-code-string: var(--tp-code-string);
|
|
56
|
+
--color-tp-code-number: var(--tp-code-number);
|
|
57
|
+
--color-tp-code-comment: var(--tp-code-comment);
|
|
58
|
+
--font-tp-reading: var(--tp-font-reading);
|
|
59
|
+
--font-tp-doing: var(--tp-font-doing);
|
|
60
|
+
--font-tp-ui: var(--tp-font-ui);
|
|
61
|
+
--radius-tp-chip: var(--tp-radius-chip);
|
|
62
|
+
--radius-tp-panel: var(--tp-radius-panel);
|
|
63
|
+
--tracking-tp-label: var(--tp-tracking-label);
|
|
64
|
+
--tracking-tp-strip: var(--tp-tracking-strip);
|
|
65
|
+
--tracking-tp-eyebrow: var(--tp-tracking-eyebrow);
|
|
66
|
+
--tracking-tp-ui: var(--tp-tracking-ui);
|
|
67
|
+
--tracking-tp-mark: var(--tp-tracking-mark);
|
|
68
|
+
--default-transition-duration: var(--tp-motion-quick);
|
|
69
|
+
--default-transition-timing-function: var(--tp-motion-easing);
|
|
70
|
+
}
|