@supersuit/artifacts 0.48.0 → 0.49.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/CHANGELOG.md CHANGED
@@ -3,6 +3,32 @@
3
3
  `@supersuit/artifacts`. One entry per version, newest first. Each entry says what changed, how a
4
4
  host can tell whether it is affected (DETECTOR), what a host does about it (REMEDY), and the tests.
5
5
 
6
+ ## 0.49.0 (2026-10-16)
7
+
8
+ **The default look is Supersuit's, the Maison.** The product the default pack is named for became
9
+ Supersuit on 2026-10-14, and its maker's house look, the Maison, became the brand on 2026-10-15.
10
+ `freedomDefault` (the export keeps its name, so no host breaks) now reads "A page from Supersuit"
11
+ and names Supersuit in metadata (`siteName`); paints night `#17171a` with mist `#d8d1c4` and bronze
12
+ `#a8834f` by default and travertine `#e8e0d2` with ink `#1c1a17` and bronze deepened with ink in
13
+ light mode, each text and accent pairing at 4.5:1 or better; and sets DM Serif Display over DM Sans
14
+ by name (Georgia and the system face if a host does not load them; README, "The default look's
15
+ faces"). The default mark (`BrandMark` with no `Mark`) is a short rule in the accent instead of a
16
+ dove: the dove stood for the agent, and no figure stands in for her until her look is designed.
17
+ What a reader reads moves with the name: the confidential door's default line ("the Google account
18
+ your Supersuit account uses"), the member reason ("you are a Supersuit user"), and the share
19
+ sheet's "Any Supersuit account" and "Supersuit accounts get in without being on the list." The
20
+ `freedom` access level keeps its identifier. A pack of a host's own is untouched, and the share
21
+ card keeps the bundled Newsreader so no tracing config changes.
22
+
23
+ - DETECTOR: a host whose operators have no pack of their own (`freedomDefault`, or a spread of it
24
+ with only the kicker changed) renders on the night ground with bronze, not the old near-black and
25
+ gold, and its kicker says Supersuit.
26
+ - REMEDY: nothing required. To draw the default look in its own faces, load DM Serif Display and
27
+ DM Sans in the host's root layout (README).
28
+ - Tests: pack.test.tsx (the default look: tokens only, 4.5:1 pairings, the house pair, no dove, no
29
+ Freedom), the default-pages golden regenerated on purpose (palette, faces and kicker only), and
30
+ the kicker, metadata and door expectations in artifacts, to, extension-points and gated.
31
+
6
32
  ## 0.48.0
7
33
 
8
34
  **Unpublishing keeps the history.** `POST /api/artifacts?id=<id>&unpublish=1` takes a page off the
package/README.md CHANGED
@@ -5,7 +5,7 @@ site shell behind that: the store, the route handlers, the read-along reader, br
5
5
  password pages, confidential pages with a signed-in reader list, and the share card a link
6
6
  unfurls as. You mount it in a Next.js App Router app with a handful of one-line route files.
7
7
 
8
- It is the shell that serves every Freedom operator's `artifacts.<theirname>` page, published
8
+ It is the shell that serves every Supersuit operator's `artifacts.<theirname>` page, published
9
9
  openly because none of it is secret and all of it is better with more eyes on it.
10
10
 
11
11
  ```bash
@@ -23,7 +23,7 @@ Firestore), and optionally `@google-cloud/storage >= 7` for uploaded files.
23
23
  | `createArtifactStore(db, path)` | Firestore store for pages and their version history, under any collection path |
24
24
  | `createReadersStore(db, path)` | The record for confidential pages: allowlist, agreements, reading sessions, flags |
25
25
  | `createArtifactAssets(bucket, prefix, { privateBucket? })` | Uploaded images for a page, in a Cloud Storage bucket; private media in a separate private bucket |
26
- | `freedomDefault`, `BrandPack` | The default look and the type any other look implements |
26
+ | `freedomDefault`, `BrandPack` | The default look (Supersuit's, the Maison) and the type any other look implements |
27
27
  | `BrandGround`, `BrandMark` | The pack's page backdrop and mark, for your own pages (a home, a 404) |
28
28
  | `parseArtifactSource` | The front-matter contract, as a parser you can call before publishing (widget fences included) |
29
29
  | `headingsOf`, `scanWidgets`, `placeNotes` | The notes widget's pure parts: heading slugs, fence validation, where each note shows |
@@ -164,7 +164,7 @@ anyone else. `PageMenu`, `roleLine` and `menuItems` are exported from `/reader`.
164
164
  The owner and the hub's admins change a page's settings from the menu, with no republish. Each item
165
165
  opens a sheet (from the bottom on a phone, under the button on a desk):
166
166
 
167
- - **Share settings.** Who can open it: anyone with the link, any Freedom account (plus the list),
167
+ - **Share settings.** Who can open it: anyone with the link, any Supersuit account (plus the list),
168
168
  or only the list. The list itself: add or remove an email. On a page open to anyone, a password
169
169
  on or off (on makes a 12-character key). The link to send, with a Copy button: on a password page
170
170
  it carries the key. Under them, read-only lines: what a stranger with the bare URL sees and what
@@ -314,7 +314,7 @@ their change alone.
314
314
 
315
315
  ## A confidential page (`access:`)
316
316
 
317
- `access: freedom` opens the page only to a signed-in reader with an active Freedom account or
317
+ `access: freedom` opens the page only to a signed-in reader with an active Supersuit account or
318
318
  on the page's list; `access: invite` opens it only to the list. Everyone else gets the title,
319
319
  the summary and a sign-in door; the body never reaches them.
320
320
 
@@ -1181,6 +1181,16 @@ an optional full-page `Wrapper` and `Mark`, and an optional `share` block
1181
1181
  (a font loader and a backdrop) that draws the unfurl card. `freedomDefault` is the default look.
1182
1182
  Keep a pack carrying your own trademark in your own app, not in a pull request here.
1183
1183
 
1184
+ **The default look's faces.** Since 0.49.0 `freedomDefault` is Supersuit's own look, the Maison:
1185
+ night with mist type and bronze by default, travertine with ink behind the light toggle, every
1186
+ colour one of seven stone tokens or a mix of two, and the mark above the kicker a short rule in
1187
+ the accent (no dove). It sets DM Serif Display for headings and DM Sans for reading, by name. The
1188
+ package does not fetch them, so a host that wants them loads them itself, for example
1189
+ `import '@fontsource/dm-serif-display/400.css'` and `import '@fontsource/dm-sans/400.css'` (plus
1190
+ 500 and 600) in its root layout, which serves the files from the host's own origin. A host that
1191
+ does not falls back to Georgia and the system face. The share card still sets its title in the
1192
+ bundled Newsreader, so no host's `outputFileTracingIncludes` has to change.
1193
+
1184
1194
  **Light, dark or system.** A pack's `ground`, `ink` and `accent` are its dark palette; `light` is
1185
1195
  the other one (absent, warm paper with the pack's accent), and `mode` is the one a page opens in
1186
1196
  (`dark` by default; a page's `theme:` overrides it). Pages paint with CSS variables
@@ -25,9 +25,10 @@ export declare function Watermark({ email }: {
25
25
  }): import("react").JSX.Element;
26
26
  /** Printing shows this and nothing else, whichever way the print dialog was reached. */
27
27
  export declare const NO_PRINT_CSS = "@media print { body * { visibility: hidden !important; } body::after { content: \"This page is confidential and cannot be printed.\"; visibility: visible; position: fixed; top: 40%; left: 0; right: 0; text-align: center; font: 16px sans-serif; color: #000; } }";
28
- /** The door's default line, for a host whose readers sign in with a Freedom account. A host
28
+ /** The door's default line, for a host whose readers sign in with a Supersuit account (the product
29
+ * called Freedom until 2026-10-14; the access level keeps its `freedom` identifier). A host
29
30
  * that signs readers in some other way passes `signInNote` to say so in its own words. */
30
- export declare const DEFAULT_SIGN_IN_NOTE = "This page is confidential. Sign in with the Google account your Freedom account uses, or the address it was shared with.";
31
+ export declare const DEFAULT_SIGN_IN_NOTE = "This page is confidential. Sign in with the Google account your Supersuit account uses, or the address it was shared with.";
31
32
  export declare function SignInDoor({ brand, href, note }: {
32
33
  brand: BrandPack;
33
34
  href?: string;
@@ -19,9 +19,10 @@ export function Watermark({ email }) {
19
19
  }
20
20
  /** Printing shows this and nothing else, whichever way the print dialog was reached. */
21
21
  export const NO_PRINT_CSS = `@media print { body * { visibility: hidden !important; } body::after { content: "This page is confidential and cannot be printed."; visibility: visible; position: fixed; top: 40%; left: 0; right: 0; text-align: center; font: 16px sans-serif; color: #000; } }`;
22
- /** The door's default line, for a host whose readers sign in with a Freedom account. A host
22
+ /** The door's default line, for a host whose readers sign in with a Supersuit account (the product
23
+ * called Freedom until 2026-10-14; the access level keeps its `freedom` identifier). A host
23
24
  * that signs readers in some other way passes `signInNote` to say so in its own words. */
24
- export const DEFAULT_SIGN_IN_NOTE = 'This page is confidential. Sign in with the Google account your Freedom account uses, or the address it was shared with.';
25
+ export const DEFAULT_SIGN_IN_NOTE = 'This page is confidential. Sign in with the Google account your Supersuit account uses, or the address it was shared with.';
25
26
  export function SignInDoor({ brand, href, note = DEFAULT_SIGN_IN_NOTE }) {
26
27
  // No href means the host configured no sign-in authority: the door stays, with no way through.
27
28
  if (!href) {
@@ -50,7 +50,7 @@ export type Decision = {
50
50
  why: 'not-allowed';
51
51
  reader: Reader;
52
52
  };
53
- export declare const MEMBER_REASON = "you are a Freedom user";
53
+ export declare const MEMBER_REASON = "you are a Supersuit user";
54
54
  export declare const LISTED_REASON = "you were given access to it by name";
55
55
  export declare const STAFF_REASON = "you are its owner or an admin of this hub";
56
56
  /** Whether this reader may open a page with this access level. The allowlist opens both
@@ -90,7 +90,7 @@ export function mintGrant(secret, reader, now = Date.now()) {
90
90
  export function verifyGrant(secret, grant, now = Date.now()) {
91
91
  return decode(secret, 'artifact-grant', 'g1', grant, now);
92
92
  }
93
- export const MEMBER_REASON = 'you are a Freedom user';
93
+ export const MEMBER_REASON = 'you are a Supersuit user';
94
94
  export const LISTED_REASON = 'you were given access to it by name';
95
95
  export const STAFF_REASON = 'you are its owner or an admin of this hub';
96
96
  /** Whether this reader may open a page with this access level. The allowlist opens both
@@ -38,7 +38,7 @@ export type BrandPack = {
38
38
  };
39
39
  /** The small uppercase line above a title, e.g. "From Sam Rivera". */
40
40
  kicker: string;
41
- /** Unused since 0.33.1: the player bar names no narrator (it read "Read aloud by Freedom").
41
+ /** Unused since 0.33.1: the player bar names no narrator.
42
42
  * Optional so a pack that still sets it compiles; nothing reads it. */
43
43
  narratorLabel?: (voice?: string) => string;
44
44
  /** Optional full-page backdrop. Receives the page as children. */
@@ -140,4 +140,19 @@ export type ShareCardAssets = {
140
140
  * a blur or a glow is baked in here; the renderer only sets type. Absent, `ground` fills. */
141
141
  backdrop?: () => Promise<string>;
142
142
  };
143
+ /**
144
+ * THE DEFAULT LOOK: Supersuit's own, for every operator with no pack of their own. Named
145
+ * `freedomDefault` because the product was Freedom until 2026-10-14; the export keeps its name so
146
+ * no host breaks, and everything a reader sees says Supersuit.
147
+ *
148
+ * Since 0.49.0 it is the Maison, the house look of Supersuit's maker: stone, and colour only for
149
+ * context. Night (#17171a) with mist type and bronze by default; travertine with ink one tap away,
150
+ * its accent bronze deepened with ink so a link reads at 4.5:1. Every value is one of the house's
151
+ * seven tokens or a mix of two (night, walnut, travertine, marble, bronze, ink, mist). The dark
152
+ * palette stays plain hex because the share card draws with it and its renderer has no color-mix().
153
+ *
154
+ * Type is the house pair: DM Serif Display to inscribe, DM Sans to read. Both are named, not
155
+ * fetched: a host loads them (README, "The default look's faces"), and one that does not falls back
156
+ * to Georgia and the system face rather than asking a font service for them on every page view.
157
+ */
143
158
  export declare const freedomDefault: BrandPack;
package/lib/brand/pack.js CHANGED
@@ -1,16 +1,31 @@
1
1
  import { newsreader } from './default-share.js';
2
+ /**
3
+ * THE DEFAULT LOOK: Supersuit's own, for every operator with no pack of their own. Named
4
+ * `freedomDefault` because the product was Freedom until 2026-10-14; the export keeps its name so
5
+ * no host breaks, and everything a reader sees says Supersuit.
6
+ *
7
+ * Since 0.49.0 it is the Maison, the house look of Supersuit's maker: stone, and colour only for
8
+ * context. Night (#17171a) with mist type and bronze by default; travertine with ink one tap away,
9
+ * its accent bronze deepened with ink so a link reads at 4.5:1. Every value is one of the house's
10
+ * seven tokens or a mix of two (night, walnut, travertine, marble, bronze, ink, mist). The dark
11
+ * palette stays plain hex because the share card draws with it and its renderer has no color-mix().
12
+ *
13
+ * Type is the house pair: DM Serif Display to inscribe, DM Sans to read. Both are named, not
14
+ * fetched: a host loads them (README, "The default look's faces"), and one that does not falls back
15
+ * to Georgia and the system face rather than asking a font service for them on every page view.
16
+ */
2
17
  export const freedomDefault = {
3
18
  id: 'freedom-default',
4
- name: 'Freedom',
5
- ground: '#0e0f13',
6
- ink: '#ece7dc',
7
- accent: '#c9a96e',
8
- light: { ground: '#f6f1e7', ink: '#1c1a17', accent: '#8a6a2c' },
19
+ name: 'Supersuit',
20
+ ground: '#17171a',
21
+ ink: '#d8d1c4',
22
+ accent: '#a8834f',
23
+ light: { ground: '#e8e0d2', ink: '#1c1a17', accent: 'color-mix(in srgb,#a8834f 60%,#1c1a17)' },
9
24
  type: {
10
- display: 'Georgia, "Iowan Old Style", "Times New Roman", serif',
11
- body: 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif',
25
+ display: '"DM Serif Display", Georgia, "Times New Roman", serif',
26
+ body: '"DM Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif',
12
27
  },
13
- kicker: 'A page from Freedom',
28
+ kicker: 'A page from Supersuit',
14
29
  // Pages unfurl as their title on the ground colour. A pack with a backdrop of its own
15
30
  // overrides this whole field.
16
31
  share: { font: newsreader },
@@ -9,7 +9,8 @@ export declare function BrandGround({ pack, mode, children }: {
9
9
  mode?: ThemeMode;
10
10
  children: ReactNode;
11
11
  }): import("react").JSX.Element;
12
- /** The mark above the kicker: the pack's own, else a small dove. */
12
+ /** The mark above the kicker: the pack's own, else a short rule in the accent. Not a dove: the dove
13
+ * stood for the agent, and no bird or figure stands in for her until her own look is designed. */
13
14
  export declare function BrandMark({ pack, className }: {
14
15
  pack: BrandPack;
15
16
  className?: string;
@@ -15,9 +15,10 @@ export function BrandGround({ pack, mode, children }) {
15
15
  // isolation puts the fixed backdrop (z-index -1) above this ground and below the page.
16
16
  style: { background: 'var(--a-ground)', color: 'var(--a-ink)', fontFamily: pack.type.body, position: 'relative', isolation: 'isolate' }, children: [css, pack.backdrop?.kind === 'bubbles' ? _jsx(Bubbles, { backdrop: pack.backdrop }) : null, children] }));
17
17
  }
18
- /** The mark above the kicker: the pack's own, else a small dove. */
18
+ /** The mark above the kicker: the pack's own, else a short rule in the accent. Not a dove: the dove
19
+ * stood for the agent, and no bird or figure stands in for her until her own look is designed. */
19
20
  export function BrandMark({ pack, className }) {
20
21
  if (pack.Mark)
21
22
  return _jsx(pack.Mark, { className: className });
22
- return (_jsx("span", { "data-brand-mark": pack.id, "aria-hidden": true, className: className, style: { color: pack.accent }, children: "\uD83D\uDD4A" }));
23
+ return (_jsx("span", { "data-brand-mark": pack.id, "aria-hidden": true, className: className, style: { display: 'inline-block', width: 28, height: 1, verticalAlign: 'middle', background: pack.accent } }));
23
24
  }
@@ -13,7 +13,7 @@ import { useEffect, useRef, useState } from 'react';
13
13
  import { NO_ZOOM_FONT } from './no-zoom.js';
14
14
  export const ACCESS_LABEL = {
15
15
  public: 'Anyone with the link',
16
- freedom: 'Any Freedom account',
16
+ freedom: 'Any Supersuit account',
17
17
  invite: 'Only the list',
18
18
  };
19
19
  export const COMMENTS_LABEL = { anyone: 'Anyone', 'signed-in': 'Signed-in readers', off: 'Nobody' };
@@ -132,7 +132,7 @@ export function SettingsSheet({ kind, settings, phone, vars, onClose, onCopy, sa
132
132
  const primary = { ...btn, background: 'var(--a-accent, #111)', borderColor: 'var(--a-accent, #111)', color: 'var(--a-ground, #fff)', fontWeight: 600 };
133
133
  const muted = { margin: '4px 0 0', fontSize: 12, opacity: 0.65 };
134
134
  const radio = (name, value, current, set, label, note) => (_jsxs("label", { style: choice, "data-settings-choice": `${name}:${value}`, children: [_jsx("input", { type: "radio", name: name, value: value, checked: current === value, onChange: () => set(value), style: { width: 18, height: 18, margin: 0, accentColor: 'var(--a-accent, #111)' } }), _jsxs("span", { children: [label, note ? _jsx("span", { style: { display: 'block', fontSize: 12, opacity: 0.6 }, children: note }) : null] })] }, value));
135
- const share = (_jsxs(_Fragment, { children: [_jsxs("fieldset", { style: fieldset, children: [_jsx("legend", { style: legend, children: "Who can open it" }), s.levels.map((l) => radio('access', l, access, setAccess, ACCESS_LABEL[l], l === 'freedom' ? 'Plus anyone on the list' : undefined))] }), access === 'public' ? (_jsxs("fieldset", { style: fieldset, children: [_jsx("legend", { style: legend, children: "Password" }), _jsxs("label", { style: choice, "data-settings-choice": "password", children: [_jsx("input", { type: "checkbox", checked: password, onChange: (e) => setPassword(e.target.checked), style: { width: 18, height: 18, margin: 0, accentColor: 'var(--a-accent, #111)' } }), _jsxs("span", { children: ["Require a password", _jsx("span", { style: { display: 'block', fontSize: 12, opacity: 0.6 }, children: "The link to send carries the key" })] })] })] })) : null, dirty || busy ? (_jsxs("div", { style: { display: 'flex', gap: 8, margin: '0 0 14px' }, children: [_jsx("button", { type: "button", "data-settings-save": true, onClick: () => void save(), disabled: busy, style: primary, children: busy ? 'Saving' : 'Save' }), _jsx("button", { type: "button", onClick: () => { setAccess(s.access); setPassword(s.password); }, disabled: busy, style: btn, children: "Undo" })] })) : null, s.list && s.access !== 'public' ? (_jsxs("fieldset", { style: fieldset, "data-settings-list": true, children: [_jsx("legend", { style: legend, children: `The list (${s.list.length})` }), s.list.length === 0 ? _jsx("p", { style: { ...muted, margin: '0 0 6px' }, children: "Nobody yet." }) : null, _jsx("ul", { style: { listStyle: 'none', margin: '0 0 8px', padding: 0 }, children: s.list.map((e) => (_jsxs("li", { "data-settings-list-entry": e.email, style: { display: 'flex', alignItems: 'center', gap: 8, padding: '4px 0', borderBottom: '1px solid var(--a-line, rgba(127,127,127,0.2))' }, children: [_jsx("span", { style: { flex: 1, minWidth: 0, overflowWrap: 'anywhere' }, children: e.name ? _jsxs(_Fragment, { children: [e.name, _jsx("span", { style: { display: 'block', fontSize: 12, opacity: 0.6 }, children: e.email })] }) : e.email }), _jsx("button", { type: "button", "aria-label": `Remove ${e.email}`, onClick: () => void listChange({ remove: [e.email] }), disabled: busy, style: btn, children: "Remove" })] }, e.email))) }), _jsxs("form", { onSubmit: onAdd, style: { display: 'flex', gap: 8 }, children: [_jsx("input", { type: "email", inputMode: "email", autoComplete: "off", placeholder: "name@example.com", "aria-label": "Email to add", value: email, onChange: (e) => setEmail(e.target.value), style: { flex: 1, minWidth: 0, font: 'inherit', ...NO_ZOOM_FONT, padding: phone ? '8px 10px' : '6px 8px', borderRadius: 8, border: '1px solid var(--a-line, rgba(127,127,127,0.4))', background: 'transparent', color: 'inherit' } }), _jsx("button", { type: "submit", disabled: busy, style: btn, children: "Add" })] }), s.access === 'invite' ? null : _jsx("p", { style: muted, children: "Freedom accounts get in without being on the list." })] })) : null, _jsxs("div", { style: { margin: '0 0 14px' }, "data-settings-link": true, children: [_jsx("p", { style: legend, children: "The link to send" }), _jsxs("div", { style: { display: 'flex', gap: 8, alignItems: 'center' }, children: [_jsx("code", { style: { flex: 1, minWidth: 0, fontSize: 13, overflowWrap: 'anywhere' }, children: s.link }), _jsx("button", { type: "button", onClick: () => void copy(), style: btn, children: "Copy" })] })] }), s.facts.length ? (_jsx("dl", { style: { display: 'grid', gap: 8, margin: 0 }, "data-settings-facts": true, children: s.facts.map((f) => (_jsxs("div", { children: [_jsx("dt", { style: { fontSize: 12, fontWeight: 600, opacity: 0.7 }, children: f.label }), _jsx("dd", { style: { margin: 0, fontSize: 13, opacity: 0.85 }, children: f.text })] }, f.label))) })) : null] }));
135
+ const share = (_jsxs(_Fragment, { children: [_jsxs("fieldset", { style: fieldset, children: [_jsx("legend", { style: legend, children: "Who can open it" }), s.levels.map((l) => radio('access', l, access, setAccess, ACCESS_LABEL[l], l === 'freedom' ? 'Plus anyone on the list' : undefined))] }), access === 'public' ? (_jsxs("fieldset", { style: fieldset, children: [_jsx("legend", { style: legend, children: "Password" }), _jsxs("label", { style: choice, "data-settings-choice": "password", children: [_jsx("input", { type: "checkbox", checked: password, onChange: (e) => setPassword(e.target.checked), style: { width: 18, height: 18, margin: 0, accentColor: 'var(--a-accent, #111)' } }), _jsxs("span", { children: ["Require a password", _jsx("span", { style: { display: 'block', fontSize: 12, opacity: 0.6 }, children: "The link to send carries the key" })] })] })] })) : null, dirty || busy ? (_jsxs("div", { style: { display: 'flex', gap: 8, margin: '0 0 14px' }, children: [_jsx("button", { type: "button", "data-settings-save": true, onClick: () => void save(), disabled: busy, style: primary, children: busy ? 'Saving' : 'Save' }), _jsx("button", { type: "button", onClick: () => { setAccess(s.access); setPassword(s.password); }, disabled: busy, style: btn, children: "Undo" })] })) : null, s.list && s.access !== 'public' ? (_jsxs("fieldset", { style: fieldset, "data-settings-list": true, children: [_jsx("legend", { style: legend, children: `The list (${s.list.length})` }), s.list.length === 0 ? _jsx("p", { style: { ...muted, margin: '0 0 6px' }, children: "Nobody yet." }) : null, _jsx("ul", { style: { listStyle: 'none', margin: '0 0 8px', padding: 0 }, children: s.list.map((e) => (_jsxs("li", { "data-settings-list-entry": e.email, style: { display: 'flex', alignItems: 'center', gap: 8, padding: '4px 0', borderBottom: '1px solid var(--a-line, rgba(127,127,127,0.2))' }, children: [_jsx("span", { style: { flex: 1, minWidth: 0, overflowWrap: 'anywhere' }, children: e.name ? _jsxs(_Fragment, { children: [e.name, _jsx("span", { style: { display: 'block', fontSize: 12, opacity: 0.6 }, children: e.email })] }) : e.email }), _jsx("button", { type: "button", "aria-label": `Remove ${e.email}`, onClick: () => void listChange({ remove: [e.email] }), disabled: busy, style: btn, children: "Remove" })] }, e.email))) }), _jsxs("form", { onSubmit: onAdd, style: { display: 'flex', gap: 8 }, children: [_jsx("input", { type: "email", inputMode: "email", autoComplete: "off", placeholder: "name@example.com", "aria-label": "Email to add", value: email, onChange: (e) => setEmail(e.target.value), style: { flex: 1, minWidth: 0, font: 'inherit', ...NO_ZOOM_FONT, padding: phone ? '8px 10px' : '6px 8px', borderRadius: 8, border: '1px solid var(--a-line, rgba(127,127,127,0.4))', background: 'transparent', color: 'inherit' } }), _jsx("button", { type: "submit", disabled: busy, style: btn, children: "Add" })] }), s.access === 'invite' ? null : _jsx("p", { style: muted, children: "Supersuit accounts get in without being on the list." })] })) : null, _jsxs("div", { style: { margin: '0 0 14px' }, "data-settings-link": true, children: [_jsx("p", { style: legend, children: "The link to send" }), _jsxs("div", { style: { display: 'flex', gap: 8, alignItems: 'center' }, children: [_jsx("code", { style: { flex: 1, minWidth: 0, fontSize: 13, overflowWrap: 'anywhere' }, children: s.link }), _jsx("button", { type: "button", onClick: () => void copy(), style: btn, children: "Copy" })] })] }), s.facts.length ? (_jsx("dl", { style: { display: 'grid', gap: 8, margin: 0 }, "data-settings-facts": true, children: s.facts.map((f) => (_jsxs("div", { children: [_jsx("dt", { style: { fontSize: 12, fontWeight: 600, opacity: 0.7 }, children: f.label }), _jsx("dd", { style: { margin: 0, fontSize: 13, opacity: 0.85 }, children: f.text })] }, f.label))) })) : null] }));
136
136
  const commentSheet = (_jsxs(_Fragment, { children: [_jsxs("fieldset", { style: fieldset, children: [_jsx("legend", { style: legend, children: "Who can comment" }), ['anyone', 'signed-in', 'off'].map((m) => radio('comments', m, comments, setComments, COMMENTS_LABEL[m], m === 'anyone' && s.gated ? 'This page is private, so only signed-in readers it is open to can' : undefined))] }), comments !== 'off' ? (_jsxs("fieldset", { style: fieldset, children: [_jsx("legend", { style: legend, children: "Who can read the comments" }), ['owner', 'readers'].map((v) => radio('visible', v, visible, setVisible, VISIBLE_LABEL[v], v === 'owner' ? 'Each commenter also sees their own' : undefined))] })) : null, dirty || busy ? (_jsxs("div", { style: { display: 'flex', gap: 8 }, children: [_jsx("button", { type: "button", "data-settings-save": true, onClick: () => void save(), disabled: busy, style: primary, children: busy ? 'Saving' : 'Save' }), _jsx("button", { type: "button", onClick: () => { setComments(s.comments); setVisible(s.commentsVisible); }, disabled: busy, style: btn, children: "Undo" })] })) : null] }));
137
137
  return (_jsxs(_Fragment, { children: [_jsx("div", { "aria-hidden": true, "data-settings-scrim": true, onClick: onClose, style: { position: 'fixed', inset: 0, zIndex: 60, background: phone ? 'rgba(0,0,0,0.28)' : 'transparent' } }), _jsxs("div", { ref: panel, role: "dialog", "aria-modal": "true", "aria-label": kind === 'share' ? 'Share settings' : 'Comment settings', tabIndex: -1, "data-settings-sheet": kind, "data-nospeak": true, "data-comment-ui": true, style: style, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 8 }, children: [_jsx("h2", { style: h, children: kind === 'share' ? 'Share settings' : 'Comment settings' }), _jsx("button", { type: "button", onClick: onClose, "aria-label": "Close", style: { ...btn, border: 0, padding: '2px 6px', fontSize: 20, lineHeight: 1 }, children: "\u00D7" })] }), kind === 'share' ? share : commentSheet, _jsx("p", { role: "status", "aria-live": "polite", "data-settings-said": true, style: { margin: said ? '10px 0 0' : 0, fontSize: 13, minHeight: 0 }, children: said ?? '' })] })] }));
138
138
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supersuit/artifacts",
3
- "version": "0.48.0",
3
+ "version": "0.49.0",
4
4
  "description": "Publish markdown as branded, read-aloud pages from a Next.js app: the artifacts store, the route factory, the read-along reader, brand packs, password and confidential pages, and share cards.",
5
5
  "license": "MIT",
6
6
  "type": "module",