@justai/cuts 0.55.0 → 0.56.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 +1 -1
- package/src/Profile.astro +46 -9
package/package.json
CHANGED
package/src/Profile.astro
CHANGED
|
@@ -22,8 +22,12 @@
|
|
|
22
22
|
// full-bleed cover reaches the SAME edge as the body — see Toolbar for the same knob.
|
|
23
23
|
//
|
|
24
24
|
// The AVATAR content is a slot (an <img>, or a build-time SVG via set:html) because it is the most
|
|
25
|
-
// persona-specific thing on the page; the tile that frames it is shared.
|
|
26
|
-
//
|
|
25
|
+
// persona-specific thing on the page; the tile that frames it is shared. The tile is a LINK to `#kernel`
|
|
26
|
+
// — see `kernelLabel` below for why it carries two labels.
|
|
27
|
+
//
|
|
28
|
+
// EVERY persona and child persona has a Profile and a Kernel (foundations 02), which is what lets that
|
|
29
|
+
// href stay local with no per-consumer escape hatch. `files`, `me` and `feed` each hand-rolled this
|
|
30
|
+
// entire section before; they are child personas, and a child persona is a persona.
|
|
27
31
|
//
|
|
28
32
|
// CONTRACT: the shell's scroll-spy reads `.pname` and `.handle` from here to name the title strip, and
|
|
29
33
|
// the section id `#profile` is one of the three cut anchors. Nothing else depends on the profile.
|
|
@@ -32,17 +36,41 @@ interface Props {
|
|
|
32
36
|
// input, so a name may carry markup (justai's "justai<span class='pro'>.pro</span>"). Same rule as the
|
|
33
37
|
// Posts cut: a persona's OWN name is authored; anything from the API is escaped.
|
|
34
38
|
name: string;
|
|
35
|
-
|
|
39
|
+
// The other three follow the SAME rule as `name`, and for a reason that only shows up on the child
|
|
40
|
+
// personas: `files`, `me` and `feed` are mounted on a HOST persona and adopt its identity at runtime,
|
|
41
|
+
// so each of these lines carries an id (`#pnHandle`, `#pnMeta`) or a translation hook (`data-i`) that
|
|
42
|
+
// a script rewrites once it knows whose page it is standing on. Text-only props forced all three to
|
|
43
|
+
// hand-roll the whole section — which is exactly how a shared layer becomes twelve copies.
|
|
44
|
+
//
|
|
45
|
+
// Authored, never user input: these strings come from the persona's own source. Anything arriving from
|
|
46
|
+
// the API is escaped instead — the Posts cut keeps the same split.
|
|
47
|
+
handle: string; // the @handle line, e.g. "@qr" or "@app.zone · files"
|
|
36
48
|
bio: string; // the one-line bio
|
|
37
49
|
meta?: string; // the small meta line under the bio (optional)
|
|
38
|
-
avatarLabel?: string; //
|
|
50
|
+
avatarLabel?: string; // what the avatar DEPICTS (qr's tile is a scannable QR); omit → the tile is inert
|
|
51
|
+
// queries: clicking the profile avatar · the big icon goes to the kernel · what does the avatar link to
|
|
52
|
+
//
|
|
53
|
+
// The tile is the face, and a face is the natural thing to press to meet what the persona actually
|
|
54
|
+
// DOES — so it carries you down to the kernel, the same destination as the toolbar's own glyph. One
|
|
55
|
+
// vocabulary: pass the SAME string you already give `<Toolbar kernelLabel=…>` and the two agree.
|
|
56
|
+
//
|
|
57
|
+
// Optional, and English when omitted, because the cut brings its own ground — a consumer that adopts a
|
|
58
|
+
// newer cuts and changes nothing must still get a working, labelled link, never a blank one.
|
|
59
|
+
//
|
|
60
|
+
// Two labels rather than one: `avatarLabel` says what the picture IS, this says where pressing it GOES.
|
|
61
|
+
// A link announced as "QR code linking to qr.app.zone" that scrolls to the kernel would be a lie to
|
|
62
|
+
// anyone who cannot see where they landed.
|
|
63
|
+
kernelLabel?: string;
|
|
39
64
|
cover?: string; // CSS background for .cover
|
|
40
65
|
coverHeight?: string; // e.g. "118px"
|
|
41
66
|
avatarBg?: string; // CSS background for the avatar tile
|
|
42
67
|
avatarBgLight?: string;// light-scheme override for the avatar tile
|
|
43
68
|
avatarPad?: string; // padding inside the avatar tile, e.g. "7px"
|
|
44
69
|
}
|
|
45
|
-
const { name, handle, bio, meta, avatarLabel, cover, coverHeight, avatarBg, avatarBgLight, avatarPad } = Astro.props;
|
|
70
|
+
const { name, handle, bio, meta, avatarLabel, kernelLabel, cover, coverHeight, avatarBg, avatarBgLight, avatarPad } = Astro.props;
|
|
71
|
+
// What the picture is, then where it takes you — in that order, because a screen reader reads the link's
|
|
72
|
+
// name before its role and the destination is the part that decides whether to press it.
|
|
73
|
+
const avatarA11y = [avatarLabel, kernelLabel ?? "What I do"].filter(Boolean).join(" — ");
|
|
46
74
|
const vars = [
|
|
47
75
|
cover && `--cover:${cover}`,
|
|
48
76
|
coverHeight && `--cover-h:${coverHeight}`,
|
|
@@ -55,14 +83,14 @@ const vars = [
|
|
|
55
83
|
<section class="cut profile" id="profile" style={vars || undefined}>
|
|
56
84
|
<div class="cover"></div>
|
|
57
85
|
<div class="pbody">
|
|
58
|
-
<
|
|
86
|
+
<a class="avatar" href="#kernel" aria-label={avatarA11y}><slot name="avatar" /></a>
|
|
59
87
|
<h1 class="pname" set:html={name} />
|
|
60
|
-
<p class="handle"
|
|
61
|
-
<p class="bio"
|
|
88
|
+
<p class="handle" set:html={handle} />
|
|
89
|
+
<p class="bio" set:html={bio} />
|
|
62
90
|
<!-- prose the face adds under its bio and ABOVE the small print (justai's purpose line). Order is
|
|
63
91
|
the point: a mission line that falls below the meta reads as a footnote. -->
|
|
64
92
|
<slot name="belowBio" />
|
|
65
|
-
{meta && <p class="meta"
|
|
93
|
+
{meta && <p class="meta" set:html={meta} />}
|
|
66
94
|
<!-- and whatever follows the small print — the apexes' privacy/terms nav. Both slots stay the
|
|
67
95
|
PERSONA's markup, and therefore the persona's scope, so their styling lives in the persona
|
|
68
96
|
while the frame around them stays shared. -->
|
|
@@ -87,7 +115,16 @@ const vars = [
|
|
|
87
115
|
background: var(--avatar-bg, #fff);
|
|
88
116
|
border: 3px solid var(--bg); box-shadow: 0 8px 22px -16px rgba(0, 0, 0, 0.22);
|
|
89
117
|
position: relative;
|
|
118
|
+
/* it is an <a> now — undo the link ground, or a slotted SVG drawn in currentColor turns accent */
|
|
119
|
+
color: inherit; text-decoration: none;
|
|
120
|
+
transition: transform 0.15s var(--ease-out, ease-out);
|
|
90
121
|
}
|
|
122
|
+
/* Pressed, not hovered. The tile is the persona's FACE: growing it under the cursor would animate the
|
|
123
|
+
one object on the page that should hold still. Depressing it on press is the iOS answer — it says
|
|
124
|
+
"this responds" at the moment of contact, on touch as well, where there is no hover to say it. */
|
|
125
|
+
.avatar:active { transform: scale(0.97); }
|
|
126
|
+
.avatar:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
|
|
127
|
+
@media (prefers-reduced-motion: reduce) { .avatar { transition: none; } }
|
|
91
128
|
/* the avatar content is slotted (persona-owned) → style it globally */
|
|
92
129
|
.avatar :global(svg) { width: 100%; height: 100%; display: block; }
|
|
93
130
|
.avatar :global(img) { width: 100%; height: 100%; display: block; object-fit: cover; }
|