@orbytes/astrolab 0.3.0 → 0.4.0-next.2
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/README.md +261 -116
- package/bin/pin-gallery.mjs +53 -19
- package/defaults.mjs +72 -20
- package/dist/core/virtual-module/virtual-routes.js +12 -1
- package/docs/PIN-CONTRACT.md +84 -10
- package/docs/PIN.md +117 -37
- package/index.d.ts +41 -23
- package/index.mjs +46 -91
- package/package.json +7 -3
- package/src/Home.astro +166 -264
- package/src/LabHead.astro +37 -1047
- package/src/chrome/ActionsMenu.astro +97 -0
- package/src/chrome/ComponentCard.astro +76 -0
- package/src/chrome/Icon.astro +21 -0
- package/src/chrome/LICENSE-icons +43 -0
- package/src/chrome/Nav.astro +131 -0
- package/src/chrome/Panel.astro +117 -0
- package/src/chrome/Properties.astro +104 -0
- package/src/chrome/SectionsTree.astro +128 -0
- package/src/chrome/Shell.astro +120 -0
- package/src/chrome/Sprite.astro +23 -0
- package/src/chrome/StoryView.astro +192 -0
- package/src/chrome/Tree.astro +86 -0
- package/src/chrome/ViewportControls.astro +173 -0
- package/src/chrome/ViewportStage.astro +55 -0
- package/src/chrome/fonts/OFL.txt +93 -0
- package/src/chrome/fonts/inter-latin-wght-normal.woff2 +0 -0
- package/src/chrome/icons.ts +68 -0
- package/src/chrome/marks-client.ts +75 -0
- package/src/chrome/model.ts +156 -0
- package/src/chrome/navbar-client.ts +324 -0
- package/src/chrome/params-client.ts +434 -0
- package/src/chrome/pins-data.ts +63 -0
- package/src/chrome/shell-client.ts +468 -0
- package/src/chrome/site-data.ts +230 -0
- package/src/chrome/trees.ts +257 -0
- package/src/chrome/viewport-client.ts +405 -0
- package/src/chrome/views/Assets.astro +125 -0
- package/src/chrome/views/Pages.astro +214 -0
- package/src/chrome/views/Placeholder.astro +37 -0
- package/src/chrome/views/Tasks.astro +79 -0
- package/src/core/LICENSE-astrobook +21 -0
- package/src/core/lib/components/home.astro +4 -2
- package/src/core/lib/pages/story.astro +12 -10
- package/src/core/utils/kebab-case.ts +2 -2
- package/src/core/virtual-module/virtual-routes.ts +20 -4
- package/src/pin/board.mjs +414 -190
- package/src/pin/index.mjs +67 -22
- package/src/pin/tickets.mjs +6 -5
- package/src/pin/toolbar.js +82 -4
- package/src/shell/Browse.astro +131 -353
- package/src/shell/Viewport.astro +22 -1315
- package/src/shell/lab-index.ts +28 -18
- package/src/shell/lab-params.ts +113 -6
- package/src/shell/live-files.mjs +212 -10
- package/src/shell/marks.mjs +17 -41
- package/src/ui/components/app.astro +5 -7
- package/src/ui/components/preview-layout.astro +17 -0
- package/src/ui/components/theme-script.astro +17 -5
- package/src/ui/lab.css +3754 -371
- package/virtual.d.ts +13 -4
- package/bin/lab-cull.mjs +0 -401
- package/src/shell/CardGrid.astro +0 -297
- package/src/ui/components/build-path.ts +0 -13
- package/src/ui/components/build-tree.ts +0 -108
- package/src/ui/components/collapse-duration.ts +0 -28
- package/src/ui/components/compress-terms.ts +0 -10
- package/src/ui/components/dashboard-layout.astro +0 -39
- package/src/ui/components/home.astro +0 -65
- package/src/ui/components/layout.astro +0 -110
- package/src/ui/components/sidebar-button-fullscreen.astro +0 -38
- package/src/ui/components/sidebar-button-search.astro +0 -23
- package/src/ui/components/sidebar-button-theme.astro +0 -9
- package/src/ui/components/sidebar-button.astro +0 -24
- package/src/ui/components/sidebar-resize-handle.astro +0 -74
- package/src/ui/components/sidebar-search-panel.astro +0 -41
- package/src/ui/components/sidebar-search-script.ts +0 -103
- package/src/ui/components/sidebar-title.astro +0 -17
- package/src/ui/components/sidebar-tree-node.astro +0 -143
- package/src/ui/components/sidebar-tree.astro +0 -84
- package/src/ui/components/sidebar.astro +0 -29
- package/src/ui/components/theme-toggle.astro +0 -63
package/src/shell/Browse.astro
CHANGED
|
@@ -1,371 +1,149 @@
|
|
|
1
1
|
---
|
|
2
|
-
// Folder pages — <subpath>/browse/[...path], injected by ../../index.mjs. One page per
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
2
|
+
// Folder pages — <subpath>/browse/[...path], injected by ../../index.mjs. One page per tier and per
|
|
3
|
+
// folder under it (sections, sections/Section02Manifesto, …), plus the root, which lists the tiers.
|
|
4
|
+
// Level 2 is the tier's tree; the content is the folder's components as cards, grouped by the
|
|
5
|
+
// sub-folder they sit in, and the panel's search and filter narrow both at once.
|
|
6
6
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
7
|
+
// REDESIGNED 2026-09-24 onto the chrome shell (../chrome/Shell.astro). Cards are per component
|
|
8
|
+
// now, not per story — a component's stories are its variants, chosen on its own page. The title
|
|
9
|
+
// bar is the listing one (Figma `Lab / Listing`): title, counts, and filter chips that drive the
|
|
10
|
+
// same filter as level 2's menu. On the sections tier, level 2 is the Sections tree.
|
|
11
11
|
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// consumer's stylesheets by hand, because Astrobook injects the `css` option into its own pages
|
|
15
|
-
// only. A folder page is pure chrome: it renders no story, and its thumbnails are iframes of
|
|
16
|
-
// /lab/stories/<id>, which are their own documents and get the consumer's CSS there. So it asks
|
|
17
|
-
// for nothing now, and the chrome below is drawn entirely in `--lab-*` tokens. Reasoning:
|
|
18
|
-
// ../ui/lab.css.
|
|
12
|
+
// Pure chrome: this page renders no story. Its thumbnails are iframes of /lab/stories/<id>, which
|
|
13
|
+
// are their own documents and get the consumer's CSS there (decided 2026-09-22 — ../ui/lab.css).
|
|
19
14
|
import type { GetStaticPaths, InferGetStaticPropsType } from "astro";
|
|
20
|
-
import
|
|
21
|
-
import
|
|
22
|
-
import
|
|
15
|
+
import Shell from "../chrome/Shell.astro";
|
|
16
|
+
import Tree from "../chrome/Tree.astro";
|
|
17
|
+
import ComponentCard from "../chrome/ComponentCard.astro";
|
|
18
|
+
import { hrefs, labConfig, type Crumb, type FilterOption, type TreeNode } from "../chrome/model";
|
|
19
|
+
import { componentsOf, tierTree, type LabComponent } from "../chrome/trees";
|
|
20
|
+
import { buildLabIndex, sortTiers, tierLabel } from "./lab-index";
|
|
23
21
|
|
|
24
22
|
export const getStaticPaths = (() => {
|
|
25
23
|
const index = buildLabIndex();
|
|
26
24
|
return [
|
|
27
|
-
{ params: { path: undefined }, props: {
|
|
28
|
-
...index.folders.map((folder) => ({ params: { path: folder.path }, props: {
|
|
25
|
+
{ params: { path: undefined }, props: { folder: "" } },
|
|
26
|
+
...index.folders.map((folder) => ({ params: { path: folder.path }, props: { folder: folder.path } })),
|
|
29
27
|
];
|
|
30
28
|
}) satisfies GetStaticPaths;
|
|
31
29
|
|
|
32
30
|
type Props = InferGetStaticPropsType<typeof getStaticPaths>;
|
|
33
31
|
|
|
34
|
-
const {
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
32
|
+
const { folder } = Astro.props as Props;
|
|
33
|
+
const index = buildLabIndex();
|
|
34
|
+
const all = componentsOf(index);
|
|
35
|
+
const segments = folder ? folder.split("/") : [];
|
|
36
|
+
const tier = segments[0] ?? "";
|
|
37
|
+
|
|
38
|
+
const under = (mod: LabComponent) => !folder || mod.directory === folder || mod.directory.startsWith(`${folder}/`);
|
|
39
|
+
|
|
40
|
+
// One grid, in the tree's own order, so the cards read in the same sequence as level 2. The root
|
|
41
|
+
// page (no folder) lists every tier in turn, each under its name.
|
|
42
|
+
const leafOrder = (tierId: string) => {
|
|
43
|
+
const ids: string[] = [];
|
|
44
|
+
const walk = (nodes: TreeNode[]) => {
|
|
45
|
+
for (const node of nodes) node.children ? walk(node.children) : ids.push(node.id);
|
|
46
|
+
};
|
|
47
|
+
walk(tierTree(index, tierId));
|
|
48
|
+
return ids;
|
|
49
|
+
};
|
|
50
|
+
const inOrder = (tierId: string) => {
|
|
51
|
+
const byId = new Map(all.filter((m) => m.tier === tierId).map((m) => [`mod:${m.moduleId}`, m]));
|
|
52
|
+
return leafOrder(tierId).map((id) => byId.get(id)!).filter(Boolean);
|
|
53
|
+
};
|
|
54
|
+
const ordered: (readonly [string, LabComponent[]])[] = folder
|
|
55
|
+
? [["", inOrder(tier).filter(under)] as const]
|
|
56
|
+
: sortTiers([...new Set(all.map((m) => m.tier))]).map((t) => [t, inOrder(t)] as const);
|
|
57
|
+
const mods = ordered.flatMap(([, list]) => list);
|
|
58
|
+
|
|
59
|
+
const tierDef = labConfig.tiers.find((t) => t.id === tier);
|
|
60
|
+
const title = folder ? (segments.length === 1 ? tierLabel(tier) : segments[segments.length - 1]!) : "All components";
|
|
61
|
+
// Level 2's breadcrumb names the panel's place (Site › Sections); a deeper folder is the navbar's.
|
|
62
|
+
const crumbs: Crumb[] = folder
|
|
63
|
+
? [{ label: "Site" }, { label: tierLabel(tier), href: hrefs.tier(tier) }]
|
|
64
|
+
: [{ label: "Site" }, { label: "All components" }];
|
|
65
|
+
|
|
66
|
+
const filters: FilterOption[] = [
|
|
67
|
+
{ value: "live", label: "Live on the site" },
|
|
68
|
+
{ value: "used", label: "Used by something live" },
|
|
69
|
+
{ value: "unused", label: "Unused" },
|
|
70
|
+
...(tierDef?.responsive
|
|
71
|
+
? [
|
|
72
|
+
{ value: "responsive", label: "Made responsive" },
|
|
73
|
+
{ value: "unresponsive", label: "Not responsive yet" },
|
|
74
|
+
]
|
|
75
|
+
: []),
|
|
76
|
+
];
|
|
77
|
+
const live = mods.filter((m) => m.live).length;
|
|
78
|
+
const used = mods.filter((m) => !m.live && m.usedBy.length).length;
|
|
79
|
+
const unused = mods.length - live - used;
|
|
80
|
+
const stories = mods.reduce((n, m) => n + m.stories.length, 0);
|
|
81
|
+
const plural = (n: number, one: string, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
|
|
82
|
+
// What one of these is called: "sections" on the sections tier, "components" anywhere else.
|
|
83
|
+
const noun = tier ? tierLabel(tier).toLowerCase() : "components";
|
|
84
|
+
const counts = [
|
|
85
|
+
segments.length > 1 ? segments.slice(0, -1).map((s, i) => (i === 0 ? tierLabel(s) : s)).join(" / ") : null,
|
|
86
|
+
plural(mods.length, noun.replace(/s$/, ""), noun),
|
|
87
|
+
plural(stories, "variant"),
|
|
88
|
+
live > 0 ? `${live} live` : null,
|
|
89
|
+
used > 0 ? `${used} used` : null,
|
|
90
|
+
unused > 0 ? `${unused} unused` : null,
|
|
91
|
+
].filter(Boolean);
|
|
92
|
+
// The title bar's filter chips set the same filter as level 2's menu (./../chrome/shell-client.ts).
|
|
93
|
+
const chips = [
|
|
94
|
+
{ value: "", label: "All", n: mods.length },
|
|
95
|
+
{ value: "live", label: "Live", n: live },
|
|
96
|
+
{ value: "used", label: "Used", n: used },
|
|
97
|
+
{ value: "unused", label: "Unused", n: unused },
|
|
58
98
|
];
|
|
59
|
-
const title = crumbs.map((c) => c.label).join(" › ");
|
|
60
99
|
---
|
|
61
100
|
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
<
|
|
71
|
-
<div class="
|
|
72
|
-
<
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
</p>
|
|
105
|
-
<label class="browse__filter">
|
|
106
|
-
<span class="browse__filter-label">// filter</span>
|
|
107
|
-
<input id="lab-filter" type="search" placeholder="name, section, version, tag…" autocomplete="off" />
|
|
108
|
-
</label>
|
|
109
|
-
</header>
|
|
110
|
-
|
|
111
|
-
{
|
|
112
|
-
children.length > 0 && (
|
|
113
|
-
<section class="browse__group">
|
|
114
|
-
<h2 class="browse__group-title">Folders</h2>
|
|
115
|
-
<div class="browse__folders">
|
|
116
|
-
{children.map((child) => {
|
|
117
|
-
const count = storyCount(child);
|
|
118
|
-
return (
|
|
119
|
-
<a class="folder-card" href={`${browseBase}/${child.path}`} data-search={child.path.toLowerCase()}>
|
|
120
|
-
<span class="folder-card__name">{child.name}</span>
|
|
121
|
-
<span class="folder-card__meta">
|
|
122
|
-
<span>
|
|
123
|
-
{count} stor{count === 1 ? "y" : "ies"}
|
|
124
|
-
</span>
|
|
125
|
-
{child.responsive.of > 0 && (
|
|
126
|
-
<span class="folder-card__responsive" title="section versions made responsive">
|
|
127
|
-
{child.responsive.done}/{child.responsive.of} responsive
|
|
128
|
-
</span>
|
|
129
|
-
)}
|
|
130
|
-
{child.liveCount > 0 && <span class="folder-card__live">{child.liveCount} live</span>}
|
|
131
|
-
</span>
|
|
132
|
-
</a>
|
|
133
|
-
);
|
|
134
|
-
})}
|
|
101
|
+
<Shell
|
|
102
|
+
title={title}
|
|
103
|
+
active={tier ? `tier:${tier}` : "home"}
|
|
104
|
+
navbarClass="lab-navbar--listing"
|
|
105
|
+
panel={folder ? { label: tierLabel(tier), crumbs, filters, filterLabel: "Show components", searchPlaceholder: `Search ${tierLabel(tier).toLowerCase()}` } : undefined}
|
|
106
|
+
>
|
|
107
|
+
{folder && <Tree slot="panel" nodes={tierTree(index, tier)} />}
|
|
108
|
+
|
|
109
|
+
<Fragment slot="navbar">
|
|
110
|
+
<div class="lab-listing-bar">
|
|
111
|
+
<h1 class="lab-listing-bar__title">{title}</h1>
|
|
112
|
+
<p class="lab-listing-bar__counts">{counts.join(" · ")}</p>
|
|
113
|
+
<div class="lab-chips" role="radiogroup" aria-label="Show">
|
|
114
|
+
{
|
|
115
|
+
chips.map((chip) => (
|
|
116
|
+
<button class="lab-chip" type="button" role="radio" aria-checked={String(chip.value === "")} data-lab-filter={chip.value}>
|
|
117
|
+
{chip.label} {chip.n}
|
|
118
|
+
</button>
|
|
119
|
+
))
|
|
120
|
+
}
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
</Fragment>
|
|
124
|
+
|
|
125
|
+
<div class="lab-page">
|
|
126
|
+
{
|
|
127
|
+
mods.length === 0 ? (
|
|
128
|
+
<div class="lab-empty">
|
|
129
|
+
<strong>Nothing here yet</strong>
|
|
130
|
+
Add a <code>*.stories.ts</code> file under <code>{labConfig.directory}/{folder}</code> and it appears here.
|
|
131
|
+
</div>
|
|
132
|
+
) : (
|
|
133
|
+
ordered.map(([key, list]) => (
|
|
134
|
+
<section data-lab-filter-section>
|
|
135
|
+
{key && (
|
|
136
|
+
<h2 class="lab-section-title">
|
|
137
|
+
<a href={hrefs.tier(key)}>{tierLabel(key)}</a>
|
|
138
|
+
<small>{list.length}</small>
|
|
139
|
+
</h2>
|
|
140
|
+
)}
|
|
141
|
+
<div class="lab-cards">
|
|
142
|
+
{list.map((mod) => <ComponentCard mod={mod} />)}
|
|
135
143
|
</div>
|
|
136
144
|
</section>
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
<section class="browse__group">
|
|
143
|
-
<h2 class="browse__group-title">Stories</h2>
|
|
144
|
-
<CardGrid items={items} />
|
|
145
|
-
</section>
|
|
146
|
-
)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
{children.length === 0 && items.length === 0 && <p class="browse__empty">Nothing in this folder.</p>}
|
|
150
|
-
</div>
|
|
151
|
-
|
|
152
|
-
<script>
|
|
153
|
-
// Client-side filter: every term typed must appear in a card's data-search text.
|
|
154
|
-
const input = document.getElementById("lab-filter") as HTMLInputElement | null;
|
|
155
|
-
const cards = document.querySelectorAll<HTMLElement>("[data-search]");
|
|
156
|
-
input?.addEventListener("input", () => {
|
|
157
|
-
const terms = input.value.trim().toLowerCase().split(/\s+/).filter(Boolean);
|
|
158
|
-
for (const card of cards) {
|
|
159
|
-
const text = card.dataset.search ?? "";
|
|
160
|
-
card.hidden = !terms.every((term) => text.includes(term));
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
</script>
|
|
164
|
-
</body>
|
|
165
|
-
</html>
|
|
166
|
-
|
|
167
|
-
<style is:global>
|
|
168
|
-
/* The filter hides cards with the `hidden` attribute; the cards' own `display` rules (CardGrid's
|
|
169
|
-
scoped styles) would otherwise beat the UA stylesheet, so this is global and !important. */
|
|
170
|
-
[hidden] {
|
|
171
|
-
display: none !important;
|
|
172
|
-
}
|
|
173
|
-
</style>
|
|
174
|
-
|
|
175
|
-
<style>
|
|
176
|
-
.browse {
|
|
177
|
-
background-color: var(--lab-color-bg);
|
|
178
|
-
color: var(--lab-color-text);
|
|
179
|
-
font-family: var(--lab-font-body);
|
|
180
|
-
min-height: 100vh;
|
|
181
|
-
padding: 3em 2.5em 5em;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
.browse__crumbs {
|
|
185
|
-
display: flex;
|
|
186
|
-
align-items: baseline;
|
|
187
|
-
gap: 1.5em;
|
|
188
|
-
font-family: var(--lab-font-mono);
|
|
189
|
-
font-weight: 500;
|
|
190
|
-
font-size: 0.85em;
|
|
191
|
-
color: var(--lab-color-text-muted);
|
|
192
|
-
margin-bottom: 1.25em;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
.browse__crumbs ol {
|
|
196
|
-
display: flex;
|
|
197
|
-
flex-wrap: wrap;
|
|
198
|
-
list-style: none;
|
|
199
|
-
gap: 0.5em;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
.browse__crumbs li + li::before {
|
|
203
|
-
content: "›";
|
|
204
|
-
color: var(--lab-color-text-faint);
|
|
205
|
-
margin-right: 0.5em;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
.browse__crumbs a,
|
|
209
|
-
.browse__back {
|
|
210
|
-
color: var(--lab-color-text-faint);
|
|
211
|
-
text-decoration: none;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
.browse__crumbs a:hover,
|
|
215
|
-
.browse__crumbs a:focus-visible,
|
|
216
|
-
.browse__back:hover,
|
|
217
|
-
.browse__back:focus-visible {
|
|
218
|
-
color: var(--lab-color-text-strong);
|
|
219
|
-
text-decoration: underline;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
.browse__crumbs [aria-current] {
|
|
223
|
-
color: var(--lab-color-text-strong);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
.browse__title {
|
|
227
|
-
font-family: var(--lab-font-heading);
|
|
228
|
-
color: var(--lab-color-text-strong);
|
|
229
|
-
font-size: 2.25em;
|
|
230
|
-
font-weight: 500;
|
|
231
|
-
letter-spacing: -0.5px;
|
|
232
|
-
margin-bottom: 0.4em;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
.browse__meta {
|
|
236
|
-
display: flex;
|
|
237
|
-
flex-wrap: wrap;
|
|
238
|
-
align-items: baseline;
|
|
239
|
-
gap: 1.25em;
|
|
240
|
-
font-family: var(--lab-font-mono);
|
|
241
|
-
font-size: 0.85em;
|
|
242
|
-
color: var(--lab-color-text-faint);
|
|
243
|
-
margin-bottom: 1.5em;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
.browse__meta a {
|
|
247
|
-
color: var(--lab-color-text-strong);
|
|
248
|
-
text-decoration: none;
|
|
249
|
-
border-bottom: 1px solid var(--lab-color-border);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
.browse__meta a:hover,
|
|
253
|
-
.browse__meta a:focus-visible {
|
|
254
|
-
border-bottom-color: var(--lab-color-text-strong);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
.browse__responsive,
|
|
258
|
-
.folder-card__responsive {
|
|
259
|
-
line-height: 1;
|
|
260
|
-
padding: 0.45em 0.85em;
|
|
261
|
-
border-radius: var(--lab-radius-pill);
|
|
262
|
-
border: 1px dashed var(--lab-color-border);
|
|
263
|
-
color: var(--lab-color-text-faint);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
.browse__live,
|
|
267
|
-
.folder-card__live {
|
|
268
|
-
font-weight: 500;
|
|
269
|
-
line-height: 1;
|
|
270
|
-
padding: 0.45em 0.85em;
|
|
271
|
-
border-radius: var(--lab-radius-pill);
|
|
272
|
-
background-color: var(--lab-color-accent);
|
|
273
|
-
color: var(--lab-color-accent-text);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
.browse__filter {
|
|
277
|
-
display: flex;
|
|
278
|
-
align-items: baseline;
|
|
279
|
-
gap: 1em;
|
|
280
|
-
max-width: 36em;
|
|
281
|
-
border: 1px solid var(--lab-color-border);
|
|
282
|
-
border-left: 1px solid var(--lab-color-text-strong);
|
|
283
|
-
padding: 0.65em 1em;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
.browse__filter-label {
|
|
287
|
-
font-family: var(--lab-font-mono);
|
|
288
|
-
font-weight: 500;
|
|
289
|
-
font-size: 0.85em;
|
|
290
|
-
color: var(--lab-color-text-muted);
|
|
291
|
-
white-space: nowrap;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
.browse__filter input {
|
|
295
|
-
flex: 1;
|
|
296
|
-
font: inherit;
|
|
297
|
-
font-family: var(--lab-font-mono);
|
|
298
|
-
font-size: 0.9em;
|
|
299
|
-
color: var(--lab-color-text-strong);
|
|
300
|
-
background: transparent;
|
|
301
|
-
border: 0;
|
|
302
|
-
outline: none;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
.browse__filter input::placeholder {
|
|
306
|
-
color: var(--lab-color-text-faint);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
.browse__filter:focus-within {
|
|
310
|
-
border-color: var(--lab-color-border-strong);
|
|
311
|
-
border-left-color: var(--lab-color-text-strong);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
.browse__group {
|
|
315
|
-
margin-top: 3em;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
.browse__group-title {
|
|
319
|
-
font-family: var(--lab-font-mono);
|
|
320
|
-
font-weight: 500;
|
|
321
|
-
font-size: 1em;
|
|
322
|
-
color: var(--lab-color-text-muted);
|
|
323
|
-
margin-bottom: 1em;
|
|
324
|
-
padding-bottom: 0.5em;
|
|
325
|
-
border-bottom: 1px solid var(--lab-color-border);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
.browse__folders {
|
|
329
|
-
display: grid;
|
|
330
|
-
grid-template-columns: repeat(auto-fill, minmax(16em, 1fr));
|
|
331
|
-
gap: 1em;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
.folder-card {
|
|
335
|
-
display: flex;
|
|
336
|
-
flex-direction: column;
|
|
337
|
-
gap: 0.75em;
|
|
338
|
-
text-decoration: none;
|
|
339
|
-
border: 1px solid var(--lab-color-border);
|
|
340
|
-
background-color: var(--lab-color-surface);
|
|
341
|
-
padding: 1.1em 1.25em;
|
|
342
|
-
transition: border-color var(--lab-transition);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
.folder-card:hover,
|
|
346
|
-
.folder-card:focus-visible {
|
|
347
|
-
border-color: var(--lab-color-border-strong);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
.folder-card__name {
|
|
351
|
-
color: var(--lab-color-text-strong);
|
|
352
|
-
font-weight: 500;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
.folder-card__meta {
|
|
356
|
-
display: flex;
|
|
357
|
-
align-items: baseline;
|
|
358
|
-
justify-content: space-between;
|
|
359
|
-
gap: 1em;
|
|
360
|
-
font-family: var(--lab-font-mono);
|
|
361
|
-
font-size: 0.75em;
|
|
362
|
-
color: var(--lab-color-text-faint);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
.browse__empty {
|
|
366
|
-
margin-top: 3em;
|
|
367
|
-
font-family: var(--lab-font-mono);
|
|
368
|
-
font-size: 0.85em;
|
|
369
|
-
color: var(--lab-color-text-faint);
|
|
370
|
-
}
|
|
371
|
-
</style>
|
|
145
|
+
))
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
</div>
|
|
149
|
+
</Shell>
|