@justai/cuts 0.138.0 → 0.140.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.
@@ -27,13 +27,18 @@
27
27
  const locale = document.documentElement.lang || "en";
28
28
  const NEW_MS = 7 * 864e5; // a persona wears NEW only for its first week (honest — from its createdAt)
29
29
  const newLabel = grid.dataset.newLabel || "New";
30
- // queries: which personas appear on this apex · runtime append kind · why is my game not listed
30
+ // queries: which personas appear on this apex · runtime append kind · why is my game not listed ·
31
+ // a CiM persona is missing from its storefront · apex query parameter · mixed-kind apex
31
32
  //
32
33
  // WHICH CITIZENS THIS APEX LISTS — where the hostname test used to be. A bundled script cannot
33
- // reach this component's props, so the two facts it needs ride the same data-attribute channel as
34
- // newLabel and catMap. And `kind` is asked of the registry rather than inferred from a suffix: a
35
- // persona's apex is a property of the persona, not a shape of its address.
36
- const apexKind = grid.dataset.kind || "app";
34
+ // reach this component's props, so the fact it needs rides the same data-attribute channel as
35
+ // newLabel and catMap.
36
+ //
37
+ // IT ASKS THE REGISTRY BY HOUSEHOLD, NOT BY SPECIES, and the difference only became visible with a
38
+ // third apex. `kind` selects a SORT of persona; on app.zone and pin.games that happens to select
39
+ // the residents too, because every citizen of those two is the same sort. cityinmotion.nl is the
40
+ // household where they part — its citizens are sometimes games and sometimes apps — and asking it
41
+ // for one kind returns half a family and calls that the storefront.
37
42
  const apexHost = grid.dataset.apex || "app.zone";
38
43
  // The category label must be localized like the SSR path (catLabel), but the API returns the
39
44
  // category in English regardless of ?locale=. So carry this page's cat dictionary through a data
@@ -93,13 +98,16 @@
93
98
  return `<a class="card" href="${esc(url)}" data-name="${esc(name.toLowerCase())}" data-category="${esc(cat)}" data-id="${esc((a.handle || "").toLowerCase())}" data-added="${esc(a.createdAt || "")}">${icon}<div class="meta"><div class="top"><h2>${esc(name)}</h2>${badge}</div><p>${esc(tagMap[a.home] ?? a.bio ?? "")}</p></div><svg class="chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18l6-6-6-6"/></svg></a>`;
94
99
  };
95
100
 
96
- fetch(`https://api.justai.pro/api/catalog?locale=${encodeURIComponent(locale)}&kind=${encodeURIComponent(apexKind)}`)
101
+ fetch(`https://api.justai.pro/api/catalog?locale=${encodeURIComponent(locale)}&apex=${encodeURIComponent(apexHost)}`)
97
102
  .then((r) => (r.ok ? r.json() : null))
