@civitai/blocks-react 0.46.0 → 0.48.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.
Files changed (51) hide show
  1. package/README.md +65 -3
  2. package/dist/hooks/useCollectionFollow.d.ts +141 -0
  3. package/dist/hooks/useCollectionFollow.d.ts.map +1 -0
  4. package/dist/hooks/useCollectionFollow.js +202 -0
  5. package/dist/hooks/useCollectionFollow.js.map +1 -0
  6. package/dist/index.d.ts +4 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +5 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/internal/iframeTransport.d.ts.map +1 -1
  11. package/dist/internal/iframeTransport.js +5 -2
  12. package/dist/internal/iframeTransport.js.map +1 -1
  13. package/dist/internal/liveHost.d.ts.map +1 -1
  14. package/dist/internal/liveHost.js +33 -0
  15. package/dist/internal/liveHost.js.map +1 -1
  16. package/dist/internal/mockHost.d.ts +17 -2
  17. package/dist/internal/mockHost.d.ts.map +1 -1
  18. package/dist/internal/mockHost.js +49 -0
  19. package/dist/internal/mockHost.js.map +1 -1
  20. package/dist/internal/requestTimeouts.d.ts.map +1 -1
  21. package/dist/internal/requestTimeouts.js +8 -0
  22. package/dist/internal/requestTimeouts.js.map +1 -1
  23. package/dist/internal/transport.d.ts +19 -0
  24. package/dist/internal/transport.d.ts.map +1 -1
  25. package/dist/internal/transport.js +24 -0
  26. package/dist/internal/transport.js.map +1 -1
  27. package/dist/internal/validate.d.ts +20 -0
  28. package/dist/internal/validate.d.ts.map +1 -1
  29. package/dist/internal/validate.js +43 -0
  30. package/dist/internal/validate.js.map +1 -1
  31. package/dist/ui/FollowButton.d.ts +85 -0
  32. package/dist/ui/FollowButton.d.ts.map +1 -0
  33. package/dist/ui/FollowButton.js +162 -0
  34. package/dist/ui/FollowButton.js.map +1 -0
  35. package/dist/ui/ResourceCard.d.ts +272 -0
  36. package/dist/ui/ResourceCard.d.ts.map +1 -0
  37. package/dist/ui/ResourceCard.js +243 -0
  38. package/dist/ui/ResourceCard.js.map +1 -0
  39. package/dist/ui/TipButton.d.ts +115 -0
  40. package/dist/ui/TipButton.d.ts.map +1 -0
  41. package/dist/ui/TipButton.js +308 -0
  42. package/dist/ui/TipButton.js.map +1 -0
  43. package/dist/ui/index.d.ts +6 -0
  44. package/dist/ui/index.d.ts.map +1 -1
  45. package/dist/ui/index.js +3 -0
  46. package/dist/ui/index.js.map +1 -1
  47. package/dist/ui/styles.d.ts +1 -1
  48. package/dist/ui/styles.d.ts.map +1 -1
  49. package/dist/ui/styles.js +202 -7
  50. package/dist/ui/styles.js.map +1 -1
  51. package/package.json +4 -4
@@ -0,0 +1,308 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useId, useRef, useState } from 'react';
3
+ import { useTip } from '../hooks/useTip.js';
4
+ import { Button } from './Button.js';
5
+ import { Group } from './Group.js';
6
+ import { useBlocksStyles } from './styles.js';
7
+ const NOTE_STYLE = {
8
+ fontSize: 12,
9
+ lineHeight: 1.45,
10
+ color: 'var(--civitai-color-text-dimmed)',
11
+ };
12
+ /**
13
+ * Send a Buzz tip, behind an in-block two-step confirm.
14
+ *
15
+ * 🔴 THE CONFIRM IS THIS COMPONENT'S, NOT HOST CHROME — state that honestly
16
+ * rather than implying platform mediation. `useTip` posts to the block-token-
17
+ * gated tip endpoint directly (scope `social:tip:self`), so unlike the
18
+ * collection-follow bridge NOTHING outside the iframe asks the viewer anything.
19
+ * A one-press money spend is therefore reachable by construction, and the
20
+ * two-step handshake here is the only thing between a stray tap and a transfer.
21
+ * Do not add a prop to skip it.
22
+ *
23
+ * 🔴 IT MINTS ONE IDEMPOTENCY KEY PER LOGICAL TIP AND REUSES IT ON RETRY. That
24
+ * is the property a hand-rolled button most reliably misses: `useTip` mints a
25
+ * FRESH key per call when you do not pass one, so retrying after a timeout
26
+ * whose response was merely LOST sends a SECOND transfer. Here the key is
27
+ * minted when the control arms and rotates only after a tip actually settles,
28
+ * so "try again" on a failed press is collapsed server-side to one transfer.
29
+ *
30
+ * The caller decides who sees it: `tip` rejects for an anonymous viewer and a
31
+ * self-tip 403s. Pass `disabledReason` for the self case rather than offering an
32
+ * error.
33
+ *
34
+ * @example
35
+ * // ONE allowance read for the whole view, passed down.
36
+ * const { allowance, refetch } = useTipAllowance();
37
+ * <TipButton
38
+ * noun="curator"
39
+ * toUserId={collection.curator.id}
40
+ * amount={50}
41
+ * entityType="Collection"
42
+ * entityId={collection.id}
43
+ * remaining={allowance?.remaining}
44
+ * tipped={tippedCurators.has(collection.id)}
45
+ * disabledReason={isSelf ? "You can't tip your own collection." : undefined}
46
+ * onTipped={() => { refetch(); markTipped(collection.id); }}
47
+ * data-testid="tip-curator"
48
+ * />
49
+ */
50
+ export function TipButton({ toUserId, amount, entityType, entityId, noun, tipped = false, remaining, disabledReason, disabled = false, onTipped, size = 'md', variant = 'light', 'data-testid': testId, }) {
51
+ useBlocksStyles();
52
+ const { tip } = useTip();
53
+ const [confirming, setConfirming] = useState(false);
54
+ const [busy, setBusy] = useState(false);
55
+ const [done, setDone] = useState(false);
56
+ const [failure, setFailure] = useState(null);
57
+ const confirmRef = useRef(null);
58
+ const cancelRef = useRef(null);
59
+ const doneRef = useRef(null);
60
+ /** Monotonic id of the current attempt — see `ReportButton` for the measured why. */
61
+ const attemptRef = useRef(0);
62
+ /**
63
+ * The idempotency keys this mount has already reported a landed transfer for.
64
+ *
65
+ * 🔴 KEYED, NOT A BOOLEAN — and the boolean it replaces was wrong in the
66
+ * OPPOSITE direction from the bug it fixed. "At most once per mount" collapses
67
+ * two transfers that are genuinely distinct: Cancel does not abort POST #1, so
68
+ * if the parent then moves `amount` or the entity a NEW key is minted, the
69
+ * server does NOT collapse them, and 150 Buzz moves while the app is told 50.
70
+ * `onTipped` is where a caller refetches the allowance and records its
71
+ * `tipped` flag, so the second transfer left no record — re-creating on
72
+ * remount the very harm the previous round cited. The correct scope is once
73
+ * per KEY: same key ⇒ the server saw one transfer ⇒ report once; different
74
+ * key ⇒ two transfers ⇒ report both.
75
+ *
76
+ * 🔴 A REF, NOT STATE. The success path reads it in the same tick it would
77
+ * write it, so two promises resolving in one turn both see stale state and
78
+ * both report; a ref is written synchronously, so the second sees the first.
79
+ */
80
+ const reportedKeysRef = useRef(new Set());
81
+ /**
82
+ * The idempotency key for THIS logical tip. Stable across retries, which is
83
+ * the point; `useId()` seeds it so two TipButtons mounted in one tree never
84
+ * collide, and the target + amount are folded in so that changing either (an
85
+ * amount preset switcher) correctly starts a NEW logical tip rather than
86
+ * colliding with the abandoned one.
87
+ *
88
+ * 🔴 EVERY FIELD OF THE TIP'S IDENTITY IS IN THE KEY, and the earlier version
89
+ * omitted two. `entityType`/`entityId` are sent in the POST body and recorded
90
+ * on the transaction, so they are part of WHICH tip this is — leaving them out
91
+ * meant that changing the entity after a failed attempt reused the same key,
92
+ * and if the first attempt had actually landed (the lost-response case this
93
+ * mechanism exists for) the second, deliberate tip to a DIFFERENT object was
94
+ * collapsed into it and the block was told it succeeded. Under-charge rather
95
+ * than double-charge, but wrong either way.
96
+ *
97
+ * 🔴 NOT ROTATED AFTER A SUCCESS, and what licenses that is narrower than it
98
+ * first appears. `done` is never cleared, so a control that has settled
99
+ * THROUGH ITS OWN SUCCESS PATH cannot send again from this mount. `settled` is
100
+ * `done || tipped`, and the `tipped` half is a PROP — a parent can withdraw it
101
+ * — so terminality is a property of `done`, not of `settled`.
102
+ *
103
+ * ⚠️ An earlier revision continued "…which is why the success path reports
104
+ * UNCONDITIONALLY". It no longer does — it reports once per KEY — and the
105
+ * sentence is corrected rather than deleted because it was load-bearing. The
106
+ * invariant that actually licenses keeping the key is unchanged: `done` is set
107
+ * on the first landed transfer for a given key, and a repeat under the SAME
108
+ * key is that same transfer. Change either and rotate.
109
+ */
110
+ const keySeed = useId();
111
+ const idempotencyKey = `${keySeed}:${toUserId}:${amount}:${entityType ?? '-'}:${entityId ?? '-'}`;
112
+ const settled = done || tipped;
113
+ // Both guards are about a NUMBER reaching a money path, and both were once
114
+ // missing: `amount={0}` rendered "Tip 0" and posted it, and an unusable
115
+ // `remaining` silently REMOVED the ceiling rather than blocking.
116
+ //
117
+ // `amount` must be a real, positive number. `Infinity` is not a tippable
118
+ // quantity, so `isFinite` is load-bearing on its own rather than a longer
119
+ // spelling of `> 0` — and it does not coerce, so a non-number is rejected too.
120
+ const amountValid = Number.isFinite(amount) && amount > 0;
121
+ // 🔴 THREE cases for `remaining`, not two, and a previous revision lost one
122
+ // while fixing another. `NaN` is an UNUSABLE reading — every comparison
123
+ // against it is false — so it must BLOCK. `Infinity` is a MEANINGFUL reading,
124
+ // an unlimited allowance, so blocking it refuses a viewer who is allowed
125
+ // everything; sweeping the two together under `!Number.isFinite` got that
126
+ // wrong. And a NON-NUMBER must block for the same reason as `NaN`: 🔴
127
+ // `Number.isNaN` does NOT coerce, so the `!isFinite` → `isNaN` fix silently
128
+ // dropped the type check and let `remaining: 'abc'` through — removing the
129
+ // ceiling, the wrong direction, and precisely what the comment it replaced
130
+ // claimed could not happen.
131
+ const overAllowance = remaining !== undefined &&
132
+ (typeof remaining !== 'number' || Number.isNaN(remaining) || amount > remaining);
133
+ const blocked = disabled || disabledReason !== undefined || !amountValid;
134
+ const ids = testId
135
+ ? {
136
+ trigger: testId,
137
+ confirm: `${testId}-confirm`,
138
+ cancel: `${testId}-cancel`,
139
+ done: `${testId}-done`,
140
+ prompt: `${testId}-prompt`,
141
+ }
142
+ : {
143
+ trigger: 'tip-button',
144
+ confirm: 'tip-confirm',
145
+ cancel: 'tip-cancel',
146
+ done: 'tip-done',
147
+ prompt: 'tip-confirm-prompt',
148
+ };
149
+ // Move focus with the control at both transitions — each step REPLACES the
150
+ // element the viewer just activated, so without this a keyboard user is
151
+ // dropped to <body>. Same three effects, and the same reasoning, as
152
+ // `ReportButton`; keyed on the transition, never on the request lifecycle.
153
+ useEffect(() => {
154
+ if (confirming)
155
+ confirmRef.current?.focus();
156
+ }, [confirming]);
157
+ useEffect(() => {
158
+ if (done)
159
+ doneRef.current?.focus();
160
+ }, [done]);
161
+ useEffect(() => {
162
+ if (busy)
163
+ cancelRef.current?.focus();
164
+ }, [busy]);
165
+ // Server/app truth ENDS the handshake rather than leaving it mounted beneath
166
+ // the settled note, where a late rejection could resurrect a stale failure
167
+ // against a tip the viewer has already seen settle.
168
+ useEffect(() => {
169
+ if (tipped) {
170
+ attemptRef.current += 1;
171
+ setConfirming(false);
172
+ setFailure(null);
173
+ setBusy(false);
174
+ }
175
+ }, [tipped]);
176
+ const confirm = useCallback(async () => {
177
+ // 🔴 RE-CHECK AT THE SPEND, NOT ONLY AT THE ARM. `blocked` gates the trigger,
178
+ // but the prompt stays mounted across a re-render — so a parent moving
179
+ // `amount` to 0 (or NaN) AFTER the viewer armed the control left an enabled
180
+ // Send that posted it. An amount switcher mid-handshake is a flow this
181
+ // component's own JSDoc contemplates, so this is reachable, not theoretical.
182
+ if (!amountValid || overAllowance || blocked) {
183
+ // 🔴 NAME THE ACTUAL REASON. A fixed "That amount cannot be sent." is
184
+ // FALSE when the block is a `disabledReason` (the amount is fine, and a
185
+ // real reason string is in hand) — and this file's own comment about the
186
+ // stale-closure bug calls that message out as "false about the amount"
187
+ // while shipping it on a path it pins. Order matches the gate.
188
+ // 🔴 `||`, NOT `??` — a mutation found this, and it is the same distinction
189
+ // as the error channel's. `blocked` tests `disabledReason !== undefined`,
190
+ // so an EMPTY-STRING reason (a consumer writing
191
+ // `disabledReason={bad ? reason : ''}`) blocks — and `??` would then set an
192
+ // EMPTY failure message, rendering a blank refusal, which is the worst
193
+ // outcome on a money control. `||` falls through to a real sentence.
194
+ setFailure(disabledReason ||
195
+ (!amountValid
196
+ ? 'That amount cannot be sent.'
197
+ : overAllowance
198
+ ? 'Over your remaining daily tip allowance.'
199
+ : 'Tipping is unavailable right now.'));
200
+ return;
201
+ }
202
+ const attempt = (attemptRef.current += 1);
203
+ const current = () => attemptRef.current === attempt;
204
+ setBusy(true);
205
+ setFailure(null);
206
+ try {
207
+ await tip({ toUserId, amount, ...(entityType ? { entityType } : {}), ...(entityId !== undefined ? { entityId } : {}) }, { idempotencyKey });
208
+ // 🔴 A LANDED TRANSFER IS REPORTED UNCONDITIONALLY — no `current()` check
209
+ // on this path, deliberately, and the earlier version's check here was the
210
+ // defect rather than the safety.
211
+ //
212
+ // Buzz has MOVED by the time this line runs. Nothing the viewer or the
213
+ // parent did in the meantime can un-send it: Cancel resets this control's
214
+ // UI but does not abort the POST, and a parent flipping `tipped` mid-flight
215
+ // bumps the attempt for its own reasons. Suppressing the settle in either
216
+ // case left the app never learning the transfer succeeded — so `onTipped`
217
+ // never fired, the allowance was never refetched, and no `tipped` record
218
+ // was written, which re-armed the control over money that was already gone.
219
+ //
220
+ // Showing "Tipped" for a transfer that happened is correct even if the
221
+ // viewer pressed Cancel a moment earlier; showing "Tip 50" again is what
222
+ // invites the second one. Only the FAILURE path below respects supersession
223
+ // — there, nothing moved, so an abandoned attempt has nothing to report.
224
+ //
225
+ // 🔴 BUT AT MOST ONCE PER KEY, and "unconditionally" without this was a
226
+ // REGRESSION the previous revision introduced. Cancel-then-retry sends a SECOND POST
227
+ // carrying the SAME idempotency key, so the server collapses the pair into
228
+ // ONE transfer while both promises resolve — and an unguarded report then
229
+ // called `onTipped(amount)` twice for money that moved once. `onTipped` is
230
+ // handed the amount precisely so a caller can decrement an allowance with
231
+ // it, so double-firing double-counts. The answer is "once per KEY" — not
232
+ // the old "never after supersession", not a bare "always", and not the
233
+ // "once per mount" an intermediate revision used, which collapsed two
234
+ // GENUINELY DISTINCT transfers (see `reportedKeysRef`).
235
+ if (!reportedKeysRef.current.has(idempotencyKey)) {
236
+ reportedKeysRef.current.add(idempotencyKey);
237
+ setDone(true);
238
+ setConfirming(false);
239
+ setFailure(null);
240
+ onTipped?.(amount);
241
+ }
242
+ }
243
+ catch (err) {
244
+ if (!current())
245
+ return;
246
+ // Show the SERVER's message. Unlike a report, a failed tip has real and
247
+ // varied causes the viewer can act on (insufficient balance, over the
248
+ // daily cap, self-tip) and a fixed "could not send" throws all of them
249
+ // away.
250
+ setFailure(err instanceof Error && err.message ? err.message : 'Could not send the tip.');
251
+ }
252
+ finally {
253
+ if (current())
254
+ setBusy(false);
255
+ }
256
+ // 🔴 `amountValid`, `overAllowance` and `blocked` MUST be here. The gate at
257
+ // the top of this callback reads all three, and the previous revision listed
258
+ // none of them — so they were frozen at whatever render last recreated the
259
+ // callback, and the gate decided a SPEND on stale values. It failed in both
260
+ // directions and there is no eslint in this repo to catch it:
261
+ //
262
+ // • STALE-RESTRICTIVE, and permanent: a parent that tops up `remaining`,
263
+ // or clears `disabled` / `disabledReason` (the "view still loading"
264
+ // usage this component's own JSDoc names), left Send refusing a
265
+ // perfectly good tip with a message that is false about the amount —
266
+ // for the life of the mount, since nothing else moved a dep.
267
+ // • STALE-PERMISSIVE: the mirror case the gate's own comment claims to
268
+ // cover — `remaining` dropping, or `disabledReason` appearing, AFTER
269
+ // arming — sailed straight through.
270
+ //
271
+ // 🔴 IT WAS ALSO CONSUMER-DEPENDENT, which is why no test caught it: an
272
+ // INLINE `onTipped={() => {}}` (the shape the @example uses) recreates the
273
+ // callback every render and hides the whole thing, while a consumer doing
274
+ // the idiomatic `useCallback` gets the wedge. Listing the derived booleans
275
+ // rather than the raw props keeps this honest — add an input to the gate and
276
+ // the dep is already named.
277
+ }, [
278
+ amount,
279
+ amountValid,
280
+ blocked,
281
+ entityId,
282
+ entityType,
283
+ idempotencyKey,
284
+ onTipped,
285
+ overAllowance,
286
+ tip,
287
+ toUserId,
288
+ ]);
289
+ if (settled) {
290
+ return (_jsx("span", { ref: doneRef, tabIndex: -1, "data-testid": ids.done, role: "status", style: { ...NOTE_STYLE, outline: 'none' }, children: `Tipped ${noun}` }));
291
+ }
292
+ if (!confirming) {
293
+ const reason = disabledReason ?? (overAllowance ? 'Over your remaining daily tip allowance.' : undefined);
294
+ return (_jsx(Button, { size: size, variant: variant, disabled: blocked || overAllowance, onClick: () => setConfirming(true), "data-testid": ids.trigger, ...(reason ? { title: reason } : {}), "aria-label": reason ? `Tip the ${noun} — unavailable: ${reason}` : `Tip the ${noun} ${amount} Buzz`, children: `Tip ${amount}` }));
295
+ }
296
+ return (_jsxs(Group, { gap: 6, align: "center", wrap: false, "data-testid": ids.prompt, children: [_jsx("span", { style: NOTE_STYLE, ...(failure ? { role: 'alert' } : {}), children: failure ?? `Send ${amount} Buzz to the ${noun}?` }), _jsx(Button, { ref: confirmRef, size: "sm", loading: busy, onClick: () => void confirm(), "data-testid": ids.confirm, "aria-label": `Confirm sending ${amount} Buzz to the ${noun}`, children: "Send" }), _jsx(Button, { ref: cancelRef, size: "sm", variant: "subtle",
297
+ // 🔴 NOT disabled in flight, for the reason `ReportButton` records: a
298
+ // reply that never arrives would otherwise leave both controls dead with
299
+ // no way back short of a remount. The attempt token closes the race
300
+ // without taking the escape hatch away.
301
+ onClick: () => {
302
+ attemptRef.current += 1;
303
+ setBusy(false);
304
+ setFailure(null);
305
+ setConfirming(false);
306
+ }, "data-testid": ids.cancel, "aria-label": "Cancel the tip", children: "Cancel" })] }));
307
+ }
308
+ //# sourceMappingURL=TipButton.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TipButton.js","sourceRoot":"","sources":["../../src/ui/TipButton.tsx"],"names":[],"mappings":"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"}
@@ -40,6 +40,12 @@ export { Badge } from './Badge.js';
40
40
  export type { BadgeProps, BadgeVariant, BadgeSize } from './Badge.js';
