@quilltap/theme-storybook 1.0.61 → 1.0.63

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/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  All notable changes to `@quilltap/theme-storybook` will be documented in this file.
4
4
 
5
+ ## [1.0.63] - 2026-08-25
6
+
7
+ ### Added
8
+ - The rest of the `qt-*` utility families the app actually ships, so a theme can be previewed against what it will really meet: the missing opacity steps for `qt-bg-muted` (20–80), `qt-bg-card` (50–90), `qt-bg-primary`, `qt-bg-destructive`, `qt-bg-success`, `qt-bg-warning`, `qt-bg-info` and `qt-bg-secondary`; `qt-border-default`, `qt-border-primary`, `qt-border-destructive`, `qt-border-warning` and `qt-border-success` at their used opacities; `qt-text-secondary/50–80` and the status colours at `/80`; and the surface colours `qt-bg-default`, `qt-bg-input`, `qt-bg-secondary`, `qt-border-accent`, `qt-border-primary-foreground`.
9
+ - 34 state forms of those utilities — `hover:`, `focus:`, `disabled:`, `placeholder:`, `file:` and one named `group-hover/thumb:`. In the app every one of these is hand-written and escaped, because Tailwind generates no variants for a class declared inside `@layer utilities`; they are mirrored here so a theme author previewing hover states sees what the app actually ships rather than a resting state that never moves.
10
+
11
+ ### Notes
12
+ - The typography-sizing classes (`qt-text-small`, `qt-text-xs`, `qt-text-label`, `qt-text-label-xs`, `qt-text-large`, `qt-text-lead`, `qt-text-section`) live in the app's `@layer components` sheet and have never been mirrored here. Still outstanding; the color-carrying `qt-text-*` utilities are complete.
13
+
14
+ ## [1.0.62] - 2026-08-25
15
+
16
+ ### Added
17
+ - `qt-text-on-primary`, `qt-text-on-success`, and `qt-text-on-destructive`: the rest of the on-a-filled-surface foreground family that `qt-text-on-accent` started, each mapping to the theme's matching `*Foreground` token. Plus the hand-written hover partners `hover:qt-text-on-accent/-primary/-success/-destructive`, for controls that sit quiet at rest and take a filled background on hover. Mirrors the app-side definitions.
18
+ - `Surfaces` story: a "Foregrounds on filled surfaces" section previewing all four fills with their foregrounds, and a hover row for the hover partners. It also spells out the naming trap — the classes are `-on-<fill>`, never `-<fill>-foreground`, which is the Tailwind spelling and no `qt-` class at all.
19
+
5
20
  ## [1.0.44] - 2026-06-14
6
21
 
7
22
  ### Added
@@ -1043,6 +1043,11 @@ var note = {
1043
1043
  };
