handzon-core 0.8.0 → 0.8.1
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 +5 -2
- 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.1",
|
|
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;
|
|
@@ -122,7 +125,7 @@ const slug = tutorial.id;
|
|
|
122
125
|
grid-template-columns: 18px 1fr auto;
|
|
123
126
|
align-items: center;
|
|
124
127
|
gap: 0.6rem;
|
|
125
|
-
padding: 0.6rem
|
|
128
|
+
padding: 0.6rem 1rem;
|
|
126
129
|
color: var(--color-fg);
|
|
127
130
|
text-decoration: none;
|
|
128
131
|
font-size: 0.92em;
|
|
@@ -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
|
}
|