handzon-core 0.8.0 → 0.8.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/package.json +1 -1
- package/src/components/Sidebar.astro +21 -4
- package/src/components/home/Hero.astro +4 -3
- package/src/layouts/BaseLayout.astro +6 -0
- package/src/layouts/TutorialLayout.astro +18 -9
- package/src/pages/Home.astro +7 -1
- package/src/pages/TutorialLanding.astro +6 -4
- package/src/pages/TutorialStep.astro +1 -0
- package/styles/base.css +16 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "handzon-core",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "Core framework for Handzon — layouts, components, content + AI libs, and server runtime (handlers, DB, auth, migration runner) consumed by Handzon scaffolds.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -106,9 +106,12 @@ const slug = tutorial.id;
|
|
|
106
106
|
font-family: var(--font-mono);
|
|
107
107
|
letter-spacing: 0.04em;
|
|
108
108
|
}
|
|
109
|
+
/* Horizontal padding lives on the <a>, not on `.sb-steps`, so the
|
|
110
|
+
* current-step background fills the sidebar edge-to-edge instead of
|
|
111
|
+
* leaving a 1rem gutter on either side. The list itself is flush. */
|
|
109
112
|
.sb-steps {
|
|
110
113
|
list-style: none;
|
|
111
|
-
padding: 0
|
|
114
|
+
padding: 0 0 2rem;
|
|
112
115
|
margin: 0.75rem 0 0;
|
|
113
116
|
flex: 1;
|
|
114
117
|
overflow-y: auto;
|
|
@@ -117,18 +120,32 @@ const slug = tutorial.id;
|
|
|
117
120
|
.sb-steps li + li {
|
|
118
121
|
border-top: var(--border-default) solid var(--color-border);
|
|
119
122
|
}
|
|
123
|
+
/* Two-row layout: duration sits flush-right on its own tight row
|
|
124
|
+
* above the title. The checkbox sits in the title row only (not
|
|
125
|
+
* spanning) so it vertically aligns with the name's center,
|
|
126
|
+
* independent of whether a duration row is present above. The first
|
|
127
|
+
* column of the duration row is empty and collapses harmlessly. */
|
|
120
128
|
.sb-steps a {
|
|
121
129
|
display: grid;
|
|
122
|
-
grid-template-columns: 18px 1fr
|
|
130
|
+
grid-template-columns: 18px 1fr;
|
|
131
|
+
grid-template-areas:
|
|
132
|
+
". dur"
|
|
133
|
+
"check name";
|
|
123
134
|
align-items: center;
|
|
124
|
-
gap: 0.6rem;
|
|
125
|
-
|
|
135
|
+
column-gap: 0.6rem;
|
|
136
|
+
row-gap: 0;
|
|
137
|
+
/* Heavier bottom padding counter-balances the duration row above
|
|
138
|
+
* so the title feels visually centered in the row. */
|
|
139
|
+
padding: 0.5rem 1rem 0.95rem;
|
|
126
140
|
color: var(--color-fg);
|
|
127
141
|
text-decoration: none;
|
|
128
142
|
font-size: 0.92em;
|
|
129
143
|
font-weight: 500;
|
|
130
144
|
letter-spacing: -0.005em;
|
|
131
145
|
}
|
|
146
|
+
.sb-check { grid-area: check; }
|
|
147
|
+
.sb-name { grid-area: name; line-height: 1.25; }
|
|
148
|
+
.sb-dur { grid-area: dur; justify-self: end; line-height: 1; }
|
|
132
149
|
.sb-steps a:hover {
|
|
133
150
|
background: var(--color-surface);
|
|
134
151
|
color: var(--color-fg);
|
|
@@ -35,9 +35,10 @@ const {
|
|
|
35
35
|
align-items: center;
|
|
36
36
|
gap: clamp(0.6rem, 1.2vw, 1rem);
|
|
37
37
|
margin: 0 0 0.75rem;
|
|
38
|
-
font-
|
|
39
|
-
font-
|
|
40
|
-
|
|
38
|
+
font-family: var(--font-display, var(--font-sans));
|
|
39
|
+
font-size: var(--text-display, clamp(2.25rem, 5vw, 3.75rem));
|
|
40
|
+
font-weight: var(--font-weight-display, 700);
|
|
41
|
+
letter-spacing: var(--tracking-display, -0.025em);
|
|
41
42
|
line-height: 1.05;
|
|
42
43
|
}
|
|
43
44
|
/* Logo height tracks the heading's cap-height (~0.72em of the
|
|
@@ -75,6 +75,12 @@ const desc = description ?? tagline;
|
|
|
75
75
|
<meta property="og:title" content={pageTitle} />
|
|
76
76
|
<meta property="og:description" content={desc} />
|
|
77
77
|
<meta property="og:type" content="website" />
|
|
78
|
+
{/* Optional consumer-side <head> injections. Forwarded by every
|
|
79
|
+
* page wrapper (Home, TutorialLanding, TutorialStep / TutorialLayout)
|
|
80
|
+
* so scaffolds can preload fonts, attach per-page OG images, add
|
|
81
|
+
* JSON-LD structured data, or wire verification meta without
|
|
82
|
+
* forking BaseLayout. See README for usage. */}
|
|
83
|
+
<slot name="head" />
|
|
78
84
|
</head>
|
|
79
85
|
<body style={`--hz-page-max-width: ${resolvedMaxWidth}; --hz-page-padding-x: ${pagePaddingX}; --hz-nav-height: 3rem;`}>
|
|
80
86
|
{nav === "full" && (
|
|
@@ -42,6 +42,7 @@ const stepSlugs = steps.map((s) => parseStepId(s.id).stepSlug);
|
|
|
42
42
|
faviconUrl={faviconUrl}
|
|
43
43
|
repoUrl={repoUrl}
|
|
44
44
|
>
|
|
45
|
+
<slot name="head" slot="head" />
|
|
45
46
|
<div class="layout">
|
|
46
47
|
<div class="sidebar-wrap">
|
|
47
48
|
<Sidebar tutorial={tutorial} steps={steps} currentStepSlug={currentStepSlug} />
|
|
@@ -177,23 +178,30 @@ const stepSlugs = steps.map((s) => parseStepId(s.id).stepSlug);
|
|
|
177
178
|
|
|
178
179
|
<style>
|
|
179
180
|
/* Draw the sidebar/main divider as a background line on the grid
|
|
180
|
-
* itself: 1px-thin vertical strip at the
|
|
181
|
+
* itself: 1px-thin vertical strip at the sidebar column boundary,
|
|
181
182
|
* from the top of the layout to the bottom. The grid auto-stretches
|
|
182
183
|
* to fit its content, so the line runs all the way down to the
|
|
183
184
|
* footer regardless of step length — without piggybacking on the
|
|
184
|
-
* (sticky, viewport-height) sidebar's own border.
|
|
185
|
+
* (sticky, viewport-height) sidebar's own border.
|
|
186
|
+
*
|
|
187
|
+
* `--sb-w` keeps the column track and the divider stop in lockstep
|
|
188
|
+
* so widening the sidebar at one breakpoint doesn't desync the line. */
|
|
185
189
|
.layout {
|
|
190
|
+
--sb-w: 280px;
|
|
186
191
|
display: grid;
|
|
187
|
-
grid-template-columns:
|
|
192
|
+
grid-template-columns: var(--sb-w) minmax(0, 1fr);
|
|
188
193
|
min-height: 100dvh;
|
|
189
194
|
background: linear-gradient(
|
|
190
195
|
to right,
|
|
191
|
-
transparent
|
|
192
|
-
var(--color-border)
|
|
193
|
-
var(--color-border)
|
|
194
|
-
transparent
|
|
196
|
+
transparent var(--sb-w),
|
|
197
|
+
var(--color-border) var(--sb-w),
|
|
198
|
+
var(--color-border) calc(var(--sb-w) + 1px),
|
|
199
|
+
transparent calc(var(--sb-w) + 1px)
|
|
195
200
|
) no-repeat;
|
|
196
201
|
}
|
|
202
|
+
@media (min-width: 1280px) {
|
|
203
|
+
.layout { --sb-w: 320px; }
|
|
204
|
+
}
|
|
197
205
|
.sidebar-wrap {
|
|
198
206
|
position: sticky;
|
|
199
207
|
top: var(--hz-nav-height, 3rem);
|
|
@@ -211,10 +219,11 @@ const stepSlugs = steps.map((s) => parseStepId(s.id).stepSlug);
|
|
|
211
219
|
color: var(--color-muted);
|
|
212
220
|
}
|
|
213
221
|
.step-title {
|
|
222
|
+
font-family: var(--font-display, var(--font-sans));
|
|
214
223
|
font-size: clamp(1.75rem, 3vw, 2.25rem);
|
|
215
|
-
font-weight: 700;
|
|
224
|
+
font-weight: var(--font-weight-display, 700);
|
|
216
225
|
margin: 0.3rem 0 0.5rem;
|
|
217
|
-
letter-spacing: -0.02em;
|
|
226
|
+
letter-spacing: var(--tracking-display, -0.02em);
|
|
218
227
|
line-height: 1.15;
|
|
219
228
|
}
|
|
220
229
|
.step-dur {
|
package/src/pages/Home.astro
CHANGED
|
@@ -60,6 +60,7 @@ for (const t of tutorials) {
|
|
|
60
60
|
repoUrl={repoUrl}
|
|
61
61
|
nav="userMenu"
|
|
62
62
|
>
|
|
63
|
+
<slot name="head" slot="head" />
|
|
63
64
|
<div class="home">
|
|
64
65
|
<Hero title={hero?.title} subtitle={hero?.subtitle} logoUrl={logoUrl} />
|
|
65
66
|
|
|
@@ -238,7 +239,12 @@ for (const t of tutorials) {
|
|
|
238
239
|
}
|
|
239
240
|
.grid {
|
|
240
241
|
display: grid;
|
|
241
|
-
|
|
242
|
+
/* Cap at 3 columns max: the lower-bound of each track is the larger
|
|
243
|
+
* of 280px or one-third of the row (minus the two 1rem gaps). On
|
|
244
|
+
* wide viewports the (100% - 2rem)/3 term wins and forces exactly
|
|
245
|
+
* 3 columns; on narrow viewports 280px wins and the grid wraps to
|
|
246
|
+
* 2 or 1 columns as usual. */
|
|
247
|
+
grid-template-columns: repeat(auto-fill, minmax(max(280px, (100% - 2rem) / 3), 1fr));
|
|
242
248
|
gap: 1rem;
|
|
243
249
|
margin-top: 0.75rem;
|
|
244
250
|
}
|
|
@@ -26,6 +26,7 @@ const firstStepSlug = steps[0] ? parseStepId(steps[0].id).stepSlug : null;
|
|
|
26
26
|
faviconUrl={faviconUrl}
|
|
27
27
|
repoUrl={repoUrl}
|
|
28
28
|
>
|
|
29
|
+
<slot name="head" slot="head" />
|
|
29
30
|
<div class="landing">
|
|
30
31
|
<a class="back" href="/">← All tutorials</a>
|
|
31
32
|
|
|
@@ -58,7 +59,7 @@ const firstStepSlug = steps[0] ? parseStepId(steps[0].id).stepSlug : null;
|
|
|
58
59
|
|
|
59
60
|
{tutorial.data.tags.length > 0 && (
|
|
60
61
|
<div class="hero-tags" aria-label="Topics">
|
|
61
|
-
{tutorial.data.tags.map((tag) => (
|
|
62
|
+
{tutorial.data.tags.map((tag: string) => (
|
|
62
63
|
<a class="hero-tag" href={`/?tag=${encodeURIComponent(tag)}`}>#{tag}</a>
|
|
63
64
|
))}
|
|
64
65
|
</div>
|
|
@@ -68,7 +69,7 @@ const firstStepSlug = steps[0] ? parseStepId(steps[0].id).stepSlug : null;
|
|
|
68
69
|
{tutorial.data.prerequisites.length > 0 && (
|
|
69
70
|
<section class="block">
|
|
70
71
|
<h2>Prerequisites</h2>
|
|
71
|
-
<ul class="prereqs">{tutorial.data.prerequisites.map((p) => <li>{p}</li>)}</ul>
|
|
72
|
+
<ul class="prereqs">{tutorial.data.prerequisites.map((p: string) => <li>{p}</li>)}</ul>
|
|
72
73
|
</section>
|
|
73
74
|
)}
|
|
74
75
|
|
|
@@ -188,10 +189,11 @@ const firstStepSlug = steps[0] ? parseStepId(steps[0].id).stepSlug : null;
|
|
|
188
189
|
}
|
|
189
190
|
.hero-tag:hover { color: var(--color-accent); }
|
|
190
191
|
h1 {
|
|
192
|
+
font-family: var(--font-display, var(--font-sans));
|
|
191
193
|
font-size: clamp(2.1rem, 4.5vw, 3rem);
|
|
192
|
-
font-weight: 800;
|
|
194
|
+
font-weight: var(--font-weight-display, 800);
|
|
193
195
|
margin: 0 0 0.75rem;
|
|
194
|
-
letter-spacing: -0.025em;
|
|
196
|
+
letter-spacing: var(--tracking-display, -0.025em);
|
|
195
197
|
line-height: 1.05;
|
|
196
198
|
}
|
|
197
199
|
.desc {
|
|
@@ -63,6 +63,7 @@ const initialContext = buildContext({
|
|
|
63
63
|
faviconUrl={faviconUrl}
|
|
64
64
|
repoUrl={repoUrl}
|
|
65
65
|
>
|
|
66
|
+
<slot name="head" slot="head" />
|
|
66
67
|
<Content components={components} />
|
|
67
68
|
{aiConfig.enabled && aiConfig.autoStepHelp && (
|
|
68
69
|
<StepHelp client:visible stepTitle={currentStep.data.title} />
|
package/styles/base.css
CHANGED
|
@@ -36,13 +36,14 @@ a:hover {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/* Tutorial prose — consistent type scale, single weight family.
|
|
39
|
-
*
|
|
40
|
-
*
|
|
39
|
+
* Defaults preserve the previous hardcoded values; themes can re-skin
|
|
40
|
+
* via the typography tokens documented in AGENTS.md (font weights,
|
|
41
|
+
* tracking, leading, type scale) without resorting to !important.
|
|
41
42
|
*/
|
|
42
43
|
.prose {
|
|
43
44
|
font-family: var(--font-sans);
|
|
44
|
-
line-height: 1.65;
|
|
45
|
-
font-size: 1rem;
|
|
45
|
+
line-height: var(--leading-body, 1.65);
|
|
46
|
+
font-size: var(--text-body, 1rem);
|
|
46
47
|
max-width: 72ch;
|
|
47
48
|
}
|
|
48
49
|
|
|
@@ -52,22 +53,25 @@ a:hover {
|
|
|
52
53
|
.prose h4,
|
|
53
54
|
.prose h5,
|
|
54
55
|
.prose h6 {
|
|
55
|
-
font-weight: 700;
|
|
56
|
-
letter-spacing: -0.015em;
|
|
57
|
-
line-height: 1.2;
|
|
56
|
+
font-weight: var(--font-weight-heading, 700);
|
|
57
|
+
letter-spacing: var(--tracking-heading, -0.015em);
|
|
58
|
+
line-height: var(--leading-heading, 1.2);
|
|
58
59
|
margin-top: 2rem;
|
|
59
60
|
margin-bottom: 0.6rem;
|
|
60
61
|
color: var(--color-fg);
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
.prose h1 {
|
|
64
|
+
.prose h1 {
|
|
65
|
+
font-size: var(--text-h1, 1.875rem);
|
|
66
|
+
letter-spacing: var(--tracking-display, -0.02em);
|
|
67
|
+
}
|
|
64
68
|
.prose h2 {
|
|
65
|
-
font-size: 1.4rem;
|
|
69
|
+
font-size: var(--text-h2, 1.4rem);
|
|
66
70
|
padding-top: 1.25rem;
|
|
67
71
|
border-top: var(--border-default) solid var(--color-border);
|
|
68
72
|
}
|
|
69
|
-
.prose h3 { font-size: 1.15rem; }
|
|
70
|
-
.prose h4 { font-size: 1rem; }
|
|
73
|
+
.prose h3 { font-size: var(--text-h3, 1.15rem); }
|
|
74
|
+
.prose h4 { font-size: var(--text-h4, 1rem); }
|
|
71
75
|
|
|
72
76
|
.prose p { margin: 0.75em 0; }
|
|
73
77
|
|
|
@@ -133,5 +137,5 @@ a:hover {
|
|
|
133
137
|
|
|
134
138
|
.prose th {
|
|
135
139
|
background: var(--color-surface);
|
|
136
|
-
font-weight: 700;
|
|
140
|
+
font-weight: var(--font-weight-strong, 700);
|
|
137
141
|
}
|