@justai/cuts 0.138.0 → 0.139.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.
- package/package.json +2 -2
- package/src/ActionList.astro +27 -31
- package/src/ActionRow.astro +64 -68
- package/src/BackToZone.astro +35 -39
- package/src/Catalogue.astro +83 -87
- package/src/CatalogueCard.astro +68 -72
- package/src/ChildPersona.astro +51 -55
- package/src/ConfirmSheet.astro +65 -69
- package/src/FilePicker.astro +13 -17
- package/src/LangPicker.astro +41 -45
- package/src/Posts.astro +169 -173
- package/src/Preroll.astro +193 -197
- package/src/Profile.astro +35 -39
- package/src/Segmented.astro +54 -58
- package/src/Sheet.astro +212 -237
- package/src/Toolbar.astro +280 -284
- package/src/ground.css +120 -124
package/src/CatalogueCard.astro
CHANGED
|
@@ -46,79 +46,75 @@ const icon = `${app.url}/icon.svg`;
|
|
|
46
46
|
</a>
|
|
47
47
|
|
|
48
48
|
<style>
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
box-shadow: var(--card-shadow-hover);
|
|
79
|
-
}
|
|
80
|
-
.card:active { transform: translateY(-1px) scale(0.99); }
|
|
81
|
-
|
|
82
|
-
/* the persona's canonical icon — a self-contained squircle, one face everywhere */
|
|
83
|
-
.icon {
|
|
84
|
-
flex: none;
|
|
85
|
-
/* 60. It was 72 for a day, on the reasoning that the icons are the only colour on this page — everything
|
|
86
|
-
else is ink on paper — so more of them should read as livelier. It read as too big instead, and that
|
|
87
|
-
turns out to be measurable rather than a matter of taste: the text beside the icon is a fixed 66px (a
|
|
88
|
-
16px title on a 1.5 line box, 3px, then two clamped bio lines at 13.44/1.45), so at 72 the icon
|
|
89
|
-
overshot the text by six pixels and the icon, not the content, was setting the card's height.
|
|
90
|
-
64 is the first size that fits inside the text block. 60 gives the same card height as 64 — past the
|
|
91
|
-
crossover the text sets it either way — so the last four pixels cost no vertical space and buy a
|
|
92
|
-
quieter mark. */
|
|
93
|
-
width: 60px;
|
|
94
|
-
height: 60px;
|
|
95
|
-
border-radius: var(--radius-md);
|
|
96
|
-
object-fit: cover;
|
|
97
|
-
box-shadow: 0 8px 20px -12px rgba(0, 0, 0, 0.35);
|
|
98
|
-
}
|
|
49
|
+
.card {
|
|
50
|
+
display: flex;
|
|
51
|
+
align-items: center;
|
|
52
|
+
gap: 16px;
|
|
53
|
+
/* 16, down from 18, to pay for the larger icon: the icon is now what sets the card's height, and the
|
|
54
|
+
two px back on each side keep the row pitch where the shorter card put it. */
|
|
55
|
+
padding: 16px;
|
|
56
|
+
/* The card's own material, not the page column's — see --card-* in tokens.css. In light mode this is
|
|
57
|
+
solid white with a real shadow; in dark it is the glass, which there actually refracts. */
|
|
58
|
+
background: var(--card-bg);
|
|
59
|
+
border: 1px solid var(--border);
|
|
60
|
+
border-radius: var(--radius-lg);
|
|
61
|
+
backdrop-filter: var(--card-blur);
|
|
62
|
+
-webkit-backdrop-filter: var(--card-blur);
|
|
63
|
+
box-shadow: var(--card-shadow);
|
|
64
|
+
text-decoration: none;
|
|
65
|
+
color: var(--text);
|
|
66
|
+
transition: transform 0.3s var(--ease-spring), border-color 0.3s, background 0.3s,
|
|
67
|
+
box-shadow 0.3s var(--ease-out);
|
|
68
|
+
will-change: transform;
|
|
69
|
+
}
|
|
70
|
+
/* the lift and the shadow move together — a card that rises without its shadow growing reads as a
|
|
71
|
+
sticker sliding, not an object leaving the page */
|
|
72
|
+
.card:hover {
|
|
73
|
+
transform: translateY(-2px);
|
|
74
|
+
border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
|
|
75
|
+
box-shadow: var(--card-shadow-hover);
|
|
76
|
+
}
|
|
77
|
+
.card:active { transform: translateY(-1px) scale(0.99); }
|
|
99
78
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
/*
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
/* Only NEW is shown now (for a persona's first week). LIVE/beta/soon badges were dropped — every card
|
|
118
|
-
in the catalog is live by definition, so a "live" badge said nothing. */
|
|
119
|
-
.badge.new { color: var(--accent); background: color-mix(in srgb, var(--accent) 16%, transparent); }
|
|
79
|
+
/* the persona's canonical icon — a self-contained squircle, one face everywhere */
|
|
80
|
+
.icon {
|
|
81
|
+
flex: none;
|
|
82
|
+
/* 60. It was 72 for a day, on the reasoning that the icons are the only colour on this page — everything
|
|
83
|
+
else is ink on paper — so more of them should read as livelier. It read as too big instead, and that
|
|
84
|
+
turns out to be measurable rather than a matter of taste: the text beside the icon is a fixed 66px (a
|
|
85
|
+
16px title on a 1.5 line box, 3px, then two clamped bio lines at 13.44/1.45), so at 72 the icon
|
|
86
|
+
overshot the text by six pixels and the icon, not the content, was setting the card's height.
|
|
87
|
+
64 is the first size that fits inside the text block. 60 gives the same card height as 64 — past the
|
|
88
|
+
crossover the text sets it either way — so the last four pixels cost no vertical space and buy a
|
|
89
|
+
quieter mark. */
|
|
90
|
+
width: 60px;
|
|
91
|
+
height: 60px;
|
|
92
|
+
border-radius: var(--radius-md);
|
|
93
|
+
object-fit: cover;
|
|
94
|
+
box-shadow: 0 8px 20px -12px rgba(0, 0, 0, 0.35);
|
|
95
|
+
}
|
|
120
96
|
|
|
121
|
-
|
|
122
|
-
|
|
97
|
+
.meta { flex: 1; min-width: 0; }
|
|
98
|
+
.top { display: flex; align-items: center; gap: 8px; }
|
|
99
|
+
h2 { font-size: var(--fs-base); font-weight: 600; letter-spacing: -0.01em; }
|
|
100
|
+
/* Clamped to two lines. The bios run from one line to four, which made every card a different height
|
|
101
|
+
and the column read ragged; the full text is on the app's own page anyway. */
|
|
102
|
+
p {
|
|
103
|
+
color: var(--text-dim); font-size: var(--fs-xs); line-height: 1.45; margin: 3px 0 0;
|
|
104
|
+
display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; overflow: hidden;
|
|
105
|
+
}
|
|
106
|
+
.badge {
|
|
107
|
+
font-size: 0.66rem;
|
|
108
|
+
font-weight: 700;
|
|
109
|
+
text-transform: uppercase;
|
|
110
|
+
letter-spacing: 0.04em;
|
|
111
|
+
padding: 2px 7px;
|
|
112
|
+
border-radius: var(--radius-pill);
|
|
123
113
|
}
|
|
114
|
+
/* Only NEW is shown now (for a persona's first week). LIVE/beta/soon badges were dropped — every card
|
|
115
|
+
in the catalog is live by definition, so a "live" badge said nothing. */
|
|
116
|
+
.badge.new { color: var(--accent); background: color-mix(in srgb, var(--accent) 16%, transparent); }
|
|
117
|
+
|
|
118
|
+
.chev { flex: none; width: 20px; height: 20px; color: var(--text-dim); transition: transform 0.3s var(--ease-out); }
|
|
119
|
+
.card:hover .chev { transform: translateX(3px); color: var(--accent); }
|
|
124
120
|
</style>
|
package/src/ChildPersona.astro
CHANGED
|
@@ -217,68 +217,64 @@ const chrome = face === "page";
|
|
|
217
217
|
</div>
|
|
218
218
|
|
|
219
219
|
<style>
|
|
220
|
-
/*
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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. */
|
|
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. */
|
|
228
225
|
|
|
229
|
-
|
|
230
|
-
|
|
226
|
+
.pp { min-height: 100vh; min-height: 100dvh; } /* the pair survives — see @justai/astro-config */
|
|
227
|
+
.pp a { color: inherit; text-decoration: none; }
|
|
231
228
|
|
|
232
|
-
|
|
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) {
|
|
233
244
|
.pp .app {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
--gutter
|
|
239
|
-
|
|
240
|
-
|
|
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
|
-
}
|
|
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);
|
|
257
253
|
}
|
|
254
|
+
}
|
|
258
255
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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); }
|
|
267
264
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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; }
|
|
274
271
|
|
|
275
|
-
|
|
276
|
-
|
|
272
|
+
/* a mark fills its box; the picture inside it covers */
|
|
273
|
+
.pp .mk { display: block; width: 100%; height: 100%; object-fit: cover; }
|
|
277
274
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
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; }
|
|
283
279
|
}
|
|
284
280
|
</style>
|
package/src/ConfirmSheet.astro
CHANGED
|
@@ -87,76 +87,72 @@ const { id, title, body, actionLabel, cancelLabel, destructive = false, ask = fa
|
|
|
87
87
|
</dialog>
|
|
88
88
|
|
|
89
89
|
<style>
|
|
90
|
-
/*
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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; }
|
|
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; }
|
|
110
107
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
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; }
|
|
160
156
|
}
|
|
161
157
|
</style>
|
|
162
158
|
|
package/src/FilePicker.astro
CHANGED
|
@@ -108,23 +108,19 @@ const { id, title = "Choose a file", src = "/files" } = Astro.props;
|
|
|
108
108
|
</Sheet>
|
|
109
109
|
|
|
110
110
|
<style>
|
|
111
|
-
/*
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
border: 0;
|
|
125
|
-
background: none;
|
|
126
|
-
color-scheme: light dark;
|
|
127
|
-
}
|
|
111
|
+
/* The frame is given the sheet's height and takes it — the kernel inside drops its own full-screen
|
|
112
|
+
minimum when it is picking, precisely so it fits somebody else's sheet instead of scrolling
|
|
113
|
+
inside it. */
|
|
114
|
+
.jfp {
|
|
115
|
+
display: flex;
|
|
116
|
+
min-height: 62dvh;
|
|
117
|
+
}
|
|
118
|
+
.jfp-frame {
|
|
119
|
+
flex: 1;
|
|
120
|
+
width: 100%;
|
|
121
|
+
border: 0;
|
|
122
|
+
background: none;
|
|
123
|
+
color-scheme: light dark;
|
|
128
124
|
}
|
|
129
125
|
</style>
|
|
130
126
|
|
package/src/LangPicker.astro
CHANGED
|
@@ -77,55 +77,51 @@ const ordered = orderLocales(locales);
|
|
|
77
77
|
</div>
|
|
78
78
|
|
|
79
79
|
<style>
|
|
80
|
-
/*
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
against the trigger. <Sheet> presents in the top layer, so the picker can no longer be clipped
|
|
85
|
-
by an overflow on the toolbar the way an absolutely-positioned dropdown could be. */
|
|
86
|
-
.picker { display: contents; }
|
|
80
|
+
/* No `position: relative` any more, and that absence is the point: nothing here is positioned
|
|
81
|
+
against the trigger. <Sheet> presents in the top layer, so the picker can no longer be clipped
|
|
82
|
+
by an overflow on the toolbar the way an absolutely-positioned dropdown could be. */
|
|
83
|
+
.picker { display: contents; }
|
|
87
84
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
85
|
+
/* most minimalist: just the 2-letter code, flush to the right edge */
|
|
86
|
+
.trigger {
|
|
87
|
+
display: inline-grid; place-items: center; min-width: 36px; height: 36px;
|
|
88
|
+
font: inherit; font-size: var(--fs-xs); font-weight: 600; letter-spacing: 0.03em;
|
|
89
|
+
color: var(--text-dim); background: none; border: none; padding: 0 8px; cursor: pointer;
|
|
90
|
+
margin-inline-end: -7px;
|
|
91
|
+
transition: color 0.2s, transform 0.2s var(--ease-spring);
|
|
92
|
+
}
|
|
93
|
+
.trigger:hover { color: var(--text); }
|
|
94
|
+
.trigger:active { transform: scale(0.9); }
|
|
98
95
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
96
|
+
/* iOS's search field: a filled pill rather than an outlined box, sitting above the list. */
|
|
97
|
+
.search {
|
|
98
|
+
width: 100%;
|
|
99
|
+
font: inherit; font-size: var(--fs-base); color: var(--text);
|
|
100
|
+
background: color-mix(in srgb, var(--text) 7%, transparent);
|
|
101
|
+
border: 1px solid transparent; border-radius: 10px;
|
|
102
|
+
padding: 10px 12px; outline: none;
|
|
103
|
+
margin: 0 0 6px;
|
|
104
|
+
}
|
|
105
|
+
.search::placeholder { color: var(--text-dim); }
|
|
106
|
+
.search:focus { border-color: color-mix(in srgb, var(--accent) 55%, transparent); }
|
|
110
107
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
/* :active before :hover — a finger never hovers, and the feedback must not be desktop-only. */
|
|
122
|
-
.item:active { background: color-mix(in srgb, var(--text) 12%, transparent); }
|
|
123
|
-
@media (hover: hover) { .item:hover { background: color-mix(in srgb, var(--text) 8%, transparent); } }
|
|
124
|
-
.item:focus-visible { outline: none; background: color-mix(in srgb, var(--accent) 16%, transparent); }
|
|
125
|
-
/* The chosen locale reads as TEXT — see ActionRow for why this takes the ink hook. */
|
|
126
|
-
.item.active { color: var(--accent-text, var(--accent)); font-weight: 600; }
|
|
127
|
-
.item svg { width: 17px; height: 17px; flex: none; }
|
|
108
|
+
.list { padding-block-end: 2px; }
|
|
109
|
+
.item {
|
|
110
|
+
display: flex; align-items: center; justify-content: space-between; gap: 12px;
|
|
111
|
+
/* 44px — the platform's touch target. The old 9px padding made a mouse's row, and row height is
|
|
112
|
+
the clearest single tell of a web dropdown wearing an app's clothes. */
|
|
113
|
+
min-height: 44px;
|
|
114
|
+
padding: 0 12px; border-radius: 10px;
|
|
115
|
+
text-decoration: none; color: var(--text); font-size: var(--fs-sm);
|
|
116
|
+
transition: background 0.12s var(--ease-out);
|
|
128
117
|
}
|
|
118
|
+
/* :active before :hover — a finger never hovers, and the feedback must not be desktop-only. */
|
|
119
|
+
.item:active { background: color-mix(in srgb, var(--text) 12%, transparent); }
|
|
120
|
+
@media (hover: hover) { .item:hover { background: color-mix(in srgb, var(--text) 8%, transparent); } }
|
|
121
|
+
.item:focus-visible { outline: none; background: color-mix(in srgb, var(--accent) 16%, transparent); }
|
|
122
|
+
/* The chosen locale reads as TEXT — see ActionRow for why this takes the ink hook. */
|
|
123
|
+
.item.active { color: var(--accent-text, var(--accent)); font-weight: 600; }
|
|
124
|
+
.item svg { width: 17px; height: 17px; flex: none; }
|
|
129
125
|
</style>
|
|
130
126
|
|
|
131
127
|
<script>
|