41
41
  export { ReportButton } from './ReportButton.js';
42
42
  export type { ReportButtonProps } from './ReportButton.js';
43
+ export { FollowButton } from './FollowButton.js';
44
+ export type { FollowButtonProps } from './FollowButton.js';
45
+ export { TipButton } from './TipButton.js';
46
+ export type { TipButtonProps } from './TipButton.js';
47
+ export { ResourceCard, resourceDisplayName } from './ResourceCard.js';
48
+ export type { ResourceCardProps, ResourceCardVariant } from './ResourceCard.js';
43
49
  export { Modal } from './Modal.js';
44
50
  export type { ModalProps, ModalSize } from './Modal.js';
45
51
  export { Slider } from './Slider.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACpF,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAK3D,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAC/D,YAAY,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAG9E,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE1E,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAEzD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE3D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEtE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAExD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE7D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,YAAY,EACV,qBAAqB,EACrB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"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"}
package/dist/ui/index.js CHANGED
@@ -34,6 +34,9 @@ export { Alert } from './Alert.js';
34
34
  export { Loader } from './Loader.js';
35
35
  export { Badge } from './Badge.js';
36
36
  export { ReportButton } from './ReportButton.js';
37
+ export { FollowButton } from './FollowButton.js';
38
+ export { TipButton } from './TipButton.js';
39
+ export { ResourceCard, resourceDisplayName } from './ResourceCard.js';
37
40
  export { Modal } from './Modal.js';
38
41
  export { Slider } from './Slider.js';
39
42
  export { NumberInput } from './NumberInput.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,oEAAoE;AACpE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGpF,4EAA4E;AAC5E,6EAA6E;AAC7E,mEAAmE;AACnE,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAG/D,gCAAgC;AAChC,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAErB,uBAAuB;AACvB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAG3C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/ui/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,oEAAoE;AACpE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGpF,4EAA4E;AAC5E,6EAA6E;AAC7E,mEAAmE;AACnE,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAG/D,gCAAgC;AAChC,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,gBAAgB,GACjB,MAAM,aAAa,CAAC;AAErB,uBAAuB;AACvB,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAG3C,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAG3C,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAGtE,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC"}
@@ -5,7 +5,7 @@
5
5
  * CSS. (At RUNTIME, `injectBlocksStyles()` injects these as three separately
6
6
  * marked `<style>` elements instead — see below.)
7
7
  */
