@cupped/tokens 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # @cupped/tokens
2
2
 
3
+ ## 0.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#6](https://github.com/ybird-labs/cupped-design-system/pull/6) [`6a9ce2a`](https://github.com/ybird-labs/cupped-design-system/commit/6a9ce2ab72710b30c1d8c12a4a4fe3a412aca246) Thanks [@JeancarloBarrios](https://github.com/JeancarloBarrios)! - Distribute via **public npm**: consumers install `@cupped/tokens` with no registry auth or `.npmrc`. The release pipeline publishes to npmjs with provenance.
8
+
9
+ - [#5](https://github.com/ybird-labs/cupped-design-system/pull/5) [`e67f352`](https://github.com/ybird-labs/cupped-design-system/commit/e67f35207e1ee75149e6a0607d7a38a87f47e089) Thanks [@JeancarloBarrios](https://github.com/JeancarloBarrios)! - Harden the release pipeline and tighten the package manifest:
10
+
11
+ - **release.yml** — resilient GitHub Release `create`-or-`upload` for the CSS
12
+ assets (no more half-published versions), plus a `.sha256` sidecar so
13
+ consumers can verify pinned downloads.
14
+ - **ci.yml** — enforce a changeset on PRs that change the package, so a release
15
+ never silently no-ops on a token/API change.
16
+ - **package.json** — add a `default` export condition (resolvers matching none
17
+ of `react-native`/`import`/`require` now resolve), and drop the `engines`
18
+ Node gate that wrongly constrained _consumers_ (Node 22 is a build-only need).
19
+ - **docs** — fine-grained-PAT first-publish runbook; Phoenix download now
20
+ verifies the checksum sidecar.
21
+
22
+ - [#7](https://github.com/ybird-labs/cupped-design-system/pull/7) [`c27e9f4`](https://github.com/ybird-labs/cupped-design-system/commit/c27e9f42b3c700807873a6265b5126f43a4391ac) Thanks [@JeancarloBarrios](https://github.com/JeancarloBarrios)! - Revise the text input to design v0.3 and change the focus/error glow tokens to carry geometry.
23
+
24
+ Shipped as a 0.2.1 follow-up to the 0.2.0 glow tokens. Note — the shape of `focus-glow`/`error-glow`/`focusGlow`/`errorGlow` changes vs the published `@cupped/tokens@0.2.0` (a breaking shape change; called out here so consumers of 0.2.0's color-only glows can migrate):
25
+
26
+ - CSS/Tailwind: `--focus-glow`/`--error-glow` previously resolved to a bare **color** (e.g. `rgba(192, 85, 57, 0.22)`); they now resolve to a full **`box-shadow`** value (e.g. `0 0 6px 2px color-mix(...)` / `0 0 6px 2px rgba(...)`). Consumers that wrote `box-shadow: 0 0 6px 2px var(--focus-glow)` must change to `box-shadow: var(--focus-glow)` (using the token as a color now produces an invalid doubled box-shadow).
27
+ - Flat JSON: `"focus-glow"`/`"error-glow"` change from a color string (`"rgba(192, 85, 57, 0.22)"`) to a box-shadow string (`"0 0 6px 2px rgba(192, 85, 57, 0.22)"`).
28
+ - Native theme object: `focusGlow`/`errorGlow` change from a **string** (`"rgba(192, 85, 57, 0.22)"`) to an **object** `{ color, blur, spread }`. Consumers reading `theme.focusGlow`/`theme.errorGlow` as a color string (e.g. passing to `shadowColor`) must migrate to `theme.focusGlow.color`.
29
+
30
+ Other changes (additive):
31
+
32
+ - `.input` revised: 44pt min-height, hover border, AA placeholder (ink-secondary), accessible focus (primary-strong border + soft `--focus-glow` halo), error that persists on focus with `--error-glow`, icon-paired error hint, and a `validating` state with a new `.spinner-ink`. Hint is neutral by default, red on `.error`, neutral on `.validating`.
33
+ - Web `Input` wrapper gains `validating`, `leading`, and `trailing` props, an error alert icon, `aria-busy`, `aria-describedby` linking the hint, and a live-region hint (assertive on error, polite while validating). The existing `label`/`hint`/`error` API is unchanged.
34
+
3
35
  ## 0.1.0
4
36
 
5
37
  Initial release. DTCG token source under `tokens/` with generated, committed
package/README.md CHANGED
@@ -5,9 +5,9 @@ community-first coffee-logging platform. The canonical source is
5
5
  platform-neutral [DTCG](https://www.designtokens.org/TR/drafts/format/)-style
6
6
  JSON under [`tokens/`](./tokens); every consumable artifact in
7
7
  [`dist/`](./dist) is **generated** from it and committed. The package is
8
- published to **GitHub Packages** (`@cupped` scope), and because `dist/` is
9
- committed it also works as a plain git dependency no registry auth, no
10
- install-time build for consumers that prefer that.
8
+ published **public on npm** (`npm install @cupped/tokens` — no auth), and
9
+ because `dist/` is committed it also works as a plain git dependency with no
10
+ install-time build, for consumers that prefer pinning a commit.
11
11
 
12
12
  > Never hard-code a hex or pixel value that exists as a token.
13
13
 
@@ -28,7 +28,7 @@ fonts are self-hosted per platform (documented).
28
28
  ## Consuming
29
29
 
30
30
  - **Phoenix LiveView (Tailwind v4):** [docs/phoenix.md](./docs/phoenix.md) —
31
- pinned CSS from the GitHub Release, npm git-dep, or zero-npm vendor mode;
31
+ pinned CSS from the GitHub Release, npm dependency, or zero-npm vendor mode;
32
32
  fonts, lint recipe.
33
33
  - **Expo / React Native (SDK 53+):** [docs/expo.md](./docs/expo.md) — the
34
34
  `theme.ts` pattern, expo-font, ESLint recipe.
@@ -71,7 +71,7 @@ npm run check # build, fail on dist/ drift, then test — what CI runs
71
71
  ## Releasing & versioning
72
72
 
73
73
  Releases are automated with [Changesets](https://github.com/changesets/changesets)
74
- and published to GitHub Packages; each release also attaches version-pinned CSS
74
+ and published to public npm; each release also attaches version-pinned CSS
75
75
  to its GitHub Release. Add a changeset with `npx changeset` in any PR that
76
76
  changes tokens.
77
77
 
@@ -94,6 +94,7 @@
94
94
  /* ——— Inputs ——— */
95
95
  .input {
96
96
  width: 100%;
97
+ min-height: var(--hit-target-min); /* ≥44pt tap target — matches .btn */
97
98
  padding: 12px 14px;
98
99
  border: 1px solid var(--canvas-border);
99
100
  border-radius: var(--radius-md);
@@ -103,21 +104,28 @@
103
104
  color: var(--ink);
104
105
  transition: border-color .15s, box-shadow .15s;
105
106
  }
106
- .input::placeholder { color: var(--ink-muted); }
107
+ /* Placeholder is essential text — ink-secondary clears AA; ink-muted does not. */
108
+ .input::placeholder { color: var(--ink-secondary); }
109
+ .input:hover:not(:focus):not([disabled]):not(.error) { border-color: var(--ink-muted); }
107
110
  .input:focus {
108
111
  outline: none;
109
- border-color: var(--primary);
110
- box-shadow: 0 0 0 3px color-mix(in srgb, var(--primary) 18%, transparent);
112
+ border-color: var(--primary-strong); /* #C05539, ≥3:1 vs field — carries the contrast */
113
+ box-shadow: var(--focus-glow); /* soft glow, decorative; buttons keep the two-tone ring */
111
114
  }
112
- .input.error {
115
+ .input.error,
116
+ .input.error:focus {
113
117
  border-color: var(--error);
114
- box-shadow: 0 0 0 3px color-mix(in srgb, var(--error) 12%, transparent);
118
+ box-shadow: var(--error-glow); /* soft glow, error color shown focused or not */
115
119
  }
116
120
  .input[disabled] { background: var(--canvas); color: var(--ink-muted); cursor: not-allowed; }
117
121
  .input-row { display: flex; flex-direction: column; gap: 6px; }
118
- .input-label { font-size: 12px; font-weight: 500; color: var(--ink-secondary); }
119
- .input-hint { font-size: 12px; color: var(--ink-secondary); }
122
+ .input-label { font-size: var(--text-caption); font-weight: 500; color: var(--ink-secondary); }
123
+ /* Hint: neutral helper by default; .error turns it red + pairs an icon (never color alone, WCAG 1.4.1). */
124
+ .input-hint { display: flex; align-items: center; gap: 6px; font-size: var(--text-caption); color: var(--ink-secondary); }
125
+ .input-hint .icon { width: 14px; height: 14px; flex-shrink: 0; }
120
126
  .input-hint.error { color: var(--error-ink); }
127
+ /* Validating — async check (e.g. "checking bean name"); pair with .spinner.spinner-ink */
128
+ .input-hint.validating { color: var(--ink-secondary); }
121
129
 
122
130
  /* ——— Cards ——— */
123
131
  .card {
@@ -177,7 +185,13 @@
177
185
  }
178
186
  @keyframes cupped-spin { to { transform: rotate(360deg); } }
179
187
 
188
+ /* Dark spinner for light surfaces (input validating, secondary/ghost buttons) */
189
+ .spinner-ink {
190
+ border-color: color-mix(in srgb, var(--ink) 18%, transparent);
191
+ border-top-color: var(--ink-secondary);
192
+ }
193
+
180
194
  @media (prefers-reduced-motion: reduce) {
181
- .btn { transition: none; }
195
+ .btn, .input { transition: none; }
182
196
  .spinner { animation: none; }
183
197
  }
@@ -119,6 +119,8 @@
119
119
  --shimmer-duration: 1.5s;
120
120
  --motion-reduced: 120ms;
121
121
  --focus-ring: 0 0 0 2px var(--card), 0 0 0 4px var(--primary-strong);
122
+ --focus-glow: 0 0 6px 2px color-mix(in srgb, var(--primary-strong) 22%, transparent);
123
+ --error-glow: 0 0 6px 2px color-mix(in srgb, var(--error) 22%, transparent);
122
124
  --hit-target-min: 44px;
123
125
 
124
126
  /* ── Materials ── */
@@ -1304,6 +1304,40 @@
1304
1304
  "outerWidthPx": 2
1305
1305
  }
1306
1306
  }
1307
+ },
1308
+ "glow": {
1309
+ "$type": "shadow",
1310
+ "$value": {
1311
+ "offsetX": "0px",
1312
+ "offsetY": "0px",
1313
+ "blur": "6px",
1314
+ "spread": "2px",
1315
+ "color": "rgba(192, 85, 57, 0.22)"
1316
+ },
1317
+ "$description": "Decorative input focus halo — 6px blur, 2px spread, primary-strong @ 22%. The primary-strong border carries the ≥3:1 contrast; this glow is decorative. Buttons keep the two-tone focus ring.",
1318
+ "$extensions": {
1319
+ "app.cupped": {
1320
+ "cssName": "focus-glow",
1321
+ "cssValue": "0 0 6px 2px color-mix(in srgb, var(--primary-strong) 22%, transparent)"
1322
+ }
1323
+ }
1324
+ },
1325
+ "errorGlow": {
1326
+ "$type": "shadow",
1327
+ "$value": {
1328
+ "offsetX": "0px",
1329
+ "offsetY": "0px",
1330
+ "blur": "6px",
1331
+ "spread": "2px",
1332
+ "color": "rgba(239, 68, 68, 0.22)"
1333
+ },
1334
+ "$description": "Decorative input error halo — 6px blur, 2px spread, error @ 22%. Pairs with the error border; shown whether or not the field is focused.",
1335
+ "$extensions": {
1336
+ "app.cupped": {
1337
+ "cssName": "error-glow",
1338
+ "cssValue": "0 0 6px 2px color-mix(in srgb, var(--error) 22%, transparent)"
1339
+ }
1340
+ }
1307
1341
  }
1308
1342
  },
1309
1343
  "hit-target": {
@@ -77,6 +77,8 @@
77
77
  "shimmer-duration": "1.5s",
78
78
  "motion-reduced": "120ms",
79
79
  "focus-ring": "0 0 0 2px #FFFFFF, 0 0 0 4px #C05539",
80
+ "focus-glow": "0 0 6px 2px rgba(192, 85, 57, 0.22)",
81
+ "error-glow": "0 0 6px 2px rgba(239, 68, 68, 0.22)",
80
82
  "hit-target-min": "44px",
81
83
  "radius-sm": "8px",
82
84
  "radius-md": "12px",
@@ -271,6 +271,16 @@ const tokens = {
271
271
  "innerColor": "#FFFFFF",
272
272
  "color": "#C05539"
273
273
  },
274
+ "focusGlow": {
275
+ "color": "rgba(192, 85, 57, 0.22)",
276
+ "blur": 6,
277
+ "spread": 2
278
+ },
279
+ "errorGlow": {
280
+ "color": "rgba(239, 68, 68, 0.22)",
281
+ "blur": 6,
282
+ "spread": 2
283
+ },
274
284
  "material": {
275
285
  "chromeBg": "rgba(255, 255, 255, 0.95)",
276
286
  "scrim": "rgba(15, 23, 42, 0.45)",
@@ -271,6 +271,16 @@ export declare const tokens: {
271
271
  readonly "innerColor": "#FFFFFF";
272
272
  readonly "color": "#C05539";
273
273
  };
274
+ readonly "focusGlow": {
275
+ readonly "color": "rgba(192, 85, 57, 0.22)";
276
+ readonly "blur": 6;
277
+ readonly "spread": 2;
278
+ };
279
+ readonly "errorGlow": {
280
+ readonly "color": "rgba(239, 68, 68, 0.22)";
281
+ readonly "blur": 6;
282
+ readonly "spread": 2;
283
+ };
274
284
  readonly "material": {
275
285
  readonly "chromeBg": "rgba(255, 255, 255, 0.95)";
276
286
  readonly "scrim": "rgba(15, 23, 42, 0.45)";
@@ -270,6 +270,16 @@ export const tokens = {
270
270
  "innerColor": "#FFFFFF",
271
271
  "color": "#C05539"
272
272
  },
273
+ "focusGlow": {
274
+ "color": "rgba(192, 85, 57, 0.22)",
275
+ "blur": 6,
276
+ "spread": 2
277
+ },
278
+ "errorGlow": {
279
+ "color": "rgba(239, 68, 68, 0.22)",
280
+ "blur": 6,
281
+ "spread": 2
282
+ },
273
283
  "material": {
274
284
  "chromeBg": "rgba(255, 255, 255, 0.95)",
275
285
  "scrim": "rgba(15, 23, 42, 0.45)",
@@ -143,6 +143,8 @@
143
143
  --shimmer-duration: 1.5s;
144
144
  --motion-reduced: 120ms;
145
145
  --focus-ring: 0 0 0 2px #FFFFFF, 0 0 0 4px #C05539;
146
+ --focus-glow: 0 0 6px 2px rgba(192, 85, 57, 0.22);
147
+ --error-glow: 0 0 6px 2px rgba(239, 68, 68, 0.22);
146
148
  --hit-target-min: 44px;
147
149
  --material-chrome-bg: rgba(255, 255, 255, 0.95);
148
150
  --material-chrome-blur: saturate(1.4) blur(20px);
package/docs/expo.md CHANGED
@@ -7,15 +7,7 @@ default). The top-level `main`/`types` fallback covers SDK 52. The
7
7
 
8
8
  ## Install
9
9
 
10
- **Primary GitHub Packages.** Map the `@cupped` scope to GitHub Packages in
11
- the app's `.npmrc`:
12
-
13
- ```ini
14
- # .npmrc
15
- @cupped:registry=https://npm.pkg.github.com
16
- # TODO: this registry needs a read token. Add the auth line for your setup, e.g.
17
- # //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
18
- ```
10
+ Published **public on npm** no registry config, no token, no `.npmrc`:
19
11
 
20
12
  ```bash
21
13
  npm install @cupped/tokens
@@ -23,8 +15,8 @@ npm install @cupped/tokens
23
15
 
24
16
  Pin the version in `package.json` as usual (`"@cupped/tokens": "^0.1.0"`).
25
17
 
26
- **Fallback — git dependency (no registry auth).** Because `dist/` is committed,
27
- the package also installs straight from git with no `.npmrc` / token setup:
18
+ **Fallback — git dependency.** Because `dist/` is committed, the package also
19
+ installs straight from git (e.g. to pin an unpublished commit):
28
20
 
29
21
  ```bash
30
22
  npm install github:ybird-labs/cupped-design-system#v0.1.0
package/docs/phoenix.md CHANGED
@@ -38,14 +38,15 @@ gh release download "v${CUPPED_TOKENS_VERSION}" \
38
38
  (`gh release download` needs an authenticated `gh` / token with read access to
39
39
  the package repo. The filenames are stable: `cupped-tokens-<version>.<target>.css`.)
40
40
 
41
- ## Mode 1 — npm git dependency
41
+ ## Mode 1 — npm dependency
42
42
 
43
- Create `assets/package.json` (or add to it):
43
+ Create `assets/package.json` (or add to it) — `@cupped/tokens` is public on npm,
44
+ so no registry auth is needed:
44
45
 
45
46
  ```jsonc
46
47
  {
47
48
  "dependencies": {
48
- "@cupped/tokens": "github:ybird-labs/cupped-design-system#v0.1.0"
49
+ "@cupped/tokens": "^0.1.0"
49
50
  }
50
51
  }
51
52
  ```
package/docs/releasing.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # Releasing @cupped/tokens
2
2
 
3
- The package is published to **GitHub Packages** (npm registry
4
- `https://npm.pkg.github.com`, `@cupped` scope, `restricted` access) under
5
- [`ybird-labs/cupped-design-system`](https://github.com/ybird-labs/cupped-design-system).
6
- Releases are driven by [Changesets](https://github.com/changesets/changesets);
7
- the [`release.yml`](../.github/workflows/release.yml) workflow does the publish
8
- and attaches versioned CSS to the GitHub Release.
3
+ The package is published **public on npm** as
4
+ [`@cupped/tokens`](https://www.npmjs.com/package/@cupped/tokens) consumers
5
+ install it with no auth. Releases are driven by
6
+ [Changesets](https://github.com/changesets/changesets); the
7
+ [`release.yml`](../.github/workflows/release.yml) workflow does the publish
8
+ (with provenance) and attaches versioned CSS to the GitHub Release.
9
9
 
10
10
  For *what* bump to choose, see [versioning.md](./versioning.md).
11
11
 
@@ -35,7 +35,7 @@ changesets/action sees pending changesets
35
35
  release.yml runs again → no pending changesets, version not yet published
36
36
 
37
37
  ▼ changesets/action runs `npm run release` (changeset publish)
38
- changeset publish → publishes to GitHub Packages + creates the git tag vX.Y.Z
38
+ changeset publish → publishes to npm (public) + creates the git tag vX.Y.Z
39
39
  the changesets/action wrapper then creates the matching GitHub Release
40
40
 
41
41
  ▼ upload step attaches the three CSS files to Release vX.Y.Z:
@@ -47,47 +47,39 @@ changeset publish → publishes to GitHub Packages + creates the git tag vX.Y.Z
47
47
  You never bump `package.json` or write `CHANGELOG.md` by hand — merging the
48
48
  Version Packages PR does it.
49
49
 
50
- ## One-time first publish (manual)
51
-
52
- The very first publish of a brand-new scoped package usually **cannot** be done
53
- by the workflow's `GITHUB_TOKEN`, because creating a new package under the org
54
- typically requires a personal access token. Do this once:
55
-
56
- 1. Create a **fine-grained PAT** scoped to just `ybird-labs/cupped-design-system`
57
- with **Packages: write** and a short expiry. (A classic PAT with
58
- `write:packages` works too, but it is org-wide and long-lived — prefer
59
- fine-grained.)
60
- 2. Point npm at GitHub Packages for the scope, and pass the token via an env
61
- var so it is never persisted to disk:
62
- ```bash
63
- echo "@cupped:registry=https://npm.pkg.github.com" >> ~/.npmrc
64
- echo '//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}' >> ~/.npmrc
65
- export NODE_AUTH_TOKEN=YOUR_PAT # this shell only — not written to ~/.npmrc
66
- ```
67
- 3. From a clean checkout on the released version:
68
- ```bash
69
- npm ci
70
- npm run check
71
- npm publish # prepublishOnly rebuilds dist/ first
72
- ```
73
-
74
- After the package exists, the workflow's `GITHUB_TOKEN` can publish every
75
- subsequent release — no PAT needed in CI.
76
-
77
- ## Granting consumer repos read access
78
-
79
- Consuming repos in the same org need **read** access to the package:
80
-
81
- - In the package settings on GitHub → *Package settings* → *Manage Actions
82
- access* / *Manage access*, grant the consumer repos (e.g. the Phoenix and
83
- Expo apps) read access.
84
- - Consumers then authenticate with a token that has `read:packages`. (Consumer
85
- `.npmrc` setup is documented per-app — see [expo.md](./expo.md). **TODO:**
86
- finalize and document the consumer read-token model.)
50
+ ## CI setup for automated releases (one-time)
51
+
52
+ Automated releases publish to **public npm** via the Release workflow. Two
53
+ one-time setups in repo settings:
54
+
55
+ 1. **`NPM_TOKEN` secret** — create an npm **Automation** token (npmjs.com →
56
+ Access Tokens Generate New Token *Automation*; it bypasses 2FA) on an
57
+ account that owns the `@cupped` npm org, and add it as the repo secret
58
+ **`NPM_TOKEN`** (Settings Secrets and variables Actions). The workflow
59
+ passes it as `NODE_AUTH_TOKEN`.
60
+ 2. **Allow Actions to open PRs** Settings Actions General Workflow
61
+ permissions check *"Allow GitHub Actions to create and approve pull
62
+ requests"* (needed for the Changesets "Version Packages" PR).
63
+
64
+ The workflow also requests `id-token: write`, so npm attaches build
65
+ **provenance** to each public release.
66
+
67
+ ## First publish (already done for 0.1.0)
68
+
69
+ `0.1.0` was published manually once to bootstrap the package:
70
+
71
+ ```bash
72
+ npm login # browser auth to npmjs (owner of the @cupped org)
73
+ npm ci && npm run check
74
+ npm publish # public; prepublishOnly rebuilds dist/ first
75
+ ```
76
+
77
+ Every subsequent release is automated — no manual publish.
87
78
 
88
79
  ## Verifying a published release
89
80
 
90
- - The package version appears under the repo's *Packages*.
91
- - The GitHub Release `vX.Y.Z` exists with the three `cupped-tokens-X.Y.Z.*.css`
92
- assets attached.
81
+ - The version is on npm: <https://www.npmjs.com/package/@cupped/tokens>
82
+ (or `npm view @cupped/tokens version`). Consumers install with no auth.
83
+ - The GitHub Release `vX.Y.Z` exists with the `cupped-tokens-X.Y.Z.*.css`
84
+ assets (+ `.sha256`) attached.
93
85
  - `CHANGELOG.md` has an entry for the version.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cupped/tokens",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Cupped design tokens — canonical DTCG source with generated CSS custom properties, Tailwind v4 @theme, React Native theme object, and raw JSON.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -40,6 +40,36 @@
40
40
  ],
41
41
  "$description": "Opaque dual focus ring (refreshed export) — ≥3:1 non-text contrast on any surface. Outline is never removed without a replacement.",
42
42
  "$extensions": { "app.cupped": { "innerWidthPx": 2, "outerWidthPx": 2 } }
43
+ },
44
+ "glow": {
45
+ "$type": "shadow",
46
+ "$value": {
47
+ "offsetX": "0px",
48
+ "offsetY": "0px",
49
+ "blur": "6px",
50
+ "spread": "2px",
51
+ "color": "rgba(192, 85, 57, 0.22)"
52
+ },
53
+ "$description": "Decorative input focus halo — 6px blur, 2px spread, primary-strong @ 22%. The primary-strong border carries the ≥3:1 contrast; this glow is decorative. Buttons keep the two-tone focus ring.",
54
+ "$extensions": { "app.cupped": {
55
+ "cssName": "focus-glow",
56
+ "cssValue": "0 0 6px 2px color-mix(in srgb, var(--primary-strong) 22%, transparent)"
57
+ } }
58
+ },
59
+ "errorGlow": {
60
+ "$type": "shadow",
61
+ "$value": {
62
+ "offsetX": "0px",
63
+ "offsetY": "0px",
64
+ "blur": "6px",
65
+ "spread": "2px",
66
+ "color": "rgba(239, 68, 68, 0.22)"
67
+ },
68
+ "$description": "Decorative input error halo — 6px blur, 2px spread, error @ 22%. Pairs with the error border; shown whether or not the field is focused.",
69
+ "$extensions": { "app.cupped": {
70
+ "cssName": "error-glow",
71
+ "cssValue": "0 0 6px 2px color-mix(in srgb, var(--error) 22%, transparent)"
72
+ } }
43
73
  }
44
74
  },
45
75
  "hit-target": {