@sableui/react 0.4.0 → 0.6.0
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/AGENTS.md +2 -2
- package/CHANGELOG.md +189 -0
- package/CONTRIBUTING.md +1 -1
- package/THIRD_PARTY_NOTICES.md +11 -0
- package/bin/sable-lint.mjs +2 -1
- package/bin/sable.mjs +3 -2
- package/dist/{_contract-Cn3lUpih.d.ts → _contract-C2M1SjBf.d.ts} +20 -2
- package/dist/blocks/index.js +21 -53
- package/dist/{chunk-CCUDFLU3.js → chunk-7QSDYBXO.js} +652 -38
- package/dist/{chunk-FBSL2LZQ.js → chunk-EDXLM7YL.js} +11 -30
- package/dist/chunk-FKZXAFIY.js +12 -0
- package/dist/{chunk-OQACYT2D.js → chunk-NETUFI34.js} +23 -1
- package/dist/{chunk-NGBDP3BM.js → chunk-QJ6FWTUG.js} +1 -1
- package/dist/{chunk-D2ETQBHV.js → chunk-VVW4UCDI.js} +5 -5
- package/dist/{chunk-TH563WLW.js → chunk-XAZGMY73.js} +10 -3
- package/dist/foundations/index.js +6 -0
- package/dist/icon/index.js +2 -2
- package/dist/icon-sets/brands.d.ts +0 -27
- package/dist/icon-sets/brands.js +7 -1
- package/dist/icon-sets/reicon.js +1 -1
- package/dist/index.d.ts +164 -23
- package/dist/index.js +116 -29
- package/dist/ios/index.d.ts +6 -1
- package/dist/ios/index.js +4 -3
- package/dist/ios/styles.css +9 -0
- package/dist/patterns/index.js +24 -30
- package/dist/presets/index.d.ts +108 -5
- package/dist/presets/index.js +239 -6
- package/dist/styles.css +57 -0
- package/dist/{theme-provider-CGeiusgu.d.ts → theme-provider-kQszHYji.d.ts} +1 -1
- package/dist/themes/atelier/atelier.css +31 -3
- package/dist/themes/atelier/index.d.ts +1 -1
- package/dist/themes/ember/ember.css +38 -7
- package/dist/themes/ember/index.d.ts +1 -1
- package/dist/themes/index.d.ts +2 -2
- package/dist/themes/meridian/index.d.ts +1 -1
- package/dist/themes/meridian/meridian.css +33 -4
- package/dist/themes/poppy/index.d.ts +1 -1
- package/dist/themes/poppy/poppy.css +28 -0
- package/dist/themes/prism/index.d.ts +1 -1
- package/dist/themes/prism/prism.css +26 -0
- package/dist/themes/sable/index.d.ts +1 -1
- package/dist/themes/sable/sable.css +24 -11
- package/dist/themes/verdant/index.d.ts +1 -1
- package/dist/themes/verdant/verdant.css +34 -4
- package/llms/blocks/danger-zone-block.md +1 -1
- package/llms/blocks/event-card-block.md +1 -1
- package/llms/blocks/feedback-form-block.md +1 -1
- package/llms/blocks/invite-team-block.md +1 -1
- package/llms/blocks/media-player-block.md +3 -16
- package/llms/blocks/notification-list-block.md +3 -3
- package/llms/blocks/product-card-block.md +3 -17
- package/llms/blocks/profile-card-block.md +4 -17
- package/llms/blocks/profile-form-block.md +2 -2
- package/llms/blocks/team-members-block.md +1 -1
- package/llms/blocks/testimonial-block.md +1 -1
- package/llms/components/avatar.md +13 -1
- package/llms/components/card.md +1 -1
- package/llms/components/gradient-art.md +80 -0
- package/llms/components/sheet.md +1 -1
- package/llms/components/tabs.md +1 -1
- package/llms/components/toast.md +12 -1
- package/llms/foundations/accessibility.md +1 -0
- package/llms/patterns/auth-page.md +5 -19
- package/llms/patterns/brand-landing-page.md +1 -1
- package/llms/patterns/chat-panel.md +1 -1
- package/llms/patterns/dashboard-page.md +4 -4
- package/llms/patterns/feed-page.md +4 -4
- package/llms/patterns/landing-page.md +1 -1
- package/llms/patterns/list-detail-page.md +3 -2
- package/llms/patterns/settings-page.md +3 -3
- package/llms/reference/token-intent.md +9 -1
- package/llms/tasks/auditing-an-app.md +6 -0
- package/llms/tasks/building-an-ios-prototype.md +3 -1
- package/llms/tasks/converting-an-app.md +1 -1
- package/llms/tasks/creating-a-preset.md +24 -0
- package/llms/tasks/reviewing-a-design.md +9 -0
- package/llms/tasks/using-presets.md +6 -1
- package/llms-full.txt +214 -106
- package/llms.txt +4 -3
- package/package.json +9 -7
- package/registry.json +310 -32
- package/scripts/design-presets.mjs +27 -4
- package/scripts/grade.mjs +2 -1
- package/scripts/label-contrast.mjs +138 -0
- package/scripts/lint-rules.mjs +23 -2
- package/skills/sable/SKILL.md +3 -1
- package/targets/ios.json +18 -2
- package/third-party/paper-shaders-LICENSE.txt +201 -0
- package/third-party/paper-shaders-NOTICE.txt +5 -0
package/dist/presets/index.d.ts
CHANGED
|
@@ -1,4 +1,81 @@
|
|
|
1
|
-
import { T as ThemeManifest } from '../_contract-
|
|
1
|
+
import { T as ThemeManifest } from '../_contract-C2M1SjBf.js';
|
|
2
|
+
|
|
3
|
+
/** An OKLCH colour, lightness 0–1. Extra fields (alpha) ride along untouched. */
|
|
4
|
+
interface OklchColor {
|
|
5
|
+
l: number;
|
|
6
|
+
c: number;
|
|
7
|
+
h: number;
|
|
8
|
+
}
|
|
9
|
+
/** Ratio and Lc of a label on a fill, and whether it clears both floors. */
|
|
10
|
+
declare function labelContrast(ink: OklchColor, fill: OklchColor): {
|
|
11
|
+
ratio: number;
|
|
12
|
+
lc: number;
|
|
13
|
+
reads: boolean;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* The ink for a fill that cannot move (a status colour, a decorative tone).
|
|
17
|
+
* One that reads; of two, the stronger. Of none, the one meeting the ratio,
|
|
18
|
+
* because APCA cannot waive it; failing that, the stronger.
|
|
19
|
+
*/
|
|
20
|
+
declare function inkFor<T extends OklchColor>(fill: OklchColor, inks: readonly T[]): T;
|
|
21
|
+
/**
|
|
22
|
+
* The fill nearest `fill` on which `ink` reads: lightness moves away from the
|
|
23
|
+
* ink in small steps, hue holds, and chroma is kept unless the new lightness
|
|
24
|
+
* cannot show it. Returns `fill` itself when the ink already reads, and the
|
|
25
|
+
* furthest step tried when nothing in range does.
|
|
26
|
+
*/
|
|
27
|
+
declare function fitFillToInk<T extends OklchColor>(fill: T, ink: OklchColor): T;
|
|
28
|
+
/**
|
|
29
|
+
* Which ink a colour takes: the one that reads on it with the least change to
|
|
30
|
+
* it — none, when one already reads (the stronger, when both do). Decide this
|
|
31
|
+
* once, on the colour as picked, then `fitFillToInk` each mode's fill to it.
|
|
32
|
+
*/
|
|
33
|
+
declare function inkPolarity<T extends OklchColor>(color: OklchColor, inks: readonly T[]): T;
|
|
34
|
+
/** Fills a theme or preset authors from its brand. Their ink follows the colour as picked. */
|
|
35
|
+
declare const BRAND_FILLS: readonly ["primary", "sidebar-primary", "highlight"];
|
|
36
|
+
/**
|
|
37
|
+
* Status fills. Each has a separate `-text` token for the colour drawn on a
|
|
38
|
+
* surface (`text-destructive` reads `--destructive-text`), so the fill is free to
|
|
39
|
+
* sit where its label reads — a dark-mode red under white ink — without taking
|
|
40
|
+
* the page's error text down with it.
|
|
41
|
+
*/
|
|
42
|
+
declare const STATUS_FILLS: readonly ["destructive", "success", "tone-warning", "tone-info"];
|
|
43
|
+
/**
|
|
44
|
+
* Every fill a label sits on that the rule measures. Sable's own themes are held
|
|
45
|
+
* to it (themes.test.ts); a preset or an app is told, offered a fix, and may keep
|
|
46
|
+
* its choice on the record.
|
|
47
|
+
*/
|
|
48
|
+
declare const LABELLED_FILLS: readonly ["primary", "sidebar-primary", "highlight", "destructive", "success", "tone-warning", "tone-info"];
|
|
49
|
+
/** The rule's id, shared by `sable-lint`, `sable preset --check` and preset `exceptions`. */
|
|
50
|
+
declare const LABEL_CONTRAST_RULE = "label-contrast";
|
|
51
|
+
interface LabelFinding {
|
|
52
|
+
mode: "light" | "dark";
|
|
53
|
+
/** The fill token, without `--`; its ink is `${fill}-foreground`. */
|
|
54
|
+
fill: string;
|
|
55
|
+
ratio: number;
|
|
56
|
+
lc: number;
|
|
57
|
+
/** The nearest pair that reads, decided as the customizer decides it. */
|
|
58
|
+
suggestion: {
|
|
59
|
+
fill: string;
|
|
60
|
+
ink: string;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Check each labelled fill against its ink, in both modes.
|
|
65
|
+
*
|
|
66
|
+
* Takes resolved values (`oklch(…)` or hex; anything else is counted as
|
|
67
|
+
* unmeasured, never guessed). Which ink a fix uses is decided on the LIGHT-mode
|
|
68
|
+
* fill — the colour as picked — so a dark-mode failure on a colour that takes
|
|
69
|
+
* white is fixed by bringing the fill down, not by flipping the label dark.
|
|
70
|
+
*/
|
|
71
|
+
declare function checkLabelledFills(tokens: {
|
|
72
|
+
light: Record<string, string | undefined>;
|
|
73
|
+
dark: Record<string, string | undefined>;
|
|
74
|
+
}): {
|
|
75
|
+
findings: LabelFinding[];
|
|
76
|
+
measured: number;
|
|
77
|
+
unmeasured: string[];
|
|
78
|
+
};
|
|
2
79
|
|
|
3
80
|
/** The public theme vocabulary. Presets never need component overrides. */
|
|
4
81
|
declare const PRESET_TOKEN_NAMES: readonly string[];
|
|
@@ -26,12 +103,38 @@ interface SablePreset {
|
|
|
26
103
|
light: Record<string, string>;
|
|
27
104
|
dark: Record<string, string>;
|
|
28
105
|
};
|
|
106
|
+
/**
|
|
107
|
+
* Sable's opinions the preset declines, on the record. A check reports an
|
|
108
|
+
* accepted finding with its reason instead of failing on it.
|
|
109
|
+
*/
|
|
110
|
+
exceptions?: PresetException[];
|
|
111
|
+
}
|
|
112
|
+
/** A deliberate departure from a Sable check — e.g. a brand that requires dark ink on its primary. */
|
|
113
|
+
interface PresetException {
|
|
114
|
+
rule: typeof LABEL_CONTRAST_RULE;
|
|
115
|
+
mode: "light" | "dark";
|
|
116
|
+
/** The fill token, e.g. `primary`. */
|
|
117
|
+
token: (typeof LABELLED_FILLS)[number];
|
|
118
|
+
reason: string;
|
|
29
119
|
}
|
|
30
|
-
|
|
31
|
-
declare function
|
|
32
|
-
declare function parsePreset(input: unknown): SablePreset;
|
|
120
|
+
declare function validatePreset(raw: unknown): string[];
|
|
121
|
+
declare function parsePreset(raw: unknown): SablePreset;
|
|
33
122
|
declare function presetManifest(input: SablePreset): ThemeManifest;
|
|
34
123
|
/** Scoped CSS can coexist with built-in presets. Never changes :root globally. */
|
|
35
124
|
declare function presetCss(input: SablePreset): string;
|
|
125
|
+
/**
|
|
126
|
+
* Labels on the preset's brand and status fills (contrast.ts, "Labels on fills"): each
|
|
127
|
+
* failing pair with the nearest pair that reads. Findings the preset accepts in
|
|
128
|
+
* `exceptions` come back under `accepted` with their reason — reported, not
|
|
129
|
+
* failed, because the choice is the preset author's to make.
|
|
130
|
+
*/
|
|
131
|
+
declare function presetLabelContrast(input: SablePreset): {
|
|
132
|
+
open: LabelFinding[];
|
|
133
|
+
accepted: (LabelFinding & {
|
|
134
|
+
reason: string;
|
|
135
|
+
})[];
|
|
136
|
+
measured: number;
|
|
137
|
+
unmeasured: string[];
|
|
138
|
+
};
|
|
36
139
|
|
|
37
|
-
export { PRESET_TOKEN_NAMES, type SablePreset, parsePreset, presetCss, presetManifest, validatePreset };
|
|
140
|
+
export { BRAND_FILLS, LABELLED_FILLS, LABEL_CONTRAST_RULE, type LabelFinding, type OklchColor, PRESET_TOKEN_NAMES, type PresetException, STATUS_FILLS, type SablePreset, checkLabelledFills, fitFillToInk, inkFor, inkPolarity, labelContrast, parsePreset, presetCss, presetLabelContrast, presetManifest, validatePreset };
|
package/dist/presets/index.js
CHANGED
|
@@ -1,10 +1,193 @@
|
|
|
1
|
-
import { SEMANTIC_TOKENS, WORKSPACE_SURFACE_TOKENS, DERIVED_EDGE_TOKENS, RADIUS_TOKENS, MOTION_TOKENS, TYPE_SCALE_TOKENS, TYPE_REFINEMENT_TOKENS, TYPE_OVERLINE_TOKENS, DENSITY_TOKENS, LAYERING_TOKENS, TYPE_UI_TOKENS } from '../chunk-
|
|
1
|
+
import { SEMANTIC_TOKENS, WORKSPACE_SURFACE_TOKENS, DERIVED_EDGE_TOKENS, RADIUS_TOKENS, MOTION_TOKENS, TYPE_SCALE_TOKENS, TYPE_REFINEMENT_TOKENS, TYPE_OVERLINE_TOKENS, DENSITY_TOKENS, LAYERING_TOKENS, TYPE_UI_TOKENS, ART_TOKENS } from '../chunk-NETUFI34.js';
|
|
2
|
+
|
|
3
|
+
// src/themes/contrast.ts
|
|
4
|
+
function oklchToLinearRgb(L, C, H) {
|
|
5
|
+
const h = H * Math.PI / 180;
|
|
6
|
+
const a = C * Math.cos(h);
|
|
7
|
+
const b = C * Math.sin(h);
|
|
8
|
+
const l = (L + 0.3963377774 * a + 0.2158037573 * b) ** 3;
|
|
9
|
+
const m = (L - 0.1055613458 * a - 0.0638541728 * b) ** 3;
|
|
10
|
+
const s = (L - 0.0894841775 * a - 1.291485548 * b) ** 3;
|
|
11
|
+
return [
|
|
12
|
+
4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
|
|
13
|
+
-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
|
|
14
|
+
-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
|
|
15
|
+
];
|
|
16
|
+
}
|
|
17
|
+
function hexToLinearRgb(hex) {
|
|
18
|
+
const m = hex.trim().match(/^#([0-9a-f]{6})$/i);
|
|
19
|
+
if (!m) return null;
|
|
20
|
+
const n = Number.parseInt(m[1], 16);
|
|
21
|
+
const toLinear = (c) => c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
22
|
+
return [
|
|
23
|
+
toLinear((n >> 16 & 255) / 255),
|
|
24
|
+
toLinear((n >> 8 & 255) / 255),
|
|
25
|
+
toLinear((n & 255) / 255)
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
var clamp01 = (x) => Math.min(1, Math.max(0, x));
|
|
29
|
+
function linearToSrgb(c) {
|
|
30
|
+
const v = clamp01(c);
|
|
31
|
+
return v <= 31308e-7 ? 12.92 * v : 1.055 * v ** (1 / 2.4) - 0.055;
|
|
32
|
+
}
|
|
33
|
+
function parseColorWithAlpha(value) {
|
|
34
|
+
const v = value.trim();
|
|
35
|
+
if (v.startsWith("#")) {
|
|
36
|
+
const rgb2 = hexToLinearRgb(v.length === 9 ? v.slice(0, 7) : v);
|
|
37
|
+
if (!rgb2) return null;
|
|
38
|
+
const alpha2 = v.length === 9 ? Number.parseInt(v.slice(7), 16) / 255 : 1;
|
|
39
|
+
return { rgb: rgb2, alpha: Number.isNaN(alpha2) ? 1 : alpha2 };
|
|
40
|
+
}
|
|
41
|
+
const m = v.match(
|
|
42
|
+
/^oklch\(\s*([\d.]+)(%?)\s+([\d.]+)\s+([\d.]+)(?:\s*\/\s*([\d.]+)(%?))?\s*\)/i
|
|
43
|
+
);
|
|
44
|
+
if (!m) return null;
|
|
45
|
+
const rgb = oklchToLinearRgb(
|
|
46
|
+
m[2] === "%" ? Number(m[1]) / 100 : Number(m[1]),
|
|
47
|
+
Number(m[3]),
|
|
48
|
+
Number(m[4])
|
|
49
|
+
);
|
|
50
|
+
const alpha = m[5] === void 0 ? 1 : m[6] === "%" ? Number(m[5]) / 100 : Number(m[5]);
|
|
51
|
+
return { rgb, alpha };
|
|
52
|
+
}
|
|
53
|
+
function luminance([r, g, b]) {
|
|
54
|
+
return 0.2126 * clamp01(r) + 0.7152 * clamp01(g) + 0.0722 * clamp01(b);
|
|
55
|
+
}
|
|
56
|
+
function contrastRatio(a, b) {
|
|
57
|
+
const [l1, l2] = [luminance(a), luminance(b)];
|
|
58
|
+
return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
|
|
59
|
+
}
|
|
60
|
+
var CONTRAST_FLOOR = 4.5;
|
|
61
|
+
var toSrgb = linearToSrgb;
|
|
62
|
+
function apcaLc(ink, surface) {
|
|
63
|
+
const Y = (rgb) => {
|
|
64
|
+
const [r, g, b] = rgb.map(toSrgb);
|
|
65
|
+
const y = 0.2126729 * r ** 2.4 + 0.7151522 * g ** 2.4 + 0.072175 * b ** 2.4;
|
|
66
|
+
return y < 0.022 ? y + (0.022 - y) ** 1.414 : y;
|
|
67
|
+
};
|
|
68
|
+
const Yt = Y(ink);
|
|
69
|
+
const Yb = Y(surface);
|
|
70
|
+
if (Math.abs(Yb - Yt) < 5e-4) return 0;
|
|
71
|
+
let out;
|
|
72
|
+
if (Yb > Yt) {
|
|
73
|
+
const s = (Yb ** 0.56 - Yt ** 0.57) * 1.14;
|
|
74
|
+
out = s < 0.035 ? 0 : s - 0.027;
|
|
75
|
+
} else {
|
|
76
|
+
const s = (Yb ** 0.65 - Yt ** 0.62) * 1.14;
|
|
77
|
+
out = s > -0.035 ? 0 : s + 0.027;
|
|
78
|
+
}
|
|
79
|
+
return out * 100;
|
|
80
|
+
}
|
|
81
|
+
var PERCEPTUAL_FLOOR = 60;
|
|
82
|
+
var GAMUT_EPSILON = 1e-4;
|
|
83
|
+
function isInSrgbGamut(rgb, epsilon = GAMUT_EPSILON) {
|
|
84
|
+
return rgb.every((c) => c >= -epsilon && c <= 1 + epsilon);
|
|
85
|
+
}
|
|
86
|
+
var linearOf = ({ l, c, h }) => oklchToLinearRgb(l, c, h);
|
|
87
|
+
function maxChromaAt(l, h) {
|
|
88
|
+
if (l <= 0 || l >= 1) return 0;
|
|
89
|
+
let lo = 0;
|
|
90
|
+
let hi = 0.4;
|
|
91
|
+
for (let i = 0; i < 24; i++) {
|
|
92
|
+
const mid = (lo + hi) / 2;
|
|
93
|
+
if (isInSrgbGamut(oklchToLinearRgb(l, mid, h))) lo = mid;
|
|
94
|
+
else hi = mid;
|
|
95
|
+
}
|
|
96
|
+
return lo;
|
|
97
|
+
}
|
|
98
|
+
function labelContrast(ink, fill) {
|
|
99
|
+
const a = linearOf(ink);
|
|
100
|
+
const b = linearOf(fill);
|
|
101
|
+
const ratio = contrastRatio(a, b);
|
|
102
|
+
const lc = apcaLc(a, b);
|
|
103
|
+
return { ratio, lc, reads: ratio >= CONTRAST_FLOOR && Math.abs(lc) >= PERCEPTUAL_FLOOR };
|
|
104
|
+
}
|
|
105
|
+
function inkFor(fill, inks) {
|
|
106
|
+
const rank = (ink) => {
|
|
107
|
+
const v = labelContrast(ink, fill);
|
|
108
|
+
return (v.reads ? 2 : v.ratio >= CONTRAST_FLOOR ? 1 : 0) * 1e3 + Math.abs(v.lc);
|
|
109
|
+
};
|
|
110
|
+
return inks.reduce((best, ink) => rank(ink) > rank(best) ? ink : best);
|
|
111
|
+
}
|
|
112
|
+
var FILL_GAMUT_SAFETY = 0.98;
|
|
113
|
+
function fitFillToInk(fill, ink) {
|
|
114
|
+
if (labelContrast(ink, fill).reads) return fill;
|
|
115
|
+
const step = ink.l > fill.l ? -5e-3 : 5e-3;
|
|
116
|
+
let candidate = fill;
|
|
117
|
+
for (let l = fill.l + step; l > 0.05 && l < 0.98; l += step) {
|
|
118
|
+
candidate = { ...fill, l, c: Math.min(fill.c, maxChromaAt(l, fill.h) * FILL_GAMUT_SAFETY) };
|
|
119
|
+
if (labelContrast(ink, candidate).reads) return candidate;
|
|
120
|
+
}
|
|
121
|
+
return candidate;
|
|
122
|
+
}
|
|
123
|
+
function inkPolarity(color, inks) {
|
|
124
|
+
const cost = (ink) => {
|
|
125
|
+
const fitted = fitFillToInk(color, ink);
|
|
126
|
+
const v = labelContrast(ink, fitted);
|
|
127
|
+
return (v.reads ? 0 : 10) + Math.abs(fitted.l - color.l) - Math.abs(v.lc) / 1e4;
|
|
128
|
+
};
|
|
129
|
+
return inks.reduce((best, ink) => cost(ink) < cost(best) ? ink : best);
|
|
130
|
+
}
|
|
131
|
+
function linearRgbToOklch([r, g, b]) {
|
|
132
|
+
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
|
|
133
|
+
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
|
|
134
|
+
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
|
|
135
|
+
const L = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s;
|
|
136
|
+
const A = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s;
|
|
137
|
+
const B = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s;
|
|
138
|
+
const c = Math.hypot(A, B);
|
|
139
|
+
return { l: L, c, h: c < 1e-4 ? 0 : (Math.atan2(B, A) * 180 / Math.PI + 360) % 360 };
|
|
140
|
+
}
|
|
141
|
+
var formatOklchColor = ({ l, c, h }) => `oklch(${(l * 100).toFixed(1)}% ${c.toFixed(3)} ${h.toFixed(1)})`;
|
|
142
|
+
var BRAND_FILLS = ["primary", "sidebar-primary", "highlight"];
|
|
143
|
+
var STATUS_FILLS = ["destructive", "success", "tone-warning", "tone-info"];
|
|
144
|
+
var LABELLED_FILLS = [...BRAND_FILLS, ...STATUS_FILLS];
|
|
145
|
+
var LABEL_CONTRAST_RULE = "label-contrast";
|
|
146
|
+
function checkLabelledFills(tokens) {
|
|
147
|
+
const findings = [];
|
|
148
|
+
const unmeasured = [];
|
|
149
|
+
let measured = 0;
|
|
150
|
+
const read = (value) => {
|
|
151
|
+
const parsed = value ? parseColorWithAlpha(value) : null;
|
|
152
|
+
return parsed && parsed.alpha >= 1 ? linearRgbToOklch(parsed.rgb) : null;
|
|
153
|
+
};
|
|
154
|
+
for (const mode of ["light", "dark"]) {
|
|
155
|
+
for (const fill of LABELLED_FILLS) {
|
|
156
|
+
const rawFill = tokens[mode][fill];
|
|
157
|
+
const rawInk = tokens[mode][`${fill}-foreground`];
|
|
158
|
+
if (!rawFill && !rawInk) continue;
|
|
159
|
+
const fillColor = read(rawFill);
|
|
160
|
+
const ink = read(rawInk);
|
|
161
|
+
if (!fillColor || !ink) {
|
|
162
|
+
unmeasured.push(`${mode}.${fill}`);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
measured++;
|
|
166
|
+
const verdict = labelContrast(ink, fillColor);
|
|
167
|
+
if (verdict.reads) continue;
|
|
168
|
+
const opposite = ink.l > 0.5 ? { l: 0.2, c: 0.03, h: fillColor.h } : { l: 0.985, c: 0.01, h: fillColor.h };
|
|
169
|
+
const reference = read(tokens.light[fill]) ?? fillColor;
|
|
170
|
+
const chosen = inkPolarity(reference, [ink, { ...opposite, h: reference.h }]);
|
|
171
|
+
const nextInk = chosen === ink ? ink : opposite;
|
|
172
|
+
findings.push({
|
|
173
|
+
mode,
|
|
174
|
+
fill,
|
|
175
|
+
ratio: verdict.ratio,
|
|
176
|
+
lc: verdict.lc,
|
|
177
|
+
suggestion: { fill: formatOklchColor(fitFillToInk(fillColor, nextInk)), ink: formatOklchColor(nextInk) }
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return { findings, measured, unmeasured };
|
|
182
|
+
}
|
|
2
183
|
|
|
3
184
|
// src/presets/index.ts
|
|
4
185
|
var PRESET_TOKEN_NAMES = [.../* @__PURE__ */ new Set([
|
|
5
186
|
...SEMANTIC_TOKENS,
|
|
6
187
|
...WORKSPACE_SURFACE_TOKENS.map((name) => `theme-${name}`),
|
|
7
188
|
...DERIVED_EDGE_TOKENS.map((name) => `theme-${name}`),
|
|
189
|
+
// Art direction: optional hooks — a preset without them paints the default art.
|
|
190
|
+
...ART_TOKENS.map((name) => `theme-${name}`),
|
|
8
191
|
...WORKSPACE_SURFACE_TOKENS,
|
|
9
192
|
...DERIVED_EDGE_TOKENS,
|
|
10
193
|
...RADIUS_TOKENS,
|
|
@@ -38,7 +221,31 @@ var tokenNames = new Set(PRESET_TOKEN_NAMES);
|
|
|
38
221
|
var record = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
39
222
|
var text = (value) => typeof value === "string" && value.trim().length > 0;
|
|
40
223
|
var safeCss = (value) => value.length <= 2048 && !/[;{}<>\\@]|\/\*|\*\/|!important|url\s*\(|expression\s*\(|REPLACE/i.test(value);
|
|
41
|
-
|
|
224
|
+
var STATUS_TEXT_FROM_FILL = {
|
|
225
|
+
"destructive-text": "destructive",
|
|
226
|
+
"success-text": "success",
|
|
227
|
+
"tone-warning-text": "tone-warning",
|
|
228
|
+
"tone-info-text": "tone-info"
|
|
229
|
+
};
|
|
230
|
+
function upgradePreset(input) {
|
|
231
|
+
if (!record(input) || !record(input.tokens)) return input;
|
|
232
|
+
const tokens = input.tokens;
|
|
233
|
+
const shared = record(tokens.shared) ? tokens.shared : {};
|
|
234
|
+
const upgraded = { ...tokens };
|
|
235
|
+
for (const mode of ["light", "dark"]) {
|
|
236
|
+
const values = tokens[mode];
|
|
237
|
+
if (!record(values)) continue;
|
|
238
|
+
const next = { ...values };
|
|
239
|
+
for (const [name, fill] of Object.entries(STATUS_TEXT_FROM_FILL)) {
|
|
240
|
+
const value = values[fill] ?? shared[fill];
|
|
241
|
+
if (!text(values[name]) && !text(shared[name]) && text(value)) next[name] = value;
|
|
242
|
+
}
|
|
243
|
+
upgraded[mode] = next;
|
|
244
|
+
}
|
|
245
|
+
return { ...input, tokens: upgraded };
|
|
246
|
+
}
|
|
247
|
+
function validatePreset(raw) {
|
|
248
|
+
const input = upgradePreset(raw);
|
|
42
249
|
if (!record(input)) return ["Preset must be a JSON object."];
|
|
43
250
|
const issues = [];
|
|
44
251
|
if (input.schemaVersion !== 1) issues.push("Unsupported preset schemaVersion; expected 1.");
|
|
@@ -58,6 +265,13 @@ function validatePreset(input) {
|
|
|
58
265
|
if (!record(item) || !text(item.source) || !text(item.observation) || !["measured", "estimated", "derived", "user-edited"].includes(String(item.method))) issues.push(`Invalid reference observation ${index + 1}.`);
|
|
59
266
|
});
|
|
60
267
|
}
|
|
268
|
+
if (input.exceptions !== void 0) {
|
|
269
|
+
if (!Array.isArray(input.exceptions) || input.exceptions.length > 50) issues.push("exceptions must be an array of at most 50 entries.");
|
|
270
|
+
else input.exceptions.forEach((item, index) => {
|
|
271
|
+
if (!record(item) || item.rule !== LABEL_CONTRAST_RULE || !["light", "dark"].includes(String(item.mode)) || !LABELLED_FILLS.includes(item.token) || !text(item.reason) || item.reason.length > 500)
|
|
272
|
+
issues.push(`Invalid exception ${index + 1}: needs rule "${LABEL_CONTRAST_RULE}", mode light|dark, token (${LABELLED_FILLS.join(", ")}) and a reason.`);
|
|
273
|
+
});
|
|
274
|
+
}
|
|
61
275
|
if (!record(input.tokens)) return [...issues, "tokens must contain shared, light, and dark objects."];
|
|
62
276
|
const groups = input.tokens;
|
|
63
277
|
for (const group of ["shared", "light", "dark"]) {
|
|
@@ -92,10 +306,10 @@ function validatePreset(input) {
|
|
|
92
306
|
}
|
|
93
307
|
return issues;
|
|
94
308
|
}
|
|
95
|
-
function parsePreset(
|
|
96
|
-
const issues = validatePreset(
|
|
309
|
+
function parsePreset(raw) {
|
|
310
|
+
const issues = validatePreset(raw);
|
|
97
311
|
if (issues.length) throw new Error(issues.join("\n"));
|
|
98
|
-
return
|
|
312
|
+
return upgradePreset(raw);
|
|
99
313
|
}
|
|
100
314
|
function presetManifest(input) {
|
|
101
315
|
const preset = parsePreset(input);
|
|
@@ -122,5 +336,24 @@ ${declarations(preset.tokens.light)}
|
|
|
122
336
|
}
|
|
123
337
|
`;
|
|
124
338
|
}
|
|
339
|
+
function presetLabelContrast(input) {
|
|
340
|
+
const preset = parsePreset(input);
|
|
341
|
+
const values = (mode) => {
|
|
342
|
+
const all = { ...preset.tokens.shared, ...preset.tokens[mode] };
|
|
343
|
+
const resolve = (value, depth = 0) => depth > 10 ? value : value.replace(/^var\(\s*--([\w-]+)\s*\)$/, (m, name) => all[name] ? resolve(all[name], depth + 1) : m);
|
|
344
|
+
return Object.fromEntries(Object.entries(all).map(([name, value]) => [name, resolve(value)]));
|
|
345
|
+
};
|
|
346
|
+
const { findings, measured, unmeasured } = checkLabelledFills({ light: values("light"), dark: values("dark") });
|
|
347
|
+
const reasonFor = (f) => preset.exceptions?.find((e) => e.rule === LABEL_CONTRAST_RULE && e.mode === f.mode && e.token === f.fill)?.reason;
|
|
348
|
+
return {
|
|
349
|
+
open: findings.filter((f) => !reasonFor(f)),
|
|
350
|
+
accepted: findings.flatMap((f) => {
|
|
351
|
+
const reason = reasonFor(f);
|
|
352
|
+
return reason ? [{ ...f, reason }] : [];
|
|
353
|
+
}),
|
|
354
|
+
measured,
|
|
355
|
+
unmeasured
|
|
356
|
+
};
|
|
357
|
+
}
|
|
125
358
|
|
|
126
|
-
export { PRESET_TOKEN_NAMES, parsePreset, presetCss, presetManifest, validatePreset };
|
|
359
|
+
export { BRAND_FILLS, LABELLED_FILLS, LABEL_CONTRAST_RULE, PRESET_TOKEN_NAMES, STATUS_FILLS, checkLabelledFills, fitFillToInk, inkFor, inkPolarity, labelContrast, parsePreset, presetCss, presetLabelContrast, presetManifest, validatePreset };
|
package/dist/styles.css
CHANGED
|
@@ -109,6 +109,39 @@
|
|
|
109
109
|
--color-tone-edition-foreground: var(--tone-edition-foreground);
|
|
110
110
|
--color-tone-warning-foreground: var(--tone-warning-foreground);
|
|
111
111
|
--color-tone-info-foreground: var(--tone-info-foreground);
|
|
112
|
+
/* Status text: a status colour drawn on a surface. Tailwind resolves each
|
|
113
|
+
property's own namespace before --color-*, so `text-destructive`,
|
|
114
|
+
`border-destructive`, `ring-destructive` (and outline, decoration, fill,
|
|
115
|
+
stroke) read --destructive-text while `bg-destructive` keeps the fill.
|
|
116
|
+
Same class names; the value fits the job. */
|
|
117
|
+
--text-color-destructive: var(--destructive-text);
|
|
118
|
+
--border-color-destructive: var(--destructive-text);
|
|
119
|
+
--ring-color-destructive: var(--destructive-text);
|
|
120
|
+
--outline-color-destructive: var(--destructive-text);
|
|
121
|
+
--text-decoration-color-destructive: var(--destructive-text);
|
|
122
|
+
--fill-destructive: var(--destructive-text);
|
|
123
|
+
--stroke-destructive: var(--destructive-text);
|
|
124
|
+
--text-color-success: var(--success-text);
|
|
125
|
+
--border-color-success: var(--success-text);
|
|
126
|
+
--ring-color-success: var(--success-text);
|
|
127
|
+
--outline-color-success: var(--success-text);
|
|
128
|
+
--text-decoration-color-success: var(--success-text);
|
|
129
|
+
--fill-success: var(--success-text);
|
|
130
|
+
--stroke-success: var(--success-text);
|
|
131
|
+
--text-color-tone-warning: var(--tone-warning-text);
|
|
132
|
+
--border-color-tone-warning: var(--tone-warning-text);
|
|
133
|
+
--ring-color-tone-warning: var(--tone-warning-text);
|
|
134
|
+
--outline-color-tone-warning: var(--tone-warning-text);
|
|
135
|
+
--text-decoration-color-tone-warning: var(--tone-warning-text);
|
|
136
|
+
--fill-tone-warning: var(--tone-warning-text);
|
|
137
|
+
--stroke-tone-warning: var(--tone-warning-text);
|
|
138
|
+
--text-color-tone-info: var(--tone-info-text);
|
|
139
|
+
--border-color-tone-info: var(--tone-info-text);
|
|
140
|
+
--ring-color-tone-info: var(--tone-info-text);
|
|
141
|
+
--outline-color-tone-info: var(--tone-info-text);
|
|
142
|
+
--text-decoration-color-tone-info: var(--tone-info-text);
|
|
143
|
+
--fill-tone-info: var(--tone-info-text);
|
|
144
|
+
--stroke-tone-info: var(--tone-info-text);
|
|
112
145
|
--color-highlight-foreground: var(--highlight-foreground);
|
|
113
146
|
|
|
114
147
|
/* Overlay — fixed dark scrim/media surface + its light ink (bg-overlay,
|
|
@@ -345,6 +378,30 @@
|
|
|
345
378
|
a dark track (well under 3:1). When ON the knob takes `--primary-foreground`,
|
|
346
379
|
the ink every theme already tunes against its primary fill. */
|
|
347
380
|
--switch-thumb: var(--theme-switch-thumb, oklch(99% 0.002 90));
|
|
381
|
+
|
|
382
|
+
/* The contact shadow under a knob: the Switch thumb, the Slider thumb, the
|
|
383
|
+
ColorField handle. NOT the elevation ladder. `shadow-sm` is a surface's
|
|
384
|
+
lift, and a theme or a Depth setting may make it a long offset (Poppy's
|
|
385
|
+
are hard 3–12px drops); on a 20px knob inside its track that drop escaped
|
|
386
|
+
the track. A knob only sits on its rail, so it keeps one small shadow. */
|
|
387
|
+
--knob-shadow: var(--theme-knob-shadow, 0 1px 2px 0 rgb(0 0 0 / 0.05));
|
|
388
|
+
|
|
389
|
+
/* Art direction for generated imagery (GradientArt; see ART_TOKENS in
|
|
390
|
+
themes/_contract.ts). The default paints the four decorative tones,
|
|
391
|
+
lifted from their text strength to an art strength, glowing over the brand
|
|
392
|
+
steeped in the first tone; a theme overrides any of it through the
|
|
393
|
+
--theme-art-* hooks. Declared here, on every scope, so a .dark island or a
|
|
394
|
+
preset-scoped preview paints from its own tokens. */
|
|
395
|
+
--art-1: var(--theme-art-1, oklch(from var(--tone-info) clamp(0.5, calc(l + 0.16), 0.9) max(calc(c * 1.25), 0.1) h));
|
|
396
|
+
--art-2: var(--theme-art-2, oklch(from var(--tone-collectible) clamp(0.5, calc(l + 0.16), 0.9) max(calc(c * 1.25), 0.1) h));
|
|
397
|
+
--art-3: var(--theme-art-3, oklch(from var(--tone-edition) clamp(0.5, calc(l + 0.16), 0.9) max(calc(c * 1.25), 0.1) h));
|
|
398
|
+
--art-4: var(--theme-art-4, oklch(from var(--tone-standard) clamp(0.5, calc(l + 0.16), 0.9) max(calc(c * 1.25), 0.1) h));
|
|
399
|
+
--art-5: var(--theme-art-5, transparent);
|
|
400
|
+
--art-6: var(--theme-art-6, transparent);
|
|
401
|
+
--art-ground: var(--theme-art-ground, color-mix(in oklab, var(--primary) 55%, oklch(from var(--tone-info) 0.3 c h)));
|
|
402
|
+
--art-warp: var(--theme-art-warp, 0.8);
|
|
403
|
+
--art-softness: var(--theme-art-softness, 0.88);
|
|
404
|
+
--art-grain: var(--theme-art-grain, 0);
|
|
348
405
|
}
|
|
349
406
|
|
|
350
407
|
/* ==========================================================================
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { T as ThemeManifest } from './_contract-
|
|
3
|
+
import { T as ThemeManifest } from './_contract-C2M1SjBf.js';
|
|
4
4
|
|
|
5
5
|
declare const SABLE_THEME_ATTRIBUTE: "data-sable-theme";
|
|
6
6
|
declare const DEFAULT_THEME_ID: string;
|
|
@@ -110,14 +110,18 @@
|
|
|
110
110
|
--accent: oklch(93% 0 0);
|
|
111
111
|
--accent-foreground: #000000;
|
|
112
112
|
--destructive: oklch(50% 0.18 22);
|
|
113
|
+
--destructive-text: oklch(50% 0.18 22);
|
|
113
114
|
--destructive-foreground: #ffffff;
|
|
114
115
|
--success: oklch(50% 0.10 173);
|
|
116
|
+
--success-text: oklch(50% 0.10 173);
|
|
115
117
|
--success-foreground: #ffffff;
|
|
116
118
|
--tone-standard: oklch(45% 0 0);
|
|
117
119
|
--tone-collectible: oklch(46% 0.11 300);
|
|
118
120
|
--tone-edition: oklch(46% 0.09 173);
|
|
119
121
|
--tone-info: oklch(47% 0.10 250);
|
|
122
|
+
--tone-info-text: oklch(47% 0.10 250);
|
|
120
123
|
--tone-warning: oklch(52% 0.12 52);
|
|
124
|
+
--tone-warning-text: oklch(52% 0.12 52);
|
|
121
125
|
--highlight: oklch(93% 0 0);
|
|
122
126
|
--tone-standard-foreground: #ffffff;
|
|
123
127
|
--tone-collectible-foreground: #ffffff;
|
|
@@ -187,15 +191,20 @@
|
|
|
187
191
|
--secondary-foreground: #ffffff;
|
|
188
192
|
--accent: oklch(28% 0 0); /* above --popover: a hover row or chip inside a menu/dialog must still read */
|
|
189
193
|
--accent-foreground: #ffffff;
|
|
190
|
-
|
|
191
|
-
--destructive
|
|
192
|
-
--
|
|
194
|
+
/* Status colours come in two: the fill carries a label (destructive sits low under white ink), and `-text` is the same colour drawn on a surface — text, borders, rings — kept light enough to read on a dark card. */
|
|
195
|
+
--destructive: oklch(58.5% 0.19 22);
|
|
196
|
+
--destructive-text: oklch(77.5% 0.129 22);
|
|
197
|
+
--destructive-foreground: #ffffff;
|
|
198
|
+
--success: oklch(74% 0.12 173);
|
|
199
|
+
--success-text: oklch(74% 0.12 173);
|
|
193
200
|
--success-foreground: oklch(13% 0 0);
|
|
194
201
|
--tone-standard: oklch(80% 0 0);
|
|
195
202
|
--tone-collectible: oklch(76% 0.12 300);
|
|
196
203
|
--tone-edition: oklch(76% 0.10 173);
|
|
197
204
|
--tone-info: oklch(76% 0.10 250);
|
|
205
|
+
--tone-info-text: oklch(76% 0.10 250);
|
|
198
206
|
--tone-warning: oklch(80% 0.13 52);
|
|
207
|
+
--tone-warning-text: oklch(80% 0.13 52);
|
|
199
208
|
--highlight: oklch(26% 0 0);
|
|
200
209
|
--tone-standard-foreground: oklch(13% 0 0);
|
|
201
210
|
--tone-collectible-foreground: oklch(13% 0 0);
|
|
@@ -214,3 +223,22 @@
|
|
|
214
223
|
--sidebar-border: oklch(26% 0 0);
|
|
215
224
|
--sidebar-ring: #ffffff;
|
|
216
225
|
}
|
|
226
|
+
|
|
227
|
+
/* Art direction for generated imagery (GradientArt; ART_TOKENS in
|
|
228
|
+
themes/_contract.ts). Cinematic monochrome (art-direction.md): a dark studio ground with
|
|
229
|
+
one warm amber glow, a little film grain. Saturation lives in the light, never
|
|
230
|
+
in the chrome.
|
|
231
|
+
Declared on every arm so a .dark or .light island derives from its own
|
|
232
|
+
tokens rather than inheriting this scope's resolved colours. */
|
|
233
|
+
[data-sable-theme="atelier"],
|
|
234
|
+
[data-sable-theme="atelier"] .dark,
|
|
235
|
+
[data-sable-theme="atelier"] .light {
|
|
236
|
+
--theme-art-1: oklch(0.16 0 0);
|
|
237
|
+
--theme-art-2: oklch(0.34 0.01 60);
|
|
238
|
+
--theme-art-3: oklch(from var(--tone-warning) 0.74 0.14 h);
|
|
239
|
+
--theme-art-4: oklch(0.1 0 0);
|
|
240
|
+
--theme-art-ground: oklch(0.12 0 0);
|
|
241
|
+
--theme-art-warp: 0.6;
|
|
242
|
+
--theme-art-softness: 0.8;
|
|
243
|
+
--theme-art-grain: 0.3;
|
|
244
|
+
}
|
|
@@ -166,14 +166,18 @@
|
|
|
166
166
|
--accent: oklch(92% 0.011 74);
|
|
167
167
|
--accent-foreground: oklch(23.1% 0.014 70);
|
|
168
168
|
--destructive: oklch(50% 0.17 25);
|
|
169
|
+
--destructive-text: oklch(50% 0.17 25);
|
|
169
170
|
--destructive-foreground: oklch(100% 0 0);
|
|
170
171
|
--success: oklch(43% 0.10 165);
|
|
172
|
+
--success-text: oklch(43% 0.10 165);
|
|
171
173
|
--success-foreground: oklch(100% 0 0);
|
|
172
174
|
--tone-standard: oklch(40% 0.07 130);
|
|
173
175
|
--tone-collectible: oklch(46% 0.14 285);
|
|
174
176
|
--tone-edition: oklch(45% 0.15 309);
|
|
175
177
|
--tone-info: oklch(48% 0.13 250);
|
|
178
|
+
--tone-info-text: oklch(48% 0.13 250);
|
|
176
179
|
--tone-warning: oklch(50% 0.12 52);
|
|
180
|
+
--tone-warning-text: oklch(50% 0.12 52);
|
|
177
181
|
--highlight: oklch(58% 0.245 32.9);
|
|
178
182
|
--tone-standard-foreground: oklch(100% 0 0);
|
|
179
183
|
--tone-collectible-foreground: oklch(100% 0 0);
|
|
@@ -240,29 +244,35 @@
|
|
|
240
244
|
--ring: oklch(65.2% 0.235 32.9);
|
|
241
245
|
/* The light-mode fill, not a brighter one: on the brighter fill WCAG preferred
|
|
242
246
|
dark ink (5.6:1), but it reads at APCA Lc 42 — muddy — against Lc 67 for
|
|
243
|
-
white. At 58% white clears both (4.8:1, Lc 76).
|
|
247
|
+
white. At 58% white clears both (4.8:1, Lc 76). The sidebar's active item
|
|
248
|
+
and the highlight are the same brand fill and follow. */
|
|
244
249
|
--primary: oklch(58% 0.245 32.9);
|
|
245
250
|
--primary-foreground: oklch(100% 0 0);
|
|
246
251
|
--secondary: oklch(26% 0.012 67.2); /* a step ABOVE the card, so a secondary Button reads on a card and on the page */
|
|
247
252
|
--secondary-foreground: oklch(100% 0 0);
|
|
248
253
|
--accent: oklch(29% 0.014 60);
|
|
249
254
|
--accent-foreground: oklch(100% 0 0);
|
|
250
|
-
|
|
251
|
-
--destructive
|
|
252
|
-
--
|
|
255
|
+
/* Status colours come in two: the fill carries a label (destructive sits low under white ink), and `-text` is the same colour drawn on a surface — text, borders, rings — kept light enough to read on a dark card. */
|
|
256
|
+
--destructive: oklch(58% 0.16 25);
|
|
257
|
+
--destructive-text: oklch(77.5% 0.129 25);
|
|
258
|
+
--destructive-foreground: oklch(100% 0 0);
|
|
259
|
+
--success: oklch(74% 0.12 165);
|
|
260
|
+
--success-text: oklch(74% 0.12 165);
|
|
253
261
|
--success-foreground: oklch(14.1% 0.018 70.5);
|
|
254
262
|
--tone-standard: oklch(70% 0.09 130);
|
|
255
263
|
--tone-collectible: oklch(70% 0.12 285);
|
|
256
264
|
--tone-edition: oklch(68% 0.14 309);
|
|
257
|
-
--tone-info: oklch(
|
|
265
|
+
--tone-info: oklch(75.5% 0.11 250);
|
|
266
|
+
--tone-info-text: oklch(75.5% 0.11 250);
|
|
258
267
|
--tone-warning: oklch(78% 0.13 60);
|
|
259
|
-
--
|
|
268
|
+
--tone-warning-text: oklch(78% 0.13 60);
|
|
269
|
+
--highlight: oklch(58% 0.245 32.9);
|
|
260
270
|
--tone-standard-foreground: oklch(14.1% 0.018 70.5);
|
|
261
271
|
--tone-collectible-foreground: oklch(14.1% 0.018 70.5);
|
|
262
272
|
--tone-edition-foreground: oklch(14.1% 0.018 70.5);
|
|
263
273
|
--tone-info-foreground: oklch(14.1% 0.018 70.5);
|
|
264
274
|
--tone-warning-foreground: oklch(14.1% 0.018 70.5);
|
|
265
|
-
--highlight-foreground: oklch(
|
|
275
|
+
--highlight-foreground: oklch(100% 0 0);
|
|
266
276
|
--overlay: oklch(14.1% 0.018 70.5);
|
|
267
277
|
--overlay-foreground: oklch(100% 0 0);
|
|
268
278
|
--sidebar: oklch(15.9% 0.016 65);
|
|
@@ -274,3 +284,24 @@
|
|
|
274
284
|
--sidebar-border: oklch(25.2% 0.012 67.2);
|
|
275
285
|
--sidebar-ring: oklch(65.2% 0.235 32.9);
|
|
276
286
|
}
|
|
287
|
+
|
|
288
|
+
/* Art direction for generated imagery (GradientArt; ART_TOKENS in
|
|
289
|
+
themes/_contract.ts). Light as the accent (art-direction.md): a hot core blooming out of a
|
|
290
|
+
warm near-black ground and falling off steeply — fire, not a wash. Perfectly
|
|
291
|
+
flat, no grain. Every colour is the brand moved along the flame, so a new
|
|
292
|
+
brand still burns in its own hue.
|
|
293
|
+
Declared on every arm so a .dark or .light island derives from its own
|
|
294
|
+
tokens rather than inheriting this scope's resolved colours. */
|
|
295
|
+
[data-sable-theme="ember"],
|
|
296
|
+
[data-sable-theme="ember"] .dark,
|
|
297
|
+
[data-sable-theme="ember"] .light {
|
|
298
|
+
--theme-art-1: oklch(from var(--primary) 0.17 0.03 calc(h + 20));
|
|
299
|
+
--theme-art-2: var(--primary);
|
|
300
|
+
--theme-art-3: oklch(from var(--primary) 0.68 0.21 calc(h + 15));
|
|
301
|
+
--theme-art-4: oklch(from var(--primary) 0.88 0.15 calc(h + 50));
|
|
302
|
+
--theme-art-5: oklch(from var(--primary) 0.38 0.15 calc(h - 5));
|
|
303
|
+
--theme-art-ground: oklch(from var(--primary) 0.14 0.02 calc(h + 20));
|
|
304
|
+
--theme-art-warp: 0.95;
|
|
305
|
+
--theme-art-softness: 0.72;
|
|
306
|
+
--theme-art-grain: 0;
|
|
307
|
+
}
|
package/dist/themes/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { A as AnyThemeId, D as DEFAULT_THEME_ID, S as SABLE_COLOR_STORAGE_KEY, a as SABLE_THEME_ATTRIBUTE, b as SABLE_THEME_STORAGE_KEY, c as SableColorMode, d as SableThemeContextValue, e as SableThemePreference, f as SableThemeProvider, g as SableThemeProviderProps, T as ThemeId, h as applySableColorMode, i as applySableTheme, j as buildThemeManifestById, k as defaultThemeId, l as featuredThemeManifests, m as getThemeManifest, n as isKnownThemeId, o as isThemeId, t as themeIds, p as themeManifestById, q as themeManifests, u as useSableTheme } from '../theme-provider-
|
|
1
|
+
export { A as AnyThemeId, D as DEFAULT_THEME_ID, S as SABLE_COLOR_STORAGE_KEY, a as SABLE_THEME_ATTRIBUTE, b as SABLE_THEME_STORAGE_KEY, c as SableColorMode, d as SableThemeContextValue, e as SableThemePreference, f as SableThemeProvider, g as SableThemeProviderProps, T as ThemeId, h as applySableColorMode, i as applySableTheme, j as buildThemeManifestById, k as defaultThemeId, l as featuredThemeManifests, m as getThemeManifest, n as isKnownThemeId, o as isThemeId, t as themeIds, p as themeManifestById, q as themeManifests, u as useSableTheme } from '../theme-provider-kQszHYji.js';
|
|
2
2
|
export { atelierTheme } from './atelier/index.js';
|
|
3
3
|
export { default as emberTheme } from './ember/index.js';
|
|
4
4
|
export { default as meridianTheme } from './meridian/index.js';
|
|
@@ -7,4 +7,4 @@ export { default as prismTheme } from './prism/index.js';
|
|
|
7
7
|
export { default as sableTheme } from './sable/index.js';
|
|
8
8
|
export { default as verdantTheme } from './verdant/index.js';
|
|
9
9
|
import 'react';
|
|
10
|
-
import '../_contract-
|
|
10
|
+
import '../_contract-C2M1SjBf.js';
|