@lessonkit/cli 0.9.3 → 1.0.0
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 +20 -34
- package/dist/bin.js +80 -127
- package/dist/index.js +80 -127
- package/package.json +3 -3
- package/template/vite-react/README.md +13 -11
- package/template/vite-react/lessonkit.json +6 -1
- package/template/vite-react/package.json +6 -6
- package/template/vite-react/dist/assets/index-CP3MNJ8s.css +0 -1
- package/template/vite-react/dist/assets/index-Dg5lKLi0.js +0 -8
- package/template/vite-react/dist/index.html +0 -14
package/dist/index.js
CHANGED
|
@@ -207,18 +207,13 @@ async function runInit(opts, logger) {
|
|
|
207
207
|
import { readFileSync, existsSync as existsSync2 } from "fs";
|
|
208
208
|
import { readFile as readFile2 } from "fs/promises";
|
|
209
209
|
import { dirname as dirname2, join as join2, parse, resolve as resolve2 } from "path";
|
|
210
|
-
import {
|
|
210
|
+
import { parseLessonkitManifest } from "@lessonkit/lxpack";
|
|
211
211
|
var LESSONKIT_JSON = "lessonkit.json";
|
|
212
212
|
var PACKAGE_JSON = "package.json";
|
|
213
|
-
var DEFAULT_PATHS = {
|
|
214
|
-
spaDistDir: "dist",
|
|
215
|
-
lxpackOutDir: ".lxpack/course",
|
|
216
|
-
outputBaseDir: ".lxpack/out"
|
|
217
|
-
};
|
|
218
213
|
function isProjectManifest(configPath) {
|
|
219
214
|
try {
|
|
220
215
|
const raw = JSON.parse(readFileSync(configPath, "utf8"));
|
|
221
|
-
return raw.schemaVersion === 1 && typeof raw.name === "string" && raw.course !== null && typeof raw.course === "object";
|
|
216
|
+
return raw.schemaVersion === 1 && typeof raw.name === "string" && raw.course !== null && typeof raw.course === "object" && !Array.isArray(raw.course);
|
|
222
217
|
} catch {
|
|
223
218
|
return false;
|
|
224
219
|
}
|
|
@@ -251,124 +246,66 @@ async function loadLessonkitJson(projectRoot) {
|
|
|
251
246
|
exitCode: EXIT_INVALID_PROJECT
|
|
252
247
|
});
|
|
253
248
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
exitCode: EXIT_INVALID_PROJECT
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
const config = raw;
|
|
261
|
-
const schemaVersion = config.schemaVersion;
|
|
262
|
-
if (schemaVersion !== 1) {
|
|
263
|
-
throw new CliError(`${configPath}: schemaVersion must be 1 (got ${String(schemaVersion)}).`, {
|
|
264
|
-
code: "INVALID_PROJECT",
|
|
265
|
-
exitCode: EXIT_INVALID_PROJECT
|
|
266
|
-
});
|
|
249
|
+
const parsed = parseLessonkitManifest(raw, configPath, projectRoot);
|
|
250
|
+
if (!parsed.ok) {
|
|
251
|
+
throwManifestCliError(configPath, parsed.issues);
|
|
267
252
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
253
|
+
return {
|
|
254
|
+
root: projectRoot,
|
|
255
|
+
schemaVersion: 1,
|
|
256
|
+
name: parsed.manifest.name,
|
|
257
|
+
course: parsed.manifest.course,
|
|
258
|
+
paths: parsed.manifest.paths
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
function throwManifestCliError(configPath, issues) {
|
|
262
|
+
const layoutIssue = issues.find((i) => i.path === "course.layout");
|
|
263
|
+
if (layoutIssue?.message.includes("per-lesson-spa")) {
|
|
264
|
+
throw new CliError(
|
|
265
|
+
`${configPath}: per-lesson-spa layout is not supported by lessonkit package yet. Use single-spa or package via @lessonkit/lxpack directly.`,
|
|
266
|
+
{ code: "INVALID_PROJECT", exitCode: EXIT_INVALID_PROJECT }
|
|
267
|
+
);
|
|
274
268
|
}
|
|
275
|
-
const
|
|
276
|
-
if (
|
|
277
|
-
throw new CliError(`${configPath}: "course" must be an
|
|
269
|
+
const lessonsIssue = issues.find((i) => i.path === "course.lessons");
|
|
270
|
+
if (lessonsIssue) {
|
|
271
|
+
throw new CliError(`${configPath}: "course.lessons" must be an array.`, {
|
|
278
272
|
code: "INVALID_PROJECT",
|
|
279
273
|
exitCode: EXIT_INVALID_PROJECT
|
|
280
274
|
});
|
|
281
275
|
}
|
|
282
|
-
const
|
|
283
|
-
if (
|
|
284
|
-
throw new CliError(`${configPath}: "
|
|
276
|
+
const spaDistTypeIssue = issues.find((i) => i.path === "paths.spaDistDir");
|
|
277
|
+
if (spaDistTypeIssue && spaDistTypeIssue.message.includes("non-empty string")) {
|
|
278
|
+
throw new CliError(`${configPath}: "paths.spaDistDir" must be a non-empty string.`, {
|
|
285
279
|
code: "INVALID_PROJECT",
|
|
286
280
|
exitCode: EXIT_INVALID_PROJECT
|
|
287
281
|
});
|
|
288
282
|
}
|
|
289
|
-
|
|
290
|
-
|
|
283
|
+
const courseSpaIssue = issues.find((i) => i.path === "course.spaDistDir");
|
|
284
|
+
if (courseSpaIssue) {
|
|
285
|
+
throw new CliError(`${configPath}: ${courseSpaIssue.message}`, {
|
|
291
286
|
code: "INVALID_PROJECT",
|
|
292
287
|
exitCode: EXIT_INVALID_PROJECT
|
|
293
288
|
});
|
|
294
289
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
throw new CliError(`${configPath}: invalid course descriptor.`, {
|
|
290
|
+
if (issues.some((i) => i.path.startsWith("paths."))) {
|
|
291
|
+
throw new CliError(`${configPath}: invalid paths.`, {
|
|
298
292
|
code: "INVALID_PROJECT",
|
|
299
293
|
exitCode: EXIT_INVALID_PROJECT,
|
|
300
|
-
issues:
|
|
301
|
-
path: i.path,
|
|
302
|
-
message: i.message
|
|
303
|
-
}))
|
|
294
|
+
issues: issues.map((i) => ({ path: i.path, message: i.message }))
|
|
304
295
|
});
|
|
305
296
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
{ code: "INVALID_PROJECT", exitCode: EXIT_INVALID_PROJECT }
|
|
310
|
-
);
|
|
311
|
-
}
|
|
312
|
-
const pathsRaw = config.paths;
|
|
313
|
-
const paths = { ...DEFAULT_PATHS };
|
|
314
|
-
if (pathsRaw !== void 0 && (typeof pathsRaw !== "object" || pathsRaw === null)) {
|
|
315
|
-
throw new CliError(`${configPath}: "paths" must be an object.`, {
|
|
297
|
+
const schemaIssue = issues.find((i) => i.path === "schemaVersion");
|
|
298
|
+
if (schemaIssue) {
|
|
299
|
+
throw new CliError(`${configPath}: schemaVersion must be 1 (got ${schemaIssue.message.replace(/^must be 1 \(got /, "").replace(/\)$/, "")}).`, {
|
|
316
300
|
code: "INVALID_PROJECT",
|
|
317
301
|
exitCode: EXIT_INVALID_PROJECT
|
|
318
302
|
});
|
|
319
303
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
code: "INVALID_PROJECT",
|
|
326
|
-
exitCode: EXIT_INVALID_PROJECT
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
paths.spaDistDir = p.spaDistDir;
|
|
330
|
-
}
|
|
331
|
-
if (p.lxpackOutDir !== void 0) {
|
|
332
|
-
if (typeof p.lxpackOutDir !== "string" || !p.lxpackOutDir.trim()) {
|
|
333
|
-
throw new CliError(`${configPath}: "paths.lxpackOutDir" must be a non-empty string.`, {
|
|
334
|
-
code: "INVALID_PROJECT",
|
|
335
|
-
exitCode: EXIT_INVALID_PROJECT
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
paths.lxpackOutDir = p.lxpackOutDir;
|
|
339
|
-
}
|
|
340
|
-
if (p.outputBaseDir !== void 0) {
|
|
341
|
-
if (typeof p.outputBaseDir !== "string" || !p.outputBaseDir.trim()) {
|
|
342
|
-
throw new CliError(`${configPath}: "paths.outputBaseDir" must be a non-empty string.`, {
|
|
343
|
-
code: "INVALID_PROJECT",
|
|
344
|
-
exitCode: EXIT_INVALID_PROJECT
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
paths.outputBaseDir = p.outputBaseDir;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
const courseSpaDistDir = validation.descriptor.spaDistDir?.trim();
|
|
351
|
-
if (courseSpaDistDir && courseSpaDistDir !== paths.spaDistDir) {
|
|
352
|
-
throw new CliError(
|
|
353
|
-
`${configPath}: "course.spaDistDir" (${courseSpaDistDir}) differs from "paths.spaDistDir" (${paths.spaDistDir}). Use paths.spaDistDir for CLI build and package.`,
|
|
354
|
-
{ code: "INVALID_PROJECT", exitCode: EXIT_INVALID_PROJECT }
|
|
355
|
-
);
|
|
356
|
-
}
|
|
357
|
-
const pathIssues = validateProjectPaths(projectRoot, paths);
|
|
358
|
-
if (pathIssues.length) {
|
|
359
|
-
throw new CliError(`${configPath}: invalid paths.`, {
|
|
360
|
-
code: "INVALID_PROJECT",
|
|
361
|
-
exitCode: EXIT_INVALID_PROJECT,
|
|
362
|
-
issues: pathIssues
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
return {
|
|
366
|
-
root: projectRoot,
|
|
367
|
-
schemaVersion: 1,
|
|
368
|
-
name,
|
|
369
|
-
course: validation.descriptor,
|
|
370
|
-
paths
|
|
371
|
-
};
|
|
304
|
+
throw new CliError(`${configPath}: invalid lessonkit manifest.`, {
|
|
305
|
+
code: "INVALID_PROJECT",
|
|
306
|
+
exitCode: EXIT_INVALID_PROJECT,
|
|
307
|
+
issues: issues.map((i) => ({ path: i.path, message: i.message }))
|
|
308
|
+
});
|
|
372
309
|
}
|
|
373
310
|
async function loadProject(cwd = process.cwd()) {
|
|
374
311
|
const root = findProjectRoot(cwd);
|
|
@@ -386,7 +323,7 @@ async function readPackageJson(projectRoot) {
|
|
|
386
323
|
}
|
|
387
324
|
}
|
|
388
325
|
function assertViteProject(pkg, projectRoot) {
|
|
389
|
-
const vite = pkg.devDependencies?.vite ?? pkg.dependencies?.vite ?? (existsSync2(join2(projectRoot, "node_modules", ".bin", "vite")) || existsSync2(join2(projectRoot, "node_modules", ".bin", "vite.cmd")) ? "present" : void 0);
|
|
326
|
+
const vite = pkg.devDependencies?.vite ?? pkg.dependencies?.vite ?? (existsSync2(join2(projectRoot, "node_modules", "vite", "bin", "vite.js")) || existsSync2(join2(projectRoot, "node_modules", ".bin", "vite")) || existsSync2(join2(projectRoot, "node_modules", ".bin", "vite.cmd")) ? "present" : void 0);
|
|
390
327
|
if (!vite) {
|
|
391
328
|
throw new CliError(
|
|
392
329
|
`No Vite dependency found in ${join2(projectRoot, PACKAGE_JSON)}. LessonKit projects require Vite.`,
|
|
@@ -394,28 +331,25 @@ function assertViteProject(pkg, projectRoot) {
|
|
|
394
331
|
);
|
|
395
332
|
}
|
|
396
333
|
}
|
|
397
|
-
function
|
|
334
|
+
function resolveViteJs(projectRoot) {
|
|
398
335
|
let dir = resolve2(projectRoot);
|
|
399
336
|
const fsRoot = parse(dir).root;
|
|
400
337
|
while (true) {
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
if (existsSync2(bin)) return bin;
|
|
404
|
-
const binCmd = join2(binDir, "vite.cmd");
|
|
405
|
-
if (existsSync2(binCmd)) return binCmd;
|
|
338
|
+
const viteJs = join2(dir, "node_modules", "vite", "bin", "vite.js");
|
|
339
|
+
if (existsSync2(viteJs)) return viteJs;
|
|
406
340
|
if (dir === fsRoot) break;
|
|
407
341
|
dir = dirname2(dir);
|
|
408
342
|
}
|
|
409
343
|
throw new CliError(
|
|
410
|
-
`Vite
|
|
344
|
+
`Vite not found near ${projectRoot}. Run npm install in the project first.`,
|
|
411
345
|
{ code: "INVALID_PROJECT", exitCode: EXIT_INVALID_PROJECT }
|
|
412
346
|
);
|
|
413
347
|
}
|
|
414
|
-
function
|
|
348
|
+
function assertNode18ForLxpack() {
|
|
415
349
|
const major = Number(process.versions.node.split(".")[0]);
|
|
416
|
-
if (major <
|
|
350
|
+
if (major < 18) {
|
|
417
351
|
throw new CliError(
|
|
418
|
-
`LMS packaging requires Node.js
|
|
352
|
+
`LMS packaging requires Node.js 18+ (current: ${process.versions.node}). See docs/PACKAGING.md.`,
|
|
419
353
|
{ code: "NODE_VERSION", exitCode: EXIT_INVALID_PROJECT }
|
|
420
354
|
);
|
|
421
355
|
}
|
|
@@ -477,17 +411,19 @@ async function runDev(opts) {
|
|
|
477
411
|
const project = await loadProject(opts.cwd ?? process.cwd());
|
|
478
412
|
const pkg = await readPackageJson(project.root);
|
|
479
413
|
assertViteProject(pkg, project.root);
|
|
480
|
-
const
|
|
481
|
-
await runCommand(
|
|
414
|
+
const viteJs = resolveViteJs(project.root);
|
|
415
|
+
await runCommand(process.execPath, [viteJs, ...opts.viteArgs ?? []], { cwd: project.root });
|
|
482
416
|
return { ok: true, command: "dev", projectRoot: project.root };
|
|
483
417
|
}
|
|
484
418
|
async function runBuild(opts) {
|
|
485
419
|
const project = await loadProject(opts.cwd ?? process.cwd());
|
|
486
420
|
const pkg = await readPackageJson(project.root);
|
|
487
421
|
assertViteProject(pkg, project.root);
|
|
488
|
-
const
|
|
422
|
+
const viteJs = resolveViteJs(project.root);
|
|
489
423
|
const buildArgs = resolveViteBuildArgs(project);
|
|
490
|
-
await runCommand(
|
|
424
|
+
await runCommand(process.execPath, [viteJs, ...buildArgs, ...opts.viteArgs ?? []], {
|
|
425
|
+
cwd: project.root
|
|
426
|
+
});
|
|
491
427
|
return { ok: true, command: "build", projectRoot: project.root };
|
|
492
428
|
}
|
|
493
429
|
|
|
@@ -522,7 +458,7 @@ async function runPackage(opts) {
|
|
|
522
458
|
}
|
|
523
459
|
return { ok: true, target, projectRoot: project.root, distDir };
|
|
524
460
|
}
|
|
525
|
-
|
|
461
|
+
assertNode18ForLxpack();
|
|
526
462
|
if (!opts.noBuild || !existsSync3(distDir)) {
|
|
527
463
|
await runBuild({ cwd: project.root, json: opts.json });
|
|
528
464
|
}
|
|
@@ -596,7 +532,10 @@ async function handleCommand(fn, logger, json) {
|
|
|
596
532
|
function createProgram(baseLogger = console) {
|
|
597
533
|
const program = new Command();
|
|
598
534
|
program.name("lessonkit").description("LessonKit CLI").version(version);
|
|
599
|
-
program.command("init").description("Initialize a LessonKit project from the Vite + React template").argument("[name]", "Project directory name").option("--here", "Initialize in the current directory").option("--skip-install", "Skip npm install").option(
|
|
535
|
+
program.command("init").description("Initialize a LessonKit project from the Vite + React template").argument("[name]", "Project directory name").option("--here", "Initialize in the current directory").option("--skip-install", "Skip npm install").option(
|
|
536
|
+
"--force",
|
|
537
|
+
"With --here, allow init when the directory is empty or contains only dotfiles"
|
|
538
|
+
).option("--json", "Emit structured JSON result").action(async (name, opts) => {
|
|
600
539
|
const logger = createLogger({ json: opts.json });
|
|
601
540
|
await handleCommand(
|
|
602
541
|
() => runInit({ name, here: opts.here, skipInstall: opts.skipInstall, force: opts.force, json: opts.json }, logger),
|
|
@@ -630,13 +569,27 @@ function createProgram(baseLogger = console) {
|
|
|
630
569
|
program.command("package").description("Build or package for web / LMS delivery").requiredOption("--target <target>", `Export target (${PACKAGE_TARGETS.join(", ")})`).option("--cwd <dir>", "Project root directory").option("--no-build", "Skip implicit Vite build for LMS targets").option("--out <path>", "Override output artifact path").option("--json", "Emit structured JSON result").action(async (opts) => {
|
|
631
570
|
const logger = createLogger({ json: opts.json });
|
|
632
571
|
await handleCommand(
|
|
633
|
-
() =>
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
572
|
+
async () => {
|
|
573
|
+
const result = await runPackage({
|
|
574
|
+
target: opts.target,
|
|
575
|
+
cwd: opts.cwd,
|
|
576
|
+
noBuild: opts.build === false,
|
|
577
|
+
out: opts.out,
|
|
578
|
+
json: opts.json
|
|
579
|
+
});
|
|
580
|
+
if (!opts.json && result.ok) {
|
|
581
|
+
if (result.target === "react-vite" && "distDir" in result) {
|
|
582
|
+
logger.log(`Built react-vite \u2192 ${result.distDir}`);
|
|
583
|
+
} else if ("outputPath" in result || "outputDir" in result) {
|
|
584
|
+
const dest = result.outputPath ?? result.outputDir;
|
|
585
|
+
const count = "fileCount" in result ? result.fileCount : void 0;
|
|
586
|
+
logger.log(
|
|
587
|
+
`Packaged ${result.target}${dest ? ` \u2192 ${dest}` : ""}${count != null ? ` (${count} files)` : ""}`
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
return result;
|
|
592
|
+
},
|
|
640
593
|
logger,
|
|
641
594
|
Boolean(opts.json)
|
|
642
595
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lessonkit/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "LessonKit CLI — init, dev, build, and package learning experiences.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"lint": "echo \"(no lint configured yet)\""
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@lessonkit/core": "0.
|
|
46
|
-
"@lessonkit/lxpack": "0.
|
|
45
|
+
"@lessonkit/core": "1.0.0",
|
|
46
|
+
"@lessonkit/lxpack": "1.0.0",
|
|
47
47
|
"commander": "^14.0.1"
|
|
48
48
|
},
|
|
49
49
|
"engines": {
|
|
@@ -1,19 +1,21 @@
|
|
|
1
|
-
# LessonKit
|
|
1
|
+
# LessonKit starter template
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[](https://github.com/eddiethedean/lessonkit/blob/main/LICENSE)
|
|
3
|
+
Vite + React scaffold for new LessonKit courses. Created by `lessonkit init`.
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
## Run
|
|
5
|
+
## Commands
|
|
9
6
|
|
|
10
7
|
```bash
|
|
11
8
|
npm install
|
|
12
|
-
npm run dev
|
|
9
|
+
npm run dev # lessonkit dev
|
|
10
|
+
npm run build # lessonkit build
|
|
11
|
+
npm run package:scorm12
|
|
13
12
|
```
|
|
14
13
|
|
|
15
|
-
##
|
|
14
|
+
## Files
|
|
15
|
+
|
|
16
|
+
- `src/App.tsx` — course UI (IDs match `lessonkit.json`)
|
|
17
|
+
- `lessonkit.json` — manifest for CLI and LXPack packaging
|
|
18
|
+
|
|
19
|
+
## Docs
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
- Copied by [`@lessonkit/cli`](https://lessonkit.readthedocs.io/en/latest/reference/cli.html) when you run `lessonkit init`.
|
|
19
|
-
- Package for an LMS with the [packaging guide](https://lessonkit.readthedocs.io/en/latest/guides/react-developers/packaging-and-cli.html).
|
|
21
|
+
[CLI reference](https://lessonkit.readthedocs.io/en/latest/reference/cli.html) · [React quickstart](https://lessonkit.readthedocs.io/en/latest/guides/react-developers/quickstart.html) · [Packaging guide](https://lessonkit.readthedocs.io/en/latest/guides/react-developers/packaging-and-cli.html)
|
|
@@ -13,16 +13,16 @@
|
|
|
13
13
|
"test:coverage": "vitest run --coverage --passWithNoTests=false"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@lessonkit/core": "^0.
|
|
17
|
-
"@lessonkit/react": "^0.
|
|
18
|
-
"@lessonkit/themes": "^0.
|
|
19
|
-
"@lessonkit/xapi": "^0.
|
|
16
|
+
"@lessonkit/core": "^1.0.0",
|
|
17
|
+
"@lessonkit/react": "^1.0.0",
|
|
18
|
+
"@lessonkit/themes": "^1.0.0",
|
|
19
|
+
"@lessonkit/xapi": "^1.0.0",
|
|
20
20
|
"react": "^18.3.1",
|
|
21
21
|
"react-dom": "^18.3.1"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@lessonkit/cli": "^0.
|
|
25
|
-
"@lessonkit/lxpack": "^0.
|
|
24
|
+
"@lessonkit/cli": "^1.0.0",
|
|
25
|
+
"@lessonkit/lxpack": "^1.0.0",
|
|
26
26
|
"@testing-library/react": "^16.3.0",
|
|
27
27
|
"@types/react": "^18.3.23",
|
|
28
28
|
"@types/react-dom": "^18.3.7",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
html,body{height:100%}body{margin:0;background:var(--lk-color-background);color:var(--lk-color-foreground);font-family:var(--lk-font-family);font-size:var(--lk-font-size-base);line-height:var(--lk-line-height-base)}.app-shell{margin:0 auto;padding:var(--lk-space-xl) var(--lk-space-lg);max-width:720px}section,article{border:1px solid var(--lk-color-border);border-radius:var(--lk-radius-lg);padding:var(--lk-space-lg);margin:var(--lk-space-md) 0;background:var(--lk-color-panel);box-shadow:var(--lk-shadow-md)}h1,h2{margin:0 0 var(--lk-space-sm);font-weight:var(--lk-font-weight-strong)}button{border:1px solid var(--lk-color-border);background:var(--lk-color-panel);color:var(--lk-color-foreground);border-radius:var(--lk-radius-md);padding:var(--lk-space-sm) var(--lk-space-md);font-weight:var(--lk-font-weight-strong);cursor:pointer}input[type=radio]{accent-color:var(--lk-color-primary)}label{display:block;margin:var(--lk-space-xs) 0}
|