anentrypoint-design 0.0.414 → 0.0.415
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
|
@@ -14,44 +14,103 @@ const swatchTokens = [
|
|
|
14
14
|
{ name: 'panel-select',hint: 'mint hover/select tone' }
|
|
15
15
|
];
|
|
16
16
|
|
|
17
|
+
// Specimens, not captions. Each tile shows an actual piece of ascii art at
|
|
18
|
+
// display size — the thing a gallery is for. The previous set was twelve
|
|
19
|
+
// equal cards whose captions were design-system documentation ("panel-on-panel
|
|
20
|
+
// rhythm", "1px panel-2 hairline"), so every tile read at one weight and the
|
|
21
|
+
// grid was a lecture wearing a gallery's layout. One of them ("no svgs in
|
|
22
|
+
// chrome") had also gone stale — chrome icons are Icon() line SVGs now.
|
|
23
|
+
//
|
|
24
|
+
// One tone across the whole set. The tiles previously alternated panel-1 /
|
|
25
|
+
// panel-2 / panel-3, which produced a checkerboard the reader has to try to
|
|
26
|
+
// decode — the tone changed but meant nothing, and because the grid reflows
|
|
27
|
+
// to the container (5-up here, 3-up narrower) any index-based tone pattern
|
|
28
|
+
// lands differently at every width. With the frame constant the specimen is
|
|
29
|
+
// the only thing that varies, which is what a gallery is for.
|
|
17
30
|
const items = [
|
|
18
|
-
{ id: 'a', label: 'mascot
|
|
19
|
-
{ id: 'b', label: '
|
|
20
|
-
{ id: 'c', label: '
|
|
21
|
-
{ id: 'd', label: '
|
|
22
|
-
{ id: 'e', label: '
|
|
23
|
-
{ id: 'f', label: '
|
|
24
|
-
{ id: 'g', label: '
|
|
25
|
-
{ id: 'h', label: '
|
|
26
|
-
{ id: 'i', label: '
|
|
27
|
-
{ id: 'j', label: '
|
|
28
|
-
{ id: 'k', label: '
|
|
29
|
-
{ id: 'l', label: '
|
|
31
|
+
{ id: 'a', label: 'the mascot', caption: '/\\_/\\\n( o.o )\n > ^ <', tone: 'panel-2', glyph: '(=)' },
|
|
32
|
+
{ id: 'b', label: 'the prompt', caption: '$ _', tone: 'panel-2', glyph: '$' },
|
|
33
|
+
{ id: 'c', label: 'the seal', caption: '(( 247 ))\n(( 420 ))', tone: 'panel-2', glyph: 'O' },
|
|
34
|
+
{ id: 'd', label: 'the arrow', caption: '-->', tone: 'panel-2', glyph: '->' },
|
|
35
|
+
{ id: 'e', label: 'the rule', caption: '---------', tone: 'panel-2', glyph: '-' },
|
|
36
|
+
{ id: 'f', label: 'the corner', caption: '+------\n|\n|', tone: 'panel-2', glyph: '[#]' },
|
|
37
|
+
{ id: 'g', label: 'the stack', caption: '[###]\n [##]\n [#]', tone: 'panel-2', glyph: '[]' },
|
|
38
|
+
{ id: 'h', label: 'the wave', caption: '~~~~~~~', tone: 'panel-2', glyph: '~' },
|
|
39
|
+
{ id: 'i', label: 'the target', caption: '(o)', tone: 'panel-2', glyph: '(o)' },
|
|
40
|
+
{ id: 'j', label: 'the ladder', caption: '|- - -|\n|- - -|', tone: 'panel-2', glyph: '=' },
|
|
41
|
+
{ id: 'k', label: 'the spark', caption: '*', tone: 'panel-2', glyph: '*' },
|
|
42
|
+
{ id: 'l', label: 'the terminus', caption: '[x]', tone: 'panel-2', glyph: '[x]' }
|
|
30
43
|
];
|
|
31
44
|
|
|
32
|
-
|
|
45
|
+
// `phase` drives the tiles panel. A gallery is the surface where a blank grid
|
|
46
|
+
// is most ambiguous (still loading? nothing uploaded? request failed?), so all
|
|
47
|
+
// three readings get distinct, reachable copy instead of one blank box.
|
|
48
|
+
const state = { open: null, density: 'comfy', phase: 'ready' };
|
|
49
|
+
const PHASES = ['ready', 'loading', 'empty', 'error'];
|
|
50
|
+
|
|
51
|
+
// Tile-shaped shimmer. Reuses the .ds-skeleton primitive (app-shell/
|
|
52
|
+
// loading-alerts.css) inside the existing .ds-tile-grid so the placeholders
|
|
53
|
+
// occupy exactly the tracks the real tiles will.
|
|
54
|
+
function TilesSkeleton() {
|
|
55
|
+
return h('div', { class: 'ds-tile-grid' + (state.density === 'tight' ? ' ds-tile-grid--tight' : '') },
|
|
56
|
+
...Array.from({ length: 8 }, (_, i) => h('div', { key: 'sk' + i, class: 'ds-gallery-tile' },
|
|
57
|
+
h('div', { class: 'ds-skeleton ds-skel-title' }),
|
|
58
|
+
h('div', { class: 'ds-skeleton ds-skel-meta' })
|
|
59
|
+
))
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function TilesEmpty() {
|
|
64
|
+
return h('div', { class: 'ds-empty-state' },
|
|
65
|
+
h('div', { class: 'ds-empty-state-glyph' }, '[ ]'),
|
|
66
|
+
h('p', { class: 'ds-empty-state-msg' }, 'no tiles in this set'),
|
|
67
|
+
h('p', { class: 'ds-empty-state-hint' }, 'a tile is one tonal card plus a caption. add entries to the items array and they land in this grid at whichever density is selected.')
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function TilesError() {
|
|
72
|
+
return h('div', { class: 'ds-alert ds-alert-error' },
|
|
73
|
+
h('span', { class: 'ds-alert-icon' }, '!'),
|
|
74
|
+
h('div', { class: 'ds-alert-content' },
|
|
75
|
+
h('div', { class: 'ds-alert-title' }, 'tile set failed to decode'),
|
|
76
|
+
h('div', { class: 'ds-alert-message' }, 'four of the twelve captions came back as malformed utf-8, so the whole set was rejected rather than rendered with holes in it. re-export the set as utf-8 and reload.'),
|
|
77
|
+
h('div', { class: 'ds-alert-retry' },
|
|
78
|
+
h('button', { class: 'btn', onclick: () => { state.phase = 'ready'; kit.render(); } }, 'reload set')
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function TilesBody() {
|
|
85
|
+
if (state.phase === 'loading') return TilesSkeleton();
|
|
86
|
+
if (state.phase === 'error') return TilesError();
|
|
87
|
+
if (state.phase === 'empty') return TilesEmpty();
|
|
88
|
+
return h('div', { class: 'ds-tile-grid' + (state.density === 'tight' ? ' ds-tile-grid--tight' : '') }, ...items.map(Tile));
|
|
89
|
+
}
|
|
33
90
|
|
|
34
91
|
function Tile(it) {
|
|
35
|
-
const size = state.density === 'tight' ? '120px' : '160px';
|
|
36
92
|
return h('button', {
|
|
37
93
|
key: it.id,
|
|
38
94
|
onclick: () => { state.open = it.id; kit.render(); },
|
|
39
|
-
|
|
95
|
+
class: 'ds-gallery-tile' + (state.density === 'tight' ? ' ds-gallery-tile--tight' : ''),
|
|
96
|
+
// custom-property-only inline: carries the per-tile tone, no layout
|
|
97
|
+
style: '--tile-tone:var(--' + it.tone + ')'
|
|
40
98
|
},
|
|
41
|
-
h('div', {
|
|
42
|
-
h('div', {
|
|
43
|
-
h('span', {
|
|
44
|
-
h('span', {
|
|
99
|
+
h('div', { class: 'ds-tile-cap' }, it.caption),
|
|
100
|
+
h('div', { class: 'ds-tile-meta' },
|
|
101
|
+
h('span', { class: 'ds-tile-glyph' }, it.glyph),
|
|
102
|
+
h('span', { class: 'ds-tile-label' }, it.label)
|
|
45
103
|
)
|
|
46
104
|
);
|
|
47
105
|
}
|
|
48
106
|
|
|
49
107
|
function Swatch(t) {
|
|
50
|
-
return h('div', { key: t.name,
|
|
51
|
-
|
|
52
|
-
h('div', {
|
|
53
|
-
|
|
54
|
-
h('span', {
|
|
108
|
+
return h('div', { key: t.name, class: 'ds-swatch-col' },
|
|
109
|
+
// custom-property-only inline: carries the per-swatch tone, no layout
|
|
110
|
+
h('div', { class: 'ds-gal-swatch', style: '--swatch:var(--' + t.name + ')' }),
|
|
111
|
+
h('div', { class: 'ds-gal-swatch-meta' },
|
|
112
|
+
h('span', { class: 'ds-gal-swatch-name' }, t.name),
|
|
113
|
+
h('span', { class: 'ds-gal-swatch-hint' }, t.hint)
|
|
55
114
|
)
|
|
56
115
|
);
|
|
57
116
|
}
|
|
@@ -59,34 +118,44 @@ function Swatch(t) {
|
|
|
59
118
|
function Lightbox() {
|
|
60
119
|
if (!state.open) return null;
|
|
61
120
|
const it = items.find((i) => i.id === state.open);
|
|
121
|
+
const close = () => { state.open = null; kit.render(); };
|
|
62
122
|
return h('div', {
|
|
63
|
-
onclick:
|
|
64
|
-
|
|
123
|
+
onclick: close,
|
|
124
|
+
onkeydown: (e) => { if (e.key === 'Escape') { e.preventDefault(); close(); } },
|
|
125
|
+
tabindex: '-1',
|
|
126
|
+
ref: (el) => { if (el && !el._dsLbFocused) { el._dsLbFocused = true; el.focus(); } },
|
|
127
|
+
class: 'ds-lightbox'
|
|
65
128
|
},
|
|
66
|
-
h('div', { onclick: (e) => e.stopPropagation(),
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
h('
|
|
70
|
-
h('button', { class: 'btn', onclick: () => { state.open = null; kit.render(); } }, 'close')
|
|
129
|
+
h('div', { onclick: (e) => e.stopPropagation(), class: 'ds-lightbox-card' },
|
|
130
|
+
h('div', { class: 'ds-lightbox-head' },
|
|
131
|
+
h('span', { class: 'ds-lightbox-tag' }, 'tile · ' + it.id),
|
|
132
|
+
h('button', { class: 'btn', onclick: close }, 'close')
|
|
71
133
|
),
|
|
72
|
-
h('div', { style: '
|
|
73
|
-
h('p', {
|
|
74
|
-
|
|
134
|
+
h('div', { class: 'ds-lightbox-preview', style: '--tile-tone:var(--' + it.tone + ')' }, it.caption),
|
|
135
|
+
h('p', { class: 'ds-m0' }, h('strong', {}, it.label)),
|
|
136
|
+
// Tells the reader how to leave, which is the one thing they need
|
|
137
|
+
// from a lightbox. It previously described its own implementation.
|
|
138
|
+
h('p', { class: 'ds-m0 ds-text-2' }, 'esc, or click anywhere outside, to close.')
|
|
75
139
|
)
|
|
76
140
|
);
|
|
77
141
|
}
|
|
78
142
|
|
|
79
143
|
function App() {
|
|
80
|
-
const cols = state.density === 'tight' ? 'repeat(auto-fill, minmax(140px, 1fr))' : 'repeat(auto-fill, minmax(180px, 1fr))';
|
|
81
144
|
return AppShell({
|
|
82
|
-
topbar: Topbar({ brand: '247420', leaf: 'gallery', items: [['index', '../../'], ['source
|
|
83
|
-
crumb: Crumb({ trail: ['247420', 'kits'], leaf: 'gallery', right: items.length + ' tiles' }),
|
|
145
|
+
topbar: Topbar({ brand: '247420', leaf: 'gallery', items: [['index', '../../'], ['source ->', 'https://github.com/AnEntrypoint/design']] }),
|
|
146
|
+
crumb: Crumb({ trail: ['247420', 'kits'], leaf: 'gallery', right: state.phase === 'ready' ? items.length + ' tiles' : state.phase }),
|
|
84
147
|
side: Side({
|
|
85
148
|
sections: [
|
|
86
149
|
{ group: 'density', items: [
|
|
87
|
-
{ glyph: state.density === 'comfy' ? '
|
|
88
|
-
{ glyph: state.density === 'tight' ? '
|
|
150
|
+
{ glyph: h('span', { class: state.density === 'comfy' ? 'ds-dot ds-dot-on' : 'ds-dot ds-dot-off' }), label: 'comfy', key: 'd1', onClick: (e) => { e.preventDefault(); state.density = 'comfy'; kit.render(); } },
|
|
151
|
+
{ glyph: h('span', { class: state.density === 'tight' ? 'ds-dot ds-dot-on' : 'ds-dot ds-dot-off' }), label: 'tight', key: 'd2', onClick: (e) => { e.preventDefault(); state.density = 'tight'; kit.render(); } }
|
|
89
152
|
] },
|
|
153
|
+
// Reachable state switcher for the tiles panel.
|
|
154
|
+
{ group: 'tile state', items: PHASES.map((p) => ({
|
|
155
|
+
glyph: h('span', { class: state.phase === p ? 'ds-dot ds-dot-on' : 'ds-dot ds-dot-off' }),
|
|
156
|
+
label: p, key: 'ph-' + p, active: state.phase === p, href: '#' + p,
|
|
157
|
+
onClick: (e) => { e.preventDefault(); state.phase = p; kit.render(); }
|
|
158
|
+
})) },
|
|
90
159
|
{ group: 'jump', items: [
|
|
91
160
|
{ glyph: '·', label: 'tiles', key: 'j1', href: '#tiles' },
|
|
92
161
|
{ glyph: '·', label: 'swatches', key: 'j2', href: '#swatches' }
|
|
@@ -94,28 +163,38 @@ function App() {
|
|
|
94
163
|
]
|
|
95
164
|
}),
|
|
96
165
|
main: [
|
|
97
|
-
h('div', { class: 'ds-section
|
|
166
|
+
h('div', { class: 'ds-section ds-section-pad' },
|
|
98
167
|
Heading({ level: 1, children: 'gallery' }),
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
168
|
+
// Says what the reader is looking at. The old lede described
|
|
169
|
+
// the implementation ("no bespoke tile component, no shadows,
|
|
170
|
+
// no borders") and was contradicted on screen — the tiles lift
|
|
171
|
+
// with a shadow on hover.
|
|
172
|
+
Lede({ children: 'twelve ascii specimens on one tonal frame. pick any tile to open it large.' }),
|
|
173
|
+
Panel({ title: 'tiles', count: state.phase === 'ready' ? items.length : 0, class: 'ds-panel-gap', children: TilesBody() }),
|
|
174
|
+
Panel({ title: 'swatches', count: swatchTokens.length, class: 'ds-panel-gap', children:
|
|
175
|
+
h('div', { class: 'ds-swatch-grid' }, ...swatchTokens.map(Swatch))
|
|
105
176
|
}),
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
177
|
+
// Two notes that say something the page does not already show.
|
|
178
|
+
// The third ("density toggle drops min tile size") described a
|
|
179
|
+
// control the reader can just press, and the second claimed the
|
|
180
|
+
// lightbox has "no transitions" while .ds-gallery-tile animates
|
|
181
|
+
// transform and box-shadow on hover.
|
|
182
|
+
Panel({ title: 'about this kit', class: 'ds-panel-gap', children: h('div', { class: 'ds-pattern-notes' },
|
|
183
|
+
h('p', {}, '· the grid is ', Chip({ tone: 'accent', children: 'auto-fill minmax' }), ' — tiles reflow to the container, never to a breakpoint list.'),
|
|
184
|
+
h('p', {}, '· the lightbox is the same tonal panel at a larger size, so there is one surface to theme, not two.')
|
|
110
185
|
) })
|
|
111
186
|
),
|
|
112
187
|
Lightbox()
|
|
113
188
|
],
|
|
114
189
|
status: Status({
|
|
115
|
-
left: ['gallery', '
|
|
190
|
+
left: ['gallery', '- ' + (state.phase === 'ready' ? items.length : 0) + ' tiles', '- density=' + state.density, '- ' + state.phase],
|
|
116
191
|
right: ['247420 / mmxxvi']
|
|
117
192
|
})
|
|
118
193
|
});
|
|
119
194
|
}
|
|
120
195
|
|
|
121
196
|
const kit = mountKit({ root, view: App, screen: '14 Gallery' });
|
|
197
|
+
|
|
198
|
+
window.addEventListener('keydown', (e) => {
|
|
199
|
+
if (e.key === 'Escape' && state.open) { state.open = null; kit.render(); }
|
|
200
|
+
});
|
|
@@ -1,166 +1,184 @@
|
|
|
1
1
|
import * as webjsx from 'webjsx';
|
|
2
2
|
import { mountKit } from 'ds/bootstrap.js';
|
|
3
|
-
|
|
3
|
+
import {
|
|
4
|
+
AppShell, Topbar, Crumb, Status, Dot,
|
|
5
|
+
Hero, Panel, Row, Section, WorksList, WritingList, Manifesto
|
|
6
|
+
} from 'ds/components.js';
|
|
4
7
|
|
|
5
|
-
const
|
|
8
|
+
const h = webjsx.createElement;
|
|
6
9
|
const root = document.getElementById('root');
|
|
7
10
|
|
|
8
|
-
const navItems = [
|
|
11
|
+
const navItems = [
|
|
12
|
+
['works', '#works'],
|
|
13
|
+
['writing', '#writing'],
|
|
14
|
+
['manifesto', '#manifesto'],
|
|
15
|
+
['source ->', 'https://github.com/AnEntrypoint']
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const shipping = [
|
|
19
|
+
{ name: 'gm', sub: 'state machine v0.4.1', live: true },
|
|
20
|
+
{ name: 'zellous', sub: 'push-to-talk', live: true },
|
|
21
|
+
{ name: 'thebird', sub: '—', live: false }
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
// `href` (the "open ->" button) and `source` (the "source" button) are the two
|
|
25
|
+
// destinations WorksList renders inside an expanded work. Both are required:
|
|
26
|
+
// WorksList falls back to '#' when they are absent, which renders the buttons
|
|
27
|
+
// as real, pressable affordances that lead nowhere. Every entry below points at
|
|
28
|
+
// a repo that actually exists under the AnEntrypoint org.
|
|
29
|
+
const GH = 'https://github.com/AnEntrypoint/';
|
|
30
|
+
const works = [
|
|
31
|
+
{ code: '001', title: 'gm', sub: 'state machine for coding agents', meta: '2025 · 3k', body: 'a tiny deterministic state machine that lets llms code without losing their minds. it thinks so you don\'t have to (as much).', href: GH + 'gm', source: GH + 'gm' },
|
|
32
|
+
{ code: '002', title: 'zellous', sub: 'production push-to-talk', meta: '2024 · shipped', body: 'hold the button. talk. someone on the other side hears you. opus codec, dynamic rooms, 50-message replay.', href: GH + 'zellous', source: GH + 'zellous' },
|
|
33
|
+
{ code: '003', title: 'spoint', sub: 'spawnpoint', meta: '2024 · shipped', body: 'the directory for "where should we start?" one url, one room, everyone lands in the same place.', href: GH + 'spoint', source: GH + 'spoint' },
|
|
34
|
+
{ code: '004', title: 'flatspace', sub: 'flat-file cms', meta: 'wip', body: 'still figuring out what to say about this one. come back tuesday.', href: GH + 'flatspace', source: GH + 'flatspace' },
|
|
35
|
+
{ code: '005', title: 'thebird', sub: '—', meta: 'wip', body: 'yes, the name is a reference. no, we won\'t tell you to what.', href: GH + 'thebird', source: GH + 'thebird' },
|
|
36
|
+
{ code: '006', title: 'mcp-repl', sub: 'repl for mcp', meta: '2024 · live', body: 'executenodejs, executedeno, executebash, astgrep_search. if you don\'t know what those are, this one isn\'t for you.', href: GH + 'mcp-repl', source: GH + 'mcp-repl' },
|
|
37
|
+
{ code: '007', title: 'mutagen', sub: 'adaptogen server', meta: '2024 · live', body: 'everything to do with a dapp deg3n. read the source.', href: GH + 'mutagen', source: GH + 'mutagen' },
|
|
38
|
+
{ code: '008', title: 'techshaman', sub: 'member site', meta: 'ongoing', body: 'the official website for the techshaman. an entrypoint probably emerging.', href: GH + 'techshaman', source: GH + 'techshaman' }
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
// There is no posts backend behind this kit, so these link to the blog kit —
|
|
42
|
+
// which renders the first entry below as a full post — rather than to invented
|
|
43
|
+
// per-post URLs that would 404. href:'#' (the previous value) rendered five
|
|
44
|
+
// anchors that looked like article links and did nothing when clicked.
|
|
45
|
+
const POST_HREF = '../blog/';
|
|
46
|
+
const posts = [
|
|
47
|
+
{ date: '2026.04.14', title: 'we were here first', tag: 'lore', href: POST_HREF },
|
|
48
|
+
{ date: '2026.03.22', title: 'gm v0.4 postmortem, or: why state machines', tag: 'gm', href: POST_HREF },
|
|
49
|
+
{ date: '2026.02.09', title: 'push-to-talk is a protocol, not a feature', tag: 'zellous', href: POST_HREF },
|
|
50
|
+
{ date: '2025.12.11', title: 'against the vibe-coded interface', tag: 'manifesto', href: POST_HREF },
|
|
51
|
+
{ date: '2025.10.03', title: 'notes on shipping weird', tag: 'notes', href: POST_HREF }
|
|
52
|
+
];
|
|
9
53
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
54
|
+
const manifesto = [
|
|
55
|
+
{ text: 'we are the creative department of the internet. always open (24/7). always a little bit high on possibility (420).' },
|
|
56
|
+
{ text: 'move fast. break things. document honestly. ship the rough draft. humor is load-bearing.' },
|
|
57
|
+
{ text: 'we will not tolerate simpleton design patterns, trifectas, gradients, or anything silly. nothing lame. we\'re internet natives and not easily pleased.', dim: true }
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
// `phase` drives the three content lists (shipping / works / writing). Cycled
|
|
61
|
+
// from the topbar so each reading is reachable in the kit — a portfolio page
|
|
62
|
+
// that only ever renders a full grid has never shown what a cold or failed
|
|
63
|
+
// fetch looks like.
|
|
64
|
+
const state = { route: 'works', opened: 0, phase: 'ready' };
|
|
65
|
+
const PHASES = ['ready', 'loading', 'empty', 'error'];
|
|
66
|
+
|
|
67
|
+
// Row shimmer. Reuses .ds-event-row-skeleton + .ds-skel* (app-shell/files.css).
|
|
68
|
+
function ListSkeleton(n, prefix) {
|
|
69
|
+
return h('div', {},
|
|
70
|
+
...Array.from({ length: n }, (_, i) => h('div', { key: prefix + i, class: 'ds-event-row-skeleton' },
|
|
71
|
+
h('span', { class: 'ds-skel ds-skel-rank' }),
|
|
72
|
+
h('span', { class: 'ds-skel ds-skel-title' }),
|
|
73
|
+
h('span', { class: 'ds-skel ds-skel-meta' })
|
|
20
74
|
))
|
|
21
75
|
);
|
|
22
76
|
}
|
|
23
77
|
|
|
24
|
-
function
|
|
25
|
-
return h('div', { class: '
|
|
26
|
-
h('
|
|
27
|
-
h('
|
|
78
|
+
function ListEmpty(msg, hint) {
|
|
79
|
+
return h('div', { class: 'ds-empty-state' },
|
|
80
|
+
h('div', { class: 'ds-empty-state-glyph' }, '[ ]'),
|
|
81
|
+
h('p', { class: 'ds-empty-state-msg' }, msg),
|
|
82
|
+
h('p', { class: 'ds-empty-state-hint' }, hint)
|
|
28
83
|
);
|
|
29
84
|
}
|
|
30
85
|
|
|
31
|
-
function
|
|
32
|
-
return h('div', {
|
|
33
|
-
h('
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
'
|
|
38
|
-
|
|
39
|
-
),
|
|
40
|
-
h('div', { class: 'panel', style: 'max-width:560px;margin:0' },
|
|
41
|
-
h('div', { class: 'panel-head' }, h('span', {}, 'currently shipping'), h('span', {}, '3')),
|
|
42
|
-
h('div', { class: 'panel-body' },
|
|
43
|
-
h('div', { class: 'row' },
|
|
44
|
-
h('span', { class: 'code' }, h('span', { style: 'color:var(--panel-accent)' }, '●')),
|
|
45
|
-
h('span', { class: 'title' }, 'gm', h('span', { class: 'sub' }, 'state machine v0.4.1')),
|
|
46
|
-
h('span', { class: 'meta' }, 'live')
|
|
47
|
-
),
|
|
48
|
-
h('div', { class: 'row' },
|
|
49
|
-
h('span', { class: 'code' }, h('span', { style: 'color:var(--panel-accent)' }, '●')),
|
|
50
|
-
h('span', { class: 'title' }, 'zellous', h('span', { class: 'sub' }, 'push-to-talk')),
|
|
51
|
-
h('span', { class: 'meta' }, 'live')
|
|
52
|
-
),
|
|
53
|
-
h('div', { class: 'row' },
|
|
54
|
-
h('span', { class: 'code' }, h('span', { style: 'color:var(--panel-text-3)' }, '○')),
|
|
55
|
-
h('span', { class: 'title' }, 'thebird', h('span', { class: 'sub' }, '—')),
|
|
56
|
-
h('span', { class: 'meta' }, 'wip')
|
|
57
|
-
)
|
|
86
|
+
function ListError(title, msg) {
|
|
87
|
+
return h('div', { class: 'ds-alert ds-alert-error' },
|
|
88
|
+
h('span', { class: 'ds-alert-icon' }, '!'),
|
|
89
|
+
h('div', { class: 'ds-alert-content' },
|
|
90
|
+
h('div', { class: 'ds-alert-title' }, title),
|
|
91
|
+
h('div', { class: 'ds-alert-message' }, msg),
|
|
92
|
+
h('div', { class: 'ds-alert-retry' },
|
|
93
|
+
h('button', { class: 'btn', onclick: () => { state.phase = 'ready'; render(); } }, 'refetch')
|
|
58
94
|
)
|
|
59
95
|
)
|
|
60
96
|
);
|
|
61
97
|
}
|
|
62
98
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
function Works() {
|
|
75
|
-
return h('div', { style: 'padding:20px 32px' },
|
|
76
|
-
h('h3', {}, '// works'),
|
|
77
|
-
h('div', { class: 'panel' },
|
|
78
|
-
h('div', { class: 'panel-head' },
|
|
79
|
-
h('span', {}, 'works · 08 of ~61'),
|
|
80
|
-
h('a', { href: 'https://github.com/AnEntrypoint', style: 'color:var(--panel-accent);text-decoration:none' }, 'all repos ↗')
|
|
81
|
-
),
|
|
82
|
-
h('div', { class: 'panel-body' }, ...works.map((w, i) => {
|
|
83
|
-
const isOpen = state.opened === i;
|
|
84
|
-
return h('div', { key: i },
|
|
85
|
-
h('div', {
|
|
86
|
-
class: 'row' + (isOpen ? ' active' : ''),
|
|
87
|
-
onclick: () => { state.opened = isOpen ? null : i; render(); }
|
|
88
|
-
},
|
|
89
|
-
h('span', { class: 'code' }, w.code),
|
|
90
|
-
h('span', { class: 'title' }, w.title, h('span', { class: 'sub' }, w.sub)),
|
|
91
|
-
h('span', { class: 'meta' }, w.meta + ' ' + (isOpen ? '−' : '+'))
|
|
92
|
-
),
|
|
93
|
-
isOpen ? h('div', { class: 'work-detail' },
|
|
94
|
-
h('p', { class: 'ds-prose ds-work-body' }, w.body),
|
|
95
|
-
h('div', { class: 'ds-work-actions' },
|
|
96
|
-
h('a', { class: 'btn-primary', href: '#' }, 'open ↗'),
|
|
97
|
-
h('a', { class: 'btn', href: '#' }, 'source')
|
|
98
|
-
)
|
|
99
|
-
) : null
|
|
100
|
-
);
|
|
101
|
-
}))
|
|
102
|
-
)
|
|
103
|
-
);
|
|
99
|
+
function ShippingBody() {
|
|
100
|
+
const p = state.phase;
|
|
101
|
+
if (p === 'loading') return ListSkeleton(3, 'sk-ship-');
|
|
102
|
+
if (p === 'error') return ListError('build status unavailable',
|
|
103
|
+
'the status endpoint timed out, so live and wip cannot be told apart right now. the projects below are still real; only their badges are unknown.');
|
|
104
|
+
if (p === 'empty') return ListEmpty('nothing in flight this week',
|
|
105
|
+
'projects appear here while they are actively being worked on. everything currently shipped is in works below.');
|
|
106
|
+
return h('div', {}, ...shipping.map((s) => Row({
|
|
107
|
+
key: s.name, leading: Dot({ tone: s.live ? 'on' : 'off' }),
|
|
108
|
+
title: s.name, sub: s.sub, meta: s.live ? 'live' : 'wip'
|
|
109
|
+
})));
|
|
104
110
|
}
|
|
105
111
|
|
|
106
|
-
function
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return h('div', { style: 'padding:20px 32px' },
|
|
115
|
-
h('h3', {}, '// recent writing'),
|
|
116
|
-
h('div', { class: 'panel' },
|
|
117
|
-
h('div', { class: 'panel-body' }, ...posts.map((p, i) =>
|
|
118
|
-
h('a', { key: i, class: 'row', href: '#' },
|
|
119
|
-
h('span', { class: 'code' }, p.date),
|
|
120
|
-
h('span', { class: 'title' }, p.title),
|
|
121
|
-
h('span', { class: 'meta' }, '§ ' + p.tag)
|
|
122
|
-
)
|
|
123
|
-
))
|
|
124
|
-
)
|
|
125
|
-
);
|
|
112
|
+
function WorksBody() {
|
|
113
|
+
const p = state.phase;
|
|
114
|
+
if (p === 'loading') return ListSkeleton(6, 'sk-work-');
|
|
115
|
+
if (p === 'error') return ListError('works index failed to load',
|
|
116
|
+
'the projects manifest returned malformed json at entry 4, so the list was rejected rather than shown with a hole in it. a refetch usually picks up the corrected file.');
|
|
117
|
+
if (p === 'empty') return ListEmpty('no works published yet',
|
|
118
|
+
'each entry is one shipped project with its year, size and a paragraph on what it does. the first one lands here as soon as it is tagged.');
|
|
119
|
+
return WorksList({ works, openedIndex: state.opened, onToggle: (i) => { state.opened = i; render(); } });
|
|
126
120
|
}
|
|
127
121
|
|
|
128
|
-
function
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function Status() {
|
|
140
|
-
return h('footer', { class: 'app-status' },
|
|
141
|
-
h('span', { class: 'item' }, 'main'),
|
|
142
|
-
h('span', { class: 'item' }, '• 8 works'),
|
|
143
|
-
h('span', { class: 'item' }, '• 5 posts'),
|
|
144
|
-
h('span', { class: 'spread' }),
|
|
145
|
-
h('span', { class: 'item' }, 'probably emerging'),
|
|
146
|
-
h('span', { class: 'item' }, h('a', { href: 'https://github.com/AnEntrypoint' }, 'source ↗'))
|
|
147
|
-
);
|
|
122
|
+
function WritingBody() {
|
|
123
|
+
const p = state.phase;
|
|
124
|
+
if (p === 'loading') return ListSkeleton(5, 'sk-post-');
|
|
125
|
+
if (p === 'error') return ListError('writing feed unreachable',
|
|
126
|
+
'the posts feed is served from a different origin and that origin is down. the works list above is local and unaffected.');
|
|
127
|
+
if (p === 'empty') return ListEmpty('nothing written lately',
|
|
128
|
+
'posts land here newest first, tagged by which project they belong to. quiet here usually means loud somewhere else.');
|
|
129
|
+
return WritingList({ posts });
|
|
148
130
|
}
|
|
149
131
|
|
|
150
132
|
function App() {
|
|
151
|
-
return
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
133
|
+
return AppShell({
|
|
134
|
+
// The nav carries destinations only. The phase cycler used to ride
|
|
135
|
+
// here as a fifth item, which made a demo control a peer of works /
|
|
136
|
+
// writing / manifesto and let it take the `active` highlight away from
|
|
137
|
+
// the section the reader is actually in. It moved onto the first panel
|
|
138
|
+
// it governs.
|
|
139
|
+
topbar: Topbar({
|
|
140
|
+
brand: '247420', leaf: 'an entrypoint',
|
|
141
|
+
items: navItems,
|
|
142
|
+
active: state.route,
|
|
143
|
+
onNav: (label) => { state.route = label; render(); }
|
|
144
|
+
}),
|
|
145
|
+
crumb: Crumb({ trail: ['247420'], leaf: state.route }),
|
|
146
|
+
main: [
|
|
147
|
+
// badges fill the Hero's narrow right column — without them the
|
|
148
|
+
// asymmetric two-column grid renders with a dead right half at
|
|
149
|
+
// desktop width, which reads as a layout bug rather than tension.
|
|
150
|
+
// They are counts this page already knows, not invented metrics.
|
|
151
|
+
Hero({
|
|
152
|
+
title: 'the creative department of the internet.',
|
|
153
|
+
body: '247420 is a collective of mercurials. we ship fast, break things on purpose, and document honestly.',
|
|
154
|
+
accent: 'humor is load-bearing.',
|
|
155
|
+
badges: state.phase === 'ready'
|
|
156
|
+
? [works.length + ' works', posts.length + ' posts', shipping.filter((s) => s.live).length + ' live']
|
|
157
|
+
: null
|
|
158
|
+
}),
|
|
159
|
+
Panel({
|
|
160
|
+
title: 'currently shipping',
|
|
161
|
+
right: h('button', { class: 'btn', onclick: () => {
|
|
162
|
+
state.phase = PHASES[(PHASES.indexOf(state.phase) + 1) % PHASES.length];
|
|
163
|
+
render();
|
|
164
|
+
} }, state.phase === 'ready' ? shipping.length + ' in flight' : state.phase),
|
|
165
|
+
children: ShippingBody()
|
|
166
|
+
}),
|
|
167
|
+
// No eyebrow: '08 of ~61' was a position indicator over a section
|
|
168
|
+
// that is not part of any sequence, and the ~61 contradicted the
|
|
169
|
+
// eight works actually listed. The count belongs in the status bar,
|
|
170
|
+
// which already carries it.
|
|
171
|
+
Section({ id: 'works', title: 'works', children: WorksBody() }),
|
|
172
|
+
Section({ id: 'writing', title: 'recent writing',
|
|
173
|
+
children: WritingBody() }),
|
|
174
|
+
Section({ id: 'manifesto', title: 'manifesto · rough draft',
|
|
175
|
+
children: Manifesto({ paragraphs: manifesto }) })
|
|
176
|
+
],
|
|
177
|
+
status: Status({
|
|
178
|
+
left: ['main', state.phase === 'ready' ? '8 works' : '0 works', state.phase === 'ready' ? '5 posts' : '0 posts', state.phase],
|
|
179
|
+
right: ['probably emerging', h('a', { href: 'https://github.com/AnEntrypoint' }, 'source ->')]
|
|
180
|
+
})
|
|
181
|
+
});
|
|
164
182
|
}
|
|
165
183
|
|
|
166
184
|
const kit = mountKit({ root, view: App, screen: '01 Homepage' });
|