anentrypoint-design 0.0.414 → 0.0.416
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.js +62 -12
- package/dist/app-shell.css +40 -594
- package/dist/colors_and_type.css +607 -47
- package/dist/index.html +227 -277
- package/dist/preview/buttons.html +39 -28
- package/dist/preview/colors-core.html +18 -8
- package/dist/preview/colors-lore.html +26 -17
- package/dist/preview/colors-semantic.html +24 -18
- package/dist/preview/dateline.html +6 -5
- package/dist/preview/dropzone.html +4 -3
- package/dist/preview/file-grid.html +10 -9
- package/dist/preview/file-row.html +14 -13
- package/dist/preview/file-toolbar.html +4 -3
- package/dist/preview/file-viewer.html +4 -3
- package/dist/preview/header.html +26 -24
- package/dist/preview/icons-unicode.html +8 -5
- package/dist/preview/index-row.html +17 -9
- package/dist/preview/inputs.html +4 -3
- package/dist/preview/manifesto.html +34 -17
- package/dist/preview/motion-default.js +77 -57
- package/dist/preview/rules.html +4 -3
- package/dist/preview/spacing.html +13 -12
- package/dist/preview/stamps-lore.html +5 -4
- package/dist/preview/stamps.html +4 -3
- package/dist/preview/theme-ink.html +7 -6
- package/dist/preview/type-display.html +14 -5
- package/dist/preview/type-mono.html +6 -5
- package/dist/preview/type-prose.html +12 -10
- package/dist/preview/type-scale.html +4 -3
- package/dist/preview/wordmarks.html +10 -7
- package/dist/site/content/globals/site.yaml +5 -3
- package/dist/site/content/pages/freddie.yaml +2 -2
- package/dist/site/content/pages/home.yaml +55 -55
- package/dist/site/theme.mjs +96 -348
- package/dist/slides/index.html +13 -13
- package/dist/src/bootstrap.js +10 -1
- package/dist/src/components/chat.js +43 -195
- package/dist/src/components/community.js +18 -166
- package/dist/src/components/content.js +30 -210
- package/dist/src/components/files-modals.js +14 -106
- package/dist/src/components/files.js +15 -116
- package/dist/src/components/freddie/helpers.js +53 -8
- package/dist/src/components/freddie.js +35 -25
- package/dist/src/components/shell.js +16 -114
- package/dist/src/components/theme-toggle.js +69 -25
- package/dist/src/components.js +92 -13
- package/dist/src/deck-stage.js +6 -4
- package/dist/src/highlight.js +51 -7
- package/dist/src/index.js +58 -12
- package/dist/src/kits/os/about-app.js +2 -3
- package/dist/src/kits/os/app-panes.css +46 -70
- package/dist/src/kits/os/browser-app.js +28 -1
- package/dist/src/kits/os/files-app.js +7 -0
- package/dist/src/kits/os/freddie/pages-chat.js +90 -75
- package/dist/src/kits/os/freddie/pages-core.js +9 -9
- package/dist/src/kits/os/freddie/pages-os.js +8 -8
- package/dist/src/kits/os/freddie/pages-tools.js +14 -14
- package/dist/src/kits/os/freddie/routes.js +21 -19
- package/dist/src/kits/os/freddie-dashboard.css +42 -21
- package/dist/src/kits/os/freddie-dashboard.js +6 -6
- package/dist/src/kits/os/icons.js +38 -16
- package/dist/src/kits/os/index.js +11 -0
- package/dist/src/kits/os/launcher.css +26 -10
- package/dist/src/kits/os/launcher.js +3 -0
- package/dist/src/kits/os/shell.js +72 -108
- package/dist/src/kits/os/terminal-app.js +1 -1
- package/dist/src/kits/os/theme.css +1330 -150
- package/dist/src/kits/os/wm.css +68 -15
- package/dist/src/kits/os/wm.js +93 -17
- package/dist/src/markdown.js +121 -12
- package/dist/src/motion.js +18 -2
- package/dist/src/page-html.js +62 -134
- package/dist/src/theme.js +106 -13
- package/dist/src/web-components/ds-chat.js +80 -9
- package/dist/ui_kits/aicat/app.js +54 -6
- package/dist/ui_kits/aicat/index.html +2 -0
- package/dist/ui_kits/blog/index.html +33 -17
- package/dist/ui_kits/chat/app.js +90 -22
- package/dist/ui_kits/chat/index.html +2 -0
- package/dist/ui_kits/community/app.js +86 -24
- package/dist/ui_kits/community/index.html +1 -0
- package/dist/ui_kits/dashboard/app.js +123 -34
- package/dist/ui_kits/dashboard/index.html +1 -0
- package/dist/ui_kits/docs/index.html +43 -33
- package/dist/ui_kits/error_404/app.js +78 -23
- package/dist/ui_kits/file_browser/README.md +1 -1
- package/dist/ui_kits/file_browser/app.js +45 -10
- package/dist/ui_kits/file_browser/index.html +1 -0
- package/dist/ui_kits/gallery/app.js +130 -51
- package/dist/ui_kits/homepage/app.js +156 -138
- package/dist/ui_kits/project_page/app.js +142 -125
- package/dist/ui_kits/project_page/index.html +2 -1
- package/dist/ui_kits/search/app.js +52 -15
- package/dist/ui_kits/settings/app.js +166 -39
- package/dist/ui_kits/signin/app.js +175 -49
- package/dist/ui_kits/signin/index.html +1 -0
- package/dist/ui_kits/slide_deck/app.js +83 -49
- package/dist/ui_kits/system_primer/app.js +57 -40
- package/dist/ui_kits/terminal/app.js +160 -43
- package/package.json +1 -1
- package/src/page-html/client-script.js +40 -11
- package/src/page-html/page-styles.js +22 -1
|
@@ -30,25 +30,41 @@
|
|
|
30
30
|
<link rel="stylesheet" href="../../colors_and_type.css">
|
|
31
31
|
<link rel="stylesheet" href="../../app-shell.css">
|
|
32
32
|
<style>
|
|
33
|
-
.post-meta{display:flex;gap:
|
|
33
|
+
.post-meta{display:flex;gap:var(--space-2-5);flex-wrap:wrap;margin-bottom:var(--space-4);font-family:var(--ff-mono);font-size:var(--fs-xs);text-transform:uppercase;letter-spacing:0.08em;color:var(--panel-text-2);}
|
|
34
34
|
.post-meta .sep{color:var(--panel-text-3);}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
/* padding right/left is 18px: off-scale between --space-3 (16px) and
|
|
36
|
+
--space-3-5 (20px), deliberately. A pull-quote's side inset is set against
|
|
37
|
+
the 62ch measure, not the 8pt rhythm — snapping to 16px lets the quote
|
|
38
|
+
text sit flush enough with body prose to stop reading as a quote, and 20px
|
|
39
|
+
over-indents it past the accent rule. Kept as a literal on purpose. */
|
|
40
|
+
blockquote{margin:var(--space-3-5) 0;padding:var(--space-2-75) 18px;background:var(--panel-1);border-radius:var(--r-1);font-size:var(--fs-sm);color:var(--panel-text);line-height:1.55;max-width:62ch;}
|
|
41
|
+
/* font-size is the GLYPH size of the decorative open-quote mark, matched to
|
|
42
|
+
the mark's optical weight beside 15px prose, not a type-ladder tier — the
|
|
43
|
+
same treatment lint-tokens.mjs gives icon sizes set via font-size. */
|
|
44
|
+
blockquote::before{content:'“';color:var(--panel-accent);font-family:var(--ff-display);font-size:28px;margin-right:var(--space-1-75);line-height:0;vertical-align:-4px;}
|
|
45
|
+
.post-body p{font-size:var(--fs-sm);line-height:1.7;max-width:62ch;color:var(--panel-text);}
|
|
46
|
+
/* --panel-accent is the FILL lime (~1.07:1 on paper — invisible as text).
|
|
47
|
+
Prose links must use the readable TEXT tone, which is exactly what
|
|
48
|
+
base.css's `.ds-prose a` already sets, so this rule only needs to not
|
|
49
|
+
fight it. Kept explicit so the intent survives the next edit. */
|
|
50
|
+
.post-body a{color:var(--accent-ink);}
|
|
39
51
|
</style>
|
|
40
52
|
|
|
41
53
|
</head>
|
|
42
54
|
<body data-screen-label="04 Blog">
|
|
43
55
|
<div class="app">
|
|
56
|
+
<!-- Matches what AppShell() emits for the JS-rendered kits. This page hand-
|
|
57
|
+
rolls the shell markup statically, so it does not inherit the skip link
|
|
58
|
+
and had no way past the header nav for a keyboard user. -->
|
|
59
|
+
<a href="#app-main" class="skip-link">skip to main content</a>
|
|
44
60
|
<header class="app-topbar">
|
|
45
61
|
<span class="brand">247420<span class="slash"> / </span>writing</span>
|
|
46
62
|
<nav>
|
|
47
|
-
<a href="../homepage/"
|
|
48
|
-
<a
|
|
49
|
-
<a
|
|
50
|
-
<a
|
|
51
|
-
<a
|
|
63
|
+
<a href="../homepage/"><- homepage</a>
|
|
64
|
+
<a class="active" aria-current="page">lore</a>
|
|
65
|
+
<a class="is-unwritten" title="no posts under this tag yet">gm</a>
|
|
66
|
+
<a class="is-unwritten" title="no posts under this tag yet">manifesto</a>
|
|
67
|
+
<a class="is-unwritten" title="no posts under this tag yet">notes</a>
|
|
52
68
|
</nav>
|
|
53
69
|
</header>
|
|
54
70
|
<div class="app-crumb">
|
|
@@ -58,9 +74,9 @@
|
|
|
58
74
|
<span class="leaf">we-were-here-first.md</span>
|
|
59
75
|
</div>
|
|
60
76
|
<div class="app-body no-side">
|
|
61
|
-
<main class="app-main narrow
|
|
77
|
+
<main class="app-main narrow" id="app-main" tabindex="0">
|
|
62
78
|
<div class="post-meta">
|
|
63
|
-
<span
|
|
79
|
+
<span>lore</span><span class="sep">·</span>
|
|
64
80
|
<span>apr 14 2026</span><span class="sep">·</span>
|
|
65
81
|
<span>8 min read</span><span class="sep">·</span>
|
|
66
82
|
<span><span class="chip accent">draft</span></span>
|
|
@@ -71,20 +87,20 @@
|
|
|
71
87
|
<p>there's a particular feeling — you know the one — of watching something you made five years ago get repackaged, re-skinned, and re-announced as if it were new. the web is a very long memory with a very short attention span, and 247420 has been, quietly, <em>around</em> for most of it.</p>
|
|
72
88
|
<p>this isn't a victory lap. it's a map. because if you're showing up to the party now, you should at least know who set the table.</p>
|
|
73
89
|
<blockquote>we don't know which ideas become the future. we just know most of them come from the same few rooms.</blockquote>
|
|
74
|
-
<p>the rooms have moved
|
|
90
|
+
<p>the rooms have moved, from irc to forums to discord to whatever-this-is-now, but the pattern is consistent. a handful of people, each a little too weird to hold a normal job, trading prototypes at 2am, throwing away 90% of them, shipping the 10% that's too strange to ignore. that's the collective. that's always been the collective. <a href="https://github.com/AnEntrypoint/mutagen">adaptogen</a>, <code>gm</code>, zellous, and <a href="https://github.com/AnEntrypoint">the long list of things you've never heard of</a> all came out of the same 2am.</p>
|
|
75
91
|
<h2>so what's the actual point</h2>
|
|
76
92
|
<p>the point is: stop asking permission. ship the rough draft. document honestly. humor is load-bearing. and if you're reading this thinking "that's obvious" — good. you're home.</p>
|
|
77
|
-
<p class="ds-prose-endnote"
|
|
93
|
+
<p class="ds-prose-endnote">end. reply by opening a pr. <a href="https://github.com/AnEntrypoint/design">source -></a></p>
|
|
78
94
|
</div>
|
|
79
95
|
</main>
|
|
80
96
|
</div>
|
|
81
97
|
<footer class="app-status">
|
|
82
98
|
<span class="item">main</span>
|
|
83
|
-
<span class="item"
|
|
84
|
-
<span class="item"
|
|
99
|
+
<span class="item">- markdown</span>
|
|
100
|
+
<span class="item">- 8 min</span>
|
|
85
101
|
<span class="spread"></span>
|
|
86
102
|
<span class="item">apr 14 2026</span>
|
|
87
|
-
<span class="item"
|
|
103
|
+
<span class="item">- draft</span>
|
|
88
104
|
</footer>
|
|
89
105
|
</div>
|
|
90
106
|
</body></html>
|
package/dist/ui_kits/chat/app.js
CHANGED
|
@@ -7,7 +7,7 @@ const h = webjsx.createElement;
|
|
|
7
7
|
const seed = [
|
|
8
8
|
{ who: 'them', avatar: 'jr', name: 'jordan', time: '14:02',
|
|
9
9
|
parts: [{ kind: 'text', text: 'pushed v0.0.27, theme cleanup looks clean now. see the **release notes** in [the changelog](https://github.com/AnEntrypoint/design/releases).' }],
|
|
10
|
-
reactions: [{ emoji: '
|
|
10
|
+
reactions: [{ emoji: 'yay', count: 3, you: true }, { emoji: 'eyes', count: 1 }] },
|
|
11
11
|
{ who: 'them', avatar: 'mk', name: 'mai', time: '14:03',
|
|
12
12
|
parts: [{ kind: 'text', text: 'nice. body-hide trick on first paint? share the diff?' }] },
|
|
13
13
|
{ who: 'you', avatar: 'me', time: '14:04', receipt: 'read',
|
|
@@ -18,9 +18,9 @@ const seed = [
|
|
|
18
18
|
] },
|
|
19
19
|
{ who: 'them', avatar: 'jr', name: 'jordan', time: '14:05',
|
|
20
20
|
parts: [{ kind: 'md', text: '## review notes\n\nlooks solid. couple things:\n\n- short timeout fallback in case fonts hang\n- announce the `ready` class via `requestIdleCallback`\n- keep no-js fallback to `visibility: visible`\n\n> "ship the rough draft" — but not the broken one.\n\nwill review the rest tonight.' }],
|
|
21
|
-
reactions: [{ emoji: '
|
|
21
|
+
reactions: [{ emoji: 'done', count: 2, you: true }] },
|
|
22
22
|
{ who: 'them', avatar: 'mk', name: 'mai', time: '14:08',
|
|
23
|
-
parts: [{ kind: 'image', src: './sample-svg.svg', alt: 'design system mascot', caption: 'spot the new mascot — final
|
|
23
|
+
parts: [{ kind: 'image', src: './sample-svg.svg', alt: 'design system mascot', caption: 'spot the new mascot — final' }] },
|
|
24
24
|
{ who: 'you', avatar: 'me', time: '14:10', receipt: 'read',
|
|
25
25
|
parts: [
|
|
26
26
|
{ kind: 'text', text: 'attaching the v0.0.27 token sheet for review:' },
|
|
@@ -33,10 +33,41 @@ const seed = [
|
|
|
33
33
|
thumb: './sample-square.png' }] },
|
|
34
34
|
{ who: 'them', avatar: 'mk', name: 'mai', time: '14:14',
|
|
35
35
|
parts: [{ kind: 'file', src: './sample.pdf', name: 'meeting-notes-2026-05-01.pdf', size: 782 }],
|
|
36
|
-
reactions: [{ emoji: '
|
|
36
|
+
reactions: [{ emoji: 'pin', count: 1 }] },
|
|
37
|
+
{ who: 'them', avatar: 'jr', name: 'jordan', time: '14:15', typing: true,
|
|
38
|
+
parts: [] }
|
|
37
39
|
];
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
// `phase` drives the thread. Chat() already owns the empty state (its
|
|
42
|
+
// .chat-empty block), so `empty` here just hands it zero messages plus the
|
|
43
|
+
// room-specific copy; loading and error are rendered by this kit around it.
|
|
44
|
+
const state = { draft: '', room: 'general', messages: seed.slice(), phase: 'ready' };
|
|
45
|
+
const PHASES = ['ready', 'loading', 'empty', 'error'];
|
|
46
|
+
|
|
47
|
+
// Message-shaped shimmer. Reuses .ds-event-row-skeleton + .ds-skel* from
|
|
48
|
+
// app-shell/files.css — the avatar/body/timestamp rhythm is the same shape.
|
|
49
|
+
function ThreadSkeleton() {
|
|
50
|
+
return Panel({ title: 'loading #' + state.room, children: h('div', {},
|
|
51
|
+
...Array.from({ length: 6 }, (_, i) => h('div', { key: 'sk' + i, class: 'ds-event-row-skeleton' },
|
|
52
|
+
h('span', { class: 'ds-skel ds-skel-icon' }),
|
|
53
|
+
h('span', { class: 'ds-skel ds-skel-title' }),
|
|
54
|
+
h('span', { class: 'ds-skel ds-skel-meta' })
|
|
55
|
+
))
|
|
56
|
+
) });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function ThreadError() {
|
|
60
|
+
return Panel({ title: 'thread unavailable', children: h('div', { class: 'ds-alert ds-alert-error' },
|
|
61
|
+
h('span', { class: 'ds-alert-icon' }, '!'),
|
|
62
|
+
h('div', { class: 'ds-alert-content' },
|
|
63
|
+
h('div', { class: 'ds-alert-title' }, 'lost the socket to #' + state.room),
|
|
64
|
+
h('div', { class: 'ds-alert-message' }, 'the connection dropped mid-sync, so the last few messages may be missing and anything you send now would not leave this tab. reconnecting replays from the last message you saw.'),
|
|
65
|
+
h('div', { class: 'ds-alert-retry' },
|
|
66
|
+
h('button', { class: 'btn', onclick: () => { state.phase = 'ready'; kit.render(); } }, 'reconnect')
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
) });
|
|
70
|
+
}
|
|
40
71
|
const rooms = [
|
|
41
72
|
{ glyph: '#', label: 'general', count: 12, key: 'general' },
|
|
42
73
|
{ glyph: '#', label: 'design', count: 4, key: 'design' },
|
|
@@ -71,38 +102,75 @@ function send(text) {
|
|
|
71
102
|
|
|
72
103
|
function App() {
|
|
73
104
|
return AppShell({
|
|
74
|
-
topbar: Topbar({ brand: '247420', leaf: 'chat', items: [['index', '../../'], ['aicat', '../aicat/'], ['source
|
|
105
|
+
topbar: Topbar({ brand: '247420', leaf: 'chat', items: [['index', '../../'], ['aicat', '../aicat/'], ['source ->', 'https://github.com/AnEntrypoint/design']] }),
|
|
75
106
|
crumb: Crumb({ trail: ['247420', 'kits'], leaf: 'chat' }),
|
|
76
107
|
side: Side({
|
|
77
108
|
sections: [
|
|
78
109
|
{ group: 'rooms', items: rooms.map(r => ({ ...r, active: state.room === r.key, onClick: (e) => { e.preventDefault(); state.room = r.key; kit.render(); } })) },
|
|
79
|
-
{ group: 'direct', items: dms.map(r => ({ ...r, active: state.room === r.key, onClick: (e) => { e.preventDefault(); state.room = r.key; kit.render(); } })) }
|
|
110
|
+
{ group: 'direct', items: dms.map(r => ({ ...r, active: state.room === r.key, onClick: (e) => { e.preventDefault(); state.room = r.key; kit.render(); } })) },
|
|
111
|
+
// Reachable state switcher for the thread.
|
|
112
|
+
{ group: 'thread state', items: PHASES.map((p) => ({
|
|
113
|
+
glyph: h('span', { class: state.phase === p ? 'ds-dot ds-dot-on' : 'ds-dot ds-dot-off' }),
|
|
114
|
+
label: p, key: 'ph-' + p, active: state.phase === p,
|
|
115
|
+
onClick: (e) => { e.preventDefault(); state.phase = p; kit.render(); }
|
|
116
|
+
})) }
|
|
80
117
|
]
|
|
81
118
|
}),
|
|
82
119
|
main: [
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
120
|
+
// Every other AppShell kit names its page with an h1; this one had
|
|
121
|
+
// none, so the document went straight from <main> to the thread and
|
|
122
|
+
// a screen reader's heading list came back empty. Visually hidden
|
|
123
|
+
// rather than drawn, because Chat() already renders the room name as
|
|
124
|
+
// its own visible title — a second visible copy would be redundant.
|
|
125
|
+
h('h1', { class: 'sr-only' }, 'chat — #' + state.room),
|
|
126
|
+
h('div', { class: 'ds-section chat-kit-page' },
|
|
127
|
+
h('div', { class: 'ds-chat-layout' },
|
|
128
|
+
state.phase === 'loading' ? ThreadSkeleton()
|
|
129
|
+
: state.phase === 'error' ? ThreadError()
|
|
130
|
+
: Chat({
|
|
131
|
+
title: state.room,
|
|
132
|
+
// Chat()'s own empty block uses `sub` as its body line,
|
|
133
|
+
// so the empty phase gets copy that names what belongs
|
|
134
|
+
// here and what puts it here — not a bare "no messages".
|
|
135
|
+
sub: state.phase === 'empty'
|
|
136
|
+
? 'nobody has posted in #' + state.room + ' yet. say something and it becomes the first message everyone sees on join.'
|
|
137
|
+
: 'public',
|
|
138
|
+
messages: state.phase === 'empty' ? [] : state.messages,
|
|
139
|
+
composer: ChatComposer({
|
|
140
|
+
value: state.draft,
|
|
141
|
+
placeholder: 'message #' + state.room + '…',
|
|
142
|
+
onInput: (v) => { state.draft = v; kit.render(); },
|
|
143
|
+
onSend: send
|
|
144
|
+
})
|
|
145
|
+
}),
|
|
146
|
+
// Persistent detail rail — only revealed once .ds-chat-layout has
|
|
147
|
+
// room to spare (>=1100px, see app-shell.css). On mobile/tablet
|
|
148
|
+
// this is display:none rather than reflowed below the thread, so
|
|
149
|
+
// the composer stays the last on-screen element there.
|
|
150
|
+
h('div', { class: 'ds-chat-detail' },
|
|
151
|
+
Panel({ title: 'this room', children: h('div', { class: 'ds-pattern-notes' },
|
|
152
|
+
h('p', {}, h('strong', {}, '#' + state.room)),
|
|
153
|
+
h('p', {}, rooms.find(r => r.key === state.room)?.count ?? dms.find(r => r.key === state.room)?.count ?? 0, ' members')
|
|
154
|
+
) }),
|
|
155
|
+
Panel({ title: 'participants', children:
|
|
156
|
+
[{ glyph: '·', label: 'jordan' }, { glyph: '·', label: 'mai' }].map((p, i) =>
|
|
157
|
+
h('div', { key: 'p' + i, class: 'ds-pattern-notes' }, h('p', {}, p.glyph + ' ' + p.label))
|
|
158
|
+
)
|
|
159
|
+
})
|
|
160
|
+
)
|
|
161
|
+
),
|
|
95
162
|
Panel({
|
|
96
163
|
title: 'pattern notes',
|
|
97
164
|
children: h('div', { class: 'ds-pattern-notes' },
|
|
98
165
|
h('p', {}, '· bubble corner-cut on the originating side (4–6px) gives directional read without arrows.'),
|
|
99
|
-
h('p', {}, '· own messages take the accent fill so the eye lands on what you said last;
|
|
100
|
-
h('p', {}, '· markdown is parsed by ', h('code', {}, 'marked'), ' and sanitized by ', h('code', {}, 'DOMPurify'), '; code blocks lit by ', h('code', {}, 'prism.js'), '.')
|
|
166
|
+
h('p', {}, '· own messages take the accent fill so the eye lands on what you said last; [x] delivered, [x][x] read.'),
|
|
167
|
+
h('p', {}, '· markdown is parsed by ', h('code', {}, 'marked'), ' and sanitized by ', h('code', {}, 'DOMPurify'), '; code blocks lit by ', h('code', {}, 'prism.js'), '.'),
|
|
168
|
+
h('p', {}, '· >=1100px viewport reveals a persistent "this room" + participants rail beside the thread instead of just widening the message column.')
|
|
101
169
|
)
|
|
102
170
|
})
|
|
103
171
|
)
|
|
104
172
|
],
|
|
105
|
-
status: Status({ left: ['main', '
|
|
173
|
+
status: Status({ left: ['main', '- ' + (state.phase === 'ready' ? state.messages.length : 0) + ' messages', '- ' + rooms.length + ' rooms', '- ' + state.phase], right: ['247420 / mmxxvi'] })
|
|
106
174
|
});
|
|
107
175
|
}
|
|
108
176
|
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
<link rel="icon" type="image/svg+xml" href="../../favicon.svg">
|
|
12
12
|
<link rel="stylesheet" href="../../colors_and_type.css">
|
|
13
13
|
<link rel="stylesheet" href="../../app-shell.css">
|
|
14
|
+
<link rel="stylesheet" href="../../chat.css">
|
|
15
|
+
<link rel="stylesheet" href="../../editor-primitives.css">
|
|
14
16
|
<script type="importmap">
|
|
15
17
|
{ "imports": {
|
|
16
18
|
"webjsx": "../../vendor/webjsx/index.js",
|
|
@@ -32,9 +32,9 @@ const channels = [
|
|
|
32
32
|
|
|
33
33
|
const members = [
|
|
34
34
|
{ label: 'online — 3', members: [
|
|
35
|
-
{ identity: 'jordan', name: 'jordan', status: 'online', color: '
|
|
36
|
-
{ identity: 'mai', name: 'mai', status: 'online', color: '
|
|
37
|
-
{ identity: 'aicat', name: 'aicat', status: 'online', color: '
|
|
35
|
+
{ identity: 'jordan', name: 'jordan', status: 'online', color: 'var(--cat-green)' },
|
|
36
|
+
{ identity: 'mai', name: 'mai', status: 'online', color: 'var(--cat-purple)' },
|
|
37
|
+
{ identity: 'aicat', name: 'aicat', status: 'online', color: 'var(--cat-mascot)' },
|
|
38
38
|
]},
|
|
39
39
|
{ label: 'offline — 2', members: [
|
|
40
40
|
{ identity: 'river', name: 'river', status: 'offline' },
|
|
@@ -47,44 +47,101 @@ const state = {
|
|
|
47
47
|
activeChannel: channels[2],
|
|
48
48
|
collapsedCats: new Set(),
|
|
49
49
|
memberListOpen: false,
|
|
50
|
+
// Which state the message list renders in; cycled from the channel header.
|
|
51
|
+
phase: 'ready',
|
|
50
52
|
voiceOpen: false,
|
|
51
53
|
muted: false,
|
|
52
54
|
deafened: false,
|
|
53
55
|
messages: [
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
// `color` is the FILL (avatar disc, ink text on top); `ink` is the
|
|
57
|
+
// readable TEXT tone for the author name. Same --accent/--accent-ink
|
|
58
|
+
// split the lead accent draws — the bare hue as `color:` on paper is
|
|
59
|
+
// ~3.1:1 and fails AA.
|
|
60
|
+
{ id: 1, author: 'jordan', color: 'var(--cat-green)', ink: 'var(--cat-green-ink)', time: '14:02', text: 'shipped the community shell component. check it out.' },
|
|
61
|
+
{ id: 2, author: 'mai', color: 'var(--cat-purple)', ink: 'var(--cat-purple-ink)', time: '14:03', text: 'looks clean. does it handle collapsed cats?' },
|
|
62
|
+
{ id: 3, author: 'aicat', color: 'var(--cat-mascot)', ink: 'var(--cat-mascot-ink)', time: '14:04', text: 'yes — click any category header.' },
|
|
57
63
|
]
|
|
58
64
|
};
|
|
59
65
|
|
|
60
66
|
const root = document.getElementById('root');
|
|
61
67
|
|
|
68
|
+
const PHASES = ['ready', 'loading', 'empty', 'error'];
|
|
69
|
+
|
|
70
|
+
// Message-shaped shimmer. Reuses .ds-event-row-skeleton + .ds-skel*
|
|
71
|
+
// (app-shell/files.css) — avatar / body / timestamp is the same rhythm.
|
|
72
|
+
function MessagesSkeleton() {
|
|
73
|
+
return h('div', { class: 'ds-community-messages' },
|
|
74
|
+
...Array.from({ length: 6 }, (_, i) => h('div', { key: 'sk' + i, class: 'ds-event-row-skeleton' },
|
|
75
|
+
h('span', { class: 'ds-skel ds-skel-icon' }),
|
|
76
|
+
h('span', { class: 'ds-skel ds-skel-title' }),
|
|
77
|
+
h('span', { class: 'ds-skel ds-skel-meta' })
|
|
78
|
+
))
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function MessagesEmpty(name) {
|
|
83
|
+
return h('div', { class: 'ds-community-messages' },
|
|
84
|
+
h('div', { class: 'ds-empty-state' },
|
|
85
|
+
h('div', { class: 'ds-empty-state-glyph' }, '[ ]'),
|
|
86
|
+
h('p', { class: 'ds-empty-state-msg' }, 'nothing posted in #' + name + ' yet'),
|
|
87
|
+
h('p', { class: 'ds-empty-state-hint' }, 'this is the start of the channel. the first message here is what everyone sees when they join it.')
|
|
88
|
+
)
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function MessagesError(name, onRetry) {
|
|
93
|
+
return h('div', { class: 'ds-community-messages' },
|
|
94
|
+
h('div', { class: 'ds-alert ds-alert-error' },
|
|
95
|
+
h('span', { class: 'ds-alert-icon' }, '!'),
|
|
96
|
+
h('div', { class: 'ds-alert-content' },
|
|
97
|
+
h('div', { class: 'ds-alert-title' }, 'cannot load #' + name),
|
|
98
|
+
h('div', { class: 'ds-alert-message' }, 'the gateway accepted the join but never sent history, so this channel is connected and blank rather than actually empty. rejoining requests the backlog again.'),
|
|
99
|
+
h('div', { class: 'ds-alert-retry' },
|
|
100
|
+
h('button', { class: 'btn', onclick: onRetry }, 'rejoin channel')
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function MessagesBody(name) {
|
|
108
|
+
if (state.phase === 'loading') return MessagesSkeleton();
|
|
109
|
+
if (state.phase === 'error') return MessagesError(name, () => { state.phase = 'ready'; kit.render(); });
|
|
110
|
+
if (state.phase === 'empty') return MessagesEmpty(name);
|
|
111
|
+
return h('div', { class: 'ds-community-messages' },
|
|
112
|
+
...state.messages.map(m => h('div', { class: 'ds-community-msg', key: String(m.id) },
|
|
113
|
+
h('div', { class: 'ds-community-avatar', style: `background:${m.color || 'var(--panel-3)'}` }, m.author[0].toUpperCase()),
|
|
114
|
+
h('div', { class: 'ds-community-msg-body' },
|
|
115
|
+
h('span', { class: 'ds-community-msg-name', style: `color:${m.ink || m.color || 'var(--fg)'}` }, m.author),
|
|
116
|
+
h('span', { class: 'ds-community-msg-time' }, m.time),
|
|
117
|
+
h('p', { class: 'ds-community-msg-text' }, m.text)
|
|
118
|
+
)
|
|
119
|
+
))
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
62
123
|
function App() {
|
|
63
124
|
const ch = state.activeChannel;
|
|
64
125
|
const chatContent = h('div', { class: 'ds-community-main' },
|
|
65
126
|
ChatHeader({
|
|
66
|
-
icon: ch.type === 'voice' ? '
|
|
127
|
+
icon: ch.type === 'voice' ? '((' : '#',
|
|
67
128
|
name: ch.name,
|
|
68
129
|
topic: ch.type === 'text' ? 'community shell demo — click channels to switch' : null,
|
|
69
130
|
toolbar: [
|
|
70
|
-
|
|
71
|
-
|
|
131
|
+
// Reachable state switcher for the message list.
|
|
132
|
+
h('button', { key: 'phase', class: 'btn btn-ghost', onclick: () => {
|
|
133
|
+
state.phase = PHASES[(PHASES.indexOf(state.phase) + 1) % PHASES.length];
|
|
134
|
+
kit.render();
|
|
135
|
+
} }, 'state: ' + state.phase),
|
|
136
|
+
h('button', { key: 'members', class: 'btn btn-ghost', onclick: () => { state.memberListOpen = !state.memberListOpen; kit.render(); } },
|
|
137
|
+
state.memberListOpen ? 'hide members' : 'show members')
|
|
72
138
|
]
|
|
73
139
|
}),
|
|
74
|
-
|
|
75
|
-
...state.messages.map(m => h('div', { class: 'ds-community-msg', key: String(m.id) },
|
|
76
|
-
h('div', { class: 'ds-community-avatar', style: `background:${m.color || 'var(--panel-3)'}` }, m.author[0].toUpperCase()),
|
|
77
|
-
h('div', { class: 'ds-community-msg-body' },
|
|
78
|
-
h('span', { class: 'ds-community-msg-name', style: `color:${m.color || 'var(--fg)'}` }, m.author),
|
|
79
|
-
h('span', { class: 'ds-community-msg-time' }, m.time),
|
|
80
|
-
h('p', { class: 'ds-community-msg-text' }, m.text)
|
|
81
|
-
)
|
|
82
|
-
))
|
|
83
|
-
)
|
|
140
|
+
MessagesBody(ch.name)
|
|
84
141
|
);
|
|
85
142
|
|
|
86
143
|
return h('div', { class: 'ds-community-page' },
|
|
87
|
-
Topbar({ brand: '247420', leaf: 'community', items: [['index', '../../'], ['chat', '../chat/'], ['source
|
|
144
|
+
Topbar({ brand: '247420', leaf: 'community', items: [['index', '../../'], ['chat', '../chat/'], ['source ->', 'https://github.com/AnEntrypoint/design']] }),
|
|
88
145
|
CommunityShell({
|
|
89
146
|
serverRailProps: {
|
|
90
147
|
servers,
|
|
@@ -104,13 +161,18 @@ function App() {
|
|
|
104
161
|
},
|
|
105
162
|
userPanelProps: {
|
|
106
163
|
name: 'you',
|
|
107
|
-
tag: '
|
|
108
|
-
color: '
|
|
164
|
+
tag: '@',
|
|
165
|
+
color: 'var(--accent)',
|
|
109
166
|
muted: state.muted,
|
|
110
167
|
deafened: state.deafened,
|
|
111
168
|
onMute: () => { state.muted = !state.muted; kit.render(); },
|
|
112
169
|
onDeafen: () => { state.deafened = !state.deafened; kit.render(); },
|
|
113
|
-
|
|
170
|
+
// 'Audio settings' opens the voice strip, which is this
|
|
171
|
+
// kit's actual audio surface (mute/deafen/leave). UserPanel
|
|
172
|
+
// renders this button whether or not a handler is passed,
|
|
173
|
+
// so leaving it unwired was a permanently inert control
|
|
174
|
+
// rather than an absent one.
|
|
175
|
+
onSettings: () => { state.voiceOpen = true; kit.render(); }
|
|
114
176
|
}
|
|
115
177
|
},
|
|
116
178
|
children: chatContent,
|
|
@@ -126,7 +188,7 @@ function App() {
|
|
|
126
188
|
open: true
|
|
127
189
|
} : null
|
|
128
190
|
}),
|
|
129
|
-
Status({ left: ['community', '
|
|
191
|
+
Status({ left: ['community', '- ' + channels.length + ' channels', '- ' + servers.length + ' servers', '- ' + state.phase], right: ['247420 / mmxxvi'] })
|
|
130
192
|
);
|
|
131
193
|
}
|
|
132
194
|
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
<link rel="icon" type="image/svg+xml" href="../../favicon.svg">
|
|
10
10
|
<link rel="stylesheet" href="../../colors_and_type.css">
|
|
11
11
|
<link rel="stylesheet" href="../../app-shell.css">
|
|
12
|
+
<link rel="stylesheet" href="../../community.css">
|
|
12
13
|
<script type="importmap">
|
|
13
14
|
{ "imports": {
|
|
14
15
|
"webjsx": "../../vendor/webjsx/index.js",
|