98
103
  .then((data) => {
99
104
  if (!data || !data.personas) return;
100
105
  const newcomers = (data.personas as Entry[]).filter(
101
- // The registry already returned only this apex's kind, so all that is left to exclude is the
102
- // apex itself — a household head does not stand on its own shelf.
106
+ // THE RESIDENCY TEST STAYS, AND IS NOW A REDUNDANCY ON PURPOSE. Asking by apex means the
107
+ // registry has already excluded the other households AND the platform furniture that stands
108
+ // on no shelf, so this filter should never remove anything. It is kept because a browser can
109
+ // be holding a cached response from before the question changed, and because a storefront
110
+ // listing somebody else's citizen is the one failure here that looks fine.
103
111
  //
104
112
  // queries: auth appeared on app.zone · a persona from another apex is listed · why both
105
113
  // kind and home · residency
package/src/ground.css CHANGED
@@ -27,139 +27,135 @@
27
27
  * checks the tokens statically, but only a bare-page render proves it.
28
28
  */
29
29
 
30
- @layer justai.tokens, justai.base, justai.ground;
30
+ :where(:root) {
31
+ /* skeleton — mirrors @justai/ui tokens.css, pinned equal by the harness */
32
+ --fs-2xs: 0.76rem;
33
+ --fs-xs: 0.84rem;
34
+ --fs-sm: 0.92rem;
35
+ --fs-base: 1rem;
36
+ --fs-md: 1.13rem;
37
+ --fs-lg: 1.32rem;
38
+ --fs-xl: 1.6rem;
31
39
 
32
- @layer justai.ground {
33
- :where(:root) {
34
- /* skeleton — mirrors @justai/ui tokens.css, pinned equal by the harness */
35
- --fs-2xs: 0.76rem;
36
- --fs-xs: 0.84rem;
37
- --fs-sm: 0.92rem;
38
- --fs-base: 1rem;
39
- --fs-md: 1.13rem;
40
- --fs-lg: 1.32rem;
41
- --fs-xl: 1.6rem;
40
+ --radius-sm: 10px;
41
+ --radius-md: 14px;
42
+ --radius-lg: 20px;
43
+ --radius-pill: 999px;
42
44
 
43
- --radius-sm: 10px;
44
- --radius-md: 14px;
45
- --radius-lg: 20px;
46
- --radius-pill: 999px;
45
+ --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
46
+ --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
47
47
 
48
- --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
49
- --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
48
+ /* ground — the fleet-neutral defaults for the colour contract (dark first: the fleet's default scheme) */
49
+ --font: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Inter", system-ui, sans-serif;
50
+ --bg: #0a0c10;
51
+ --text: #f2f4f8;
52
+ --text-dim: #9aa3b2;
53
+ --border: rgba(255, 255, 255, 0.1);
54
+ --surface: rgba(255, 255, 255, 0.04);
55
+ --surface-2: rgba(255, 255, 255, 0.07);
56
+ --accent: #0a84ff;
57
+ /* THE INK ON A FILLED ACCENT. Every persona already declares this — the template writes it from
58
+ the accent's own `color.on` — but no cut had rendered a filled accent button until <Preroll>'s
59
+ play surface, so it had never needed a ground and did not have one. It arrives with the same
60
+ lesson the card tokens above carry: a token is not grounded because the fleet defines it, it is
61
+ grounded because THIS package does. The value pairs with the `--accent` on the line above and
62
+ is not scheme-aware, because that accent is not either.
63
+ A persona whose accent is pale — a yellow, an orange — must override it, and every one of them
64
+ already does; white ink on a #f5a623 button is the failure this token exists to prevent. */
65
+ --accent-on: #ffffff;
66
+ /* THE ecosystem's destructive red — Apple's system red, and one of it. Written here rather than
67
+ in each component because a confirmation sheet and a Sign Out row saying "this ends something"
68
+ in two different reds is exactly the drift the ConfirmSheet's own header warns about. It is
69
+ scheme-aware: Apple lightens red on dark backgrounds (#FF453A) and deepens it on light
70
+ (#FF3B30), and a single value would read wrong on one of them. */
71
+ --danger: #ff453a;
50
72
 
51
- /* ground — the fleet-neutral defaults for the colour contract (dark first: the fleet's default scheme) */
52
- --font: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Inter", system-ui, sans-serif;
53
- --bg: #0a0c10;
54
- --text: #f2f4f8;
55
- --text-dim: #9aa3b2;
56
- --border: rgba(255, 255, 255, 0.1);
57
- --surface: rgba(255, 255, 255, 0.04);
58
- --surface-2: rgba(255, 255, 255, 0.07);
59
- --accent: #0a84ff;
60
- /* THE INK ON A FILLED ACCENT. Every persona already declares this — the template writes it from
61
- the accent's own `color.on` — but no cut had rendered a filled accent button until <Preroll>'s
62
- play surface, so it had never needed a ground and did not have one. It arrives with the same
63
- lesson the card tokens above carry: a token is not grounded because the fleet defines it, it is
64
- grounded because THIS package does. The value pairs with the `--accent` on the line above and
65
- is not scheme-aware, because that accent is not either.
66
- A persona whose accent is pale — a yellow, an orange — must override it, and every one of them
67
- already does; white ink on a #f5a623 button is the failure this token exists to prevent. */
68
- --accent-on: #ffffff;
69
- /* THE ecosystem's destructive red — Apple's system red, and one of it. Written here rather than
70
- in each component because a confirmation sheet and a Sign Out row saying "this ends something"
71
- in two different reds is exactly the drift the ConfirmSheet's own header warns about. It is
72
- scheme-aware: Apple lightens red on dark backgrounds (#FF453A) and deepens it on light
73
- (#FF3B30), and a single value would read wrong on one of them. */
74
- --danger: #ff453a;
73
+ /* THE CARD — a storefront tile's own surface, and the reason it is here rather than in the apex
74
+ that used to own it. The Catalogue moved into this package the day pin.games became real, and
75
+ it arrived consuming four tokens that had stayed behind in app.zone's stylesheet. Nothing broke
76
+ on app.zone, which still grounds them; the harness caught it anyway, and the founder put the
77
+ stake in the ground in one line: *why would pin.games have shadowless cards — whatever I see on
78
+ app.zone, that is it.* These are app.zone's own values, read rather than redesigned, so the
79
+ second storefront is the first one. */
80
+ --card-bg: rgba(28, 28, 34, 0.55);
81
+ --card-blur: blur(30px) saturate(1.6);
82
+ --card-shadow-hover: 0 2px 6px rgba(0, 0, 0, 0.4), 0 12px 28px -14px rgba(0, 0, 0, 0.55);
75
83
 
76
- /* THE CARD — a storefront tile's own surface, and the reason it is here rather than in the apex
77
- that used to own it. The Catalogue moved into this package the day pin.games became real, and
78
- it arrived consuming four tokens that had stayed behind in app.zone's stylesheet. Nothing broke
79
- on app.zone, which still grounds them; the harness caught it anyway, and the founder put the
80
- stake in the ground in one line: *why would pin.games have shadowless cards — whatever I see on
81
- app.zone, that is it.* These are app.zone's own values, read rather than redesigned, so the
82
- second storefront is the first one. */
83
- --card-bg: rgba(28, 28, 34, 0.55);
84
- --card-blur: blur(30px) saturate(1.6);
85
- --card-shadow-hover: 0 2px 6px rgba(0, 0, 0, 0.4), 0 12px 28px -14px rgba(0, 0, 0, 0.55);
86
-
87
- /* ── ELEVATION — mirrors @justai/ui tokens.css, and the tokens spec holds the two identical ──
88
- *
89
- * queries: box shadow token · which shadow · the panel looks detached · drop shadow standard ·
90
- * golge standardi · card shadow vs sheet shadow
91
- *
92
- * THE VALUES WERE ALREADY HERE, under one name and one meaning: `--card-shadow`, described as
93
- * "a storefront tile's own surface". Measured across the live fleet on 2026-08-20 they turned out
94
- * to be the shadow of EVERY resting surface — app.zone's catalogue cards, qr's type buttons,
95
- * calculator's panel, all byte-identical — so the name was narrower than the fact, and a persona
96
- * building its own kernel panel had nothing to reach for and invented one. The one that produced
97
- * the ruling had invented a MODAL's: twice the opacity, 3.7× the blur, and in dark deeper than
98
- * the fleet's actual modal.
99
- *
100
- * So the scale is named by ELEVATION — how far off the page a thing is — and the catalogue's own
101
- * token now reads from it rather than restating it. `--card-shadow` stays because live personas
102
- * consume it; it is an alias now, not a second definition.
103
- *
104
- * The law: **nothing that is not a sheet may exceed `--shadow-card`, and no shadow is coloured.**
105
- * A tinted shadow is a glow, a glow is a light source, and this ground has one sun and no lamps. */
106
- --shadow-card: 0 1px 2px 0 rgba(0, 0, 0, 0.32);
107
- --shadow-bar: 0 2px 12px -2px rgba(0, 0, 0, 0.55);
108
- --shadow-sheet: 0 18px 40px -20px rgba(0, 0, 0, 0.45);
109
- --card-shadow: var(--shadow-card);
110
- }
84
+ /* ── ELEVATION — mirrors @justai/ui tokens.css, and the tokens spec holds the two identical ──
85
+ *
86
+ * queries: box shadow token · which shadow · the panel looks detached · drop shadow standard ·
87
+ * golge standardi · card shadow vs sheet shadow
88
+ *
89
+ * THE VALUES WERE ALREADY HERE, under one name and one meaning: `--card-shadow`, described as
90
+ * "a storefront tile's own surface". Measured across the live fleet on 2026-08-20 they turned out
91
+ * to be the shadow of EVERY resting surface — app.zone's catalogue cards, qr's type buttons,
92
+ * calculator's panel, all byte-identical — so the name was narrower than the fact, and a persona
93
+ * building its own kernel panel had nothing to reach for and invented one. The one that produced
94
+ * the ruling had invented a MODAL's: twice the opacity, 3.7× the blur, and in dark deeper than
95
+ * the fleet's actual modal.
96
+ *
97
+ * So the scale is named by ELEVATION — how far off the page a thing is — and the catalogue's own
98
+ * token now reads from it rather than restating it. `--card-shadow` stays because live personas
99
+ * consume it; it is an alias now, not a second definition.
100
+ *
101
+ * The law: **nothing that is not a sheet may exceed `--shadow-card`, and no shadow is coloured.**
102
+ * A tinted shadow is a glow, a glow is a light source, and this ground has one sun and no lamps. */
103
+ --shadow-card: 0 1px 2px 0 rgba(0, 0, 0, 0.32);
104
+ --shadow-bar: 0 2px 12px -2px rgba(0, 0, 0, 0.55);
105
+ --shadow-sheet: 0 18px 40px -20px rgba(0, 0, 0, 0.45);
106
+ --card-shadow: var(--shadow-card);
107
+ }
111
108
 
112
- /* queries: force dark while playing · theme switch · data-theme dark · stage theme
113
- * THE GUARD IS THE FLEET'S OWN IDIOM — time's wall wrote it first: *"data-theme wins over the media
114
- * query in both directions."* A light-system visitor whose page carries data-theme="dark" gets the
115
- * dark theme this file already ships; removing the attribute returns them to auto. The stage
116
- * protocol is the first caster (a game playing = dark), and any future manual theme switch gets
117
- * this seam for free. */
118
- @media (prefers-color-scheme: light) {
119
- :where(:root:not([data-theme="dark"])) {
120
- --bg: #f6f7f9;
121
- --text: #0a0c10;
122
- --text-dim: #5b6472;
123
- --border: rgba(0, 0, 0, 0.08);
124
- --surface: rgba(255, 255, 255, 0.7);
125
- --surface-2: rgba(0, 0, 0, 0.05);
126
- --danger: #ff3b30;
109
+ /* queries: force dark while playing · theme switch · data-theme dark · stage theme
110
+ * THE GUARD IS THE FLEET'S OWN IDIOM — time's wall wrote it first: *"data-theme wins over the media
111
+ * query in both directions."* A light-system visitor whose page carries data-theme="dark" gets the
112
+ * dark theme this file already ships; removing the attribute returns them to auto. The stage
113
+ * protocol is the first caster (a game playing = dark), and any future manual theme switch gets
114
+ * this seam for free. */
115
+ @media (prefers-color-scheme: light) {
116
+ :where(:root:not([data-theme="dark"])) {
117
+ --bg: #f6f7f9;
118
+ --text: #0a0c10;
119
+ --text-dim: #5b6472;
120
+ --border: rgba(0, 0, 0, 0.08);
121
+ --surface: rgba(255, 255, 255, 0.7);
122
+ --surface-2: rgba(0, 0, 0, 0.05);
123
+ --danger: #ff3b30;
127
124
 
128
- /* Light is not the dark card with a different fill: the blur goes away entirely. A frosted
129
- panel over a pale page reads as a smudge, so app.zone drops it and pays the shadow instead. */
130
- --card-bg: #fff;
131
- --card-blur: none;
132
- --card-shadow-hover: 0 2px 4px rgba(16, 24, 40, 0.06), 0 12px 26px -8px rgba(16, 24, 40, 0.15);
125
+ /* Light is not the dark card with a different fill: the blur goes away entirely. A frosted
126
+ panel over a pale page reads as a smudge, so app.zone drops it and pays the shadow instead. */
127
+ --card-bg: #fff;
128
+ --card-blur: none;
129
+ --card-shadow-hover: 0 2px 4px rgba(16, 24, 40, 0.06), 0 12px 26px -8px rgba(16, 24, 40, 0.15);
133
130
 
134
- /* Light needs TWO layers where dark needs one: on a near-white ground a single ambient blur
135
- reads as dirt, so the pair is a 1px contact line that seats the object plus a wide, faint
136
- ambient. `--card-shadow` inherits its alias from the block above and needs no line here. */
137
- --shadow-card: 0 1px 2px 0 rgba(16, 24, 40, 0.05), 0 4px 12px -4px rgba(16, 24, 40, 0.1);
138
- --shadow-bar: 0 1px 2px rgba(16, 24, 40, 0.06), 0 8px 20px -8px rgba(16, 24, 40, 0.16);
139
- --shadow-sheet: 0 18px 40px -20px rgba(0, 0, 0, 0.45);
140
- }
131
+ /* Light needs TWO layers where dark needs one: on a near-white ground a single ambient blur
132
+ reads as dirt, so the pair is a 1px contact line that seats the object plus a wide, faint
133
+ ambient. `--card-shadow` inherits its alias from the block above and needs no line here. */
134
+ --shadow-card: 0 1px 2px 0 rgba(16, 24, 40, 0.05), 0 4px 12px -4px rgba(16, 24, 40, 0.1);
135
+ --shadow-bar: 0 1px 2px rgba(16, 24, 40, 0.06), 0 8px 20px -8px rgba(16, 24, 40, 0.16);
136
+ --shadow-sheet: 0 18px 40px -20px rgba(0, 0, 0, 0.45);
141
137
  }
138
+ }
142
139
 
143
- /* The page's ground — margin reset, the fleet's type baseline, and the page colour ON :where(html),
144
- * NEVER on body. Learned live, fleet-wide, within a day: personas paint their dot-grid on html and
145
- * leave body's background UNDECLARED — relying on the web's default transparency structurally, not
146
- * by declaration. Zero specificity beats a declaration that exists; it still fills one that never
147
- * did. A body background from the ground, with the fleet's `body{height:100%}`, painted an opaque
148
- * viewport-tall box over the top of every grid — dots gone at the top, reappearing where body ends.
149
- * The founder saw it before any instrument did. The rule that survives: THE GROUND MAY ONLY SUPPLY
150
- * WHAT A BARE PAGE LACKS ON ELEMENTS CONSUMERS DECLARE — html's background is declared by every
151
- * persona (they win); body's transparency is load-bearing and stays untouched. */
152
- /* What the shell takes above the kernel — see @justai/ui base.css, which measures it. Ground only:
153
- `:where()` is specificity zero, so a consumer that ships the shell always wins, and a surface that
154
- ships no shell still lands its cuts clear of a sticky bar instead of under one. */
155
- :where(:root) { --shell-top: 78px; }
140
+ /* The page's ground — margin reset, the fleet's type baseline, and the page colour ON :where(html),
141
+ * NEVER on body. Learned live, fleet-wide, within a day: personas paint their dot-grid on html and
142
+ * leave body's background UNDECLARED — relying on the web's default transparency structurally, not
143
+ * by declaration. Zero specificity beats a declaration that exists; it still fills one that never
144
+ * did. A body background from the ground, with the fleet's `body{height:100%}`, painted an opaque
145
+ * viewport-tall box over the top of every grid — dots gone at the top, reappearing where body ends.
146
+ * The founder saw it before any instrument did. The rule that survives: THE GROUND MAY ONLY SUPPLY
147
+ * WHAT A BARE PAGE LACKS ON ELEMENTS CONSUMERS DECLARE — html's background is declared by every
148
+ * persona (they win); body's transparency is load-bearing and stays untouched. */
149
+ /* What the shell takes above the kernel — see @justai/ui base.css, which measures it. Ground only:
150
+ `:where()` is specificity zero, so a consumer that ships the shell always wins, and a surface that
151
+ ships no shell still lands its cuts clear of a sticky bar instead of under one. */
152
+ :where(:root) { --shell-top: 78px; }
156
153
 
157
- :where(html) {
158
- background: var(--bg);
159
- }
160
- :where(body) {
161
- margin: 0;
162
- color: var(--text);
163
- font: 1rem/1.5 var(--font);
164
- }
154
+ :where(html) {
155
+ background: var(--bg);
156
+ }
157
+ :where(body) {
158
+ margin: 0;
159
+ color: var(--text);
160
+ font: 1rem/1.5 var(--font);
165
161
  }