1044
1044
  var Surfaces = () => {
1045
1045
  return /* @__PURE__ */ jsxs9("div", { style: { padding: "1.5rem" }, children: [
1046
+ /* @__PURE__ */ jsx9("style", { children: `
1047
+ .surfaces-demo-fill-primary:hover { background-color: var(--color-primary); }
1048
+ .surfaces-demo-fill-success:hover { background-color: var(--color-success); }
1049
+ .surfaces-demo-fill-destructive:hover { background-color: var(--color-destructive); }
1050
+ ` }),
1046
1051
  /* @__PURE__ */ jsx9("h2", { style: { fontSize: "1.5rem", fontWeight: 700, marginBottom: "0.5rem" }, children: "Surfaces" }),
1047
1052
  /* @__PURE__ */ jsxs9("p", { style: { ...note, maxWidth: "44rem" }, children: [
1048
1053
  "How accent-coloured surfaces stay legible no matter how bold a theme makes its ",
@@ -1079,6 +1084,53 @@ var Surfaces = () => {
1079
1084
  /* @__PURE__ */ jsx9("kbd", { className: "qt-bg-accent qt-text-on-accent", style: { padding: "0.125rem 0.375rem", borderRadius: "0.25rem", fontSize: "0.75rem" }, children: "\u21B5" })
1080
1085
  ] })
1081
1086
  ] }),
1087
+ /* @__PURE__ */ jsxs9("section", { style: { marginBottom: "2rem" }, children: [
1088
+ /* @__PURE__ */ jsxs9("h3", { style: sectionHeading2, children: [
1089
+ "Foregrounds on filled surfaces \u2014 the ",
1090
+ /* @__PURE__ */ jsx9("code", { children: "qt-text-on-*" }),
1091
+ " family"
1092
+ ] }),
1093
+ /* @__PURE__ */ jsxs9("p", { style: note, children: [
1094
+ "The same contract, extended to every filled surface the app paints:",
1095
+ " ",
1096
+ /* @__PURE__ */ jsx9("code", { children: "qt-text-on-accent" }),
1097
+ ", ",
1098
+ /* @__PURE__ */ jsx9("code", { children: "qt-text-on-primary" }),
1099
+ ",",
1100
+ " ",
1101
+ /* @__PURE__ */ jsx9("code", { children: "qt-text-on-success" }),
1102
+ ", and ",
1103
+ /* @__PURE__ */ jsx9("code", { children: "qt-text-on-destructive" }),
1104
+ ", each mapping to the theme's matching ",
1105
+ /* @__PURE__ */ jsx9("code", { children: "*Foreground" }),
1106
+ " token. Note the naming \u2014 ",
1107
+ /* @__PURE__ */ jsxs9("code", { children: [
1108
+ "-on-",
1109
+ /* @__PURE__ */ jsx9("em", { children: "fill" })
1110
+ ] }),
1111
+ ", never ",
1112
+ /* @__PURE__ */ jsxs9("code", { children: [
1113
+ "-",
1114
+ /* @__PURE__ */ jsx9("em", { children: "fill" }),
1115
+ "-foreground"
1116
+ ] }),
1117
+ ", which is the Tailwind spelling and is not a ",
1118
+ /* @__PURE__ */ jsx9("code", { children: "qt-" }),
1119
+ " class at all."
1120
+ ] }),
1121
+ /* @__PURE__ */ jsxs9("div", { style: { display: "flex", flexWrap: "wrap", gap: "0.75rem", alignItems: "center" }, children: [
1122
+ /* @__PURE__ */ jsx9("span", { className: "qt-bg-accent qt-text-on-accent", style: { padding: "0.25rem 0.625rem", borderRadius: "0.375rem", fontSize: "0.8125rem", fontWeight: 600 }, children: "on accent" }),
1123
+ /* @__PURE__ */ jsx9("span", { className: "qt-bg-primary qt-text-on-primary", style: { padding: "0.25rem 0.625rem", borderRadius: "0.375rem", fontSize: "0.8125rem", fontWeight: 600 }, children: "on primary" }),
1124
+ /* @__PURE__ */ jsx9("span", { className: "qt-bg-success qt-text-on-success", style: { padding: "0.25rem 0.625rem", borderRadius: "0.375rem", fontSize: "0.8125rem", fontWeight: 600 }, children: "on success" }),
1125
+ /* @__PURE__ */ jsx9("span", { className: "qt-bg-destructive qt-text-on-destructive", style: { padding: "0.25rem 0.625rem", borderRadius: "0.375rem", fontSize: "0.8125rem", fontWeight: 600 }, children: "on destructive" })
1126
+ ] }),
1127
+ /* @__PURE__ */ jsx9("p", { style: { ...note, marginTop: "1rem" }, children: "Each has a hover partner for controls that stay quiet at rest and take the fill on hover. Tailwind generates no variants for these classes, so the hover forms are written out by hand \u2014 reach for one that was never written and it is simply inert. Hover the buttons below." }),
1128
+ /* @__PURE__ */ jsxs9("div", { style: { display: "flex", flexWrap: "wrap", gap: "0.75rem", alignItems: "center" }, children: [
1129
+ /* @__PURE__ */ jsx9("button", { className: "qt-bg-card qt-text-secondary surfaces-demo-fill-primary hover:qt-text-on-primary", style: { padding: "0.25rem 0.625rem", borderRadius: "0.375rem", fontSize: "0.8125rem", fontWeight: 600, border: "1px solid var(--color-border)", cursor: "pointer" }, children: "hover:qt-text-on-primary" }),
1130
+ /* @__PURE__ */ jsx9("button", { className: "qt-bg-card qt-text-secondary surfaces-demo-fill-success hover:qt-text-on-success", style: { padding: "0.25rem 0.625rem", borderRadius: "0.375rem", fontSize: "0.8125rem", fontWeight: 600, border: "1px solid var(--color-border)", cursor: "pointer" }, children: "hover:qt-text-on-success" }),
1131
+ /* @__PURE__ */ jsx9("button", { className: "qt-bg-card qt-text-secondary surfaces-demo-fill-destructive hover:qt-text-on-destructive", style: { padding: "0.25rem 0.625rem", borderRadius: "0.375rem", fontSize: "0.8125rem", fontWeight: 600, border: "1px solid var(--color-border)", cursor: "pointer" }, children: "hover:qt-text-on-destructive" })
1132
+ ] })
1133
+ ] }),
1082
1134
  /* @__PURE__ */ jsxs9("section", { style: { marginBottom: "2rem" }, children: [
1083
1135
  /* @__PURE__ */ jsxs9("h3", { style: sectionHeading2, children: [
1084
1136
  "Hover-highlight rows \u2014 ",
package/dist/index.css CHANGED
@@ -1948,6 +1948,150 @@ body {
1948
1948
  .qt-text-on-accent {
1949
1949
  color: var(--color-accent-foreground);
1950
1950
  }
1951
+ .qt-text-on-primary {
1952
+ color: var(--color-primary-foreground);
1953
+ }
1954
+ .qt-text-on-success {
1955
+ color: var(--color-success-foreground);
1956
+ }
1957
+ .qt-text-on-destructive {
1958
+ color: var(--color-destructive-foreground);
1959
+ }
1960
+ .qt-bg-card\/50 {
1961
+ background-color: color-mix(in srgb, var(--color-card) 50%, transparent);
1962
+ }
1963
+ .qt-bg-card\/60 {
1964
+ background-color: color-mix(in srgb, var(--color-card) 60%, transparent);
1965
+ }
1966
+ .qt-bg-card\/80 {
1967
+ background-color: color-mix(in srgb, var(--color-card) 80%, transparent);
1968
+ }
1969
+ .qt-bg-card\/90 {
1970
+ background-color: color-mix(in srgb, var(--color-card) 90%, transparent);
1971
+ }
1972
+ .qt-bg-default {
1973
+ background-color: var(--color-background);
1974
+ }
1975
+ .qt-bg-destructive\/15 {
1976
+ background-color: color-mix(in srgb, var(--color-destructive) 15%, transparent);
1977
+ }
1978
+ .qt-bg-destructive\/80 {
1979
+ background-color: color-mix(in srgb, var(--color-destructive) 80%, transparent);
1980
+ }
1981
+ .qt-bg-info\/15 {
1982
+ background-color: color-mix(in srgb, var(--color-info) 15%, transparent);
1983
+ }
1984
+ .qt-bg-input {
1985
+ background-color: var(--color-input);
1986
+ }
1987
+ .qt-bg-muted-foreground\/20 {
1988
+ background-color: color-mix(in srgb, var(--color-muted-foreground) 20%, transparent);
1989
+ }
1990
+ .qt-bg-muted\/20 {
1991
+ background-color: color-mix(in srgb, var(--color-muted) 20%, transparent);
1992
+ }
1993
+ .qt-bg-muted\/30 {
1994
+ background-color: color-mix(in srgb, var(--color-muted) 30%, transparent);
1995
+ }
1996
+ .qt-bg-muted\/40 {
1997
+ background-color: color-mix(in srgb, var(--color-muted) 40%, transparent);
1998
+ }
1999
+ .qt-bg-muted\/50 {
2000
+ background-color: color-mix(in srgb, var(--color-muted) 50%, transparent);
2001
+ }
2002
+ .qt-bg-muted\/60 {
2003
+ background-color: color-mix(in srgb, var(--color-muted) 60%, transparent);
2004
+ }
2005
+ .qt-bg-muted\/70 {
2006
+ background-color: color-mix(in srgb, var(--color-muted) 70%, transparent);
2007
+ }
2008
+ .qt-bg-muted\/80 {
2009
+ background-color: color-mix(in srgb, var(--color-muted) 80%, transparent);
2010
+ }
2011
+ .qt-bg-primary\/15 {
2012
+ background-color: color-mix(in srgb, var(--color-primary) 15%, transparent);
2013
+ }
2014
+ .qt-bg-primary\/30 {
2015
+ background-color: color-mix(in srgb, var(--color-primary) 30%, transparent);
2016
+ }
2017
+ .qt-bg-secondary\/50 {
2018
+ background-color: color-mix(in srgb, var(--color-secondary) 50%, transparent);
2019
+ }
2020
+ .qt-bg-success\/15 {
2021
+ background-color: color-mix(in srgb, var(--color-success) 15%, transparent);
2022
+ }
2023
+ .qt-bg-warning\/15 {
2024
+ background-color: color-mix(in srgb, var(--color-warning) 15%, transparent);
2025
+ }
2026
+ .qt-border-accent {
2027
+ border-color: var(--color-accent);
2028
+ }
2029
+ .qt-border-default\/50 {
2030
+ border-color: color-mix(in srgb, var(--color-border) 50%, transparent);
2031
+ }
2032
+ .qt-border-default\/60 {
2033
+ border-color: color-mix(in srgb, var(--color-border) 60%, transparent);
2034
+ }
2035
+ .qt-border-default\/70 {
2036
+ border-color: color-mix(in srgb, var(--color-border) 70%, transparent);
2037
+ }
2038
+ .qt-border-destructive\/20 {
2039
+ border-color: color-mix(in srgb, var(--color-destructive) 20%, transparent);
2040
+ }
2041
+ .qt-border-destructive\/40 {
2042
+ border-color: color-mix(in srgb, var(--color-destructive) 40%, transparent);
2043
+ }
2044
+ .qt-border-destructive\/50 {
2045
+ border-color: color-mix(in srgb, var(--color-destructive) 50%, transparent);
2046
+ }
2047
+ .qt-border-primary-foreground {
2048
+ border-color: var(--color-primary-foreground);
2049
+ }
2050
+ .qt-border-primary\/20 {
2051
+ border-color: color-mix(in srgb, var(--color-primary) 20%, transparent);
2052
+ }
2053
+ .qt-border-primary\/30 {
2054
+ border-color: color-mix(in srgb, var(--color-primary) 30%, transparent);
2055
+ }
2056
+ .qt-border-primary\/40 {
2057
+ border-color: color-mix(in srgb, var(--color-primary) 40%, transparent);
2058
+ }
2059
+ .qt-border-primary\/50 {
2060
+ border-color: color-mix(in srgb, var(--color-primary) 50%, transparent);
2061
+ }
2062
+ .qt-border-primary\/60 {
2063
+ border-color: color-mix(in srgb, var(--color-primary) 60%, transparent);
2064
+ }
2065
+ .qt-border-success\/50 {
2066
+ border-color: color-mix(in srgb, var(--color-success) 50%, transparent);
2067
+ }
2068
+ .qt-border-warning\/50 {
2069
+ border-color: color-mix(in srgb, var(--color-warning) 50%, transparent);
2070
+ }
2071
+ .qt-text-destructive\/80 {
2072
+ color: color-mix(in srgb, var(--color-destructive) 80%, transparent);
2073
+ }
2074
+ .qt-text-info\/80 {
2075
+ color: color-mix(in srgb, var(--color-info) 80%, transparent);
2076
+ }
2077
+ .qt-text-secondary\/50 {
2078
+ color: color-mix(in srgb, var(--color-muted-foreground) 50%, transparent);
2079
+ }
2080
+ .qt-text-secondary\/60 {
2081
+ color: color-mix(in srgb, var(--color-muted-foreground) 60%, transparent);
2082
+ }
2083
+ .qt-text-secondary\/70 {
2084
+ color: color-mix(in srgb, var(--color-muted-foreground) 70%, transparent);
2085
+ }
2086
+ .qt-text-secondary\/80 {
2087
+ color: color-mix(in srgb, var(--color-muted-foreground) 80%, transparent);
2088
+ }
2089
+ .qt-text-success\/80 {
2090
+ color: color-mix(in srgb, var(--color-success) 80%, transparent);
2091
+ }
2092
+ .qt-text-warning\/80 {
2093
+ color: color-mix(in srgb, var(--color-warning) 80%, transparent);
2094
+ }
1951
2095
  .qt-hover-accent {
1952
2096
  transition: background-color 0.15s ease, color 0.15s ease;
1953
2097
  }
@@ -2078,6 +2222,123 @@ body {
2078
2222
  .hover\:qt-bg-primary\/10:hover {
2079
2223
  background-color: color-mix(in srgb, var(--color-primary) 10%, transparent);
2080
2224
  }
2225
+ .hover\:qt-text-on-accent:hover {
2226
+ color: var(--color-accent-foreground);
2227
+ }
2228
+ .hover\:qt-text-on-primary:hover {
2229
+ color: var(--color-primary-foreground);
2230
+ }
2231
+ .hover\:qt-text-on-success:hover {
2232
+ color: var(--color-success-foreground);
2233
+ }
2234
+ .hover\:qt-text-on-destructive:hover {
2235
+ color: var(--color-destructive-foreground);
2236
+ }
2237
+ .hover\:qt-bg-surface-hover:hover {
2238
+ background-color: var(--color-muted);
2239
+ }
2240
+ .hover\:qt-bg-muted:hover {
2241
+ background-color: var(--color-muted);
2242
+ }
2243
+ .hover\:qt-bg-muted\/30:hover {
2244
+ background-color: color-mix(in srgb, var(--color-muted) 30%, transparent);
2245
+ }
2246
+ .hover\:qt-bg-muted\/40:hover {
2247
+ background-color: color-mix(in srgb, var(--color-muted) 40%, transparent);
2248
+ }
2249
+ .hover\:qt-bg-muted\/50:hover {
2250
+ background-color: color-mix(in srgb, var(--color-muted) 50%, transparent);
2251
+ }
2252
+ .hover\:qt-bg-muted\/80:hover {
2253
+ background-color: color-mix(in srgb, var(--color-muted) 80%, transparent);
2254
+ }
2255
+ .hover\:qt-bg-primary\/5:hover {
2256
+ background-color: color-mix(in srgb, var(--color-primary) 5%, transparent);
2257
+ }
2258
+ .hover\:qt-bg-primary\/15:hover {
2259
+ background-color: color-mix(in srgb, var(--color-primary) 15%, transparent);
2260
+ }
2261
+ .hover\:qt-bg-primary\/20:hover {
2262
+ background-color: color-mix(in srgb, var(--color-primary) 20%, transparent);
2263
+ }
2264
+ .hover\:qt-bg-primary\/90:hover {
2265
+ background-color: color-mix(in srgb, var(--color-primary) 90%, transparent);
2266
+ }
2267
+ .hover\:qt-bg-destructive:hover {
2268
+ background-color: var(--color-destructive);
2269
+ }
2270
+ .hover\:qt-bg-destructive\/20:hover {
2271
+ background-color: color-mix(in srgb, var(--color-destructive) 20%, transparent);
2272
+ }
2273
+ .hover\:qt-bg-destructive\/90:hover {
2274
+ background-color: color-mix(in srgb, var(--color-destructive) 90%, transparent);
2275
+ }
2276
+ .hover\:qt-bg-success\/20:hover {
2277
+ background-color: color-mix(in srgb, var(--color-success) 20%, transparent);
2278
+ }
2279
+ .hover\:qt-bg-success\/90:hover {
2280
+ background-color: color-mix(in srgb, var(--color-success) 90%, transparent);
2281
+ }
2282
+ .hover\:qt-bg-warning\/20:hover {
2283
+ background-color: color-mix(in srgb, var(--color-warning) 20%, transparent);
2284
+ }
2285
+ .hover\:qt-bg-info\/20:hover {
2286
+ background-color: color-mix(in srgb, var(--color-info) 20%, transparent);
2287
+ }
2288
+ .hover\:qt-text-destructive:hover {
2289
+ color: var(--color-destructive);
2290
+ }
2291
+ .hover\:qt-text-destructive\/80:hover {
2292
+ color: color-mix(in srgb, var(--color-destructive) 80%, transparent);
2293
+ }
2294
+ .hover\:qt-text-warning\/80:hover {
2295
+ color: color-mix(in srgb, var(--color-warning) 80%, transparent);
2296
+ }
2297
+ .hover\:qt-text-info\/80:hover {
2298
+ color: color-mix(in srgb, var(--color-info) 80%, transparent);
2299
+ }
2300
+ .hover\:qt-text-primary:hover {
2301
+ color: var(--qt-text-primary-fg, var(--color-foreground));
2302
+ }
2303
+ .hover\:qt-border-primary:hover {
2304
+ border-color: var(--color-primary);
2305
+ }
2306
+ .hover\:qt-border-primary\/50:hover {
2307
+ border-color: color-mix(in srgb, var(--color-primary) 50%, transparent);
2308
+ }
2309
+ .hover\:qt-border-primary\/60:hover {
2310
+ border-color: color-mix(in srgb, var(--color-primary) 60%, transparent);
2311
+ }
2312
+ .hover\:qt-border-accent:hover {
2313
+ border-color: var(--color-accent);
2314
+ }
2315
+ .focus\:qt-border-primary:focus {
2316
+ border-color: var(--color-primary);
2317
+ }
2318
+ .disabled\:qt-bg-muted:disabled {
2319
+ background-color: var(--color-muted);
2320
+ }
2321
+ .disabled\:qt-text-secondary:disabled {
2322
+ color: var(--color-muted-foreground);
2323
+ }
2324
+ .placeholder\:qt-text-secondary::-moz-placeholder {
2325
+ color: var(--color-muted-foreground);
2326
+ }
2327
+ .placeholder\:qt-text-secondary::placeholder {
2328
+ color: var(--color-muted-foreground);
2329
+ }
2330
+ .file\:qt-bg-primary\/20::file-selector-button {
2331
+ background-color: color-mix(in srgb, var(--color-primary) 20%, transparent);
2332
+ }
2333
+ .hover\:file\:qt-bg-primary\/30:hover::file-selector-button {
2334
+ background-color: color-mix(in srgb, var(--color-primary) 30%, transparent);
2335
+ }
2336
+ .hover\:qt-shadow-md:hover {
2337
+ box-shadow: var(--qt-card-shadow-hover, 0 4px 12px hsl(225 18% 10% / 0.1), 0 2px 4px hsl(225 18% 10% / 0.05));
2338
+ }
2339
+ .group\/thumb:hover .group-hover\/thumb\:qt-bg-overlay-light {
2340
+ background-color: var(--qt-overlay-light-bg, rgb(0 0 0 / 0.2));
2341
+ }
2081
2342
  .qt-bg-overlay {
2082
2343
  background-color: var(--qt-overlay-bg, rgb(0 0 0 / 0.85));
2083
2344
  }
package/dist/index.js CHANGED
@@ -1153,6 +1153,11 @@ var note = {
1153
1153
  };
1154
1154
  var Surfaces = () => {
1155
1155
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { padding: "1.5rem" }, children: [
1156
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("style", { children: `
1157
+ .surfaces-demo-fill-primary:hover { background-color: var(--color-primary); }
1158
+ .surfaces-demo-fill-success:hover { background-color: var(--color-success); }
1159
+ .surfaces-demo-fill-destructive:hover { background-color: var(--color-destructive); }
1160
+ ` }),
1156
1161
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h2", { style: { fontSize: "1.5rem", fontWeight: 700, marginBottom: "0.5rem" }, children: "Surfaces" }),
1157
1162
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("p", { style: { ...note, maxWidth: "44rem" }, children: [
1158
1163
  "How accent-coloured surfaces stay legible no matter how bold a theme makes its ",
@@ -1189,6 +1194,53 @@ var Surfaces = () => {
1189
1194
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("kbd", { className: "qt-bg-accent qt-text-on-accent", style: { padding: "0.125rem 0.375rem", borderRadius: "0.25rem", fontSize: "0.75rem" }, children: "\u21B5" })
1190
1195
  ] })
1191
1196
  ] }),
1197
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { style: { marginBottom: "2rem" }, children: [
1198
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("h3", { style: sectionHeading2, children: [
1199
+ "Foregrounds on filled surfaces \u2014 the ",
1200
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("code", { children: "qt-text-on-*" }),
1201
+ " family"
1202
+ ] }),
1203
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("p", { style: note, children: [
1204
+ "The same contract, extended to every filled surface the app paints:",
1205
+ " ",
1206
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("code", { children: "qt-text-on-accent" }),
1207
+ ", ",
1208
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("code", { children: "qt-text-on-primary" }),
1209
+ ",",
1210
+ " ",
1211
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("code", { children: "qt-text-on-success" }),
1212
+ ", and ",
1213
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("code", { children: "qt-text-on-destructive" }),
1214
+ ", each mapping to the theme's matching ",
1215
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("code", { children: "*Foreground" }),
1216
+ " token. Note the naming \u2014 ",
1217
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("code", { children: [
1218
+ "-on-",
1219
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("em", { children: "fill" })
1220
+ ] }),
1221
+ ", never ",
1222
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("code", { children: [
1223
+ "-",
1224
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("em", { children: "fill" }),
1225
+ "-foreground"
1226
+ ] }),
1227
+ ", which is the Tailwind spelling and is not a ",
1228
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("code", { children: "qt-" }),
1229
+ " class at all."
1230
+ ] }),
1231
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { display: "flex", flexWrap: "wrap", gap: "0.75rem", alignItems: "center" }, children: [
1232
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "qt-bg-accent qt-text-on-accent", style: { padding: "0.25rem 0.625rem", borderRadius: "0.375rem", fontSize: "0.8125rem", fontWeight: 600 }, children: "on accent" }),
1233
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "qt-bg-primary qt-text-on-primary", style: { padding: "0.25rem 0.625rem", borderRadius: "0.375rem", fontSize: "0.8125rem", fontWeight: 600 }, children: "on primary" }),
1234
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "qt-bg-success qt-text-on-success", style: { padding: "0.25rem 0.625rem", borderRadius: "0.375rem", fontSize: "0.8125rem", fontWeight: 600 }, children: "on success" }),
1235
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "qt-bg-destructive qt-text-on-destructive", style: { padding: "0.25rem 0.625rem", borderRadius: "0.375rem", fontSize: "0.8125rem", fontWeight: 600 }, children: "on destructive" })
1236
+ ] }),
1237
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: { ...note, marginTop: "1rem" }, children: "Each has a hover partner for controls that stay quiet at rest and take the fill on hover. Tailwind generates no variants for these classes, so the hover forms are written out by hand \u2014 reach for one that was never written and it is simply inert. Hover the buttons below." }),
1238
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: { display: "flex", flexWrap: "wrap", gap: "0.75rem", alignItems: "center" }, children: [
1239
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { className: "qt-bg-card qt-text-secondary surfaces-demo-fill-primary hover:qt-text-on-primary", style: { padding: "0.25rem 0.625rem", borderRadius: "0.375rem", fontSize: "0.8125rem", fontWeight: 600, border: "1px solid var(--color-border)", cursor: "pointer" }, children: "hover:qt-text-on-primary" }),
1240
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { className: "qt-bg-card qt-text-secondary surfaces-demo-fill-success hover:qt-text-on-success", style: { padding: "0.25rem 0.625rem", borderRadius: "0.375rem", fontSize: "0.8125rem", fontWeight: 600, border: "1px solid var(--color-border)", cursor: "pointer" }, children: "hover:qt-text-on-success" }),
1241
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { className: "qt-bg-card qt-text-secondary surfaces-demo-fill-destructive hover:qt-text-on-destructive", style: { padding: "0.25rem 0.625rem", borderRadius: "0.375rem", fontSize: "0.8125rem", fontWeight: 600, border: "1px solid var(--color-border)", cursor: "pointer" }, children: "hover:qt-text-on-destructive" })
1242
+ ] })
1243
+ ] }),
1192
1244
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { style: { marginBottom: "2rem" }, children: [
1193
1245
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("h3", { style: sectionHeading2, children: [
1194
1246
  "Hover-highlight rows \u2014 ",
package/dist/index.mjs CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  ThemeComparison,
22
22
  Typography,
23
23
  Workspace
24
- } from "./chunk-XSNC4J5T.mjs";
24
+ } from "./chunk-TESCTXU3.mjs";
25
25
  import "./chunk-WUKYLWAZ.mjs";
26
26
 
27
27
  // src/preview.tsx
@@ -1090,6 +1090,11 @@ var note = {
1090
1090
  };
1091
1091
  var Surfaces = () => {
1092
1092
  return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { padding: "1.5rem" }, children: [
1093
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("style", { children: `
1094
+ .surfaces-demo-fill-primary:hover { background-color: var(--color-primary); }
1095
+ .surfaces-demo-fill-success:hover { background-color: var(--color-success); }
1096
+ .surfaces-demo-fill-destructive:hover { background-color: var(--color-destructive); }
1097
+ ` }),
1093
1098
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h2", { style: { fontSize: "1.5rem", fontWeight: 700, marginBottom: "0.5rem" }, children: "Surfaces" }),
1094
1099
  /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("p", { style: { ...note, maxWidth: "44rem" }, children: [
1095
1100
  "How accent-coloured surfaces stay legible no matter how bold a theme makes its ",
@@ -1126,6 +1131,53 @@ var Surfaces = () => {
1126
1131
  /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("kbd", { className: "qt-bg-accent qt-text-on-accent", style: { padding: "0.125rem 0.375rem", borderRadius: "0.25rem", fontSize: "0.75rem" }, children: "\u21B5" })
1127
1132
  ] })
1128
1133
  ] }),
1134
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("section", { style: { marginBottom: "2rem" }, children: [
1135
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("h3", { style: sectionHeading2, children: [
1136
+ "Foregrounds on filled surfaces \u2014 the ",
1137
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("code", { children: "qt-text-on-*" }),
1138
+ " family"
1139
+ ] }),
1140
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("p", { style: note, children: [
1141
+ "The same contract, extended to every filled surface the app paints:",
1142
+ " ",
1143
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("code", { children: "qt-text-on-accent" }),
1144
+ ", ",
1145
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("code", { children: "qt-text-on-primary" }),
1146
+ ",",
1147
+ " ",
1148
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("code", { children: "qt-text-on-success" }),
1149
+ ", and ",
1150
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("code", { children: "qt-text-on-destructive" }),
1151
+ ", each mapping to the theme's matching ",
1152
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("code", { children: "*Foreground" }),
1153
+ " token. Note the naming \u2014 ",
1154
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("code", { children: [
1155
+ "-on-",
1156
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("em", { children: "fill" })
1157
+ ] }),
1158
+ ", never ",
1159
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("code", { children: [
1160
+ "-",
1161
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("em", { children: "fill" }),
1162
+ "-foreground"
1163
+ ] }),
1164
+ ", which is the Tailwind spelling and is not a ",
1165
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("code", { children: "qt-" }),
1166
+ " class at all."
1167
+ ] }),
1168
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", flexWrap: "wrap", gap: "0.75rem", alignItems: "center" }, children: [
1169
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "qt-bg-accent qt-text-on-accent", style: { padding: "0.25rem 0.625rem", borderRadius: "0.375rem", fontSize: "0.8125rem", fontWeight: 600 }, children: "on accent" }),
1170
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "qt-bg-primary qt-text-on-primary", style: { padding: "0.25rem 0.625rem", borderRadius: "0.375rem", fontSize: "0.8125rem", fontWeight: 600 }, children: "on primary" }),
1171
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "qt-bg-success qt-text-on-success", style: { padding: "0.25rem 0.625rem", borderRadius: "0.375rem", fontSize: "0.8125rem", fontWeight: 600 }, children: "on success" }),
1172
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "qt-bg-destructive qt-text-on-destructive", style: { padding: "0.25rem 0.625rem", borderRadius: "0.375rem", fontSize: "0.8125rem", fontWeight: 600 }, children: "on destructive" })
1173
+ ] }),
1174
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: { ...note, marginTop: "1rem" }, children: "Each has a hover partner for controls that stay quiet at rest and take the fill on hover. Tailwind generates no variants for these classes, so the hover forms are written out by hand \u2014 reach for one that was never written and it is simply inert. Hover the buttons below." }),
1175
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: { display: "flex", flexWrap: "wrap", gap: "0.75rem", alignItems: "center" }, children: [
1176
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { className: "qt-bg-card qt-text-secondary surfaces-demo-fill-primary hover:qt-text-on-primary", style: { padding: "0.25rem 0.625rem", borderRadius: "0.375rem", fontSize: "0.8125rem", fontWeight: 600, border: "1px solid var(--color-border)", cursor: "pointer" }, children: "hover:qt-text-on-primary" }),
1177
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { className: "qt-bg-card qt-text-secondary surfaces-demo-fill-success hover:qt-text-on-success", style: { padding: "0.25rem 0.625rem", borderRadius: "0.375rem", fontSize: "0.8125rem", fontWeight: 600, border: "1px solid var(--color-border)", cursor: "pointer" }, children: "hover:qt-text-on-success" }),
1178
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { className: "qt-bg-card qt-text-secondary surfaces-demo-fill-destructive hover:qt-text-on-destructive", style: { padding: "0.25rem 0.625rem", borderRadius: "0.375rem", fontSize: "0.8125rem", fontWeight: 600, border: "1px solid var(--color-border)", cursor: "pointer" }, children: "hover:qt-text-on-destructive" })
1179
+ ] })
1180
+ ] }),
1129
1181
  /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("section", { style: { marginBottom: "2rem" }, children: [
1130
1182
  /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("h3", { style: sectionHeading2, children: [
1131
1183
  "Hover-highlight rows \u2014 ",
@@ -21,7 +21,7 @@ import {
21
21
  ThemeComparison,
22
22
  Typography,
23
23
  Workspace
24
- } from "../chunk-XSNC4J5T.mjs";
24
+ } from "../chunk-TESCTXU3.mjs";
25
25
  import "../chunk-WUKYLWAZ.mjs";
26
26
  export {
27
27
  Avatars,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quilltap/theme-storybook",
3
- "version": "1.0.61",
3
+ "version": "1.0.63",
4
4
  "description": "Storybook preset and stories for developing Quilltap theme plugins",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -65,7 +65,7 @@
65
65
  "license": "MIT",
66
66
  "repository": {
67
67
  "type": "git",
68
- "url": "https://github.com/foundry-9/quilltap-server.git",
68
+ "url": "git+https://github.com/foundry-9/quilltap-server.git",
69
69
  "directory": "packages/theme-storybook"
70
70
  },
71
71
  "homepage": "https://github.com/foundry-9/quilltap-server/tree/main/packages/theme-storybook#readme",
@@ -2354,10 +2354,216 @@
2354
2354
  background-color: var(--color-muted-foreground);
2355
2355
  }
2356
2356
 
2357
+ /* Foregrounds intended for use ON a filled surface — the paired counterparts of
2358
+ the accent/primary/success/destructive fills. Each maps to the token a theme
2359
+ defines for text sitting on that fill, so a badge or a solid button keeps its
2360
+ contrast on every theme instead of inheriting whatever colour the surrounding
2361
+ page happens to be using.
2362
+
2363
+ <span class="qt-bg-accent qt-text-on-accent">…</span>
2364
+ <button class="qt-bg-destructive qt-text-on-destructive">…</button>
2365
+
2366
+ Mind the naming: these are `-on-<fill>`, never `-<fill>-foreground`. */
2357
2367
  .qt-text-on-accent {
2358
2368
  color: var(--color-accent-foreground);
2359
2369
  }
2360
2370
 
2371
+ .qt-text-on-primary {
2372
+ color: var(--color-primary-foreground);
2373
+ }
2374
+
2375
+ .qt-text-on-success {
2376
+ color: var(--color-success-foreground);
2377
+ }
2378
+
2379
+ .qt-text-on-destructive {
2380
+ color: var(--color-destructive-foreground);
2381
+ }
2382
+
2383
+ /* The remaining opacity steps and surface colours of the utility families. A
2384
+ theme that overrides --color-muted or --color-border gets all of these for
2385
+ free; they exist so that the app never has to reach for a raw Tailwind
2386
+ colour on a themed surface. */
2387
+ .qt-bg-card\/50 {
2388
+ background-color: color-mix(in srgb, var(--color-card) 50%, transparent);
2389
+ }
2390
+
2391
+ .qt-bg-card\/60 {
2392
+ background-color: color-mix(in srgb, var(--color-card) 60%, transparent);
2393
+ }
2394
+
2395
+ .qt-bg-card\/80 {
2396
+ background-color: color-mix(in srgb, var(--color-card) 80%, transparent);
2397
+ }
2398
+
2399
+ .qt-bg-card\/90 {
2400
+ background-color: color-mix(in srgb, var(--color-card) 90%, transparent);
2401
+ }
2402
+
2403
+ .qt-bg-default {
2404
+ background-color: var(--color-background);
2405
+ }
2406
+
2407
+ .qt-bg-destructive\/15 {
2408
+ background-color: color-mix(in srgb, var(--color-destructive) 15%, transparent);
2409
+ }
2410
+
2411
+ .qt-bg-destructive\/80 {
2412
+ background-color: color-mix(in srgb, var(--color-destructive) 80%, transparent);
2413
+ }
2414
+
2415
+ .qt-bg-info\/15 {
2416
+ background-color: color-mix(in srgb, var(--color-info) 15%, transparent);
2417
+ }
2418
+
2419
+ .qt-bg-input {
2420
+ background-color: var(--color-input);
2421
+ }
2422
+
2423
+ .qt-bg-muted-foreground\/20 {
2424
+ background-color: color-mix(in srgb, var(--color-muted-foreground) 20%, transparent);
2425
+ }
2426
+
2427
+ .qt-bg-muted\/20 {
2428
+ background-color: color-mix(in srgb, var(--color-muted) 20%, transparent);
2429
+ }
2430
+
2431
+ .qt-bg-muted\/30 {
2432
+ background-color: color-mix(in srgb, var(--color-muted) 30%, transparent);
2433
+ }
2434
+
2435
+ .qt-bg-muted\/40 {
2436
+ background-color: color-mix(in srgb, var(--color-muted) 40%, transparent);
2437
+ }
2438
+
2439
+ .qt-bg-muted\/50 {
2440
+ background-color: color-mix(in srgb, var(--color-muted) 50%, transparent);
2441
+ }
2442
+
2443
+ .qt-bg-muted\/60 {
2444
+ background-color: color-mix(in srgb, var(--color-muted) 60%, transparent);
2445
+ }
2446
+
2447
+ .qt-bg-muted\/70 {
2448
+ background-color: color-mix(in srgb, var(--color-muted) 70%, transparent);
2449
+ }
2450
+
2451
+ .qt-bg-muted\/80 {
2452
+ background-color: color-mix(in srgb, var(--color-muted) 80%, transparent);
2453
+ }
2454
+
2455
+ .qt-bg-primary\/15 {
2456
+ background-color: color-mix(in srgb, var(--color-primary) 15%, transparent);
2457
+ }
2458
+
2459
+ .qt-bg-primary\/30 {
2460
+ background-color: color-mix(in srgb, var(--color-primary) 30%, transparent);
2461
+ }
2462
+
2463
+ .qt-bg-secondary\/50 {
2464
+ background-color: color-mix(in srgb, var(--color-secondary) 50%, transparent);
2465
+ }
2466
+
2467
+ .qt-bg-success\/15 {
2468
+ background-color: color-mix(in srgb, var(--color-success) 15%, transparent);
2469
+ }
2470
+
2471
+ .qt-bg-warning\/15 {
2472
+ background-color: color-mix(in srgb, var(--color-warning) 15%, transparent);
2473
+ }
2474
+
2475
+ .qt-border-accent {
2476
+ border-color: var(--color-accent);
2477
+ }
2478
+
2479
+ .qt-border-default\/50 {
2480
+ border-color: color-mix(in srgb, var(--color-border) 50%, transparent);
2481
+ }
2482
+
2483
+ .qt-border-default\/60 {
2484
+ border-color: color-mix(in srgb, var(--color-border) 60%, transparent);
2485
+ }
2486
+
2487
+ .qt-border-default\/70 {
2488
+ border-color: color-mix(in srgb, var(--color-border) 70%, transparent);
2489
+ }
2490
+
2491
+ .qt-border-destructive\/20 {
2492
+ border-color: color-mix(in srgb, var(--color-destructive) 20%, transparent);
2493
+ }
2494
+
2495
+ .qt-border-destructive\/40 {
2496
+ border-color: color-mix(in srgb, var(--color-destructive) 40%, transparent);
2497
+ }
2498
+
2499
+ .qt-border-destructive\/50 {
2500
+ border-color: color-mix(in srgb, var(--color-destructive) 50%, transparent);
2501
+ }
2502
+
2503
+ .qt-border-primary-foreground {
2504
+ border-color: var(--color-primary-foreground);
2505
+ }
2506
+
2507
+ .qt-border-primary\/20 {
2508
+ border-color: color-mix(in srgb, var(--color-primary) 20%, transparent);
2509
+ }
2510
+
2511
+ .qt-border-primary\/30 {
2512
+ border-color: color-mix(in srgb, var(--color-primary) 30%, transparent);
2513
+ }
2514
+
2515
+ .qt-border-primary\/40 {
2516
+ border-color: color-mix(in srgb, var(--color-primary) 40%, transparent);
2517
+ }
2518
+
2519
+ .qt-border-primary\/50 {
2520
+ border-color: color-mix(in srgb, var(--color-primary) 50%, transparent);
2521
+ }
2522
+
2523
+ .qt-border-primary\/60 {
2524
+ border-color: color-mix(in srgb, var(--color-primary) 60%, transparent);
2525
+ }
2526
+
2527
+ .qt-border-success\/50 {
2528
+ border-color: color-mix(in srgb, var(--color-success) 50%, transparent);
2529
+ }
2530
+
2531
+ .qt-border-warning\/50 {
2532
+ border-color: color-mix(in srgb, var(--color-warning) 50%, transparent);
2533
+ }
2534
+
2535
+ .qt-text-destructive\/80 {
2536
+ color: color-mix(in srgb, var(--color-destructive) 80%, transparent);
2537
+ }
2538
+
2539
+ .qt-text-info\/80 {
2540
+ color: color-mix(in srgb, var(--color-info) 80%, transparent);
2541
+ }
2542
+
2543
+ .qt-text-secondary\/50 {
2544
+ color: color-mix(in srgb, var(--color-muted-foreground) 50%, transparent);
2545
+ }
2546
+
2547
+ .qt-text-secondary\/60 {
2548
+ color: color-mix(in srgb, var(--color-muted-foreground) 60%, transparent);
2549
+ }
2550
+
2551
+ .qt-text-secondary\/70 {
2552
+ color: color-mix(in srgb, var(--color-muted-foreground) 70%, transparent);
2553
+ }
2554
+
2555
+ .qt-text-secondary\/80 {
2556
+ color: color-mix(in srgb, var(--color-muted-foreground) 80%, transparent);
2557
+ }
2558
+
2559
+ .qt-text-success\/80 {
2560
+ color: color-mix(in srgb, var(--color-success) 80%, transparent);
2561
+ }
2562
+
2563
+ .qt-text-warning\/80 {
2564
+ color: color-mix(in srgb, var(--color-warning) 80%, transparent);
2565
+ }
2566
+
2361
2567
  /* Hoverable accent surface — list rows, menu items, and option cards that
2362
2568
  should highlight on hover. On hover it paints the accent background AND
2363
2569
  forces the accent foreground onto the row and every descendant, so the whole
@@ -2549,6 +2755,167 @@
2549
2755
  background-color: color-mix(in srgb, var(--color-primary) 10%, transparent);
2550
2756
  }
2551
2757
 
2758
+ /* Hover foregrounds for filled surfaces — the hover partners of the
2759
+ qt-text-on-* family, for controls that sit quiet at rest and take a filled
2760
+ background on hover (`qt-text-secondary hover:qt-bg-success
2761
+ hover:qt-text-on-success`). */
2762
+ .hover\:qt-text-on-accent:hover {
2763
+ color: var(--color-accent-foreground);
2764
+ }
2765
+
2766
+ .hover\:qt-text-on-primary:hover {
2767
+ color: var(--color-primary-foreground);
2768
+ }
2769
+
2770
+ .hover\:qt-text-on-success:hover {
2771
+ color: var(--color-success-foreground);
2772
+ }
2773
+
2774
+ .hover\:qt-text-on-destructive:hover {
2775
+ color: var(--color-destructive-foreground);
2776
+ }
2777
+
2778
+ /* State forms of the utilities above. Tailwind generates variants only for
2779
+ utilities it knows about, and a class declared in @layer utilities is
2780
+ invisible to it — so in the app every one of these is hand-written, escaped,
2781
+ with its own pseudo-selector, and an unwritten one is inert. Mirrored here so
2782
+ a theme author previewing hover states sees what the app actually ships. */
2783
+ .hover\:qt-bg-surface-hover:hover {
2784
+ background-color: var(--color-muted);
2785
+ }
2786
+
2787
+ .hover\:qt-bg-muted:hover {
2788
+ background-color: var(--color-muted);
2789
+ }
2790
+
2791
+ .hover\:qt-bg-muted\/30:hover {
2792
+ background-color: color-mix(in srgb, var(--color-muted) 30%, transparent);
2793
+ }
2794
+
2795
+ .hover\:qt-bg-muted\/40:hover {
2796
+ background-color: color-mix(in srgb, var(--color-muted) 40%, transparent);
2797
+ }
2798
+
2799
+ .hover\:qt-bg-muted\/50:hover {
2800
+ background-color: color-mix(in srgb, var(--color-muted) 50%, transparent);
2801
+ }
2802
+
2803
+ .hover\:qt-bg-muted\/80:hover {
2804
+ background-color: color-mix(in srgb, var(--color-muted) 80%, transparent);
2805
+ }
2806
+
2807
+ .hover\:qt-bg-primary\/5:hover {
2808
+ background-color: color-mix(in srgb, var(--color-primary) 5%, transparent);
2809
+ }
2810
+
2811
+ .hover\:qt-bg-primary\/15:hover {
2812
+ background-color: color-mix(in srgb, var(--color-primary) 15%, transparent);
2813
+ }
2814
+
2815
+ .hover\:qt-bg-primary\/20:hover {
2816
+ background-color: color-mix(in srgb, var(--color-primary) 20%, transparent);
2817
+ }
2818
+
2819
+ .hover\:qt-bg-primary\/90:hover {
2820
+ background-color: color-mix(in srgb, var(--color-primary) 90%, transparent);
2821
+ }
2822
+
2823
+ .hover\:qt-bg-destructive:hover {
2824
+ background-color: var(--color-destructive);
2825
+ }
2826
+
2827
+ .hover\:qt-bg-destructive\/20:hover {
2828
+ background-color: color-mix(in srgb, var(--color-destructive) 20%, transparent);
2829
+ }
2830
+
2831
+ .hover\:qt-bg-destructive\/90:hover {
2832
+ background-color: color-mix(in srgb, var(--color-destructive) 90%, transparent);
2833
+ }
2834
+
2835
+ .hover\:qt-bg-success\/20:hover {
2836
+ background-color: color-mix(in srgb, var(--color-success) 20%, transparent);
2837
+ }
2838
+
2839
+ .hover\:qt-bg-success\/90:hover {
2840
+ background-color: color-mix(in srgb, var(--color-success) 90%, transparent);
2841
+ }
2842
+
2843
+ .hover\:qt-bg-warning\/20:hover {
2844
+ background-color: color-mix(in srgb, var(--color-warning) 20%, transparent);
2845
+ }
2846
+
2847
+ .hover\:qt-bg-info\/20:hover {
2848
+ background-color: color-mix(in srgb, var(--color-info) 20%, transparent);
2849
+ }
2850
+
2851
+ .hover\:qt-text-destructive:hover {
2852
+ color: var(--color-destructive);
2853
+ }
2854
+
2855
+ .hover\:qt-text-destructive\/80:hover {
2856
+ color: color-mix(in srgb, var(--color-destructive) 80%, transparent);
2857
+ }
2858
+
2859
+ .hover\:qt-text-warning\/80:hover {
2860
+ color: color-mix(in srgb, var(--color-warning) 80%, transparent);
2861
+ }
2862
+
2863
+ .hover\:qt-text-info\/80:hover {
2864
+ color: color-mix(in srgb, var(--color-info) 80%, transparent);
2865
+ }
2866
+
2867
+ .hover\:qt-text-primary:hover {
2868
+ color: var(--qt-text-primary-fg, var(--color-foreground));
2869
+ }
2870
+
2871
+ .hover\:qt-border-primary:hover {
2872
+ border-color: var(--color-primary);
2873
+ }
2874
+
2875
+ .hover\:qt-border-primary\/50:hover {
2876
+ border-color: color-mix(in srgb, var(--color-primary) 50%, transparent);
2877
+ }
2878
+
2879
+ .hover\:qt-border-primary\/60:hover {
2880
+ border-color: color-mix(in srgb, var(--color-primary) 60%, transparent);
2881
+ }
2882
+
2883
+ .hover\:qt-border-accent:hover {
2884
+ border-color: var(--color-accent);
2885
+ }
2886
+
2887
+ .focus\:qt-border-primary:focus {
2888
+ border-color: var(--color-primary);
2889
+ }
2890
+
2891
+ .disabled\:qt-bg-muted:disabled {
2892
+ background-color: var(--color-muted);
2893
+ }
2894
+
2895
+ .disabled\:qt-text-secondary:disabled {
2896
+ color: var(--color-muted-foreground);
2897
+ }
2898
+
2899
+ .placeholder\:qt-text-secondary::placeholder {
2900
+ color: var(--color-muted-foreground);
2901
+ }
2902
+
2903
+ .file\:qt-bg-primary\/20::file-selector-button {
2904
+ background-color: color-mix(in srgb, var(--color-primary) 20%, transparent);
2905
+ }
2906
+
2907
+ .hover\:file\:qt-bg-primary\/30:hover::file-selector-button {
2908
+ background-color: color-mix(in srgb, var(--color-primary) 30%, transparent);
2909
+ }
2910
+
2911
+ .hover\:qt-shadow-md:hover {
2912
+ box-shadow: var(--qt-card-shadow-hover, 0 4px 12px hsl(225 18% 10% / 0.1), 0 2px 4px hsl(225 18% 10% / 0.05));
2913
+ }
2914
+
2915
+ .group\/thumb:hover .group-hover\/thumb\:qt-bg-overlay-light {
2916
+ background-color: var(--qt-overlay-light-bg, rgb(0 0 0 / 0.2));
2917
+ }
2918
+
2552
2919
  /* ==========================================================================
2553
2920
  OVERLAY UTILITIES
2554
2921
  For image modals, lightboxes, and overlay UI elements.
@@ -27,6 +27,14 @@ const note: React.CSSProperties = {
27
27
  export const Surfaces: React.FC = () => {
28
28
  return (
29
29
  <div style={{ padding: '1.5rem' }}>
30
+ {/* Story-local: the app pairs the hover foregrounds with Tailwind's
31
+ hover:bg-* fills, which don't exist here. Demo fills only — these are
32
+ not qt-* utilities and must not be copied into an app or a theme. */}
33
+ <style>{`
34
+ .surfaces-demo-fill-primary:hover { background-color: var(--color-primary); }
35
+ .surfaces-demo-fill-success:hover { background-color: var(--color-success); }
36
+ .surfaces-demo-fill-destructive:hover { background-color: var(--color-destructive); }
37
+ `}</style>
30
38
  <h2 style={{ fontSize: '1.5rem', fontWeight: 700, marginBottom: '0.5rem' }}>Surfaces</h2>
31
39
  <p style={{ ...note, maxWidth: '44rem' }}>
32
40
  How accent-coloured surfaces stay legible no matter how bold a theme makes
@@ -57,6 +65,50 @@ export const Surfaces: React.FC = () => {
57
65
  </div>
58
66
  </section>
59
67
 
68
+ {/* Foregrounds on filled surfaces */}
69
+ <section style={{ marginBottom: '2rem' }}>
70
+ <h3 style={sectionHeading}>Foregrounds on filled surfaces — the <code>qt-text-on-*</code> family</h3>
71
+ <p style={note}>
72
+ The same contract, extended to every filled surface the app paints:{' '}
73
+ <code>qt-text-on-accent</code>, <code>qt-text-on-primary</code>,{' '}
74
+ <code>qt-text-on-success</code>, and <code>qt-text-on-destructive</code>, each
75
+ mapping to the theme&apos;s matching <code>*Foreground</code> token. Note the
76
+ naming — <code>-on-<em>fill</em></code>, never <code>-<em>fill</em>-foreground</code>,
77
+ which is the Tailwind spelling and is not a <code>qt-</code> class at all.
78
+ </p>
79
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.75rem', alignItems: 'center' }}>
80
+ <span className="qt-bg-accent qt-text-on-accent" style={{ padding: '0.25rem 0.625rem', borderRadius: '0.375rem', fontSize: '0.8125rem', fontWeight: 600 }}>
81
+ on accent
82
+ </span>
83
+ <span className="qt-bg-primary qt-text-on-primary" style={{ padding: '0.25rem 0.625rem', borderRadius: '0.375rem', fontSize: '0.8125rem', fontWeight: 600 }}>
84
+ on primary
85
+ </span>
86
+ <span className="qt-bg-success qt-text-on-success" style={{ padding: '0.25rem 0.625rem', borderRadius: '0.375rem', fontSize: '0.8125rem', fontWeight: 600 }}>
87
+ on success
88
+ </span>
89
+ <span className="qt-bg-destructive qt-text-on-destructive" style={{ padding: '0.25rem 0.625rem', borderRadius: '0.375rem', fontSize: '0.8125rem', fontWeight: 600 }}>
90
+ on destructive
91
+ </span>
92
+ </div>
93
+ <p style={{ ...note, marginTop: '1rem' }}>
94
+ Each has a hover partner for controls that stay quiet at rest and take the fill
95
+ on hover. Tailwind generates no variants for these classes, so the hover forms
96
+ are written out by hand — reach for one that was never written and it is simply
97
+ inert. Hover the buttons below.
98
+ </p>
99
+ <div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.75rem', alignItems: 'center' }}>
100
+ <button className="qt-bg-card qt-text-secondary surfaces-demo-fill-primary hover:qt-text-on-primary" style={{ padding: '0.25rem 0.625rem', borderRadius: '0.375rem', fontSize: '0.8125rem', fontWeight: 600, border: '1px solid var(--color-border)', cursor: 'pointer' }}>
101
+ hover:qt-text-on-primary
102
+ </button>
103
+ <button className="qt-bg-card qt-text-secondary surfaces-demo-fill-success hover:qt-text-on-success" style={{ padding: '0.25rem 0.625rem', borderRadius: '0.375rem', fontSize: '0.8125rem', fontWeight: 600, border: '1px solid var(--color-border)', cursor: 'pointer' }}>
104
+ hover:qt-text-on-success
105
+ </button>
106
+ <button className="qt-bg-card qt-text-secondary surfaces-demo-fill-destructive hover:qt-text-on-destructive" style={{ padding: '0.25rem 0.625rem', borderRadius: '0.375rem', fontSize: '0.8125rem', fontWeight: 600, border: '1px solid var(--color-border)', cursor: 'pointer' }}>
107
+ hover:qt-text-on-destructive
108
+ </button>
109
+ </div>
110
+ </section>
111
+
60
112
  {/* Hover-highlight rows */}
61
113
  <section style={{ marginBottom: '2rem' }}>
62
114
  <h3 style={sectionHeading}>Hover-highlight rows — <code>qt-hover-accent</code></h3>