8
- export declare const BLOCKS_UI_STYLES = "/* AUTOGENERATED by @civitai/theme (scripts/build-tokens.ts). DO NOT EDIT.\n Regenerate: pnpm --filter @civitai/theme build. Guarded by generation-parity test. */\n@property --civitai-color-text {\n syntax: '<color>';\n inherits: true;\n initial-value: #222;\n}\n@property --civitai-color-text-dimmed {\n syntax: '<color>';\n inherits: true;\n initial-value: #868e96;\n}\n@property --civitai-color-body {\n syntax: '<color>';\n inherits: true;\n initial-value: #fefefe;\n}\n@property --civitai-color-surface {\n syntax: '<color>';\n inherits: true;\n initial-value: #fefefe;\n}\n@property --civitai-color-surface-2 {\n syntax: '<color>';\n inherits: true;\n initial-value: #fefefe;\n}\n@property --civitai-color-border {\n syntax: '<color>';\n inherits: true;\n initial-value: #ced4da;\n}\n@property --civitai-color-primary {\n syntax: '<color>';\n inherits: true;\n initial-value: #228BE6;\n}\n@property --civitai-color-primary-hover {\n syntax: '<color>';\n inherits: true;\n initial-value: #1C7ED6;\n}\n@property --civitai-color-primary-fg {\n syntax: '<color>';\n inherits: true;\n initial-value: #fefefe;\n}\n@property --civitai-color-primary-light {\n syntax: '<color>';\n inherits: true;\n initial-value: rgba(34, 139, 230, 0.1);\n}\n@property --civitai-color-error {\n syntax: '<color>';\n inherits: true;\n initial-value: #fa5252;\n}\n@property --civitai-color-success {\n syntax: '<color>';\n inherits: true;\n initial-value: #299C7A;\n}\n@property --civitai-color-warning {\n syntax: '<color>';\n inherits: true;\n initial-value: #fd7e14;\n}\n@property --civitai-color-info {\n syntax: '<color>';\n inherits: true;\n initial-value: #228BE6;\n}\n@property --civitai-color-gray-0 {\n syntax: '<color>';\n inherits: true;\n initial-value: #f8f9fa;\n}\n@property --civitai-color-gray-1 {\n syntax: '<color>';\n inherits: true;\n initial-value: #f1f3f5;\n}\n@property --civitai-color-gray-2 {\n syntax: '<color>';\n inherits: true;\n initial-value: #e9ecef;\n}\n@property --civitai-color-gray-3 {\n syntax: '<color>';\n inherits: true;\n initial-value: #dee2e6;\n}\n@property --civitai-color-gray-4 {\n syntax: '<color>';\n inherits: true;\n initial-value: #ced4da;\n}\n@property --civitai-color-gray-5 {\n syntax: '<color>';\n inherits: true;\n initial-value: #adb5bd;\n}\n@property --civitai-color-gray-6 {\n syntax: '<color>';\n inherits: true;\n initial-value: #868e96;\n}\n@property --civitai-color-gray-7 {\n syntax: '<color>';\n inherits: true;\n initial-value: #495057;\n}\n@property --civitai-color-gray-8 {\n syntax: '<color>';\n inherits: true;\n initial-value: #343a40;\n}\n@property --civitai-color-gray-9 {\n syntax: '<color>';\n inherits: true;\n initial-value: #212529;\n}\n\n:root {\n --civitai-font: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji;\n --civitai-font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;\n --civitai-radius: 0.25rem;\n --civitai-color-text: #222;\n --civitai-color-text-dimmed: #868e96;\n --civitai-color-body: #fefefe;\n --civitai-color-surface: #fefefe;\n --civitai-color-surface-2: #fefefe;\n --civitai-color-border: #ced4da;\n --civitai-color-primary: #228BE6;\n --civitai-color-primary-hover: #1C7ED6;\n --civitai-color-primary-fg: #fefefe;\n --civitai-color-primary-light: rgba(34, 139, 230, 0.1);\n --civitai-color-error: #fa5252;\n --civitai-color-success: #299C7A;\n --civitai-color-warning: #fd7e14;\n --civitai-color-info: #228BE6;\n --civitai-color-gray-0: #f8f9fa;\n --civitai-color-gray-1: #f1f3f5;\n --civitai-color-gray-2: #e9ecef;\n --civitai-color-gray-3: #dee2e6;\n --civitai-color-gray-4: #ced4da;\n --civitai-color-gray-5: #adb5bd;\n --civitai-color-gray-6: #868e96;\n --civitai-color-gray-7: #495057;\n --civitai-color-gray-8: #343a40;\n --civitai-color-gray-9: #212529;\n --civitai-bp-xs: 480px;\n --civitai-bp-sm: 768px;\n --civitai-bp-md: 1024px;\n --civitai-bp-lg: 1184px;\n --civitai-bp-xl: 1440px;\n}\n\n[data-theme='light'] {\n --civitai-font: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji;\n --civitai-font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;\n --civitai-radius: 0.25rem;\n --civitai-color-text: #222;\n --civitai-color-text-dimmed: #868e96;\n --civitai-color-body: #fefefe;\n --civitai-color-surface: #fefefe;\n --civitai-color-surface-2: #fefefe;\n --civitai-color-border: #ced4da;\n --civitai-color-primary: #228BE6;\n --civitai-color-primary-hover: #1C7ED6;\n --civitai-color-primary-fg: #fefefe;\n --civitai-color-primary-light: rgba(34, 139, 230, 0.1);\n --civitai-color-error: #fa5252;\n --civitai-color-success: #299C7A;\n --civitai-color-warning: #fd7e14;\n --civitai-color-info: #228BE6;\n --civitai-color-gray-0: #f8f9fa;\n --civitai-color-gray-1: #f1f3f5;\n --civitai-color-gray-2: #e9ecef;\n --civitai-color-gray-3: #dee2e6;\n --civitai-color-gray-4: #ced4da;\n --civitai-color-gray-5: #adb5bd;\n --civitai-color-gray-6: #868e96;\n --civitai-color-gray-7: #495057;\n --civitai-color-gray-8: #343a40;\n --civitai-color-gray-9: #212529;\n --civitai-bp-xs: 480px;\n --civitai-bp-sm: 768px;\n --civitai-bp-md: 1024px;\n --civitai-bp-lg: 1184px;\n --civitai-bp-xl: 1440px;\n}\n\n[data-theme='dark'] {\n --civitai-color-text: #C1C2C5;\n --civitai-color-text-dimmed: #8c8fa3;\n --civitai-color-body: #1A1B1E;\n --civitai-color-surface: #1A1B1E;\n --civitai-color-surface-2: #25262B;\n --civitai-color-border: #373A40;\n --civitai-color-primary: #1971C2;\n --civitai-color-primary-hover: #1864AB;\n --civitai-color-primary-fg: #fefefe;\n --civitai-color-primary-light: rgba(34, 139, 230, 0.15);\n --civitai-color-error: #e03131;\n --civitai-color-success: #326D5C;\n --civitai-color-warning: #e8590c;\n --civitai-color-info: #1971C2;\n}\n\n/*\n * @civitai/components \u2014 attribute-driven, framework-agnostic component CSS.\n *\n * Contract: style is selected by `data-civitai-ui=\"<name>\"` + `data-variant` +\n * `data-size` (+ a few component-specific `data-*`). Theme via an ancestor\n * `[data-theme='light'|'dark']`. Tokens come from @civitai/theme's `--civitai-*`\n * custom properties (link `@civitai/theme/styles.css` alongside this file, or\n * call `injectStyles()` which injects both).\n *\n * ALL rules live in `@layer civitai.components` so a consumer's own unlayered\n * CSS always wins the cascade WITHOUT specificity fights. State colors are\n * derived with `color-mix()` from base tokens (no shade enumeration), and\n * structure uses native CSS nesting (no preprocessor).\n *\n * See MARKUP.md for the full per-component markup + ARIA contract.\n */\n@layer civitai.components {\n [data-civitai-ui] {\n box-sizing: border-box;\n font-family: var(--civitai-font);\n\n & *,\n & *::before,\n & *::after {\n box-sizing: border-box;\n }\n }\n\n /* ----- Button ----- */\n [data-civitai-ui='button'] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n border: 1px solid transparent;\n border-radius: var(--civitai-radius);\n font-family: var(--civitai-font);\n font-weight: 600;\n line-height: 1;\n cursor: pointer;\n user-select: none;\n text-decoration: none;\n transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;\n\n /*\n * Documented DEFAULTS (MARKUP.md): variant=filled, size=md. Applied on the\n * BASE rule (unconditionally) so BARE markup \u2014 a `<button data-civitai-ui=\n * \"button\">` with no `data-variant`/`data-size` \u2014 renders the documented\n * default (filled + md), not an unstyled/zero-size button. The explicit\n * `[data-variant]` / `[data-size]` rules below OVERRIDE (all four variants\n * set their own bg/border/hover, so the base filled default only ever wins\n * for a bare button). Every non-filled variant also sets its own\n * `border-color`, so the base primary border never leaks to them.\n */\n background: var(--civitai-color-primary);\n color: var(--civitai-color-primary-fg);\n border-color: var(--civitai-color-primary);\n height: 36px;\n padding: 0 18px;\n font-size: 14px;\n\n &:hover:not(:disabled) {\n background: var(--civitai-color-primary-hover);\n border-color: var(--civitai-color-primary-hover);\n }\n\n &[data-size='sm'] { height: 30px; padding: 0 14px; font-size: 13px; }\n &[data-size='md'] { height: 36px; padding: 0 18px; font-size: 14px; }\n &[data-size='lg'] { height: 44px; padding: 0 22px; font-size: 16px; }\n &[data-full-width='true'] { width: 100%; }\n\n &[data-variant='filled'] {\n background: var(--civitai-color-primary);\n color: var(--civitai-color-primary-fg);\n border-color: var(--civitai-color-primary);\n\n &:hover:not(:disabled) {\n background: var(--civitai-color-primary-hover);\n border-color: var(--civitai-color-primary-hover);\n }\n }\n &[data-variant='light'] {\n background: color-mix(in srgb, var(--civitai-color-primary) 12%, transparent);\n color: var(--civitai-color-primary);\n border-color: transparent;\n\n &:hover:not(:disabled) {\n background: color-mix(in srgb, var(--civitai-color-primary) 22%, transparent);\n }\n }\n &[data-variant='outline'] {\n background: transparent;\n color: var(--civitai-color-primary);\n border-color: var(--civitai-color-primary);\n\n &:hover:not(:disabled) {\n background: color-mix(in srgb, var(--civitai-color-primary) 10%, transparent);\n }\n }\n &[data-variant='subtle'] {\n background: transparent;\n color: var(--civitai-color-primary);\n border-color: transparent;\n\n &:hover:not(:disabled) {\n background: color-mix(in srgb, var(--civitai-color-primary) 10%, transparent);\n }\n }\n &:disabled,\n &[aria-busy='true'] {\n opacity: 0.6;\n cursor: not-allowed;\n }\n & [data-civitai-ui-section] {\n display: inline-flex;\n align-items: center;\n }\n }\n\n /* ----- TextInput / Textarea / NumberInput / Select ----- */\n [data-civitai-ui='text-input'],\n [data-civitai-ui='textarea'],\n [data-civitai-ui='number-input'],\n [data-civitai-ui='select'] {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n [data-civitai-ui-label] {\n font-size: 14px;\n font-weight: 600;\n color: var(--civitai-color-text);\n }\n [data-civitai-ui-required] {\n color: var(--civitai-color-error);\n margin-left: 2px;\n }\n [data-civitai-ui-description] {\n font-size: 12px;\n color: var(--civitai-color-text-dimmed);\n }\n [data-civitai-ui-error] {\n font-size: 12px;\n color: var(--civitai-color-error);\n }\n [data-civitai-ui-control] {\n width: 100%;\n font-family: var(--civitai-font);\n font-size: 14px;\n color: var(--civitai-color-text);\n background: var(--civitai-color-surface);\n border: 1px solid var(--civitai-color-border);\n border-radius: var(--civitai-radius);\n padding: 8px 12px;\n transition: border-color 120ms ease;\n\n &:focus {\n outline: none;\n border-color: var(--civitai-color-primary);\n }\n &[aria-invalid='true'] {\n border-color: var(--civitai-color-error);\n }\n &:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n }\n textarea[data-civitai-ui-control] {\n resize: vertical;\n line-height: 1.5;\n }\n /*\n * Select (issue #181 F6): a native <select> reusing the shared\n * `-control` field chrome (border/background/radius/focus/invalid, above).\n * Keep the native disclosure caret (most robust + accessible); just make the\n * control feel interactive and reserve room on the right so the caret never\n * overlaps a long option label.\n */\n select[data-civitai-ui-control] {\n cursor: pointer;\n padding-right: 28px;\n line-height: 1.4;\n }\n select[data-civitai-ui-control]:disabled {\n cursor: not-allowed;\n }\n\n /* ----- Checkbox / Radio (issue #181 F6) ----- */\n /*\n * Themed native <input type=\"checkbox\">/<input type=\"radio\">: `accent-color`\n * carries the primary tint (native control, so keyboard/indeterminate/tab all\n * work for free), plus custom sizing, a token focus ring, and disabled state.\n * The box + its inline label sit in a `-choice` row; description + error live\n * below, reusing the shared field markers. The inputs deliberately do NOT\n * carry `data-civitai-ui-control` (that is the full-width field-input chrome).\n */\n [data-civitai-ui='checkbox'],\n [data-civitai-ui='radio'] {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n [data-civitai-ui-choice] {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n [data-civitai-ui='checkbox'] [data-civitai-ui-label],\n [data-civitai-ui='radio'] [data-civitai-ui-label] {\n font-weight: 500;\n cursor: pointer;\n user-select: none;\n }\n [data-civitai-ui='checkbox'] input[type='checkbox'],\n [data-civitai-ui='radio'] input[type='radio'] {\n accent-color: var(--civitai-color-primary);\n width: 16px;\n height: 16px;\n margin: 0;\n flex: none;\n cursor: pointer;\n }\n [data-civitai-ui='checkbox'] input[type='checkbox']:focus-visible,\n [data-civitai-ui='radio'] input[type='radio']:focus-visible {\n outline: 2px solid var(--civitai-color-primary);\n outline-offset: 2px;\n }\n [data-civitai-ui='checkbox'] input[type='checkbox']:disabled,\n [data-civitai-ui='radio'] input[type='radio']:disabled {\n cursor: not-allowed;\n opacity: 0.6;\n }\n [data-civitai-ui='checkbox'] input[type='checkbox']:disabled ~ [data-civitai-ui-label],\n [data-civitai-ui='radio'] input[type='radio']:disabled ~ [data-civitai-ui-label] {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n /* ----- RadioGroup (issue #181 F6) \u2014 role=radiogroup layout ----- */\n [data-civitai-ui='radio-group'] {\n display: flex;\n flex-direction: column;\n gap: 8px;\n }\n [data-civitai-ui-radio-options] {\n display: flex;\n flex-direction: column;\n gap: 8px;\n\n &[data-orientation='horizontal'] {\n flex-direction: row;\n flex-wrap: wrap;\n gap: 16px;\n }\n }\n /*\n * RadioGroup group-level error (0.2.0): mirrors the field `-error` treatment.\n * The message reuses the shared `[data-civitai-ui-error]` styling (12px, error\n * token); the invalid state (`data-invalid` on the group, set alongside\n * `aria-invalid`) tints the child radios' `accent-color` to the error token so\n * the invalid cue reads on the group, not just the message text \u2014 the group\n * analogue of a field control's error-colored border.\n */\n [data-civitai-ui='radio-group'] [data-civitai-ui-error] {\n font-size: 12px;\n color: var(--civitai-color-error);\n }\n [data-civitai-ui='radio-group'][data-invalid] input[type='radio'] {\n accent-color: var(--civitai-color-error);\n }\n\n /* ----- Card ----- */\n /*\n * F5 (issue #181): in LIGHT mode --civitai-color-surface == --civitai-color-body\n * (both #fefefe), so a borderless card is invisible against the page. Give\n * every card a subtle DEFAULT hairline (a low-alpha mix of the border token)\n * so a card WITHOUT `data-with-border` is still visible. `data-with-border`\n * stays the stronger, explicit, fully-opaque border. Dark mode already\n * differentiates surface from body, so the default hairline is removed there\n * (see the [data-theme='dark'] rule below) \u2014 no over-adding. The default\n * (no [data-theme]) light palette keeps the hairline, matching light mode.\n */\n [data-civitai-ui='card'] {\n background: var(--civitai-color-surface);\n border-radius: var(--civitai-radius);\n color: var(--civitai-color-text);\n border: 1px solid color-mix(in srgb, var(--civitai-color-border) 55%, transparent);\n\n &[data-with-border='true'] {\n border: 1px solid var(--civitai-color-border);\n }\n &[data-padding='sm'] { padding: 10px; }\n &[data-padding='md'] { padding: 16px; }\n &[data-padding='lg'] { padding: 24px; }\n }\n /*\n * Dark already differentiates surface (#1A1B1E) from body \u2014 drop the default\n * hairline on a borderless card so dark is visually unchanged. An explicit\n * `data-with-border` still renders its full border.\n */\n [data-theme='dark'] [data-civitai-ui='card']:not([data-with-border='true']) {\n border: 0;\n }\n\n /* ----- Stack / Group ----- */\n [data-civitai-ui='stack'] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n\n &[data-gap='sm'] { gap: 8px; }\n &[data-gap='md'] { gap: 16px; }\n &[data-gap='lg'] { gap: 24px; }\n }\n [data-civitai-ui='group'] {\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 8px;\n\n /*\n * RESPONSIVE BASE. A group is a ROW, and a row of three or more controls\n * has no escape at phone widths \u2014 a block's slot is ~360px there, so the\n * row simply ran off the edge.\n *\n * \uD83D\uDD34 There are THREE `group` surfaces, and for TWO of them this is a NEW\n * DEFAULT \u2014 do not read it as the CSS merely catching up:\n * - `@civitai/blocks-react`'s `<Group>` defaults `wrap = true` and writes\n * `flex-wrap` as an INLINE style, so its consumers already wrapped.\n * For that one, and only that one, this is the CSS agreeing with a\n * default that was already shipping.\n * - `@civitai/components-react`'s `<Group>` writes NO inline style and has\n * NO `wrap` prop, so it resolves against this rule \u2014 a React consumer\n * that did NOT wrap before.\n * - bare `data-civitai-ui=\"group\"` markup, the framework-agnostic contract\n * this package exists to serve \u2014 likewise.\n * Nothing could see the disagreement, because each surface was only ever\n * tested against itself. TWO tests now pin it, one per React surface \u2014 there\n * is no single \"the parity test\":\n * - `civitai-blocks-react/test/Group.test.tsx` reads the wrap default off\n * ITS `<Group>` and compares it to this rule, so flipping either alone\n * fails;\n * - `civitai-components-react/test/html-vs-react-parity.browser.test.tsx`\n * (\"styling anchors \u2014 Group\") asserts the COMPUTED value on both the\n * React and the bare-markup render, which is what covers the other two\n * surfaces. A parity check alone cannot: deleting this rule moves both\n * of its arms identically, so only the absolute anchor sees it.\n *\n * \uD83D\uDD34 The three-surface distinction is load-bearing for the RELEASE, not just\n * the prose: \"existing callers notice\" is what `RELEASING.md` reserves\n * `major` for, and it is true for two of the three surfaces. Shipped as a\n * MINOR by maintainer decision \u2014 see the changeset for the reasoning and for\n * the upgrade note that decision makes load-bearing.\n *\n * Opt out with `data-nowrap=\"true\"`. An inline `flex-wrap` also still\n * outranks this layer, so existing markup that already sets it is untouched.\n */\n flex-wrap: wrap;\n\n /*\n * A flex item defaults to `min-width: auto`, which refuses to shrink below\n * its CONTENT width \u2014 so one long label (a model name, a prompt) pushes the\n * row past the slot even WITH wrap. `min-width: 0` lets it shrink.\n *\n * \uD83D\uDD34 SCOPE, measured: this only matters for a child whose computed\n * `overflow` is `visible`. Per CSS Flexbox \u00A74.5 an item with any other\n * `overflow` ALREADY has an automatic minimum size of 0 \u2014 so a child that\n * sets `overflow: hidden`, as any `text-overflow: ellipsis` child must, is\n * unaffected by this line. An earlier version of this comment claimed\n * ellipsis \"can never engage\" without it, which was exactly backwards; the\n * browser guard built on that belief used an ellipsis fixture and therefore\n * passed with this rule DELETED. Both are fixed \u2014 see\n * `civitai-blocks-react/test/responsive-group.browser.test.tsx`.\n *\n * Specificity is (0,1,0), not zero: `&` carries the parent selector's, and\n * `:where(*)` is specificity-identical to `*`, so that wrapper bought\n * nothing and has been dropped. What actually lets a CONSUMER's own rule\n * win is `@layer civitai.components`, which every rule in this file sits\n * inside \u2014 unlayered author CSS beats it regardless of specificity. Note\n * the limit: another rule INSIDE this layer does not get that protection,\n * so a future non-zero `min-width` on a nested component here must be more\n * specific or come later.\n */\n & > * { min-width: 0; }\n\n &[data-nowrap='true'] { flex-wrap: nowrap; }\n\n &[data-gap='sm'] { gap: 6px; }\n &[data-gap='md'] { gap: 16px; }\n &[data-gap='lg'] { gap: 24px; }\n }\n\n /* ----- Alert ----- */\n [data-civitai-ui='alert'] {\n display: flex;\n gap: 10px;\n align-items: flex-start;\n padding: 12px 14px;\n border-radius: var(--civitai-radius);\n border: 1px solid transparent;\n font-size: 14px;\n color: var(--civitai-color-text);\n\n /*\n * Documented DEFAULT (MARKUP.md): color=info (default intent). Applied on\n * the base rule so a BARE alert \u2014 `<div data-civitai-ui=\"alert\">` with no\n * `data-color` \u2014 renders the info intent (tinted bg + border), not just the\n * neutral chrome. The explicit `[data-color]` rules below OVERRIDE (success\n * / warning / error each set both bg + border-color), so the info default\n * only ever wins when `data-color` is omitted.\n */\n background: color-mix(in srgb, var(--civitai-color-info) 12%, transparent);\n border-color: color-mix(in srgb, var(--civitai-color-info) 35%, transparent);\n\n &[data-color='info'] {\n background: color-mix(in srgb, var(--civitai-color-info) 12%, transparent);\n border-color: color-mix(in srgb, var(--civitai-color-info) 35%, transparent);\n }\n &[data-color='success'] {\n background: color-mix(in srgb, var(--civitai-color-success) 12%, transparent);\n border-color: color-mix(in srgb, var(--civitai-color-success) 35%, transparent);\n }\n &[data-color='warning'] {\n background: color-mix(in srgb, var(--civitai-color-warning) 14%, transparent);\n border-color: color-mix(in srgb, var(--civitai-color-warning) 35%, transparent);\n }\n &[data-color='error'] {\n background: color-mix(in srgb, var(--civitai-color-error) 12%, transparent);\n border-color: color-mix(in srgb, var(--civitai-color-error) 35%, transparent);\n }\n & [data-civitai-ui-alert-body] {\n flex: 1;\n min-width: 0;\n }\n & [data-civitai-ui-alert-title] {\n font-weight: 600;\n margin-bottom: 2px;\n }\n & [data-civitai-ui-alert-close] {\n background: transparent;\n border: none;\n cursor: pointer;\n color: inherit;\n font-size: 16px;\n line-height: 1;\n padding: 0;\n opacity: 0.7;\n\n &:hover { opacity: 1; }\n }\n }\n\n /* ----- Loader ----- */\n [data-civitai-ui='loader'] {\n display: inline-block;\n border-radius: 50%;\n border-style: solid;\n border-color: color-mix(in srgb, currentColor 25%, transparent);\n border-top-color: currentColor;\n color: var(--civitai-color-primary);\n animation: civitai-ui-spin 0.7s linear infinite;\n\n /*\n * Documented DEFAULT (MARKUP.md): size=md. Applied on the base rule so a\n * BARE loader \u2014 `<span data-civitai-ui=\"loader\">` with no `data-size` \u2014 has\n * real dimensions (otherwise 0\u00D70 and invisible). sm/lg OVERRIDE below.\n */\n width: 22px;\n height: 22px;\n border-width: 3px;\n\n &[data-size='sm'] { width: 16px; height: 16px; border-width: 2px; }\n &[data-size='md'] { width: 22px; height: 22px; border-width: 3px; }\n &[data-size='lg'] { width: 32px; height: 32px; border-width: 4px; }\n }\n [data-civitai-ui='button'] [data-civitai-ui='loader'] {\n color: currentColor;\n }\n @keyframes civitai-ui-spin {\n to { transform: rotate(360deg); }\n }\n\n /* ----- Badge ----- */\n [data-civitai-ui='badge'] {\n display: inline-flex;\n align-items: center;\n border: 1px solid transparent;\n border-radius: 999px;\n font-weight: 600;\n line-height: 1;\n white-space: nowrap;\n text-transform: uppercase;\n letter-spacing: 0.02em;\n\n /*\n * Documented DEFAULTS (MARKUP.md): variant=filled, size=md. Applied on the\n * base rule so a BARE badge \u2014 `<span data-civitai-ui=\"badge\">` with no\n * `data-variant`/`data-size` (MARKUP's own minimal example) \u2014 renders\n * filled + md (padding + primary fill), not an unstyled, zero-padding pill.\n * The explicit `[data-variant]`/`[data-size]` rules below OVERRIDE. Because\n * the base now carries a primary `border-color`, the `light` variant (which\n * previously relied on the base transparent border) explicitly resets it to\n * transparent so light badges are visually unchanged.\n */\n height: 22px;\n padding: 0 10px;\n font-size: 11px;\n background: var(--civitai-color-primary);\n color: var(--civitai-color-primary-fg);\n border-color: var(--civitai-color-primary);\n\n &[data-size='sm'] { height: 18px; padding: 0 8px; font-size: 10px; }\n &[data-size='md'] { height: 22px; padding: 0 10px; font-size: 11px; }\n &[data-size='lg'] { height: 26px; padding: 0 12px; font-size: 13px; }\n &[data-variant='filled'] {\n background: var(--civitai-color-primary);\n color: var(--civitai-color-primary-fg);\n border-color: var(--civitai-color-primary);\n }\n &[data-variant='light'] {\n background: color-mix(in srgb, var(--civitai-color-primary) 14%, transparent);\n color: var(--civitai-color-primary);\n border-color: transparent;\n }\n &[data-variant='outline'] {\n background: transparent;\n color: var(--civitai-color-primary);\n border-color: var(--civitai-color-primary);\n }\n\n /*\n * Intent color via `data-color`, mirroring Alert's `data-color` contract\n * (info / success / warning / error). Absent `data-color` => the default\n * primary above (non-breaking). Each intent recolors the `filled`, `light`\n * and `outline` variants with the same `color-mix()` token approach Alert\n * uses; `filled` keeps the white `--civitai-color-primary-fg` text. These\n * `[data-color][data-variant]` rules out-specify the plain-variant rules\n * above, so order-independence holds.\n */\n &[data-color='info'] {\n &[data-variant='filled'] {\n background: var(--civitai-color-info);\n border-color: var(--civitai-color-info);\n }\n &[data-variant='light'] {\n background: color-mix(in srgb, var(--civitai-color-info) 14%, transparent);\n color: var(--civitai-color-info);\n }\n &[data-variant='outline'] {\n color: var(--civitai-color-info);\n border-color: var(--civitai-color-info);\n }\n }\n &[data-color='success'] {\n &[data-variant='filled'] {\n background: var(--civitai-color-success);\n border-color: var(--civitai-color-success);\n }\n &[data-variant='light'] {\n background: color-mix(in srgb, var(--civitai-color-success) 14%, transparent);\n color: var(--civitai-color-success);\n }\n &[data-variant='outline'] {\n color: var(--civitai-color-success);\n border-color: var(--civitai-color-success);\n }\n }\n &[data-color='warning'] {\n &[data-variant='filled'] {\n background: var(--civitai-color-warning);\n border-color: var(--civitai-color-warning);\n }\n &[data-variant='light'] {\n background: color-mix(in srgb, var(--civitai-color-warning) 14%, transparent);\n color: var(--civitai-color-warning);\n }\n &[data-variant='outline'] {\n color: var(--civitai-color-warning);\n border-color: var(--civitai-color-warning);\n }\n }\n &[data-color='error'] {\n &[data-variant='filled'] {\n background: var(--civitai-color-error);\n border-color: var(--civitai-color-error);\n }\n &[data-variant='light'] {\n background: color-mix(in srgb, var(--civitai-color-error) 14%, transparent);\n color: var(--civitai-color-error);\n }\n &[data-variant='outline'] {\n color: var(--civitai-color-error);\n border-color: var(--civitai-color-error);\n }\n }\n }\n\n /* ----- Slider ----- */\n /*\n * A themed native <input type=\"range\">: `accent-color` carries the primary\n * tint (native control, so keyboard arrow-key nav + ARIA come for free), plus\n * a full-width track, a token focus ring, and disabled/invalid states. Like\n * checkbox/radio, the range input deliberately does NOT carry\n * `data-civitai-ui-control` (that is the bordered field-input chrome). The\n * label/description/error reuse the shared field markers, laid out in a\n * column; an optional value read-out sits inline with the label in a header.\n */\n [data-civitai-ui='slider'] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n [data-civitai-ui-slider-header] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n }\n [data-civitai-ui-slider-value] {\n font-size: 13px;\n font-weight: 600;\n color: var(--civitai-color-text-dimmed);\n font-variant-numeric: tabular-nums;\n }\n [data-civitai-ui='slider'] input[type='range'] {\n -webkit-appearance: none;\n appearance: none;\n width: 100%;\n height: 6px;\n margin: 6px 0;\n padding: 0;\n border-radius: 999px;\n accent-color: var(--civitai-color-primary);\n background: var(--civitai-color-gray-2);\n cursor: pointer;\n }\n [data-theme='dark'] [data-civitai-ui='slider'] input[type='range'] {\n background: var(--civitai-color-surface-2);\n }\n [data-civitai-ui='slider'] input[type='range']:focus-visible {\n outline: 2px solid var(--civitai-color-primary);\n outline-offset: 4px;\n }\n [data-civitai-ui='slider'] input[type='range']:disabled {\n cursor: not-allowed;\n opacity: 0.6;\n accent-color: var(--civitai-color-gray-5);\n }\n [data-civitai-ui='slider'][data-invalid] input[type='range'] {\n accent-color: var(--civitai-color-error);\n }\n\n /* ----- SegmentedControl / Tabs ----- */\n /*\n * A `role=tablist` of segment buttons (`role=tab`). Presentational chrome\n * only \u2014 the roving-tabindex + arrow-key nav + selection follow-focus live in\n * the React binding (or must be author-provided for hand HTML). The selected\n * segment (`aria-selected='true'`) lifts to the surface color with a soft\n * shadow; unselected segments read dimmed. Paired tab panels use\n * `data-civitai-ui-tabpanel` (role=tabpanel).\n */\n [data-civitai-ui='segmented-control'] {\n display: inline-flex;\n gap: 2px;\n padding: 4px;\n background: var(--civitai-color-gray-1);\n border-radius: var(--civitai-radius);\n max-width: 100%;\n overflow-x: auto;\n }\n [data-theme='dark'] [data-civitai-ui='segmented-control'] {\n background: var(--civitai-color-surface-2);\n }\n [data-civitai-ui-segment] {\n -webkit-appearance: none;\n appearance: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: 0;\n background: transparent;\n color: var(--civitai-color-text-dimmed);\n font-family: var(--civitai-font);\n font-weight: 600;\n line-height: 1;\n white-space: nowrap;\n cursor: pointer;\n border-radius: calc(var(--civitai-radius) - 1px);\n transition: background-color 120ms ease, color 120ms ease;\n\n /* Documented DEFAULT (MARKUP.md): size=md. On the base rule so a bare\n segment renders at md height, not zero-height. sm/lg override below. */\n height: 30px;\n padding: 0 14px;\n font-size: 13px;\n\n &[data-size='sm'] { height: 26px; padding: 0 10px; font-size: 12px; }\n &[data-size='md'] { height: 30px; padding: 0 14px; font-size: 13px; }\n &[data-size='lg'] { height: 38px; padding: 0 18px; font-size: 15px; }\n\n &:hover:not(:disabled):not([aria-selected='true']):not([aria-checked='true']) {\n color: var(--civitai-color-text);\n }\n /* Selected state \u2014 `aria-selected` in tabs mode (role=tab) OR `aria-checked`\n in toggle mode (role=radio). Same visual treatment for both. */\n &[aria-selected='true'],\n &[aria-checked='true'] {\n background: var(--civitai-color-surface);\n color: var(--civitai-color-text);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);\n }\n &:focus-visible {\n outline: 2px solid var(--civitai-color-primary);\n outline-offset: 2px;\n }\n &:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n }\n [data-civitai-ui-tabpanel] {\n color: var(--civitai-color-text);\n\n &:focus-visible {\n outline: 2px solid var(--civitai-color-primary);\n outline-offset: 2px;\n }\n &[hidden] { display: none; }\n }\n\n /* ----- Toast ----- */\n /*\n * `toast-region` is the fixed-position `aria-live` host (bottom-right stack).\n * The behavior \u2014 enqueue, auto-dismiss timers, portal \u2014 lives in the React\n * binding's ToastProvider/useToast; the presentational `toast` is a standalone\n * component so hand HTML and React share one visual contract. Each toast is\n * opaque (it sits over content) with an intent-colored left accent, mirroring\n * Alert's `data-color` set.\n */\n [data-civitai-ui='toast-region'] {\n position: fixed;\n bottom: 16px;\n right: 16px;\n z-index: 9999;\n display: flex;\n flex-direction: column;\n gap: 8px;\n width: min(92vw, 380px);\n pointer-events: none;\n }\n [data-civitai-ui='toast'] {\n pointer-events: auto;\n display: flex;\n gap: 10px;\n align-items: flex-start;\n padding: 12px 14px;\n border-radius: var(--civitai-radius);\n background: var(--civitai-color-surface);\n color: var(--civitai-color-text);\n border: 1px solid var(--civitai-color-border);\n border-left: 4px solid var(--civitai-color-border);\n box-shadow: 0 6px 18px rgba(0, 0, 0, 0.18);\n font-size: 14px;\n\n &[data-color='info'] { border-left-color: var(--civitai-color-info); }\n &[data-color='success'] { border-left-color: var(--civitai-color-success); }\n &[data-color='warning'] { border-left-color: var(--civitai-color-warning); }\n &[data-color='error'] { border-left-color: var(--civitai-color-error); }\n\n & [data-civitai-ui-toast-body] {\n flex: 1;\n min-width: 0;\n }\n & [data-civitai-ui-toast-title] {\n font-weight: 600;\n margin-bottom: 2px;\n }\n & [data-civitai-ui-toast-close] {\n background: transparent;\n border: none;\n cursor: pointer;\n color: inherit;\n font-size: 16px;\n line-height: 1;\n padding: 0;\n opacity: 0.7;\n\n &:hover { opacity: 1; }\n }\n }\n\n /* ----- Tooltip ----- */\n /*\n * A hover/focus tooltip: a positioned `role=tooltip` bubble revealed when the\n * wrapper is hovered or contains focus (pure-CSS reveal), or explicitly via\n * `data-open='true'` (the React binding also wires `aria-describedby` +\n * Escape-to-dismiss). The bubble is a dark chip (gray-9) with light text in\n * both themes.\n */\n [data-civitai-ui='tooltip'] {\n position: relative;\n display: inline-flex;\n }\n [data-civitai-ui-tooltip-bubble] {\n position: absolute;\n bottom: calc(100% + 6px);\n left: 50%;\n transform: translateX(-50%);\n z-index: 200;\n max-width: 260px;\n width: max-content;\n padding: 4px 8px;\n border-radius: var(--civitai-radius);\n background: var(--civitai-color-gray-9);\n color: var(--civitai-color-primary-fg);\n font-size: 12px;\n font-weight: 500;\n line-height: 1.4;\n text-align: center;\n pointer-events: none;\n opacity: 0;\n visibility: hidden;\n transition: opacity 120ms ease;\n }\n /*\n * Reveal on hover/focus or an explicit `data-open='true'` \u2014 BUT\n * `data-dismissed='true'` overrides (gates every reveal selector), so the\n * React binding's Escape-to-dismiss can force-HIDE the bubble even while the\n * pointer still hovers or focus is still within. The dismissed flag is cleared\n * on the next hover/focus so the tooltip can re-open normally.\n */\n [data-civitai-ui='tooltip']:hover [data-civitai-ui-tooltip-bubble]:not([data-dismissed='true']),\n [data-civitai-ui='tooltip']:focus-within [data-civitai-ui-tooltip-bubble]:not([data-dismissed='true']),\n [data-civitai-ui-tooltip-bubble][data-open='true']:not([data-dismissed='true']) {\n opacity: 1;\n visibility: visible;\n }\n\n /* ----- Image ----- */\n /*\n * A media container with a token placeholder background (visible while the\n * image loads), object-fit control, and a broken-image fallback. `data-status`\n * (loading|loaded|error) \u2014 set by the React binding's onLoad/onError, or by\n * the author for hand HTML \u2014 fades the <img> in on load and swaps to the\n * fallback overlay on error. Bare markup (no `data-status`) shows the image.\n */\n [data-civitai-ui='image'] {\n position: relative;\n display: block;\n overflow: hidden;\n background: var(--civitai-color-gray-2);\n border-radius: var(--civitai-radius);\n }\n [data-theme='dark'] [data-civitai-ui='image'] {\n background: var(--civitai-color-surface-2);\n }\n [data-civitai-ui-image-img] {\n display: block;\n width: 100%;\n height: 100%;\n object-fit: cover;\n opacity: 1;\n transition: opacity 200ms ease;\n\n &[data-fit='contain'] { object-fit: contain; }\n &[data-fit='cover'] { object-fit: cover; }\n }\n [data-civitai-ui='image'][data-status='loading'] [data-civitai-ui-image-img],\n [data-civitai-ui='image'][data-status='error'] [data-civitai-ui-image-img] {\n opacity: 0;\n }\n [data-civitai-ui-image-fallback] {\n position: absolute;\n inset: 0;\n display: none;\n align-items: center;\n justify-content: center;\n padding: 8px;\n color: var(--civitai-color-text-dimmed);\n font-size: 13px;\n text-align: center;\n }\n [data-civitai-ui='image'][data-status='error'] [data-civitai-ui-image-fallback] {\n display: flex;\n }\n}\n\n\n/* ----- Select / Slider field wrappers -----\n The shared -control/-label/-required/-description/-error primitives come from\n @civitai/components (layered). Only the select/slider WRAPPERS live here \u2014 the\n design-system package scopes its field-wrapper rule to the presentational\n inputs (text-input/textarea/number-input). */\n[data-civitai-ui='select'],\n[data-civitai-ui='slider'] {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n/* ----- Modal ----- */\n[data-civitai-ui='modal-overlay'] {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.55);\n display: flex;\n align-items: flex-start;\n justify-content: center;\n padding: 32px 16px;\n overflow-y: auto;\n z-index: 1000;\n}\n[data-civitai-ui='modal'] {\n background: var(--civitai-color-surface);\n color: var(--civitai-color-text);\n border: 1px solid var(--civitai-color-border);\n border-radius: var(--civitai-radius);\n box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3);\n width: 100%;\n max-width: 440px;\n outline: none;\n}\n[data-civitai-ui='modal'][data-size='sm'] { max-width: 340px; }\n[data-civitai-ui='modal'][data-size='md'] { max-width: 440px; }\n[data-civitai-ui='modal'][data-size='lg'] { max-width: 620px; }\n[data-civitai-ui='modal'] [data-civitai-ui-modal-header] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 16px 18px;\n border-bottom: 1px solid var(--civitai-color-border);\n}\n[data-civitai-ui='modal'] [data-civitai-ui-modal-title] {\n font-size: 16px;\n font-weight: 700;\n margin: 0;\n}\n[data-civitai-ui='modal'] [data-civitai-ui-modal-close] {\n background: transparent;\n border: none;\n cursor: pointer;\n color: var(--civitai-color-text-dimmed);\n font-size: 20px;\n line-height: 1;\n padding: 0;\n}\n[data-civitai-ui='modal'] [data-civitai-ui-modal-close]:hover {\n color: var(--civitai-color-text);\n}\n[data-civitai-ui='modal'] [data-civitai-ui-modal-body] {\n padding: 18px;\n}\n\n/* ----- Slider ----- */\n[data-civitai-ui='slider'] [data-civitai-ui-label] {\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n gap: 8px;\n}\n[data-civitai-ui-slider-value] {\n font-weight: 500;\n color: var(--civitai-color-text-dimmed);\n font-variant-numeric: tabular-nums;\n}\n[data-civitai-ui-range] {\n width: 100%;\n margin: 0;\n accent-color: var(--civitai-color-primary);\n cursor: pointer;\n}\n[data-civitai-ui-range]:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n[data-civitai-ui-range]:focus-visible {\n outline: 2px solid var(--civitai-color-primary);\n outline-offset: 2px;\n}\n\n/* ----- Collapse ----- */\n[data-civitai-ui='collapse'] [data-civitai-ui-collapse-trigger] {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n padding: 6px 0;\n background: transparent;\n border: none;\n font-family: var(--civitai-font);\n font-size: 14px;\n font-weight: 600;\n color: var(--civitai-color-text);\n text-align: left;\n cursor: pointer;\n}\n[data-civitai-ui='collapse'] [data-civitai-ui-collapse-trigger]:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n[data-civitai-ui='collapse'] [data-civitai-ui-collapse-chevron] {\n display: inline-block;\n width: 1em;\n color: var(--civitai-color-text-dimmed);\n}\n[data-civitai-ui='collapse'] [data-civitai-ui-collapse-region] {\n padding-top: 4px;\n}\n\n/* ----- SegmentedControl ----- */\n[data-civitai-ui='segmented-control'] {\n display: inline-flex;\n flex-direction: row;\n gap: 2px;\n padding: 3px;\n background: var(--civitai-color-surface-2);\n border: 1px solid var(--civitai-color-border);\n border-radius: var(--civitai-radius);\n vertical-align: middle;\n}\n[data-civitai-ui='segmented-control'][data-full-width='true'] {\n display: flex;\n width: 100%;\n}\n[data-civitai-ui='segmented-control'] [data-civitai-ui-segment] {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: none;\n border-radius: calc(var(--civitai-radius) - 3px);\n background: transparent;\n color: var(--civitai-color-text-dimmed);\n font-family: var(--civitai-font);\n font-weight: 600;\n line-height: 1;\n white-space: nowrap;\n cursor: pointer;\n user-select: none;\n transition: background-color 120ms ease, color 120ms ease, box-shadow 120ms ease;\n}\n[data-civitai-ui='segmented-control'][data-full-width='true'] [data-civitai-ui-segment] {\n flex: 1 1 0;\n}\n[data-civitai-ui='segmented-control'][data-size='sm'] [data-civitai-ui-segment] { height: 24px; padding: 0 12px; font-size: 13px; }\n[data-civitai-ui='segmented-control'][data-size='md'] [data-civitai-ui-segment] { height: 30px; padding: 0 16px; font-size: 14px; }\n[data-civitai-ui='segmented-control'][data-size='lg'] [data-civitai-ui-segment] { height: 38px; padding: 0 20px; font-size: 16px; }\n[data-civitai-ui='segmented-control'] [data-civitai-ui-segment]:hover:not(:disabled):not([data-active]) {\n color: var(--civitai-color-text);\n background: color-mix(in srgb, var(--civitai-color-text) 6%, transparent);\n}\n[data-civitai-ui='segmented-control'] [data-civitai-ui-segment][data-active] {\n background: var(--civitai-color-surface);\n color: var(--civitai-color-primary);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);\n}\n[data-civitai-ui='segmented-control'] [data-civitai-ui-segment]:focus-visible {\n outline: 2px solid var(--civitai-color-primary);\n outline-offset: 1px;\n}\n[data-civitai-ui='segmented-control'] [data-civitai-ui-segment]:disabled {\n opacity: 0.55;\n cursor: not-allowed;\n}\n[data-civitai-ui='segmented-control'][data-disabled='true'] {\n opacity: 0.7;\n}\n";
8
+ export declare const BLOCKS_UI_STYLES = "/* AUTOGENERATED by @civitai/theme (scripts/build-tokens.ts). DO NOT EDIT.\n Regenerate: pnpm --filter @civitai/theme build. Guarded by generation-parity test. */\n@property --civitai-color-text {\n syntax: '<color>';\n inherits: true;\n initial-value: #222;\n}\n@property --civitai-color-text-dimmed {\n syntax: '<color>';\n inherits: true;\n initial-value: #868e96;\n}\n@property --civitai-color-body {\n syntax: '<color>';\n inherits: true;\n initial-value: #fefefe;\n}\n@property --civitai-color-surface {\n syntax: '<color>';\n inherits: true;\n initial-value: #fefefe;\n}\n@property --civitai-color-surface-2 {\n syntax: '<color>';\n inherits: true;\n initial-value: #fefefe;\n}\n@property --civitai-color-border {\n syntax: '<color>';\n inherits: true;\n initial-value: #ced4da;\n}\n@property --civitai-color-primary {\n syntax: '<color>';\n inherits: true;\n initial-value: #228BE6;\n}\n@property --civitai-color-primary-hover {\n syntax: '<color>';\n inherits: true;\n initial-value: #1C7ED6;\n}\n@property --civitai-color-primary-fg {\n syntax: '<color>';\n inherits: true;\n initial-value: #fefefe;\n}\n@property --civitai-color-primary-light {\n syntax: '<color>';\n inherits: true;\n initial-value: rgba(34, 139, 230, 0.1);\n}\n@property --civitai-color-error {\n syntax: '<color>';\n inherits: true;\n initial-value: #fa5252;\n}\n@property --civitai-color-success {\n syntax: '<color>';\n inherits: true;\n initial-value: #299C7A;\n}\n@property --civitai-color-warning {\n syntax: '<color>';\n inherits: true;\n initial-value: #fd7e14;\n}\n@property --civitai-color-info {\n syntax: '<color>';\n inherits: true;\n initial-value: #228BE6;\n}\n@property --civitai-color-gray-0 {\n syntax: '<color>';\n inherits: true;\n initial-value: #f8f9fa;\n}\n@property --civitai-color-gray-1 {\n syntax: '<color>';\n inherits: true;\n initial-value: #f1f3f5;\n}\n@property --civitai-color-gray-2 {\n syntax: '<color>';\n inherits: true;\n initial-value: #e9ecef;\n}\n@property --civitai-color-gray-3 {\n syntax: '<color>';\n inherits: true;\n initial-value: #dee2e6;\n}\n@property --civitai-color-gray-4 {\n syntax: '<color>';\n inherits: true;\n initial-value: #ced4da;\n}\n@property --civitai-color-gray-5 {\n syntax: '<color>';\n inherits: true;\n initial-value: #adb5bd;\n}\n@property --civitai-color-gray-6 {\n syntax: '<color>';\n inherits: true;\n initial-value: #868e96;\n}\n@property --civitai-color-gray-7 {\n syntax: '<color>';\n inherits: true;\n initial-value: #495057;\n}\n@property --civitai-color-gray-8 {\n syntax: '<color>';\n inherits: true;\n initial-value: #343a40;\n}\n@property --civitai-color-gray-9 {\n syntax: '<color>';\n inherits: true;\n initial-value: #212529;\n}\n\n:root {\n --civitai-font: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji;\n --civitai-font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;\n --civitai-radius: 0.25rem;\n --civitai-color-text: #222;\n --civitai-color-text-dimmed: #868e96;\n --civitai-color-body: #fefefe;\n --civitai-color-surface: #fefefe;\n --civitai-color-surface-2: #fefefe;\n --civitai-color-border: #ced4da;\n --civitai-color-primary: #228BE6;\n --civitai-color-primary-hover: #1C7ED6;\n --civitai-color-primary-fg: #fefefe;\n --civitai-color-primary-light: rgba(34, 139, 230, 0.1);\n --civitai-color-error: #fa5252;\n --civitai-color-success: #299C7A;\n --civitai-color-warning: #fd7e14;\n --civitai-color-info: #228BE6;\n --civitai-color-gray-0: #f8f9fa;\n --civitai-color-gray-1: #f1f3f5;\n --civitai-color-gray-2: #e9ecef;\n --civitai-color-gray-3: #dee2e6;\n --civitai-color-gray-4: #ced4da;\n --civitai-color-gray-5: #adb5bd;\n --civitai-color-gray-6: #868e96;\n --civitai-color-gray-7: #495057;\n --civitai-color-gray-8: #343a40;\n --civitai-color-gray-9: #212529;\n --civitai-bp-xs: 480px;\n --civitai-bp-sm: 768px;\n --civitai-bp-md: 1024px;\n --civitai-bp-lg: 1184px;\n --civitai-bp-xl: 1440px;\n}\n\n[data-theme='light'] {\n --civitai-font: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji;\n --civitai-font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;\n --civitai-radius: 0.25rem;\n --civitai-color-text: #222;\n --civitai-color-text-dimmed: #868e96;\n --civitai-color-body: #fefefe;\n --civitai-color-surface: #fefefe;\n --civitai-color-surface-2: #fefefe;\n --civitai-color-border: #ced4da;\n --civitai-color-primary: #228BE6;\n --civitai-color-primary-hover: #1C7ED6;\n --civitai-color-primary-fg: #fefefe;\n --civitai-color-primary-light: rgba(34, 139, 230, 0.1);\n --civitai-color-error: #fa5252;\n --civitai-color-success: #299C7A;\n --civitai-color-warning: #fd7e14;\n --civitai-color-info: #228BE6;\n --civitai-color-gray-0: #f8f9fa;\n --civitai-color-gray-1: #f1f3f5;\n --civitai-color-gray-2: #e9ecef;\n --civitai-color-gray-3: #dee2e6;\n --civitai-color-gray-4: #ced4da;\n --civitai-color-gray-5: #adb5bd;\n --civitai-color-gray-6: #868e96;\n --civitai-color-gray-7: #495057;\n --civitai-color-gray-8: #343a40;\n --civitai-color-gray-9: #212529;\n --civitai-bp-xs: 480px;\n --civitai-bp-sm: 768px;\n --civitai-bp-md: 1024px;\n --civitai-bp-lg: 1184px;\n --civitai-bp-xl: 1440px;\n}\n\n[data-theme='dark'] {\n --civitai-color-text: #C1C2C5;\n --civitai-color-text-dimmed: #8c8fa3;\n --civitai-color-body: #1A1B1E;\n --civitai-color-surface: #1A1B1E;\n --civitai-color-surface-2: #25262B;\n --civitai-color-border: #373A40;\n --civitai-color-primary: #1971C2;\n --civitai-color-primary-hover: #1864AB;\n --civitai-color-primary-fg: #fefefe;\n --civitai-color-primary-light: rgba(34, 139, 230, 0.15);\n --civitai-color-error: #e03131;\n --civitai-color-success: #326D5C;\n --civitai-color-warning: #e8590c;\n --civitai-color-info: #1971C2;\n}\n\n/*\n * @civitai/components \u2014 attribute-driven, framework-agnostic component CSS.\n *\n * Contract: style is selected by `data-civitai-ui=\"<name>\"` + `data-variant` +\n * `data-size` (+ a few component-specific `data-*`). Theme via an ancestor\n * `[data-theme='light'|'dark']`. Tokens come from @civitai/theme's `--civitai-*`\n * custom properties (link `@civitai/theme/styles.css` alongside this file, or\n * call `injectStyles()` which injects both).\n *\n * ALL rules live in `@layer civitai.components` so a consumer's own unlayered\n * CSS always wins the cascade WITHOUT specificity fights. State colors are\n * derived with `color-mix()` from base tokens (no shade enumeration), and\n * structure uses native CSS nesting (no preprocessor).\n *\n * See MARKUP.md for the full per-component markup + ARIA contract.\n */\n@layer civitai.components {\n [data-civitai-ui] {\n box-sizing: border-box;\n font-family: var(--civitai-font);\n\n & *,\n & *::before,\n & *::after {\n box-sizing: border-box;\n }\n }\n\n /* ----- Button ----- */\n [data-civitai-ui='button'] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n border: 1px solid transparent;\n border-radius: var(--civitai-radius);\n font-family: var(--civitai-font);\n font-weight: 600;\n line-height: 1;\n cursor: pointer;\n user-select: none;\n text-decoration: none;\n transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;\n\n /*\n * Documented DEFAULTS (MARKUP.md): variant=filled, size=md. Applied on the\n * BASE rule (unconditionally) so BARE markup \u2014 a `<button data-civitai-ui=\n * \"button\">` with no `data-variant`/`data-size` \u2014 renders the documented\n * default (filled + md), not an unstyled/zero-size button. The explicit\n * `[data-variant]` / `[data-size]` rules below OVERRIDE (all four variants\n * set their own bg/border/hover, so the base filled default only ever wins\n * for a bare button). Every non-filled variant also sets its own\n * `border-color`, so the base primary border never leaks to them.\n */\n background: var(--civitai-color-primary);\n color: var(--civitai-color-primary-fg);\n border-color: var(--civitai-color-primary);\n height: 36px;\n padding: 0 18px;\n font-size: 14px;\n\n &:hover:not(:disabled) {\n background: var(--civitai-color-primary-hover);\n border-color: var(--civitai-color-primary-hover);\n }\n\n &[data-size='sm'] { height: 30px; padding: 0 14px; font-size: 13px; }\n &[data-size='md'] { height: 36px; padding: 0 18px; font-size: 14px; }\n &[data-size='lg'] { height: 44px; padding: 0 22px; font-size: 16px; }\n &[data-full-width='true'] { width: 100%; }\n\n &[data-variant='filled'] {\n background: var(--civitai-color-primary);\n color: var(--civitai-color-primary-fg);\n border-color: var(--civitai-color-primary);\n\n &:hover:not(:disabled) {\n background: var(--civitai-color-primary-hover);\n border-color: var(--civitai-color-primary-hover);\n }\n }\n &[data-variant='light'] {\n background: color-mix(in srgb, var(--civitai-color-primary) 12%, transparent);\n color: var(--civitai-color-primary);\n border-color: transparent;\n\n &:hover:not(:disabled) {\n background: color-mix(in srgb, var(--civitai-color-primary) 22%, transparent);\n }\n }\n &[data-variant='outline'] {\n background: transparent;\n color: var(--civitai-color-primary);\n border-color: var(--civitai-color-primary);\n\n &:hover:not(:disabled) {\n background: color-mix(in srgb, var(--civitai-color-primary) 10%, transparent);\n }\n }\n &[data-variant='subtle'] {\n background: transparent;\n color: var(--civitai-color-primary);\n border-color: transparent;\n\n &:hover:not(:disabled) {\n background: color-mix(in srgb, var(--civitai-color-primary) 10%, transparent);\n }\n }\n &:disabled,\n &[aria-busy='true'] {\n opacity: 0.6;\n cursor: not-allowed;\n }\n & [data-civitai-ui-section] {\n display: inline-flex;\n align-items: center;\n }\n }\n\n /* ----- TextInput / Textarea / NumberInput / Select ----- */\n [data-civitai-ui='text-input'],\n [data-civitai-ui='textarea'],\n [data-civitai-ui='number-input'],\n [data-civitai-ui='select'] {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n [data-civitai-ui-label] {\n font-size: 14px;\n font-weight: 600;\n color: var(--civitai-color-text);\n }\n [data-civitai-ui-required] {\n color: var(--civitai-color-error);\n margin-left: 2px;\n }\n [data-civitai-ui-description] {\n font-size: 12px;\n color: var(--civitai-color-text-dimmed);\n }\n [data-civitai-ui-error] {\n font-size: 12px;\n color: var(--civitai-color-error);\n }\n [data-civitai-ui-control] {\n width: 100%;\n font-family: var(--civitai-font);\n font-size: 14px;\n color: var(--civitai-color-text);\n background: var(--civitai-color-surface);\n border: 1px solid var(--civitai-color-border);\n border-radius: var(--civitai-radius);\n padding: 8px 12px;\n transition: border-color 120ms ease;\n\n &:focus {\n outline: none;\n border-color: var(--civitai-color-primary);\n }\n &[aria-invalid='true'] {\n border-color: var(--civitai-color-error);\n }\n &:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n }\n }\n textarea[data-civitai-ui-control] {\n resize: vertical;\n line-height: 1.5;\n }\n /*\n * Select (issue #181 F6): a native <select> reusing the shared\n * `-control` field chrome (border/background/radius/focus/invalid, above).\n * Keep the native disclosure caret (most robust + accessible); just make the\n * control feel interactive and reserve room on the right so the caret never\n * overlaps a long option label.\n */\n select[data-civitai-ui-control] {\n cursor: pointer;\n padding-right: 28px;\n line-height: 1.4;\n }\n select[data-civitai-ui-control]:disabled {\n cursor: not-allowed;\n }\n\n /* ----- Checkbox / Radio (issue #181 F6) ----- */\n /*\n * Themed native <input type=\"checkbox\">/<input type=\"radio\">: `accent-color`\n * carries the primary tint (native control, so keyboard/indeterminate/tab all\n * work for free), plus custom sizing, a token focus ring, and disabled state.\n * The box + its inline label sit in a `-choice` row; description + error live\n * below, reusing the shared field markers. The inputs deliberately do NOT\n * carry `data-civitai-ui-control` (that is the full-width field-input chrome).\n */\n [data-civitai-ui='checkbox'],\n [data-civitai-ui='radio'] {\n display: flex;\n flex-direction: column;\n gap: 4px;\n }\n [data-civitai-ui-choice] {\n display: flex;\n align-items: center;\n gap: 8px;\n }\n [data-civitai-ui='checkbox'] [data-civitai-ui-label],\n [data-civitai-ui='radio'] [data-civitai-ui-label] {\n font-weight: 500;\n cursor: pointer;\n user-select: none;\n }\n [data-civitai-ui='checkbox'] input[type='checkbox'],\n [data-civitai-ui='radio'] input[type='radio'] {\n accent-color: var(--civitai-color-primary);\n width: 16px;\n height: 16px;\n margin: 0;\n flex: none;\n cursor: pointer;\n }\n [data-civitai-ui='checkbox'] input[type='checkbox']:focus-visible,\n [data-civitai-ui='radio'] input[type='radio']:focus-visible {\n outline: 2px solid var(--civitai-color-primary);\n outline-offset: 2px;\n }\n [data-civitai-ui='checkbox'] input[type='checkbox']:disabled,\n [data-civitai-ui='radio'] input[type='radio']:disabled {\n cursor: not-allowed;\n opacity: 0.6;\n }\n [data-civitai-ui='checkbox'] input[type='checkbox']:disabled ~ [data-civitai-ui-label],\n [data-civitai-ui='radio'] input[type='radio']:disabled ~ [data-civitai-ui-label] {\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n /* ----- RadioGroup (issue #181 F6) \u2014 role=radiogroup layout ----- */\n [data-civitai-ui='radio-group'] {\n display: flex;\n flex-direction: column;\n gap: 8px;\n }\n [data-civitai-ui-radio-options] {\n display: flex;\n flex-direction: column;\n gap: 8px;\n\n &[data-orientation='horizontal'] {\n flex-direction: row;\n flex-wrap: wrap;\n gap: 16px;\n }\n }\n /*\n * RadioGroup group-level error (0.2.0): mirrors the field `-error` treatment.\n * The message reuses the shared `[data-civitai-ui-error]` styling (12px, error\n * token); the invalid state (`data-invalid` on the group, set alongside\n * `aria-invalid`) tints the child radios' `accent-color` to the error token so\n * the invalid cue reads on the group, not just the message text \u2014 the group\n * analogue of a field control's error-colored border.\n */\n [data-civitai-ui='radio-group'] [data-civitai-ui-error] {\n font-size: 12px;\n color: var(--civitai-color-error);\n }\n [data-civitai-ui='radio-group'][data-invalid] input[type='radio'] {\n accent-color: var(--civitai-color-error);\n }\n\n /* ----- Card ----- */\n /*\n * F5 (issue #181): in LIGHT mode --civitai-color-surface == --civitai-color-body\n * (both #fefefe), so a borderless card is invisible against the page. Give\n * every card a subtle DEFAULT hairline (a low-alpha mix of the border token)\n * so a card WITHOUT `data-with-border` is still visible. `data-with-border`\n * stays the stronger, explicit, fully-opaque border. Dark mode already\n * differentiates surface from body, so the default hairline is removed there\n * (see the [data-theme='dark'] rule below) \u2014 no over-adding. The default\n * (no [data-theme]) light palette keeps the hairline, matching light mode.\n */\n [data-civitai-ui='card'] {\n background: var(--civitai-color-surface);\n border-radius: var(--civitai-radius);\n color: var(--civitai-color-text);\n border: 1px solid color-mix(in srgb, var(--civitai-color-border) 55%, transparent);\n\n &[data-with-border='true'] {\n border: 1px solid var(--civitai-color-border);\n }\n &[data-padding='sm'] { padding: 10px; }\n &[data-padding='md'] { padding: 16px; }\n &[data-padding='lg'] { padding: 24px; }\n }\n /*\n * Dark already differentiates surface (#1A1B1E) from body \u2014 drop the default\n * hairline on a borderless card so dark is visually unchanged. An explicit\n * `data-with-border` still renders its full border.\n */\n [data-theme='dark'] [data-civitai-ui='card']:not([data-with-border='true']) {\n border: 0;\n }\n\n /* ----- Stack / Group ----- */\n [data-civitai-ui='stack'] {\n display: flex;\n flex-direction: column;\n gap: 12px;\n\n &[data-gap='sm'] { gap: 8px; }\n &[data-gap='md'] { gap: 16px; }\n &[data-gap='lg'] { gap: 24px; }\n }\n [data-civitai-ui='group'] {\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 8px;\n\n /*\n * RESPONSIVE BASE. A group is a ROW, and a row of three or more controls\n * has no escape at phone widths \u2014 a block's slot is ~360px there, so the\n * row simply ran off the edge.\n *\n * \uD83D\uDD34 There are THREE `group` surfaces, and for TWO of them this is a NEW\n * DEFAULT \u2014 do not read it as the CSS merely catching up:\n * - `@civitai/blocks-react`'s `<Group>` defaults `wrap = true` and writes\n * `flex-wrap` as an INLINE style, so its consumers already wrapped.\n * For that one, and only that one, this is the CSS agreeing with a\n * default that was already shipping.\n * - `@civitai/components-react`'s `<Group>` writes NO inline style and has\n * NO `wrap` prop, so it resolves against this rule \u2014 a React consumer\n * that did NOT wrap before.\n * - bare `data-civitai-ui=\"group\"` markup, the framework-agnostic contract\n * this package exists to serve \u2014 likewise.\n * Nothing could see the disagreement, because each surface was only ever\n * tested against itself. TWO tests now pin it, one per React surface \u2014 there\n * is no single \"the parity test\":\n * - `civitai-blocks-react/test/Group.test.tsx` reads the wrap default off\n * ITS `<Group>` and compares it to this rule, so flipping either alone\n * fails;\n * - `civitai-components-react/test/html-vs-react-parity.browser.test.tsx`\n * (\"styling anchors \u2014 Group\") asserts the COMPUTED value on both the\n * React and the bare-markup render, which is what covers the other two\n * surfaces. A parity check alone cannot: deleting this rule moves both\n * of its arms identically, so only the absolute anchor sees it.\n *\n * \uD83D\uDD34 The three-surface distinction is load-bearing for the RELEASE, not just\n * the prose: \"existing callers notice\" is what `RELEASING.md` reserves\n * `major` for, and it is true for two of the three surfaces. Shipped as a\n * MINOR by maintainer decision \u2014 see the changeset for the reasoning and for\n * the upgrade note that decision makes load-bearing.\n *\n * Opt out with `data-nowrap=\"true\"`. An inline `flex-wrap` also still\n * outranks this layer, so existing markup that already sets it is untouched.\n */\n flex-wrap: wrap;\n\n /*\n * A flex item defaults to `min-width: auto`, which refuses to shrink below\n * its CONTENT width \u2014 so one long label (a model name, a prompt) pushes the\n * row past the slot even WITH wrap. `min-width: 0` lets it shrink.\n *\n * \uD83D\uDD34 SCOPE, measured: this only matters for a child whose computed\n * `overflow` is `visible`. Per CSS Flexbox \u00A74.5 an item with any other\n * `overflow` ALREADY has an automatic minimum size of 0 \u2014 so a child that\n * sets `overflow: hidden`, as any `text-overflow: ellipsis` child must, is\n * unaffected by this line. An earlier version of this comment claimed\n * ellipsis \"can never engage\" without it, which was exactly backwards; the\n * browser guard built on that belief used an ellipsis fixture and therefore\n * passed with this rule DELETED. Both are fixed \u2014 see\n * `civitai-blocks-react/test/responsive-group.browser.test.tsx`.\n *\n * Specificity is (0,1,0), not zero: `&` carries the parent selector's, and\n * `:where(*)` is specificity-identical to `*`, so that wrapper bought\n * nothing and has been dropped. What actually lets a CONSUMER's own rule\n * win is `@layer civitai.components`, which every rule in this file sits\n * inside \u2014 unlayered author CSS beats it regardless of specificity. Note\n * the limit: another rule INSIDE this layer does not get that protection,\n * so a future non-zero `min-width` on a nested component here must be more\n * specific or come later.\n */\n & > * { min-width: 0; }\n\n &[data-nowrap='true'] { flex-wrap: nowrap; }\n\n &[data-gap='sm'] { gap: 6px; }\n &[data-gap='md'] { gap: 16px; }\n &[data-gap='lg'] { gap: 24px; }\n }\n\n /* ----- Alert ----- */\n [data-civitai-ui='alert'] {\n display: flex;\n gap: 10px;\n align-items: flex-start;\n padding: 12px 14px;\n border-radius: var(--civitai-radius);\n border: 1px solid transparent;\n font-size: 14px;\n color: var(--civitai-color-text);\n\n /*\n * Documented DEFAULT (MARKUP.md): color=info (default intent). Applied on\n * the base rule so a BARE alert \u2014 `<div data-civitai-ui=\"alert\">` with no\n * `data-color` \u2014 renders the info intent (tinted bg + border), not just the\n * neutral chrome. The explicit `[data-color]` rules below OVERRIDE (success\n * / warning / error each set both bg + border-color), so the info default\n * only ever wins when `data-color` is omitted.\n */\n background: color-mix(in srgb, var(--civitai-color-info) 12%, transparent);\n border-color: color-mix(in srgb, var(--civitai-color-info) 35%, transparent);\n\n &[data-color='info'] {\n background: color-mix(in srgb, var(--civitai-color-info) 12%, transparent);\n border-color: color-mix(in srgb, var(--civitai-color-info) 35%, transparent);\n }\n &[data-color='success'] {\n background: color-mix(in srgb, var(--civitai-color-success) 12%, transparent);\n border-color: color-mix(in srgb, var(--civitai-color-success) 35%, transparent);\n }\n &[data-color='warning'] {\n background: color-mix(in srgb, var(--civitai-color-warning) 14%, transparent);\n border-color: color-mix(in srgb, var(--civitai-color-warning) 35%, transparent);\n }\n &[data-color='error'] {\n background: color-mix(in srgb, var(--civitai-color-error) 12%, transparent);\n border-color: color-mix(in srgb, var(--civitai-color-error) 35%, transparent);\n }\n & [data-civitai-ui-alert-body] {\n flex: 1;\n min-width: 0;\n }\n & [data-civitai-ui-alert-title] {\n font-weight: 600;\n margin-bottom: 2px;\n }\n & [data-civitai-ui-alert-close] {\n background: transparent;\n border: none;\n cursor: pointer;\n color: inherit;\n font-size: 16px;\n line-height: 1;\n padding: 0;\n opacity: 0.7;\n\n &:hover { opacity: 1; }\n }\n }\n\n /* ----- Loader ----- */\n [data-civitai-ui='loader'] {\n display: inline-block;\n border-radius: 50%;\n border-style: solid;\n border-color: color-mix(in srgb, currentColor 25%, transparent);\n border-top-color: currentColor;\n color: var(--civitai-color-primary);\n animation: civitai-ui-spin 0.7s linear infinite;\n\n /*\n * Documented DEFAULT (MARKUP.md): size=md. Applied on the base rule so a\n * BARE loader \u2014 `<span data-civitai-ui=\"loader\">` with no `data-size` \u2014 has\n * real dimensions (otherwise 0\u00D70 and invisible). sm/lg OVERRIDE below.\n */\n width: 22px;\n height: 22px;\n border-width: 3px;\n\n &[data-size='sm'] { width: 16px; height: 16px; border-width: 2px; }\n &[data-size='md'] { width: 22px; height: 22px; border-width: 3px; }\n &[data-size='lg'] { width: 32px; height: 32px; border-width: 4px; }\n }\n [data-civitai-ui='button'] [data-civitai-ui='loader'] {\n color: currentColor;\n }\n @keyframes civitai-ui-spin {\n to { transform: rotate(360deg); }\n }\n\n /* ----- Badge ----- */\n [data-civitai-ui='badge'] {\n display: inline-flex;\n align-items: center;\n border: 1px solid transparent;\n border-radius: 999px;\n font-weight: 600;\n line-height: 1;\n white-space: nowrap;\n text-transform: uppercase;\n letter-spacing: 0.02em;\n\n /*\n * Documented DEFAULTS (MARKUP.md): variant=filled, size=md. Applied on the\n * base rule so a BARE badge \u2014 `<span data-civitai-ui=\"badge\">` with no\n * `data-variant`/`data-size` (MARKUP's own minimal example) \u2014 renders\n * filled + md (padding + primary fill), not an unstyled, zero-padding pill.\n * The explicit `[data-variant]`/`[data-size]` rules below OVERRIDE. Because\n * the base now carries a primary `border-color`, the `light` variant (which\n * previously relied on the base transparent border) explicitly resets it to\n * transparent so light badges are visually unchanged.\n */\n height: 22px;\n padding: 0 10px;\n font-size: 11px;\n background: var(--civitai-color-primary);\n color: var(--civitai-color-primary-fg);\n border-color: var(--civitai-color-primary);\n\n &[data-size='sm'] { height: 18px; padding: 0 8px; font-size: 10px; }\n &[data-size='md'] { height: 22px; padding: 0 10px; font-size: 11px; }\n &[data-size='lg'] { height: 26px; padding: 0 12px; font-size: 13px; }\n &[data-variant='filled'] {\n background: var(--civitai-color-primary);\n color: var(--civitai-color-primary-fg);\n border-color: var(--civitai-color-primary);\n }\n &[data-variant='light'] {\n background: color-mix(in srgb, var(--civitai-color-primary) 14%, transparent);\n color: var(--civitai-color-primary);\n border-color: transparent;\n }\n &[data-variant='outline'] {\n background: transparent;\n color: var(--civitai-color-primary);\n border-color: var(--civitai-color-primary);\n }\n\n /*\n * Intent color via `data-color`, mirroring Alert's `data-color` contract\n * (info / success / warning / error). Absent `data-color` => the default\n * primary above (non-breaking). Each intent recolors the `filled`, `light`\n * and `outline` variants with the same `color-mix()` token approach Alert\n * uses; `filled` keeps the white `--civitai-color-primary-fg` text. These\n * `[data-color][data-variant]` rules out-specify the plain-variant rules\n * above, so order-independence holds.\n */\n &[data-color='info'] {\n &[data-variant='filled'] {\n background: var(--civitai-color-info);\n border-color: var(--civitai-color-info);\n }\n &[data-variant='light'] {\n background: color-mix(in srgb, var(--civitai-color-info) 14%, transparent);\n color: var(--civitai-color-info);\n }\n &[data-variant='outline'] {\n color: var(--civitai-color-info);\n border-color: var(--civitai-color-info);\n }\n }\n &[data-color='success'] {\n &[data-variant='filled'] {\n background: var(--civitai-color-success);\n border-color: var(--civitai-color-success);\n }\n &[data-variant='light'] {\n background: color-mix(in srgb, var(--civitai-color-success) 14%, transparent);\n color: var(--civitai-color-success);\n }\n &[data-variant='outline'] {\n color: var(--civitai-color-success);\n border-color: var(--civitai-color-success);\n }\n }\n &[data-color='warning'] {\n &[data-variant='filled'] {\n background: var(--civitai-color-warning);\n border-color: var(--civitai-color-warning);\n }\n &[data-variant='light'] {\n background: color-mix(in srgb, var(--civitai-color-warning) 14%, transparent);\n color: var(--civitai-color-warning);\n }\n &[data-variant='outline'] {\n color: var(--civitai-color-warning);\n border-color: var(--civitai-color-warning);\n }\n }\n &[data-color='error'] {\n &[data-variant='filled'] {\n background: var(--civitai-color-error);\n border-color: var(--civitai-color-error);\n }\n &[data-variant='light'] {\n background: color-mix(in srgb, var(--civitai-color-error) 14%, transparent);\n color: var(--civitai-color-error);\n }\n &[data-variant='outline'] {\n color: var(--civitai-color-error);\n border-color: var(--civitai-color-error);\n }\n }\n }\n\n /* ----- Slider ----- */\n /*\n * A themed native <input type=\"range\">: `accent-color` carries the primary\n * tint (native control, so keyboard arrow-key nav + ARIA come for free), plus\n * a full-width track, a token focus ring, and disabled/invalid states. Like\n * checkbox/radio, the range input deliberately does NOT carry\n * `data-civitai-ui-control` (that is the bordered field-input chrome). The\n * label/description/error reuse the shared field markers, laid out in a\n * column; an optional value read-out sits inline with the label in a header.\n */\n [data-civitai-ui='slider'] {\n display: flex;\n flex-direction: column;\n gap: 6px;\n }\n [data-civitai-ui-slider-header] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n }\n [data-civitai-ui-slider-value] {\n font-size: 13px;\n font-weight: 600;\n color: var(--civitai-color-text-dimmed);\n font-variant-numeric: tabular-nums;\n }\n [data-civitai-ui='slider'] input[type='range'] {\n -webkit-appearance: none;\n appearance: none;\n width: 100%;\n height: 6px;\n margin: 6px 0;\n padding: 0;\n border-radius: 999px;\n accent-color: var(--civitai-color-primary);\n background: var(--civitai-color-gray-2);\n cursor: pointer;\n }\n [data-theme='dark'] [data-civitai-ui='slider'] input[type='range'] {\n background: var(--civitai-color-surface-2);\n }\n [data-civitai-ui='slider'] input[type='range']:focus-visible {\n outline: 2px solid var(--civitai-color-primary);\n outline-offset: 4px;\n }\n [data-civitai-ui='slider'] input[type='range']:disabled {\n cursor: not-allowed;\n opacity: 0.6;\n accent-color: var(--civitai-color-gray-5);\n }\n [data-civitai-ui='slider'][data-invalid] input[type='range'] {\n accent-color: var(--civitai-color-error);\n }\n\n /* ----- SegmentedControl / Tabs ----- */\n /*\n * A `role=tablist` of segment buttons (`role=tab`). Presentational chrome\n * only \u2014 the roving-tabindex + arrow-key nav + selection follow-focus live in\n * the React binding (or must be author-provided for hand HTML). The selected\n * segment (`aria-selected='true'`) lifts to the surface color with a soft\n * shadow; unselected segments read dimmed. Paired tab panels use\n * `data-civitai-ui-tabpanel` (role=tabpanel).\n */\n [data-civitai-ui='segmented-control'] {\n display: inline-flex;\n gap: 2px;\n padding: 4px;\n background: var(--civitai-color-gray-1);\n border-radius: var(--civitai-radius);\n max-width: 100%;\n overflow-x: auto;\n }\n [data-theme='dark'] [data-civitai-ui='segmented-control'] {\n background: var(--civitai-color-surface-2);\n }\n [data-civitai-ui-segment] {\n -webkit-appearance: none;\n appearance: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: 0;\n background: transparent;\n color: var(--civitai-color-text-dimmed);\n font-family: var(--civitai-font);\n font-weight: 600;\n line-height: 1;\n white-space: nowrap;\n cursor: pointer;\n border-radius: calc(var(--civitai-radius) - 1px);\n transition: background-color 120ms ease, color 120ms ease;\n\n /* Documented DEFAULT (MARKUP.md): size=md. On the base rule so a bare\n segment renders at md height, not zero-height. sm/lg override below. */\n height: 30px;\n padding: 0 14px;\n font-size: 13px;\n\n &[data-size='sm'] { height: 26px; padding: 0 10px; font-size: 12px; }\n &[data-size='md'] { height: 30px; padding: 0 14px; font-size: 13px; }\n &[data-size='lg'] { height: 38px; padding: 0 18px; font-size: 15px; }\n\n &:hover:not(:disabled):not([aria-selected='true']):not([aria-checked='true']) {\n color: var(--civitai-color-text);\n }\n /* Selected state \u2014 `aria-selected` in tabs mode (role=tab) OR `aria-checked`\n in toggle mode (role=radio). Same visual treatment for both. */\n &[aria-selected='true'],\n &[aria-checked='true'] {\n background: var(--civitai-color-surface);\n color: var(--civitai-color-text);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);\n }\n &:focus-visible {\n outline: 2px solid var(--civitai-color-primary);\n outline-offset: 2px;\n }\n &:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n }\n }\n [data-civitai-ui-tabpanel] {\n color: var(--civitai-color-text);\n\n &:focus-visible {\n outline: 2px solid var(--civitai-color-primary);\n outline-offset: 2px;\n }\n &[hidden] { display: none; }\n }\n\n /* ----- Toast ----- */\n /*\n * `toast-region` is the fixed-position `aria-live` host (bottom-right stack).\n * The behavior \u2014 enqueue, auto-dismiss timers, portal \u2014 lives in the React\n * binding's ToastProvider/useToast; the presentational `toast` is a standalone\n * component so hand HTML and React share one visual contract. Each toast is\n * opaque (it sits over content) with an intent-colored left accent, mirroring\n * Alert's `data-color` set.\n */\n [data-civitai-ui='toast-region'] {\n position: fixed;\n bottom: 16px;\n right: 16px;\n z-index: 9999;\n display: flex;\n flex-direction: column;\n gap: 8px;\n width: min(92vw, 380px);\n pointer-events: none;\n }\n [data-civitai-ui='toast'] {\n pointer-events: auto;\n display: flex;\n gap: 10px;\n align-items: flex-start;\n padding: 12px 14px;\n border-radius: var(--civitai-radius);\n background: var(--civitai-color-surface);\n color: var(--civitai-color-text);\n border: 1px solid var(--civitai-color-border);\n border-left: 4px solid var(--civitai-color-border);\n box-shadow: 0 6px 18px rgba(0, 0, 0, 0.18);\n font-size: 14px;\n\n &[data-color='info'] { border-left-color: var(--civitai-color-info); }\n &[data-color='success'] { border-left-color: var(--civitai-color-success); }\n &[data-color='warning'] { border-left-color: var(--civitai-color-warning); }\n &[data-color='error'] { border-left-color: var(--civitai-color-error); }\n\n & [data-civitai-ui-toast-body] {\n flex: 1;\n min-width: 0;\n }\n & [data-civitai-ui-toast-title] {\n font-weight: 600;\n margin-bottom: 2px;\n }\n & [data-civitai-ui-toast-close] {\n background: transparent;\n border: none;\n cursor: pointer;\n color: inherit;\n font-size: 16px;\n line-height: 1;\n padding: 0;\n opacity: 0.7;\n\n &:hover { opacity: 1; }\n }\n }\n\n /* ----- Tooltip ----- */\n /*\n * A hover/focus tooltip: a positioned `role=tooltip` bubble revealed when the\n * wrapper is hovered or contains focus (pure-CSS reveal), or explicitly via\n * `data-open='true'` (the React binding also wires `aria-describedby` +\n * Escape-to-dismiss). The bubble is a dark chip (gray-9) with light text in\n * both themes.\n */\n [data-civitai-ui='tooltip'] {\n position: relative;\n display: inline-flex;\n }\n [data-civitai-ui-tooltip-bubble] {\n position: absolute;\n bottom: calc(100% + 6px);\n left: 50%;\n transform: translateX(-50%);\n z-index: 200;\n max-width: 260px;\n width: max-content;\n padding: 4px 8px;\n border-radius: var(--civitai-radius);\n background: var(--civitai-color-gray-9);\n color: var(--civitai-color-primary-fg);\n font-size: 12px;\n font-weight: 500;\n line-height: 1.4;\n text-align: center;\n pointer-events: none;\n opacity: 0;\n visibility: hidden;\n transition: opacity 120ms ease;\n }\n /*\n * Reveal on hover/focus or an explicit `data-open='true'` \u2014 BUT\n * `data-dismissed='true'` overrides (gates every reveal selector), so the\n * React binding's Escape-to-dismiss can force-HIDE the bubble even while the\n * pointer still hovers or focus is still within. The dismissed flag is cleared\n * on the next hover/focus so the tooltip can re-open normally.\n */\n [data-civitai-ui='tooltip']:hover [data-civitai-ui-tooltip-bubble]:not([data-dismissed='true']),\n [data-civitai-ui='tooltip']:focus-within [data-civitai-ui-tooltip-bubble]:not([data-dismissed='true']),\n [data-civitai-ui-tooltip-bubble][data-open='true']:not([data-dismissed='true']) {\n opacity: 1;\n visibility: visible;\n }\n\n /* ----- Image ----- */\n /*\n * A media container with a token placeholder background (visible while the\n * image loads), object-fit control, and a broken-image fallback. `data-status`\n * (loading|loaded|error) \u2014 set by the React binding's onLoad/onError, or by\n * the author for hand HTML \u2014 fades the <img> in on load and swaps to the\n * fallback overlay on error. Bare markup (no `data-status`) shows the image.\n */\n [data-civitai-ui='image'] {\n position: relative;\n display: block;\n overflow: hidden;\n background: var(--civitai-color-gray-2);\n border-radius: var(--civitai-radius);\n }\n [data-theme='dark'] [data-civitai-ui='image'] {\n background: var(--civitai-color-surface-2);\n }\n [data-civitai-ui-image-img] {\n display: block;\n width: 100%;\n height: 100%;\n object-fit: cover;\n opacity: 1;\n transition: opacity 200ms ease;\n\n &[data-fit='contain'] { object-fit: contain; }\n &[data-fit='cover'] { object-fit: cover; }\n }\n [data-civitai-ui='image'][data-status='loading'] [data-civitai-ui-image-img],\n [data-civitai-ui='image'][data-status='error'] [data-civitai-ui-image-img] {\n opacity: 0;\n }\n [data-civitai-ui-image-fallback] {\n position: absolute;\n inset: 0;\n display: none;\n align-items: center;\n justify-content: center;\n padding: 8px;\n color: var(--civitai-color-text-dimmed);\n font-size: 13px;\n text-align: center;\n }\n [data-civitai-ui='image'][data-status='error'] [data-civitai-ui-image-fallback] {\n display: flex;\n }\n}\n\n\n/* ----- Select / Slider field wrappers -----\n The shared -control/-label/-required/-description/-error primitives come from\n @civitai/components (layered). Only the select/slider WRAPPERS live here \u2014 the\n design-system package scopes its field-wrapper rule to the presentational\n inputs (text-input/textarea/number-input). */\n[data-civitai-ui='select'],\n[data-civitai-ui='slider'] {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n/* ----- Modal ----- */\n[data-civitai-ui='modal-overlay'] {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.55);\n display: flex;\n align-items: flex-start;\n justify-content: center;\n padding: 32px 16px;\n overflow-y: auto;\n z-index: 1000;\n}\n[data-civitai-ui='modal'] {\n background: var(--civitai-color-surface);\n color: var(--civitai-color-text);\n border: 1px solid var(--civitai-color-border);\n border-radius: var(--civitai-radius);\n box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3);\n width: 100%;\n max-width: 440px;\n outline: none;\n}\n[data-civitai-ui='modal'][data-size='sm'] { max-width: 340px; }\n[data-civitai-ui='modal'][data-size='md'] { max-width: 440px; }\n[data-civitai-ui='modal'][data-size='lg'] { max-width: 620px; }\n[data-civitai-ui='modal'] [data-civitai-ui-modal-header] {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 16px 18px;\n border-bottom: 1px solid var(--civitai-color-border);\n}\n[data-civitai-ui='modal'] [data-civitai-ui-modal-title] {\n font-size: 16px;\n font-weight: 700;\n margin: 0;\n}\n[data-civitai-ui='modal'] [data-civitai-ui-modal-close] {\n background: transparent;\n border: none;\n cursor: pointer;\n color: var(--civitai-color-text-dimmed);\n font-size: 20px;\n line-height: 1;\n padding: 0;\n}\n[data-civitai-ui='modal'] [data-civitai-ui-modal-close]:hover {\n color: var(--civitai-color-text);\n}\n[data-civitai-ui='modal'] [data-civitai-ui-modal-body] {\n padding: 18px;\n}\n\n/* ----- Slider ----- */\n[data-civitai-ui='slider'] [data-civitai-ui-label] {\n display: flex;\n justify-content: space-between;\n align-items: baseline;\n gap: 8px;\n}\n[data-civitai-ui-slider-value] {\n font-weight: 500;\n color: var(--civitai-color-text-dimmed);\n font-variant-numeric: tabular-nums;\n}\n[data-civitai-ui-range] {\n width: 100%;\n margin: 0;\n accent-color: var(--civitai-color-primary);\n cursor: pointer;\n}\n[data-civitai-ui-range]:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n[data-civitai-ui-range]:focus-visible {\n outline: 2px solid var(--civitai-color-primary);\n outline-offset: 2px;\n}\n\n/* ----- Collapse ----- */\n[data-civitai-ui='collapse'] [data-civitai-ui-collapse-trigger] {\n display: flex;\n align-items: center;\n gap: 6px;\n width: 100%;\n padding: 6px 0;\n background: transparent;\n border: none;\n font-family: var(--civitai-font);\n font-size: 14px;\n font-weight: 600;\n color: var(--civitai-color-text);\n text-align: left;\n cursor: pointer;\n}\n[data-civitai-ui='collapse'] [data-civitai-ui-collapse-trigger]:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n[data-civitai-ui='collapse'] [data-civitai-ui-collapse-chevron] {\n display: inline-block;\n width: 1em;\n color: var(--civitai-color-text-dimmed);\n}\n[data-civitai-ui='collapse'] [data-civitai-ui-collapse-region] {\n padding-top: 4px;\n}\n\n/* ----- ResourceCard -----\n Two variants off ONE box. data-variant='card' stacks (grid tile),\n data-variant='row' runs inline (compact list line). Every selector is\n double-qualified with [data-civitai-ui='resource-card'] on purpose:\n data-variant is also emitted by Button and Badge, so a bare\n [data-variant='card'] would reach across the whole pack.\n NOTE: this block lives inside a JS TEMPLATE LITERAL. No backticks, and no\n dollar-brace, or the string ends here and the file stops parsing as\n TypeScript (backticks in this comment did exactly that once). */\n[data-civitai-ui='resource-card'] {\n display: flex;\n box-sizing: border-box;\n /* The positioned ancestor for the overlay slot. It lives on the ROOT, not on\n the thumbnail frame, so the overlay can be a SIBLING of the hit <button>\n rather than a descendant of it \u2014 see the overlay prop's doc for why that\n distinction is the whole point of the slot. */\n position: relative;\n font-family: var(--civitai-font);\n color: var(--civitai-color-text);\n background: var(--civitai-color-surface);\n border: 1px solid var(--civitai-color-border);\n border-radius: var(--civitai-radius);\n overflow: hidden;\n}\n[data-civitai-ui='resource-card'][data-variant='card'] {\n flex-direction: column;\n align-items: stretch;\n}\n[data-civitai-ui='resource-card'][data-variant='row'] {\n flex-direction: row;\n align-items: center;\n gap: 8px;\n padding: 6px 8px;\n}\n[data-civitai-ui='resource-card'][data-selected='true'] {\n border-color: var(--civitai-color-primary);\n}\n[data-civitai-ui='resource-card'][data-disabled='true'] {\n opacity: 0.6;\n}\n/* The hit area. A <button> when interactive, a <div> when not \u2014 both are reset\n to the same box so the two variants lay out identically either way. */\n[data-civitai-ui='resource-card'] [data-civitai-ui-resource-hit] {\n display: flex;\n /* \uD83D\uDD34 A flex ITEM defaults to min-width:auto, which refuses to shrink below its\n content \u2014 so without this the name's nowrap+ellipsis never engages and a\n long model name blows the card out of its grid cell instead of truncating. */\n min-width: 0;\n box-sizing: border-box;\n margin: 0;\n border: none;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n}\n[data-civitai-ui='resource-card'][data-variant='card'] [data-civitai-ui-resource-hit] {\n flex-direction: column;\n align-items: stretch;\n gap: 6px;\n padding: 6px;\n}\n[data-civitai-ui='resource-card'][data-variant='row'] [data-civitai-ui-resource-hit] {\n flex: 1 1 auto;\n flex-direction: row;\n align-items: center;\n gap: 8px;\n padding: 0;\n}\n[data-civitai-ui='resource-card'] button[data-civitai-ui-resource-hit] {\n cursor: pointer;\n}\n[data-civitai-ui='resource-card'] button[data-civitai-ui-resource-hit]:disabled {\n cursor: not-allowed;\n}\n[data-civitai-ui='resource-card'] button[data-civitai-ui-resource-hit]:focus-visible {\n outline: 2px solid var(--civitai-color-primary);\n outline-offset: -2px;\n}\n/* The thumbnail frame. Rendered whether or not there is an image \u2014 see the\n component's thumbnailUrl doc: BlockResourceInfo has no image field, so \"no\n image\" is the COMMON case and the frame must keep its box regardless. */\n[data-civitai-ui='resource-card'] [data-civitai-ui-resource-thumb] {\n display: flex;\n align-items: center;\n justify-content: center;\n flex: none;\n overflow: hidden;\n background: var(--civitai-color-surface-2);\n border-radius: calc(var(--civitai-radius) - 1px);\n color: var(--civitai-color-text-dimmed);\n}\n[data-civitai-ui='resource-card'][data-variant='card'] [data-civitai-ui-resource-thumb] {\n /* \uD83D\uDD34 aspect-ratio, not a fixed height: the tile is grid-sized by the caller,\n and this is the line that stops a thumbnail-less card collapsing to a\n text-height sliver. */\n width: 100%;\n aspect-ratio: 1 / 1;\n font-size: 11px;\n}\n[data-civitai-ui='resource-card'][data-variant='row'] [data-civitai-ui-resource-thumb] {\n width: 36px;\n height: 36px;\n font-size: 9px;\n}\n[data-civitai-ui='resource-card'] [data-civitai-ui-resource-thumb] img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n}\n[data-civitai-ui='resource-card'] [data-civitai-ui-resource-placeholder] {\n padding: 0 4px;\n text-align: center;\n line-height: 1.2;\n}\n/* Positioned from the ROOT, over the thumbnail corner. 10px = the hit area's\n 6px padding plus a 4px inset inside the frame; the three move together, and\n the browser tier asserts the result lands inside the frame's own rect rather\n than trusting the arithmetic.\n pointer-events: none is load-bearing, not polish: this slot is STATUS, and a\n pill that can swallow a click meant for the card is the same class of bug as\n nesting a control inside the hit <button>. A consumer who really wants an\n interactive badge opts back in with pointer-events: auto on their own node. */\n[data-civitai-ui='resource-card'] [data-civitai-ui-resource-overlay] {\n position: absolute;\n top: 10px;\n right: 10px;\n z-index: 1;\n pointer-events: none;\n display: flex;\n align-items: center;\n gap: 4px;\n max-width: calc(100% - 20px);\n font-size: 11px;\n line-height: 1;\n}\n[data-civitai-ui='resource-card'] [data-civitai-ui-resource-nameline] {\n display: flex;\n align-items: center;\n gap: 4px;\n min-width: 0;\n}\n/* The non-colour half of the selected affordance (WCAG 1.4.1). The border-hue\n change on the root is reinforcement; this glyph is what carries it. */\n[data-civitai-ui='resource-card'] [data-civitai-ui-resource-selected] {\n flex: none;\n font-size: 12px;\n font-weight: 700;\n line-height: 1;\n color: var(--civitai-color-primary);\n}\n[data-civitai-ui='resource-card'] [data-civitai-ui-resource-text] {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n flex: 1 1 auto;\n}\n[data-civitai-ui='resource-card'] [data-civitai-ui-resource-name] {\n display: block;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.3;\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n[data-civitai-ui='resource-card'] [data-civitai-ui-resource-meta] {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 4px 6px;\n min-width: 0;\n font-size: 11px;\n color: var(--civitai-color-text-dimmed);\n}\n[data-civitai-ui='resource-card'] [data-civitai-ui-resource-actions] {\n display: flex;\n align-items: center;\n flex: none;\n gap: 6px;\n}\n[data-civitai-ui='resource-card'][data-variant='card'] [data-civitai-ui-resource-actions] {\n padding: 0 6px 6px;\n}\n\n/* ----- SegmentedControl ----- */\n[data-civitai-ui='segmented-control'] {\n display: inline-flex;\n flex-direction: row;\n gap: 2px;\n padding: 3px;\n background: var(--civitai-color-surface-2);\n border: 1px solid var(--civitai-color-border);\n border-radius: var(--civitai-radius);\n vertical-align: middle;\n}\n[data-civitai-ui='segmented-control'][data-full-width='true'] {\n display: flex;\n width: 100%;\n}\n[data-civitai-ui='segmented-control'] [data-civitai-ui-segment] {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: none;\n border-radius: calc(var(--civitai-radius) - 3px);\n background: transparent;\n color: var(--civitai-color-text-dimmed);\n font-family: var(--civitai-font);\n font-weight: 600;\n line-height: 1;\n white-space: nowrap;\n cursor: pointer;\n user-select: none;\n transition: background-color 120ms ease, color 120ms ease, box-shadow 120ms ease;\n}\n[data-civitai-ui='segmented-control'][data-full-width='true'] [data-civitai-ui-segment] {\n flex: 1 1 0;\n}\n[data-civitai-ui='segmented-control'][data-size='sm'] [data-civitai-ui-segment] { height: 24px; padding: 0 12px; font-size: 13px; }\n[data-civitai-ui='segmented-control'][data-size='md'] [data-civitai-ui-segment] { height: 30px; padding: 0 16px; font-size: 14px; }\n[data-civitai-ui='segmented-control'][data-size='lg'] [data-civitai-ui-segment] { height: 38px; padding: 0 20px; font-size: 16px; }\n[data-civitai-ui='segmented-control'] [data-civitai-ui-segment]:hover:not(:disabled):not([data-active]) {\n color: var(--civitai-color-text);\n background: color-mix(in srgb, var(--civitai-color-text) 6%, transparent);\n}\n[data-civitai-ui='segmented-control'] [data-civitai-ui-segment][data-active] {\n background: var(--civitai-color-surface);\n color: var(--civitai-color-primary);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);\n}\n[data-civitai-ui='segmented-control'] [data-civitai-ui-segment]:focus-visible {\n outline: 2px solid var(--civitai-color-primary);\n outline-offset: 1px;\n}\n[data-civitai-ui='segmented-control'] [data-civitai-ui-segment]:disabled {\n opacity: 0.55;\n cursor: not-allowed;\n}\n[data-civitai-ui='segmented-control'][data-disabled='true'] {\n opacity: 0.7;\n}\n";
9
9
  /**
10
10
  * Inject the pack's stylesheets into a document's `<head>`, idempotently.
11
11
  *
@@ -1 +1 @@
1
- {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../src/ui/styles.ts"],"names":[],"mappings":"AAqPA;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,4r3CAA0D,CAAC;AAExF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,CAAC,EAAE,QAAQ,GAAG,IAAI,CAmBvD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAItC"}
1
+ {"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../../src/ui/styles.ts"],"names":[],"mappings":"AAwbA;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB,8wkDAA0D,CAAC;AAExF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,CAAC,EAAE,QAAQ,GAAG,IAAI,CAmBvD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAItC"}