@justai/cuts 0.133.0 → 0.138.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.
@@ -217,64 +217,68 @@ const chrome = face === "page";
217
217
  </div>
218
218
 
219
219
  <style>
220
- /* THE SHELL, scoped under `.pp` — the 26 rules files, me and feed each carried verbatim.
221
- *
222
- * They are written here at the SAME depth the children wrote them, and that is deliberate: a
223
- * child's own `.pp .x` rule ties with this one and wins on order, so a child that needs to differ
224
- * still can, without !important and without knowing anything about this file. */
220
+ /* Scoped by Astro's hash AND placed in the cascade: a persona overriding this never has to
221
+ out-specify it. See @justai/ui/tokens.css for the order, and for what deliberately stays out. */
222
+ @layer justai.components {
223
+ /* THE SHELL, scoped under `.pp` — the 26 rules files, me and feed each carried verbatim.
224
+ *
225
+ * They are written here at the SAME depth the children wrote them, and that is deliberate: a
226
+ * child's own `.pp .x` rule ties with this one and wins on order, so a child that needs to differ
227
+ * still can, without !important and without knowing anything about this file. */
225
228
 
226
- .pp { min-height: 100vh; min-height: 100dvh; } /* the pair survives — see @justai/astro-config */
227
- .pp a { color: inherit; text-decoration: none; }
229
+ .pp { min-height: 100vh; min-height: 100dvh; } /* the pair survives — see @justai/astro-config */
230
+ .pp a { color: inherit; text-decoration: none; }
228
231
 
229
- /* the column — the fleet's one presentation at every width */
230
- .pp .app {
231
- /* Set here AND read with a fallback, which is not belt-and-braces: a consumer that overrides
232
- `.pp .app` — same selector, later stylesheet — drops the declaration and keeps the reads, and
233
- an unresolved var() invalidates the whole `padding-inline`. The cut brings its own ground
234
- (law 1) means the ground has to survive being partly overwritten. */
235
- --gutter: 16px;
236
- display: flex; flex-direction: column;
237
- padding-inline: max(var(--gutter, 16px), env(safe-area-inset-left)) max(var(--gutter, 16px), env(safe-area-inset-right));
238
- }
239
- /* 640 is the fleet's one threshold and the fleet's one column width — @justai/ui declares
240
- `--column`, and a media query cannot read a custom property, so the NUMBER has to be written
241
- again here. Written again is not decided again: if this ever disagrees with base.css, this is
242
- the copy that is wrong. */
243
- @media (min-width: 640px) {
232
+ /* the column — the fleet's one presentation at every width */
244
233
  .pp .app {
245
- --gutter: 28px;
246
- max-width: 640px;
247
- max-width: var(--column, 640px);
248
- margin-left: auto; margin-right: auto; margin-inline: auto;
249
- padding-left: 28px; padding-right: 28px; padding-inline: var(--gutter, 28px);
250
- border-left: 1px solid var(--border); border-right: 1px solid var(--border);
251
- border-inline: 1px solid var(--border); background: var(--surface);
252
- backdrop-filter: blur(40px) saturate(1.4); -webkit-backdrop-filter: blur(40px) saturate(1.4);
234
+ /* Set here AND read with a fallback, which is not belt-and-braces: a consumer that overrides
235
+ `.pp .app` — same selector, later stylesheet — drops the declaration and keeps the reads, and
236
+ an unresolved var() invalidates the whole `padding-inline`. The cut brings its own ground
237
+ (law 1) means the ground has to survive being partly overwritten. */
238
+ --gutter: 16px;
239
+ display: flex; flex-direction: column;
240
+ padding-inline: max(var(--gutter, 16px), env(safe-area-inset-left)) max(var(--gutter, 16px), env(safe-area-inset-right));
241
+ }
242
+ /* 640 is the fleet's one threshold and the fleet's one column width — @justai/ui declares
243
+ `--column`, and a media query cannot read a custom property, so the NUMBER has to be written
244
+ again here. Written again is not decided again: if this ever disagrees with base.css, this is
245
+ the copy that is wrong. */
246
+ @media (min-width: 640px) {
247
+ .pp .app {
248
+ --gutter: 28px;
249
+ max-width: 640px;
250
+ max-width: var(--column, 640px);
251
+ margin-left: auto; margin-right: auto; margin-inline: auto;
252
+ padding-left: 28px; padding-right: 28px; padding-inline: var(--gutter, 28px);
253
+ border-left: 1px solid var(--border); border-right: 1px solid var(--border);
254
+ border-inline: 1px solid var(--border); background: var(--surface);
255
+ backdrop-filter: blur(40px) saturate(1.4); -webkit-backdrop-filter: blur(40px) saturate(1.4);
256
+ }
253
257
  }
254
- }
255
258
 
256
- .pp .below { position: relative; }
257
- /* the sticky row means a section reached by its id arrives UNDER it unless it says otherwise */
258
- /* The landing offset is `--shell-top` and only that — measured by the shell, published on :root,
259
- enforced by personaScroll. This line carried a literal 82px through THREE fleet-wide sweeps of
260
- exactly that constant (base.css, Posts.astro, eight persona repos) and surfaced only when the
261
- children's console started warning about it: a scoped rule inside a cut is where a stale number
262
- hides longest, because nobody greps a component for a layout constant. */
263
- .pp #profile, .pp #kernel { scroll-margin-top: var(--shell-top, 78px); }
259
+ .pp .below { position: relative; }
260
+ /* the sticky row means a section reached by its id arrives UNDER it unless it says otherwise */
261
+ /* The landing offset is `--shell-top` and only that — measured by the shell, published on :root,
262
+ enforced by personaScroll. This line carried a literal 82px through THREE fleet-wide sweeps of
263
+ exactly that constant (base.css, Posts.astro, eight persona repos) and surfaced only when the
264
+ children's console started warning about it: a scoped rule inside a cut is where a stale number
265
+ hides longest, because nobody greps a component for a layout constant. */
266
+ .pp #profile, .pp #kernel { scroll-margin-top: var(--shell-top, 78px); }
264
267
 
265
- /* THE `sheet` FACE: no column, no ground, no chrome. The sheet around it IS the frame, and a
266
- second frame inside one is a box in a box. Keyed on the face name rather than on a boolean, so a
267
- third face styles itself by adding a selector instead of by renaming this one. */
268
- .pp[data-face="sheet"] { min-height: 0; }
269
- .pp[data-face="sheet"] .app { max-width: none; margin-inline: 0; border: 0; background: none;
270
- backdrop-filter: none; -webkit-backdrop-filter: none; }
268
+ /* THE `sheet` FACE: no column, no ground, no chrome. The sheet around it IS the frame, and a
269
+ second frame inside one is a box in a box. Keyed on the face name rather than on a boolean, so a
270
+ third face styles itself by adding a selector instead of by renaming this one. */
271
+ .pp[data-face="sheet"] { min-height: 0; }
272
+ .pp[data-face="sheet"] .app { max-width: none; margin-inline: 0; border: 0; background: none;
273
+ backdrop-filter: none; -webkit-backdrop-filter: none; }
271
274
 
272
- /* a mark fills its box; the picture inside it covers */
273
- .pp .mk { display: block; width: 100%; height: 100%; object-fit: cover; }
275
+ /* a mark fills its box; the picture inside it covers */
276
+ .pp .mk { display: block; width: 100%; height: 100%; object-fit: cover; }
274
277
 
275
- /* Motion is never forced on somebody who asked it not to be — and the biggest effects, the ones a
276
- surface is proudest of, are exactly what that setting exists to suppress. */
277
- @media (prefers-reduced-motion: reduce) {
278
- .pp *, .pp *::before, .pp *::after { animation: none !important; transition: none !important; }
278
+ /* Motion is never forced on somebody who asked it not to be — and the biggest effects, the ones a
279
+ surface is proudest of, are exactly what that setting exists to suppress. */
280
+ @media (prefers-reduced-motion: reduce) {
281
+ .pp *, .pp *::before, .pp *::after { animation: none !important; transition: none !important; }
282
+ }
279
283
  }
280
284
  </style>
@@ -87,72 +87,76 @@ const { id, title, body, actionLabel, cancelLabel, destructive = false, ask = fa
87
87
  </dialog>
88
88
 
89
89
  <style>
90
- /* The dialog sits in the browser's top layer — above every z-index in every persona, by law. */
91
- .confirm {
92
- /* dock to the bottom like an iOS sheet (native <dialog> centers by default) */
93
- margin: auto auto max(12px, env(safe-area-inset-bottom));
94
- width: min(400px, calc(100vw - 24px));
95
- padding: 0;
96
- border: 0;
97
- background: none;
98
- overflow: visible;
99
- }
100
- .confirm::backdrop {
101
- background: rgba(0, 0, 0, 0.4);
102
- backdrop-filter: blur(4px);
103
- -webkit-backdrop-filter: blur(4px);
104
- }
105
- .confirm[open] .confirm-form { animation: confirm-up 0.4s var(--ease-spring) both; }
106
- .confirm[open]::backdrop { animation: confirm-fade 0.2s var(--ease-out) both; }
90
+ /* Scoped by Astro's hash AND placed in the cascade: a persona overriding this never has to
91
+ out-specify it. See @justai/ui/tokens.css for the order, and for what deliberately stays out. */
92
+ @layer justai.components {
93
+ /* The dialog sits in the browser's top layer — above every z-index in every persona, by law. */
94
+ .confirm {
95
+ /* dock to the bottom like an iOS sheet (native <dialog> centers by default) */
96
+ margin: auto auto max(12px, env(safe-area-inset-bottom));
97
+ width: min(400px, calc(100vw - 24px));
98
+ padding: 0;
99
+ border: 0;
100
+ background: none;
101
+ overflow: visible;
102
+ }
103
+ .confirm::backdrop {
104
+ background: rgba(0, 0, 0, 0.4);
105
+ backdrop-filter: blur(4px);
106
+ -webkit-backdrop-filter: blur(4px);
107
+ }
108
+ .confirm[open] .confirm-form { animation: confirm-up 0.4s var(--ease-spring) both; }
109
+ .confirm[open]::backdrop { animation: confirm-fade 0.2s var(--ease-out) both; }
107
110
 
108
- .confirm-form { display: flex; flex-direction: column; gap: 8px; }
109
- .confirm-group {
110
- border-radius: 18px; overflow: hidden; border: 1px solid var(--border);
111
- /* Apple's THICK material, deliberately: at 80% the kernel behind ghosted through the card
112
- (founder-caught on the QR grid). The frosted feel comes from the blur, not from letting
113
- content bleed into the words — a sheet asking for a decision must be legible first. */
114
- background: color-mix(in srgb, var(--bg) 93%, transparent);
115
- backdrop-filter: blur(30px) saturate(1.5); -webkit-backdrop-filter: blur(30px) saturate(1.5);
116
- }
117
- .confirm-msg { padding: 18px 18px 16px; text-align: center; display: flex; flex-direction: column; gap: 5px; }
118
- .confirm-msg strong { font-size: var(--fs-md); font-weight: 700; letter-spacing: -0.01em; color: var(--text); }
119
- .confirm-msg span { font-size: var(--fs-xs); color: var(--text-dim); line-height: 1.45; }
120
- /* a title-less sheet's body is not supporting text under a headline — it IS the voice AND the
121
- title, so it speaks in the primary colour at the native body size (founder: the question
122
- deserves reading size, not caption size) */
123
- .confirm-msg span:only-child { color: var(--text); font-size: var(--fs-base); line-height: 1.5; }
124
- .confirm-act {
125
- width: 100%; padding: 15px; border: 0; border-block-start: 1px solid var(--border);
126
- /* --confirm-accent: the declared hook for personas whose accent is too BRIGHT to read as text
127
- on the frosted card in light mode (cleanmylink's mint, howlong's amber, whatismyip's mint —
128
- the same three that once hooked the old filled consent button). They set a theme-aware
129
- darker tone of their own hue; everyone else says nothing. */
130
- background: none; font: 600 var(--fs-md) var(--font, inherit); color: var(--confirm-accent, var(--accent)); cursor: pointer;
131
- transition: background 0.15s;
132
- }
133
- .confirm-act:active { background: color-mix(in srgb, var(--text) 9%, transparent); }
134
- /* focus is a wash, not a ring: the fleet's 4px focus ring gets CLIPPED by the card's
135
- overflow:hidden into two stray bars — inside a sheet, the focused button fills with a
136
- whisper of its own colour instead (currentColor: accent, red, or text alike) */
137
- .confirm-act:focus-visible { box-shadow: none; outline: none; background: color-mix(in srgb, currentColor 12%, transparent); }
138
- /* iOS system red — the one colour that means "this ends something" on every persona alike */
139
- .confirm-act.destructive { color: var(--danger); }
140
- /* the ASK genre: the invitation is the preferred path (Apple's bold preferredAction)… */
141
- .confirm-act.preferred { font-weight: 700; }
142
- /* …and the safe choice answers in a calm, full-contrast regular voice — equal size, equal tap,
143
- never dimmed (a decline you have to hunt for is not a free choice), just not the emphasis */
144
- .confirm-act.quiet { color: var(--text); font-weight: 400; }
145
- /* the safe choice wears the boldest face, in its own detached card */
146
- .confirm-cancel {
147
- border: 1px solid var(--border); border-radius: 18px;
148
- background: color-mix(in srgb, var(--bg) 93%, transparent);
149
- backdrop-filter: blur(30px) saturate(1.5); -webkit-backdrop-filter: blur(30px) saturate(1.5);
150
- font-weight: 700; color: var(--text);
151
- }
152
- @keyframes confirm-up { from { transform: translateY(20px); opacity: 0; } }
153
- @keyframes confirm-fade { from { opacity: 0; } }
154
- @media (prefers-reduced-motion: reduce) {
155
- .confirm[open] .confirm-form, .confirm[open]::backdrop { animation: none; }
111
+ .confirm-form { display: flex; flex-direction: column; gap: 8px; }
112
+ .confirm-group {
113
+ border-radius: 18px; overflow: hidden; border: 1px solid var(--border);
114
+ /* Apple's THICK material, deliberately: at 80% the kernel behind ghosted through the card
115
+ (founder-caught on the QR grid). The frosted feel comes from the blur, not from letting
116
+ content bleed into the words — a sheet asking for a decision must be legible first. */
117
+ background: color-mix(in srgb, var(--bg) 93%, transparent);
118
+ backdrop-filter: blur(30px) saturate(1.5); -webkit-backdrop-filter: blur(30px) saturate(1.5);
119
+ }
120
+ .confirm-msg { padding: 18px 18px 16px; text-align: center; display: flex; flex-direction: column; gap: 5px; }
121
+ .confirm-msg strong { font-size: var(--fs-md); font-weight: 700; letter-spacing: -0.01em; color: var(--text); }
122
+ .confirm-msg span { font-size: var(--fs-xs); color: var(--text-dim); line-height: 1.45; }
123
+ /* a title-less sheet's body is not supporting text under a headline — it IS the voice AND the
124
+ title, so it speaks in the primary colour at the native body size (founder: the question
125
+ deserves reading size, not caption size) */
126
+ .confirm-msg span:only-child { color: var(--text); font-size: var(--fs-base); line-height: 1.5; }
127
+ .confirm-act {
128
+ width: 100%; padding: 15px; border: 0; border-block-start: 1px solid var(--border);
129
+ /* --confirm-accent: the declared hook for personas whose accent is too BRIGHT to read as text
130
+ on the frosted card in light mode (cleanmylink's mint, howlong's amber, whatismyip's mint —
131
+ the same three that once hooked the old filled consent button). They set a theme-aware
132
+ darker tone of their own hue; everyone else says nothing. */
133
+ background: none; font: 600 var(--fs-md) var(--font, inherit); color: var(--confirm-accent, var(--accent)); cursor: pointer;
134
+ transition: background 0.15s;
135
+ }
136
+ .confirm-act:active { background: color-mix(in srgb, var(--text) 9%, transparent); }
137
+ /* focus is a wash, not a ring: the fleet's 4px focus ring gets CLIPPED by the card's
138
+ overflow:hidden into two stray bars — inside a sheet, the focused button fills with a
139
+ whisper of its own colour instead (currentColor: accent, red, or text alike) */
140
+ .confirm-act:focus-visible { box-shadow: none; outline: none; background: color-mix(in srgb, currentColor 12%, transparent); }
141
+ /* iOS system red — the one colour that means "this ends something" on every persona alike */
142
+ .confirm-act.destructive { color: var(--danger); }
143
+ /* the ASK genre: the invitation is the preferred path (Apple's bold preferredAction)… */
144
+ .confirm-act.preferred { font-weight: 700; }
145
+ /* …and the safe choice answers in a calm, full-contrast regular voice — equal size, equal tap,
146
+ never dimmed (a decline you have to hunt for is not a free choice), just not the emphasis */
147
+ .confirm-act.quiet { color: var(--text); font-weight: 400; }
148
+ /* the safe choice wears the boldest face, in its own detached card */
149
+ .confirm-cancel {
150
+ border: 1px solid var(--border); border-radius: 18px;
151
+ background: color-mix(in srgb, var(--bg) 93%, transparent);
152
+ backdrop-filter: blur(30px) saturate(1.5); -webkit-backdrop-filter: blur(30px) saturate(1.5);
153
+ font-weight: 700; color: var(--text);
154
+ }
155
+ @keyframes confirm-up { from { transform: translateY(20px); opacity: 0; } }
156
+ @keyframes confirm-fade { from { opacity: 0; } }
157
+ @media (prefers-reduced-motion: reduce) {
158
+ .confirm[open] .confirm-form, .confirm[open]::backdrop { animation: none; }
159
+ }
156
160
  }
157
161
  </style>
158
162