@norskvideo/ctl-dev-kit 0.2.2 → 0.2.4

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.
@@ -14,6 +14,7 @@
14
14
  // validate(), INVARIANTS + parity + byte-snapshot + studio-load wired into
15
15
  // test:unit, and an image smoke tier.
16
16
  import { type GeneratedFile, type ShapeContext, type ShapeModule, studioPackage } from "./create-product.ts";
17
+ import { DOCS_SITE_GITIGNORE, DOCS_SITE_SCRIPTS, docsSiteFiles } from "./docs-site.ts";
17
18
  import {
18
19
  COMPONENTS_GITIGNORE,
19
20
  componentsFiles,
@@ -188,6 +189,32 @@ fresh image and checks the pins before relaunching.
188
189
  6. \`bun run docs:handover\` — once a daemon has this product added; fills the
189
190
  handover's generated block from the launch truth.
190
191
 
192
+ ## The docs site (\`website/\`)
193
+
194
+ A **standalone package**, deliberately outside the root \`workspaces\` array:
195
+ Astro's dependency tree has no business in the product's install. Two
196
+ consequences that bite in that order —
197
+
198
+ - \`bun install\` at the root does NOT install it. \`bun install --cwd website\`
199
+ does, once; then \`bun run docs:dev\` / \`bun run docs:site\`.
200
+ - \`website/src/starlight.ts\`, \`website/src/custom.css\`,
201
+ \`website/src/content.config.ts\`, \`website/src/components/\`,
202
+ \`website/src/fonts/\` and \`website/public/favicon.png\` are **copies of the
203
+ dev-kit's shared theme**, not this repo's files. \`bun run check:drift\`
204
+ fails on a hand-edited one; \`bun run sync:drift\` rewrites them. A styling or
205
+ layout change belongs in the dev-kit, where it reaches every product's manual.
206
+
207
+ This repo owns four things: the origin (\`website/src/lib/site.ts\`), the two
208
+ names and the sidebar (\`website/astro.config.mjs\`), and the content
209
+ (\`website/src/content/docs/\`). Keep the five top-level sections — Start here,
210
+ Guides, How-to, Concepts, Reference — they are what make several Norsk manuals
211
+ navigable by one reader. The placeholder pages say what belongs in each.
212
+
213
+ It is NOT the bundle the image serves at /docs (\`bun run docs:manual\`); both
214
+ channels exist on purpose, and the dev-kit's
215
+ \`node_modules/@norskvideo/ctl-dev-kit/conventions/docs-site.md\` says which to
216
+ reach for.
217
+
191
218
  ## Version pins
192
219
 
193
220
  Media + Studio container tags derive from **\`manifest.seed.json\`** (the
@@ -273,6 +300,9 @@ function rootPackageJson(ctx: ShapeContext): string {
273
300
  "docs:manual": `bun run node_modules/@norskvideo/ctl-dev-kit/doc-guide/prose-bundle.js --brand "${ctx.name}" --title "${ctx.name} — Documentation" --link "norsk.video=https://norsk.video/" --out docs/generated/bundle docs/handover.md docs/known-limitations.md`,
274
301
  "docs:handover": "bash scripts/docs-handover.sh",
275
302
  "docs:handover:check": "bash scripts/docs-handover.sh --check",
303
+ // The docs SITE (website/) — a standalone package, so its install is its
304
+ // own: `bun install --cwd website` before the first build.
305
+ ...DOCS_SITE_SCRIPTS,
276
306
  "sync:drift": "bun run node_modules/@norskvideo/ctl-dev-kit/conventions/sync-drift.ts",
277
307
  // codegen first: components/_gen/types.ts is gitignored build output,
278
308
  // so on a clean checkout tsc has nothing to resolve against without it.
@@ -1880,6 +1910,7 @@ ${optional}- \`backend/\` — the control-plane service (manifest, product-templ
1880
1910
  - \`examples/\` — starter configs; \`examples/default/input.json\` is the default template.
1881
1911
  - \`tests/\` — unit, image and demo tiers; \`tests/demo.spec.ts\` is the customer journey.
1882
1912
  - \`docs/\` — reader-facing documents; \`docs/README.md\` is the index.
1913
+ - \`website/\` — the public docs site (Astro + Starlight on the shared Norsk theme).
1883
1914
  - \`deployment/\` — the image build wrapper and the iterate loop.
1884
1915
 
1885
1916
  ## First run
@@ -1903,11 +1934,20 @@ norsk-ctl and the pinned Norsk SDK:
1903
1934
 
1904
1935
  ## Documentation
1905
1936
 
1906
- \`bun run docs:manual\` builds the docs bundle from \`docs/handover.md\` and
1907
- \`docs/known-limitations.md\`; the image build runs it, so every image serves
1908
- them at /docs (linked from the product's page in norsk-ctl). \`bun run
1909
- docs:handover\` regenerates the handover's generated block from a daemon that
1910
- has this product added; CI runs its \`--check\`.
1937
+ Two channels, deliberately:
1938
+
1939
+ - **The bundle the image serves.** \`bun run docs:manual\` builds it from
1940
+ \`docs/handover.md\` and \`docs/known-limitations.md\`; the image build runs
1941
+ it, so every image serves them at /docs at the version that is running
1942
+ (linked from the product's page in norsk-ctl). \`bun run docs:handover\`
1943
+ regenerates the handover's generated block from a daemon that has this
1944
+ product added; CI runs its \`--check\`.
1945
+ - **The docs site**, \`website/\` — the public manual on the shared Norsk theme,
1946
+ five sections deep, faster-moving and readable without the product in hand.
1947
+ \`bun install --cwd website\` once, then \`bun run docs:dev\` /
1948
+ \`bun run docs:site\`. \`website/README.md\` says what is the site's and what
1949
+ is the theme's; the dev-kit's
1950
+ \`node_modules/@norskvideo/ctl-dev-kit/conventions/docs-site.md\` is the contract.
1911
1951
 
1912
1952
  \`CLAUDE.md\` has the conventions and the traps.
1913
1953
  `;
@@ -1924,6 +1964,10 @@ served at /docs at the version that is running.
1924
1964
  | \`docs/handover.md\` | the platform team deploying this product | Deploying from the exported bundle without norsk-ctl: every mount, environment and injected setting |
1925
1965
  | \`docs/known-limitations.md\` | operators | What the product does not do yet, with the consequence and the workaround |
1926
1966
 
1967
+ The **docs site** is a separate channel and lives in \`website/\`: the public
1968
+ manual, on the shared Norsk theme, not built from these files. See
1969
+ \`website/README.md\`.
1970
+
1927
1971
  \`bun run docs:manual\` builds the bundle from these files. The tables in the
1928
1972
  handover are generated by norsk-ctl (\`bun run docs:handover\`) — regenerate
1929
1973
  them, never edit them.
@@ -2012,6 +2056,7 @@ function gitignoreTail(ctx: ShapeContext): string {
2012
2056
  const blocks = ["# Repo-specific entries go below (the block above is drift-gated verbatim).\n"];
2013
2057
  if (ctx.features.has("components")) blocks.push(COMPONENTS_GITIGNORE);
2014
2058
  if (ctx.features.has("dashboard")) blocks.push(DASHBOARD_GITIGNORE);
2059
+ blocks.push(DOCS_SITE_GITIGNORE);
2015
2060
  return blocks.join("\n");
2016
2061
  }
2017
2062
 
@@ -2033,6 +2078,7 @@ export const turnkey: ShapeModule = {
2033
2078
  { path: "deployment/iterate.sh", content: iterateSh(ctx), executable: true },
2034
2079
  { path: "scripts/demo", content: ctx.canon.demoShim, executable: true },
2035
2080
  { path: "README.md", content: readmeMd(ctx) },
2081
+ ...docsSiteFiles(ctx),
2036
2082
  { path: "docs/README.md", content: docsReadmeMd(ctx) },
2037
2083
  { path: "docs/handover.md", content: handoverMd(ctx) },
2038
2084
  { path: "docs/known-limitations.md", content: KNOWN_LIMITATIONS_MD },
@@ -0,0 +1,106 @@
1
+ # `docs-theme/` — the shared Norsk docs site theme
2
+
3
+ The parts of an Astro + Starlight docs site that must **never** differ between
4
+ Norsk products: the theme, the three component overrides, the content schema,
5
+ and the Astro/Starlight configuration itself. A product's site owns only what
6
+ genuinely differs — its content, its sidebar, its origin and its two names.
7
+
8
+ custom.css the palette + self-hosted Geist faces
9
+ fonts/ the woff2 those @font-face rules point at
10
+ public/favicon.png the favicon `starlight.ts` names (copied to the site ROOT, not src/)
11
+ components/Footer.astro the "captured against" line on a generated guide
12
+ components/Sidebar.astro the "Built against" versions footer
13
+ components/SiteTitle.astro the masthead wordmark, decoupled from the tab title
14
+ content.config.ts the docs collection, incl. `capturedAgainst`
15
+ starlight.ts `norskDocsSite()` — the whole Astro config bar the site's own parts
16
+ sync.ts the writer and the drift gate
17
+
18
+ ## Consuming it
19
+
20
+ A docs site's `astro.config.mjs` becomes its sidebar plus five lines:
21
+
22
+ ```js
23
+ import { defineConfig } from "astro/config";
24
+ import { isPublicBuild, norskDocsSite } from "./src/starlight.ts";
25
+
26
+ export default defineConfig(
27
+ norskDocsSite({
28
+ origin: "https://ctl.norsk.video",
29
+ tabTitle: "Norsk", // browser tab suffix — tabs truncate
30
+ wordmark: "norsk-ctl", // masthead — names the tool in hand
31
+ buildVersions: [{ label: "norsk-ctl", value: version }],
32
+ sidebar: [...],
33
+ }),
34
+ );
35
+ ```
36
+
37
+ `norskDocsSite()` supplies the two-build shape (`dist-embedded` at base `/docs`
38
+ for the copy baked into a product binary, `dist-public` at base `/` for the
39
+ site on its own domain, selected by `ASTRO_BUILD=public`), the /docs-prefix
40
+ strip that makes the public build's absolute links work, the mermaid pipeline,
41
+ the favicon convention, and the component + CSS wiring.
42
+
43
+ ## Why the files are copied into the site, not imported from here
44
+
45
+ A docs site is a **standalone package** — its own `bun.lock`, its own
46
+ `node_modules`, outside the monorepo's workspaces, because Astro's dependency
47
+ tree has no business in a product install. It therefore cannot depend on this
48
+ package the way every other dev-kit consumer does. That is measured, not
49
+ assumed: adding `"@norskvideo/ctl-dev-kit": "file:../../dev-kit"` to norsk-ctl's
50
+ website and installing fails with
51
+
52
+ error: @norskvideo/ctl-sdk@workspace:* failed to resolve
53
+
54
+ because bun must then resolve this package's own `workspace:*` dependencies
55
+ from a directory that is not in a workspace.
56
+
57
+ So the theme is a **forced copy**, handled the way this package already handles
58
+ every forced copy — canonical bytes here, a writer, and a gate:
59
+
60
+ ```
61
+ bun node_modules/@norskvideo/ctl-dev-kit/docs-theme/sync.ts sync <site>
62
+ bun node_modules/@norskvideo/ctl-dev-kit/docs-theme/sync.ts check <site>
63
+ ```
64
+
65
+ `<site>` is the directory holding `astro.config.mjs`; the copies land in its
66
+ `src/`, so this directory mirrors a site's `src/` and the path map is an
67
+ identity. The one exception is `public/`, which mirrors the site's `public/` —
68
+ Astro serves it verbatim from the site root, and `starlight.ts` already dictates
69
+ the favicon's path (`favicon: "/favicon.png"`), so its bytes belong here too.
70
+
71
+ A site runs `check` in its own test suite (norsk-ctl:
72
+ `website/tests/theme-drift.test.ts`, which `bun run check` runs), so a
73
+ hand-edited copy fails before it is published. A PRODUCT repo has no such suite;
74
+ there, `conventions/check-drift.ts` runs the same check as part of
75
+ `bun run check:drift`, and `sync-drift.ts` rewrites the copies as part of
76
+ `bun run sync:drift` — see below.
77
+
78
+ **Keeping the copies at those exact paths is load-bearing.** Astro derives a
79
+ component's scoped-style class from its path, so moving `Footer.astro` rewrites
80
+ a class attribute on every page of the site.
81
+
82
+ ## The two gates are one gate
83
+
84
+ `conventions/check-drift.ts` gains a clause when — and only when — the repo has a
85
+ site: `hasDocsSite()` tests for `website/astro.config.mjs`, and
86
+ `docsThemeProblems()` routes this directory's `checkTheme` into the same report as
87
+ `biome.json` and the shared workflows. `conventions/sync-drift.ts` is the writer
88
+ half, so `sync-dev-kit.yml` rolls a theme change out to every product hands-free
89
+ like any other convention. The site directory is `website/` across the fleet
90
+ (`DOCS_SITE_DIR`).
91
+
92
+ A site inside THIS monorepo still runs `sync.ts check` from its own test suite,
93
+ because it has one and because `bun run check` is what a monorepo contributor
94
+ runs; a product repo has no second suite, which is why the clause exists.
95
+
96
+ ## `create-product` renders a site
97
+
98
+ Every generated repo comes out with a `website/` on this theme, five sections
99
+ deep, that builds — `create-product/docs-site.ts`. Two notes on how:
100
+
101
+ - the theme files are written by **this directory's `syncTheme`**, called from
102
+ `createProduct` after the plan is written, not re-rendered by the generator: one
103
+ copier, one gate, and the woff2 faces are not strings;
104
+ - what a product repo needs in order to have a site at all — the five sections,
105
+ the two builds, publishing, and how the site relates to the illustrated manual
106
+ the image serves — is `conventions/docs-site.md`.
@@ -0,0 +1,40 @@
1
+ ---
2
+ /**
3
+ * Starlight's footer plus the "captured against" line on a generated guide.
4
+ *
5
+ * The test-driven guides every Norsk docs site carries are captured by a
6
+ * Playwright run against a real stack and republished from a cache, so a page
7
+ * can be older than the product it documents — some guides ride every release,
8
+ * the expensive ones a weekly run. The shared renderer writes what the run knew
9
+ * into the page's front matter (`capturedAgainst`, see content.config.ts) and
10
+ * this says it on the page: "Captured 2026-09-17 against norsk-mgr 0.1.0-…". A
11
+ * page without the field (hand-written, or captured before the field existed)
12
+ * shows nothing extra.
13
+ */
14
+ import Default from "@astrojs/starlight/components/Footer.astro";
15
+
16
+ const against = Astro.locals.starlightRoute.entry.data.capturedAgainst;
17
+ const parts: string[] = [];
18
+ if (against?.norskMgr) parts.push(`norsk-mgr ${against.norskMgr}`);
19
+ if (against?.norsk) parts.push(`Norsk Manager ${against.norsk}`);
20
+ const line = against ? `Captured ${against.when}${parts.length ? ` against ${parts.join(", ")}` : ""}` : null;
21
+ ---
22
+
23
+ {
24
+ line && (
25
+ <p class="captured-against">
26
+ {line} — every command and screenshot on this page is from that run.
27
+ </p>
28
+ )
29
+ }
30
+ <Default><slot /></Default>
31
+
32
+ <style>
33
+ .captured-against {
34
+ margin-top: 3rem;
35
+ padding-top: 1rem;
36
+ border-top: 1px solid var(--sl-color-hairline-light);
37
+ font-size: var(--sl-text-sm);
38
+ color: var(--sl-color-gray-3);
39
+ }
40
+ </style>
@@ -0,0 +1,73 @@
1
+ ---
2
+ /**
3
+ * Starlight's sidebar plus a footer stating what this docs build was built
4
+ * against. Every product site wants this line and none of them should word it
5
+ * differently, so the markup is shared and only the rows differ: the site
6
+ * passes them to `norskDocsSite({ buildVersions })`, which injects them as
7
+ * `__BUILD_VERSIONS__` (see starlight.ts on why a vite define rather than an
8
+ * import). They are read at build time from the product's own files, so they
9
+ * cannot drift from what ships the way a hand-written literal in MDX did.
10
+ */
11
+ import Default from "@astrojs/starlight/components/Sidebar.astro";
12
+
13
+ // Injected by vite.define in starlight.ts — see the note there on why this is
14
+ // not resolved from within the component.
15
+ declare const __BUILD_VERSIONS__: Array<{ label: string; value: string; splitOnLastColon?: boolean }>;
16
+ // The repo/tag split keeps a long image pin wrapping sensibly in a narrow
17
+ // sidebar: `norskvideo/x:tag` breaks after the colon rather than mid-word.
18
+ const rows = __BUILD_VERSIONS__.map((v) => ({
19
+ label: v.label,
20
+ parts: v.splitOnLastColon ? v.value.split(/:(?=[^:]*$)/) : [v.value],
21
+ }));
22
+ ---
23
+
24
+ <Default><slot /></Default>
25
+
26
+ <div class="build-versions">
27
+ <p class="build-versions__heading">Built against</p>
28
+ <dl>
29
+ {/* One line per row on purpose: whitespace between expressions inside a
30
+ template expression survives into the emitted <code>, and a stray
31
+ space in the middle of an image pin is a rendering change. */}
32
+ {rows.map((row) => (
33
+ <Fragment><dt>{row.label}</dt><dd><code>{row.parts[0]}{row.parts.length > 1 && <Fragment>:<wbr />{row.parts[1]}</Fragment>}</code></dd></Fragment>
34
+ ))}
35
+ </dl>
36
+ </div>
37
+
38
+ <style>
39
+ .build-versions {
40
+ margin-top: 1.5rem;
41
+ padding-top: 1rem;
42
+ border-top: 1px solid var(--sl-color-hairline-light);
43
+ font-size: var(--sl-text-xs);
44
+ color: var(--sl-color-gray-3);
45
+ }
46
+
47
+ .build-versions__heading {
48
+ margin: 0 0 0.5rem;
49
+ font-weight: 600;
50
+ text-transform: uppercase;
51
+ letter-spacing: 0.05em;
52
+ color: var(--sl-color-gray-2);
53
+ }
54
+
55
+ .build-versions dl {
56
+ margin: 0;
57
+ }
58
+
59
+ .build-versions dt {
60
+ margin-top: 0.5rem;
61
+ }
62
+
63
+ .build-versions dd {
64
+ margin: 0.125rem 0 0;
65
+ }
66
+
67
+ .build-versions code {
68
+ font-size: var(--sl-text-xs);
69
+ overflow-wrap: anywhere;
70
+ background: none;
71
+ padding: 0;
72
+ }
73
+ </style>
@@ -0,0 +1,52 @@
1
+ ---
2
+ /**
3
+ * The masthead wordmark, top-left of every page.
4
+ *
5
+ * Exists only to say something different from `starlight.title`. Starlight
6
+ * drives the masthead and the browser-tab suffix from that single value, and
7
+ * the two want different things: the tab wants the short family name because
8
+ * tabs truncate, the masthead wants to name the tool the reader is holding.
9
+ * `norskDocsSite({ tabTitle, wordmark })` is where a site says both, and the
10
+ * wordmark reaches here as `__SITE_WORDMARK__`.
11
+ *
12
+ * The wordmark is the binary name, deliberately. Everything under it — the
13
+ * sidebar, the commands, the paths — says the binary's name, and a masthead
14
+ * reading anything else would be a second name for one thing: it splits
15
+ * searches and makes the reader learn a mapping. How to SAY it is a real
16
+ * question, and it is answered in prose on the index page rather than by
17
+ * renaming the product.
18
+ *
19
+ * Not a wrapper around Starlight's own SiteTitle, because the text it renders
20
+ * comes from `Astro.locals.starlightRoute.siteTitle` and mutating that would
21
+ * also move the <title> — the exact coupling this override is here to break.
22
+ * Reimplemented instead, which is cheap: the upstream component is a link, a
23
+ * span and some logo handling, and no logo is configured here.
24
+ */
25
+ declare const __SITE_WORDMARK__: string;
26
+
27
+ const { siteTitleHref } = Astro.locals.starlightRoute;
28
+ ---
29
+
30
+ <a href={siteTitleHref} class="site-title sl-flex">
31
+ <span translate="no">{__SITE_WORDMARK__}</span>
32
+ </a>
33
+
34
+ <style>
35
+ /* Mirrors upstream SiteTitle.astro. Same layer, so a Starlight upgrade that
36
+ restyles the masthead still applies here. */
37
+ @layer starlight.core {
38
+ .site-title {
39
+ align-items: center;
40
+ gap: var(--sl-nav-gap);
41
+ font-size: var(--sl-text-h4);
42
+ font-weight: 600;
43
+ color: var(--sl-color-text-accent);
44
+ text-decoration: none;
45
+ white-space: nowrap;
46
+ min-width: 0;
47
+ }
48
+ span {
49
+ overflow: hidden;
50
+ }
51
+ }
52
+ </style>
@@ -0,0 +1,25 @@
1
+ import { defineCollection, z } from "astro:content";
2
+ import { docsLoader } from "@astrojs/starlight/loaders";
3
+ import { docsSchema } from "@astrojs/starlight/schema";
4
+
5
+ // The docs content collection every Norsk product site declares. Shared so a
6
+ // page authored for one site is loadable by another and the guide renderer has
7
+ // one schema to satisfy.
8
+ //
9
+ // `capturedAgainst` is what the test-driven guides' renderer writes into a
10
+ // generated page's front matter (`renderMarkdown` in the shared guide
11
+ // renderer): the versions the page was captured against and the capture date.
12
+ // Footer.astro renders it; a hand-written page simply has none. Strings, not
13
+ // dates — the renderer quotes them for exactly that reason.
14
+ export const capturedAgainstSchema = z.object({
15
+ norsk: z.string().optional(),
16
+ norskMgr: z.string().optional(),
17
+ when: z.string(),
18
+ });
19
+
20
+ export const collections = {
21
+ docs: defineCollection({
22
+ loader: docsLoader(),
23
+ schema: docsSchema({ extend: z.object({ capturedAgainst: capturedAgainstSchema.optional() }) }),
24
+ }),
25
+ };
@@ -0,0 +1,108 @@
1
+ /* Match the norsk-ctl frontend's Tailwind zinc palette. */
2
+
3
+ /*
4
+ * Self-hosted Geist Sans + Mono, byte-identical to @norskvideo/ctl-sdk/base.css so the
5
+ * docs chrome renders in the same face as the app and the guide screenshots.
6
+ * Standalone package — it can't @import the SDK's base.css, so it vendors the
7
+ * woff2 (see src/fonts/README obligations). Relative url() → Vite fingerprints
8
+ * them into the build; never a CDN.
9
+ */
10
+ @font-face {
11
+ font-family: "Geist";
12
+ font-style: normal;
13
+ font-weight: 100 900; /* variable range → no faux bold */
14
+ font-display: swap;
15
+ src: url("./fonts/Geist.woff2") format("woff2-variations"),
16
+ url("./fonts/Geist.woff2") format("woff2");
17
+ }
18
+
19
+ @font-face {
20
+ font-family: "Geist Mono";
21
+ font-style: normal;
22
+ font-weight: 100 900;
23
+ font-display: swap;
24
+ src: url("./fonts/GeistMono.woff2") format("woff2-variations"),
25
+ url("./fonts/GeistMono.woff2") format("woff2");
26
+ }
27
+
28
+ :root {
29
+ --sl-font: "Geist", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
30
+ --sl-font-mono: "Geist Mono", ui-monospace, "SF Mono", "Cascadia Code", "Fira Code", Menlo, Consolas, monospace;
31
+ }
32
+
33
+ /* Dark theme — primary. Tailwind zinc + indigo accent matching the React app. */
34
+ :root[data-theme="dark"] {
35
+ /* Backgrounds */
36
+ --sl-color-bg: #18181b; /* zinc-900 */
37
+ --sl-color-bg-nav: #1f1f23; /* zinc-850 */
38
+ --sl-color-bg-sidebar: #1f1f23; /* zinc-850 */
39
+ --sl-color-bg-inline-code: #27272a; /* zinc-800 */
40
+ --sl-color-bg-accent: #818cf8; /* indigo-400 */
41
+
42
+ /* Text */
43
+ --sl-color-text: #e4e4e7; /* zinc-200 */
44
+ --sl-color-text-accent: #a5b4fc; /* indigo-300 */
45
+ --sl-color-text-invert: #18181b; /* zinc-900 */
46
+
47
+ /* Gray ramp */
48
+ --sl-color-white: #fafafa; /* zinc-50 */
49
+ --sl-color-gray-1: #e4e4e7; /* zinc-200 */
50
+ --sl-color-gray-2: #a1a1aa; /* zinc-400 */
51
+ --sl-color-gray-3: #71717a; /* zinc-500 */
52
+ --sl-color-gray-4: #52525b; /* zinc-600 */
53
+ --sl-color-gray-5: #3f3f46; /* zinc-700 */
54
+ --sl-color-gray-6: #27272a; /* zinc-800 */
55
+ --sl-color-black: #18181b; /* zinc-900 */
56
+
57
+ /* Accent */
58
+ --sl-color-accent-low: #1e1b4b; /* indigo-950 */
59
+ --sl-color-accent: #6366f1; /* indigo-500 */
60
+ --sl-color-accent-high: #c7d2fe; /* indigo-200 */
61
+
62
+ /* Hairlines */
63
+ --sl-color-hairline-light: #27272a; /* zinc-800 */
64
+ --sl-color-hairline: #3f3f46; /* zinc-700 */
65
+ --sl-color-hairline-shade: #52525b; /* zinc-600 */
66
+ }
67
+
68
+ /* Light theme — reasonable defaults, zinc-tinted. */
69
+ :root {
70
+ --sl-color-accent-low: #eef2ff; /* indigo-50 */
71
+ --sl-color-accent: #4f46e5; /* indigo-600 */
72
+ --sl-color-accent-high: #312e81; /* indigo-900 */
73
+ --sl-color-hairline-light: #e4e4e7; /* zinc-200 */
74
+ --sl-color-hairline: #d4d4d8; /* zinc-300 */
75
+ }
76
+
77
+ /* Scrollbars — mirror frontend. */
78
+ ::-webkit-scrollbar {
79
+ width: 6px;
80
+ height: 6px;
81
+ }
82
+ ::-webkit-scrollbar-track {
83
+ background: #18181b;
84
+ }
85
+ ::-webkit-scrollbar-thumb {
86
+ background: #3f3f46;
87
+ border-radius: 3px;
88
+ }
89
+
90
+ /* Nav title weight + tracking to match the app brand. */
91
+ .site-title {
92
+ font-weight: 700 !important;
93
+ letter-spacing: -0.02em;
94
+ }
95
+
96
+ /* Tighter heading tracking. */
97
+ h1 { letter-spacing: -0.035em; }
98
+ h2 { letter-spacing: -0.025em; }
99
+ h3 { letter-spacing: -0.015em; }
100
+
101
+ /* Cards — rounded, accent on hover. */
102
+ .sl-link-card,
103
+ .card {
104
+ border-radius: 12px;
105
+ }
106
+ .sl-link-card:hover {
107
+ border-color: var(--sl-color-accent);
108
+ }
@@ -0,0 +1,92 @@
1
+ Copyright (c) 2023 Vercel, in collaboration with basement.studio
2
+
3
+ This Font Software is licensed under the SIL Open Font License, Version 1.1.
4
+ This license is copied below, and is also available with a FAQ at:
5
+ http://scripts.sil.org/OFL
6
+
7
+ -----------------------------------------------------------
8
+ SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
9
+ -----------------------------------------------------------
10
+
11
+ PREAMBLE
12
+ The goals of the Open Font License (OFL) are to stimulate worldwide
13
+ development of collaborative font projects, to support the font creation
14
+ efforts of academic and linguistic communities, and to provide a free and
15
+ open framework in which fonts may be shared and improved in partnership
16
+ with others.
17
+
18
+ The OFL allows the licensed fonts to be used, studied, modified and
19
+ redistributed freely as long as they are not sold by themselves. The
20
+ fonts, including any derivative works, can be bundled, embedded,
21
+ redistributed and/or sold with any software provided that any reserved
22
+ names are not used by derivative works. The fonts and derivatives,
23
+ however, cannot be released under any other type of license. The
24
+ requirement for fonts to remain under this license does not apply
25
+ to any document created using the fonts or their derivatives.
26
+
27
+ DEFINITIONS
28
+ "Font Software" refers to the set of files released by the Copyright
29
+ Holder(s) under this license and clearly marked as such. This may
30
+ include source files, build scripts and documentation.
31
+
32
+ "Reserved Font Name" refers to any names specified as such after the
33
+ copyright statement(s).
34
+
35
+ "Original Version" refers to the collection of Font Software components as
36
+ distributed by the Copyright Holder(s).
37
+
38
+ "Modified Version" refers to any derivative made by adding to, deleting,
39
+ or substituting -- in part or in whole -- any of the components of the
40
+ Original Version, by changing formats or by porting the Font Software to a
41
+ new environment.
42
+
43
+ "Author" refers to any designer, engineer, programmer, technical
44
+ writer or other person who contributed to the Font Software.
45
+
46
+ PERMISSION AND CONDITIONS
47
+ Permission is hereby granted, free of charge, to any person obtaining
48
+ a copy of the Font Software, to use, study, copy, merge, embed, modify,
49
+ redistribute, and sell modified and unmodified copies of the Font
50
+ Software, subject to the following conditions:
51
+
52
+ 1) Neither the Font Software nor any of its individual components,
53
+ in Original or Modified Versions, may be sold by itself.
54
+
55
+ 2) Original or Modified Versions of the Font Software may be bundled,
56
+ redistributed and/or sold with any software, provided that each copy
57
+ contains the above copyright notice and this license. These can be
58
+ included either as stand-alone text files, human-readable headers or
59
+ in the appropriate machine-readable metadata fields within text or
60
+ binary files as long as those fields can be easily viewed by the user.
61
+
62
+ 3) No Modified Version of the Font Software may use the Reserved Font
63
+ Name(s) unless explicit written permission is granted by the corresponding
64
+ Copyright Holder. This restriction only applies to the primary font name as
65
+ presented to the users.
66
+
67
+ 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
68
+ Software shall not be used to promote, endorse or advertise any
69
+ Modified Version, except to acknowledge the contribution(s) of the
70
+ Copyright Holder(s) and the Author(s) or with their explicit written
71
+ permission.
72
+
73
+ 5) The Font Software, modified or unmodified, in part or in whole,
74
+ must be distributed entirely under this license, and must not be
75
+ distributed under any other license. The requirement for fonts to
76
+ remain under this license does not apply to any document created
77
+ using the Font Software.
78
+
79
+ TERMINATION
80
+ This license becomes null and void if any of the above conditions are
81
+ not met.
82
+
83
+ DISCLAIMER
84
+ THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
85
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
86
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
87
+ OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
88
+ COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
89
+ INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
90
+ DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
91
+ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
92
+ OTHER DEALINGS IN THE FONT SOFTWARE.
Binary file
Binary file
Binary file