anentrypoint-design 0.0.385 → 0.0.387
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/dist/247420.css +33 -0
- package/dist/247420.js +53 -51
- package/package.json +1 -1
- package/src/components/agent-chat/controls.js +143 -0
- package/src/components/agent-chat/empty-state.js +63 -0
- package/src/components/agent-chat/message-rows.js +141 -0
- package/src/components/agent-chat/surface.js +206 -0
- package/src/components/agent-chat/thread-behaviour.js +50 -0
- package/src/components/agent-chat.js +6 -546
- package/src/components/chat/composer-affordances.js +109 -0
- package/src/components/chat/composer.js +256 -0
- package/src/components/chat/threads.js +105 -0
- package/src/components/chat-message-parts/agent-nodes.js +103 -0
- package/src/components/chat-message-parts/inline.js +106 -0
- package/src/components/chat-message-parts/prose-nodes.js +133 -0
- package/src/components/chat-message-parts/renderers.js +89 -0
- package/src/components/chat-message-parts.js +12 -408
- package/src/components/chat-minimap/minimap.js +167 -0
- package/src/components/chat-minimap/paint.js +73 -0
- package/src/components/chat-minimap/preview.js +41 -0
- package/src/components/chat-minimap.js +7 -263
- package/src/components/chat.js +20 -628
- package/src/components/community/chrome.js +63 -0
- package/src/components/community/navigation.js +167 -0
- package/src/components/community/presence.js +106 -0
- package/src/components/community/shell.js +17 -0
- package/src/components/community/views.js +131 -0
- package/src/components/community.js +18 -447
- package/src/components/files/chrome.js +140 -0
- package/src/components/files/entries.js +156 -0
- package/src/components/files/grid-controls.js +63 -0
- package/src/components/files/grid.js +177 -0
- package/src/components/files/types.js +69 -0
- package/src/components/files-modals/dialogs.js +118 -0
- package/src/components/files-modals/modal-shell.js +153 -0
- package/src/components/files-modals/preview-bodies.js +129 -0
- package/src/components/files-modals/preview-containers.js +96 -0
- package/src/components/files-modals.js +14 -475
- package/src/components/files.js +15 -564
- package/src/components/freddie/pages-config.js +3 -94
- package/src/components/freddie/pages-models.js +97 -0
- package/src/components/freddie.js +2 -1
- package/src/components/interaction-primitives/mobile.js +25 -0
- package/src/components/interaction-primitives/pointer.js +214 -0
- package/src/components/interaction-primitives/reorderable.js +47 -0
- package/src/components/interaction-primitives/shortcuts.js +119 -0
- package/src/components/interaction-primitives.js +16 -388
- package/src/components/sessions/conversation-list.js +230 -0
- package/src/components/sessions/dashboard.js +202 -0
- package/src/components/sessions/detail-bits.js +49 -0
- package/src/components/sessions/format.js +42 -0
- package/src/components/sessions/session-card.js +92 -0
- package/src/components/sessions.js +16 -579
- package/src/components/voice/audio-cue.js +39 -0
- package/src/components/voice/capture.js +90 -0
- package/src/components/voice/playback.js +75 -0
- package/src/components/voice/settings-modal.js +104 -0
- package/src/components/voice.js +16 -293
- package/src/css/app-shell/base.css +23 -0
- package/src/css/app-shell/states-interactions.css +10 -0
- package/src/kits/os/freddie/chat-protocol.js +32 -0
- package/src/kits/os/freddie/chat-transport.js +178 -0
- package/src/kits/os/freddie/pages-chat.js +10 -180
- package/src/kits/os/shell-chrome.js +163 -0
- package/src/kits/os/shell-geometry.js +59 -0
- package/src/kits/os/shell.js +178 -335
- package/src/page-html/client-script.js +151 -0
- package/src/page-html/head-tags.js +59 -0
- package/src/page-html/markdown.js +68 -0
- package/src/page-html/page-styles.js +44 -0
- package/src/page-html.js +14 -291
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// The client-side mount script the SSR document embeds, as a string. It is
|
|
2
|
+
// NOT module code in this file's own scope: it ships verbatim inside the
|
|
3
|
+
// emitted <script type="module"> and runs in the browser against the SDK's
|
|
4
|
+
// `mount`/`components`/`h` exports plus the `__site__` JSON payload.
|
|
5
|
+
//
|
|
6
|
+
// It stays a template literal (not a real module compiled and inlined) because
|
|
7
|
+
// it must reach the browser as source with its own escaping intact — every
|
|
8
|
+
// backslash/backtick sequence below is written for the emitted output, not for
|
|
9
|
+
// this file. `clientScriptExtra` is appended raw after the mount() call.
|
|
10
|
+
|
|
11
|
+
export const CLIENT_SCRIPT = `import { mount, components as C, h } from 'anentrypoint-design';
|
|
12
|
+
const data = JSON.parse(document.getElementById('__site__').textContent);
|
|
13
|
+
|
|
14
|
+
function heroNode(hero) {
|
|
15
|
+
if (!hero) return null;
|
|
16
|
+
const badges = Array.isArray(hero.badges) ? hero.badges.filter(Boolean) : [];
|
|
17
|
+
const badgeRow = badges.length
|
|
18
|
+
? h('div', { class: 'ds-hero-stats' }, ...badges.map((b, i) =>
|
|
19
|
+
h('span', { key: i, class: 'ds-hero-stat' },
|
|
20
|
+
h('strong', { class: 'ds-hero-stat-n' }, String(b.label != null ? b.label : b)),
|
|
21
|
+
b.desc ? h('span', { class: 'ds-hero-stat-l' }, String(b.desc)) : null,
|
|
22
|
+
)))
|
|
23
|
+
: null;
|
|
24
|
+
return h('div', { class: 'ds-hero' },
|
|
25
|
+
hero.eyebrow ? h('span', { class: 'eyebrow' }, hero.eyebrow) : null,
|
|
26
|
+
h('h1', { class: 'ds-hero-title' }, hero.heading || hero.title || data.title),
|
|
27
|
+
(hero.body || hero.subheading) ? h('p', { class: 'ds-hero-body' },
|
|
28
|
+
hero.body || hero.subheading,
|
|
29
|
+
hero.accent ? h('span', { class: 'ds-hero-accent' }, ' ' + hero.accent) : null,
|
|
30
|
+
) : null,
|
|
31
|
+
Array.isArray(hero.ctas) && hero.ctas.length
|
|
32
|
+
? h('div', { class: 'ds-hero-actions' }, ...hero.ctas.map((c, i) =>
|
|
33
|
+
h('a', { key: i, class: i === 0 ? 'btn btn-accent' : 'btn btn-ghost', href: c.href || '#' }, c.label || c.cta || 'go')))
|
|
34
|
+
: null,
|
|
35
|
+
badgeRow,
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function sectionNode(sec, idx) {
|
|
40
|
+
const features = sec.features || sec.items || [];
|
|
41
|
+
const rows = features.map((f, i) => {
|
|
42
|
+
const kids = [h('div', { key: 't', class: 'ds-feature-title' }, String(f.name || ''))];
|
|
43
|
+
if (f.desc) kids.push(h('div', { key: 'd', class: 'ds-feature-desc', innerHTML: String(f.desc).replace(/\`([^\`]+)\`/g, '<code>$1</code>') }));
|
|
44
|
+
if (f.benefit) kids.push(h('div', { key: 'b', class: 'ds-feature-benefit' }, String(f.benefit)));
|
|
45
|
+
return h('div', { key: i, class: 'ds-feature' }, ...kids);
|
|
46
|
+
});
|
|
47
|
+
return C.Section({
|
|
48
|
+
id: sec.id || null,
|
|
49
|
+
title: sec.name || sec.title || sec.id,
|
|
50
|
+
children: [
|
|
51
|
+
sec.lede ? h('p', { class: 'ds-lede' }, sec.lede) : null,
|
|
52
|
+
...rows,
|
|
53
|
+
sec.body && String(sec.body).trim() ? h('div', { class: 'page-body', innerHTML: __md(sec.body) }) : null,
|
|
54
|
+
].filter(Boolean),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function examplesNode(examples) {
|
|
59
|
+
if (!examples || !examples.length) return null;
|
|
60
|
+
return C.Section({
|
|
61
|
+
title: 'explore',
|
|
62
|
+
children: examples.map((e, i) => {
|
|
63
|
+
const kids = [
|
|
64
|
+
h('span', { key: 'c', class: 'code' }, String(i + 1).padStart(2, '0')),
|
|
65
|
+
h('span', { key: 't', class: 'title' }, String(e.label || e.name || e.href || '')),
|
|
66
|
+
];
|
|
67
|
+
if (e.desc) kids.push(h('span', { key: 'm', class: 'meta dim' }, ' — ' + e.desc));
|
|
68
|
+
kids.push(h('span', { key: 'a', class: 'ds-row-arrow' }, '->'));
|
|
69
|
+
return h('a', { key: i, class: 'row', href: e.href || '#' }, ...kids);
|
|
70
|
+
}),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function panelNode(panel, idx) {
|
|
75
|
+
const items = Array.isArray(panel.items) ? panel.items : [];
|
|
76
|
+
if (!items.length) return null;
|
|
77
|
+
const rows = items.map((it, i) => {
|
|
78
|
+
const kids = [
|
|
79
|
+
h('span', { key: 'c', class: 'code' }, String(it.code || String(i + 1).padStart(2, '0'))),
|
|
80
|
+
h('span', { key: 't', class: 'title' }, String(it.title || it.name || '')),
|
|
81
|
+
];
|
|
82
|
+
if (it.sub || it.desc) kids.push(h('span', { key: 'm', class: 'meta dim' }, ' — ' + (it.sub || it.desc)));
|
|
83
|
+
kids.push(h('span', { key: 'a', class: 'ds-row-arrow' }, it.meta || '->'));
|
|
84
|
+
return h('a', { key: i, class: 'row', href: it.href || '#' }, ...kids);
|
|
85
|
+
});
|
|
86
|
+
return C.Panel({ id: panel.id || null, title: panel.title || panel.name || '', count: panel.count || items.length, children: rows });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function marqueeNode(marquee) {
|
|
90
|
+
if (!marquee || !Array.isArray(marquee.items) || !marquee.items.length) return null;
|
|
91
|
+
return C.Marquee ? C.Marquee({ items: marquee.items, sep: marquee.sep || '/' }) : null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function quickstartNode(quickstart) {
|
|
95
|
+
if (!quickstart || !Array.isArray(quickstart.lines) || !quickstart.lines.length) return null;
|
|
96
|
+
const lineNodes = quickstart.lines.map((l, i) => h('div', { key: 'q' + i, class: 'cli' },
|
|
97
|
+
h('span', { class: 'prompt' }, l.kind === 'cmt' ? '#' : '$'),
|
|
98
|
+
h('span', { class: 'cmd' }, l.text)
|
|
99
|
+
));
|
|
100
|
+
return C.Panel({ title: quickstart.heading || 'quick start', children: h('div', { class: 'ds-quickstart' }, ...lineNodes) });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function sideNode(sidebar) {
|
|
104
|
+
if (!sidebar || !Array.isArray(sidebar.sections) || !sidebar.sections.length || !C.Side) return null;
|
|
105
|
+
return C.Side({ sections: sidebar.sections });
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// minimal client-side markdown renderer matching server-side renderer (idempotent for already-html bodies)
|
|
109
|
+
function __slug(s) { return String(s || '').trim().toLowerCase().replace(/[^\\w\\s-]/g, '').replace(/\\s+/g, '-'); }
|
|
110
|
+
function __md(md) {
|
|
111
|
+
const lines = String(md || '').split('\\n');
|
|
112
|
+
const out = []; let inCode = false, inList = false;
|
|
113
|
+
const esc = (s) => String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
114
|
+
const inl = (s) => s.replace(/\`([^\`]+)\`/g, '<code>$1</code>').replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>').replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href="$2">$1</a>');
|
|
115
|
+
for (const line of lines) {
|
|
116
|
+
if (line.startsWith('\`\`\`')) { if (inCode) { out.push('</pre>'); inCode = false; } else { out.push('<pre>'); inCode = true; } continue; }
|
|
117
|
+
if (inCode) { out.push(esc(line)); continue; }
|
|
118
|
+
if (line.startsWith('# ')) { const t = line.slice(2); out.push('<h1 id="' + __slug(t) + '">' + esc(t) + '</h1>'); }
|
|
119
|
+
else if (line.startsWith('## ')) { const t = line.slice(3); out.push('<h2 id="' + __slug(t) + '">' + esc(t) + '</h2>'); }
|
|
120
|
+
else if (line.startsWith('### ')) { const t = line.slice(4); out.push('<h3 id="' + __slug(t) + '">' + esc(t) + '</h3>'); }
|
|
121
|
+
else if (line.startsWith('- ')) { if (!inList) { out.push('<ul>'); inList = true; } out.push('<li>' + inl(esc(line.slice(2))) + '</li>'); }
|
|
122
|
+
else { if (inList) { out.push('</ul>'); inList = false; } if (line.trim()) out.push('<p>' + inl(esc(line)) + '</p>'); }
|
|
123
|
+
}
|
|
124
|
+
if (inList) out.push('</ul>');
|
|
125
|
+
if (inCode) out.push('</pre>');
|
|
126
|
+
return out.join('\\n');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const bodyNode = data.bodyHtml ? C.Section({ children: h('div', { class: 'page-body', innerHTML: data.bodyHtml }) }) : null;
|
|
130
|
+
|
|
131
|
+
const mainChildren = [
|
|
132
|
+
heroNode(data.hero),
|
|
133
|
+
marqueeNode(data.marquee),
|
|
134
|
+
...data.sections.map(sectionNode),
|
|
135
|
+
...(data.panels || []).map(panelNode),
|
|
136
|
+
quickstartNode(data.quickstart),
|
|
137
|
+
examplesNode(data.examples),
|
|
138
|
+
bodyNode,
|
|
139
|
+
].filter(Boolean);
|
|
140
|
+
|
|
141
|
+
mount(document.getElementById('app'), () => C.AppShell({
|
|
142
|
+
topbar: C.Topbar({ brand: data.siteName, items: data.navItems, active: data.title }),
|
|
143
|
+
crumb: C.Crumb({ leaf: data.title }),
|
|
144
|
+
side: sideNode(data.sidebar),
|
|
145
|
+
main: h('div', { class: 'app-stage' }, ...mainChildren),
|
|
146
|
+
status: C.Status({
|
|
147
|
+
left: data.statusLeft || [data.siteName.toLowerCase(), data.slug],
|
|
148
|
+
right: data.statusRight || ['live'],
|
|
149
|
+
}),
|
|
150
|
+
}));
|
|
151
|
+
`;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Document <head> fragments for the static-site renderer: the full
|
|
2
|
+
// SEO/OG/twitter/schema.org meta block, the favicon link (static href or an
|
|
3
|
+
// inline data: SVG from a single glyph), and the stylesheet link.
|
|
4
|
+
|
|
5
|
+
import { escape } from './markdown.js';
|
|
6
|
+
|
|
7
|
+
// Full SEO/OG/twitter/schema.org meta block, extracted so renderPageHtml
|
|
8
|
+
// consumers (design's own marketing site, thebird's landing) can opt in
|
|
9
|
+
// instead of hand-rolling ~40 lines of <meta> tags per theme.mjs.
|
|
10
|
+
export function renderSeoTags({ title, siteName, seo }) {
|
|
11
|
+
const desc = escape(seo.description || '');
|
|
12
|
+
const url = escape(seo.url || '');
|
|
13
|
+
const image = escape(seo.image || '');
|
|
14
|
+
const author = escape(seo.author || siteName);
|
|
15
|
+
const twitter = escape(seo.twitter || '');
|
|
16
|
+
const locale = escape(seo.locale || 'en_US');
|
|
17
|
+
const keywords = escape(Array.isArray(seo.keywords) ? seo.keywords.join(', ') : (seo.keywords || ''));
|
|
18
|
+
const ogTitle = escape(title);
|
|
19
|
+
const ogDesc = escape(seo.description || siteName);
|
|
20
|
+
let out = `
|
|
21
|
+
<meta name="description" content="${desc}">
|
|
22
|
+
${keywords ? `<meta name="keywords" content="${keywords}">` : ''}
|
|
23
|
+
<meta name="author" content="${author}">
|
|
24
|
+
<meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1">
|
|
25
|
+
<meta name="generator" content="anentrypoint-design">
|
|
26
|
+
${url ? `<link rel="canonical" href="${url}">` : ''}
|
|
27
|
+
<meta property="og:type" content="website">
|
|
28
|
+
<meta property="og:title" content="${ogTitle}">
|
|
29
|
+
<meta property="og:description" content="${ogDesc}">
|
|
30
|
+
${url ? `<meta property="og:url" content="${url}">` : ''}
|
|
31
|
+
<meta property="og:site_name" content="${escape(siteName)}">
|
|
32
|
+
<meta property="og:locale" content="${locale}">
|
|
33
|
+
${image ? `<meta property="og:image" content="${image}">` : ''}
|
|
34
|
+
<meta name="twitter:card" content="summary_large_image">
|
|
35
|
+
<meta name="twitter:title" content="${ogTitle}">
|
|
36
|
+
<meta name="twitter:description" content="${ogDesc}">
|
|
37
|
+
${twitter ? `<meta name="twitter:site" content="${twitter}">` : ''}
|
|
38
|
+
${image ? `<meta name="twitter:image" content="${image}">` : ''}`;
|
|
39
|
+
if (seo.ldJson !== false && (seo.description || url)) {
|
|
40
|
+
const ld = JSON.stringify({
|
|
41
|
+
'@context': 'https://schema.org', '@type': 'WebSite',
|
|
42
|
+
name: title, url: seo.url || '', description: seo.description || '', inLanguage: seo.lang || 'en',
|
|
43
|
+
}).replace(/</g, '\\u003c');
|
|
44
|
+
out += `\n<script type="application/ld+json">${ld}</script>`;
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function renderFaviconTags({ faviconHref, faviconGlyph }) {
|
|
50
|
+
if (faviconHref) return `<link rel="icon" href="${escape(faviconHref)}">`;
|
|
51
|
+
if (faviconGlyph) return `<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Ctext y='26' font-size='26'%3E${encodeURIComponent(faviconGlyph)}%3C/text%3E%3C/svg%3E">`;
|
|
52
|
+
return '';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function renderCssLink({ cssHref, pkgVersion }) {
|
|
56
|
+
return cssHref
|
|
57
|
+
? `<link rel="stylesheet" href="${cssHref}">`
|
|
58
|
+
: `<link rel="stylesheet" href="https://unpkg.com/anentrypoint-design@${pkgVersion}/dist/247420.css">`;
|
|
59
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Server-side markdown for the static-site renderer: HTML escaping, the
|
|
2
|
+
// inline subset, GitHub-flavored heading slugs, and the block renderer with
|
|
3
|
+
// its opt-in ```html raw-passthrough fence.
|
|
4
|
+
|
|
5
|
+
// Single source of HTML escaping lives in markdown.js (full entity set). Kept
|
|
6
|
+
// the `escape` export name for backward compatibility with any consumer.
|
|
7
|
+
import { escapeHtml } from '../markdown.js';
|
|
8
|
+
export const escape = escapeHtml;
|
|
9
|
+
|
|
10
|
+
export function inlineMd(s) {
|
|
11
|
+
return s
|
|
12
|
+
.replace(/`([^`]+)`/g, '<code>$1</code>')
|
|
13
|
+
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
|
|
14
|
+
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// GitHub-flavored heading slug: lowercase, strip non-word/non-space/non-hyphen,
|
|
18
|
+
// collapse whitespace to hyphens. Matches the fallback anchor target a hero/nav
|
|
19
|
+
// CTA's `#slug` href expects to resolve against a `## slug text` body heading.
|
|
20
|
+
export function slugify(s) {
|
|
21
|
+
return String(s || '').trim().toLowerCase()
|
|
22
|
+
.replace(/[^\w\s-]/g, '')
|
|
23
|
+
.replace(/\s+/g, '-');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Raw-HTML passthrough block, opt-in via ```html fences. Distinct from a
|
|
27
|
+
// plain ``` code fence (which still escapes+<pre>-wraps its contents) — this
|
|
28
|
+
// is for SSR call sites that are trusted, repo-authored content (a theme.mjs
|
|
29
|
+
// page body sourced from the project's own YAML, never end-user input) and
|
|
30
|
+
// need to emit real markup (e.g. an <iframe> demo embed) that must NOT be
|
|
31
|
+
// escaped. There is no sanitization here by design: the caller owns trust.
|
|
32
|
+
// A consumer rendering untrusted content must not route it through this
|
|
33
|
+
// path — use markdown.js's DOMPurify-backed renderMarkdown for that instead.
|
|
34
|
+
export function renderMarkdown(md) {
|
|
35
|
+
const lines = String(md || '').split('\n');
|
|
36
|
+
const out = [];
|
|
37
|
+
let inCode = false, inList = false, inRawHtml = false;
|
|
38
|
+
for (const line of lines) {
|
|
39
|
+
if (line.trim() === '```html') { if (!inCode) { inRawHtml = true; continue; } }
|
|
40
|
+
if (line.startsWith('```')) {
|
|
41
|
+
if (inRawHtml) { inRawHtml = false; continue; }
|
|
42
|
+
if (inCode) { out.push('</pre>'); inCode = false; } else { out.push('<pre>'); inCode = true; }
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (inRawHtml) { out.push(line); continue; }
|
|
46
|
+
if (inCode) { out.push(escape(line)); continue; }
|
|
47
|
+
if (line.startsWith('# ')) { const t = line.slice(2); out.push(`<h1 id="${slugify(t)}">${escape(t)}</h1>`); }
|
|
48
|
+
else if (line.startsWith('## ')) { const t = line.slice(3); out.push(`<h2 id="${slugify(t)}">${escape(t)}</h2>`); }
|
|
49
|
+
else if (line.startsWith('### ')) { const t = line.slice(4); out.push(`<h3 id="${slugify(t)}">${escape(t)}</h3>`); }
|
|
50
|
+
else if (line.startsWith('- ')) { if (!inList) { out.push('<ul>'); inList = true; } out.push(`<li>${inlineMd(escape(line.slice(2)))}</li>`); }
|
|
51
|
+
else { if (inList) { out.push('</ul>'); inList = false; } if (line.trim()) out.push(`<p>${inlineMd(escape(line))}</p>`); }
|
|
52
|
+
}
|
|
53
|
+
if (inList) out.push('</ul>');
|
|
54
|
+
if (inCode) out.push('</pre>');
|
|
55
|
+
return out.join('\n');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Join a basePath prefix to a nav href. Absolute URLs and hash links pass
|
|
59
|
+
// through unchanged; leading-slash paths get the prefix.
|
|
60
|
+
export function joinHref(basePath, href) {
|
|
61
|
+
if (!href) return '#';
|
|
62
|
+
const h = String(href);
|
|
63
|
+
if (/^([a-z]+:|#|\/\/)/i.test(h)) return h;
|
|
64
|
+
if (!basePath) return h;
|
|
65
|
+
const base = basePath.replace(/\/+$/, '');
|
|
66
|
+
if (h.startsWith('/')) return base + h;
|
|
67
|
+
return base + '/' + h.replace(/^\.?\//, '');
|
|
68
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// The inline <style> block the SSR document ships ahead of the unpkg CSS
|
|
2
|
+
// bundle: page-stage rhythm, body-prose spacing, hero stat strip, and the
|
|
3
|
+
// feature-row stack. Kept inline (not in a .css file) because it must apply
|
|
4
|
+
// before the bundle loads — several rules carry !important purely to beat the
|
|
5
|
+
// bundle on load-order at equal specificity.
|
|
6
|
+
|
|
7
|
+
export const PAGE_INLINE_STYLES = `
|
|
8
|
+
.app-stage { width: 100%; max-width: var(--stage-wide, min(96vw, 1440px)); margin-inline: auto; padding: var(--space-6, 48px) var(--space-4, 24px) var(--space-8, 96px); display: grid; gap: var(--space-6, 48px); box-sizing: border-box }
|
|
9
|
+
@media (max-width: 768px) { .app-stage { padding: var(--space-4, 24px) var(--space-3, 16px) var(--space-6, 48px); gap: var(--space-5, 32px) } }
|
|
10
|
+
.page-body > :first-child { margin-top: 0 }
|
|
11
|
+
.page-body h1 { margin-top: 0 } .page-body h2 { margin-top: var(--space-5, 32px) } .page-body h3 { margin-top: var(--space-4, 24px) }
|
|
12
|
+
.page-body > * + * { margin-top: var(--space-3, 16px) }
|
|
13
|
+
.page-body pre { margin: var(--space-3, 16px) 0; background: var(--panel-2); padding: var(--space-3, 16px); border-radius: var(--r-1, 10px); overflow-x: auto }
|
|
14
|
+
/* .app-stage owns inter-block rhythm via grid gap; sections/hero must not double it.
|
|
15
|
+
These selectors carry !important because this inline block loads before the
|
|
16
|
+
unpkg CSS bundle, which would otherwise win on load-order for equal specificity. */
|
|
17
|
+
.ds-247420 .app-stage > .ds-hero { margin: 0 !important; padding: var(--space-4, 24px) 0 0 !important; max-width: none !important; gap: var(--space-4, 24px) !important }
|
|
18
|
+
.ds-247420 .app-stage > .ds-section { margin: 0 !important }
|
|
19
|
+
.app-stage .row + .row { margin-top: var(--space-1, 4px) }
|
|
20
|
+
.app-stage .ds-section .row { margin-top: var(--space-2, 8px) }
|
|
21
|
+
.app-stage .ds-section > p.ds-lede { margin: 0 0 var(--space-3, 16px); max-width: var(--measure, 68ch); color: var(--fg-2) }
|
|
22
|
+
.row-benefit { font-style: italic; color: var(--fg-3); font-size: var(--fs-sm); margin-top: var(--space-1, 4px) }
|
|
23
|
+
.ds-row-arrow { margin-left: auto; opacity: .5; transition: opacity var(--dur-snap, 80ms) var(--ease) }
|
|
24
|
+
a.row:hover .ds-row-arrow { opacity: 1 }
|
|
25
|
+
/* hero stat strip — all badges as a wrapping inline rhythm, not one empty panel */
|
|
26
|
+
.ds-hero-stats { display: flex; flex-wrap: wrap; gap: var(--space-3, 16px) var(--space-5, 32px); margin-top: var(--space-2, 8px) }
|
|
27
|
+
.ds-hero-stat { display: flex; align-items: baseline; gap: var(--space-2, 8px) }
|
|
28
|
+
.ds-hero-stat-n { font-family: var(--ff-body); font-weight: 700; font-size: var(--fs-lg, 18px); color: var(--fg) }
|
|
29
|
+
.ds-hero-stat-l { font-size: var(--fs-sm, 15px); color: var(--fg-3) }
|
|
30
|
+
/* accent sits on its own line, muted, so it reads as a distinct aside instead
|
|
31
|
+
of running on from the hero body sentence. */
|
|
32
|
+
.ds-hero-accent { display: block; margin-top: var(--space-2, 8px); color: var(--fg-3) }
|
|
33
|
+
/* feature rows — single-column stack (the dashboard .row grid forces a 3-col
|
|
34
|
+
code/title/meta layout that mangles title+desc+benefit) */
|
|
35
|
+
/* background uses a theme-neutral panel token (resolves per data-theme) so dark
|
|
36
|
+
mode doesn't flash a literal white card before/independent of the bundle.
|
|
37
|
+
Flat tonal fill only — no border-left rail accent (house style: no bespoke
|
|
38
|
+
tile chrome, no shadows, no borders; see ui_kits/gallery/app.js). */
|
|
39
|
+
.ds-feature { padding: var(--space-3, 16px) var(--space-4, 24px); background: var(--panel-1, var(--bg)); border-radius: var(--r-2, 14px); display: grid; gap: var(--space-1, 4px) }
|
|
40
|
+
.ds-feature + .ds-feature { margin-top: var(--space-2, 8px) }
|
|
41
|
+
.ds-feature-title { font-weight: 600; font-size: var(--fs-lg, 18px); color: var(--fg) }
|
|
42
|
+
.ds-feature-desc { font-size: var(--fs-sm, 15px); color: var(--fg-2); line-height: 1.5; overflow-wrap: anywhere }
|
|
43
|
+
.ds-feature-benefit { font-style: italic; font-size: var(--fs-sm, 15px); color: var(--fg-3); margin-top: var(--space-1, 4px) }
|
|
44
|
+
`.trim();
|