@omniharmonic/openspace-cli 0.0.0-stage → 0.1.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/LICENSE +26 -0
- package/README.md +74 -2
- package/dist/agents-md-2DTXWUZQ.js +8 -0
- package/dist/agents-md-2DTXWUZQ.js.map +1 -0
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +11 -0
- package/dist/bin.js.map +1 -0
- package/dist/chunk-3F6TAUAT.js +41167 -0
- package/dist/chunk-3F6TAUAT.js.map +1 -0
- package/dist/chunk-LTJEM3DN.js +17161 -0
- package/dist/chunk-LTJEM3DN.js.map +1 -0
- package/dist/index.d.ts +83 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/starter/site/.github/workflows/publish.yml +38 -0
- package/dist/starter/site/AGENTS.md +392 -0
- package/dist/starter/site/astro.config.mjs +8 -0
- package/dist/starter/site/package.json +21 -0
- package/dist/starter/site/public/.gitkeep +0 -0
- package/dist/starter/site/src/pages/[...slug].astro +10 -0
- package/dist/starter/site/tsconfig.json +5 -0
- package/dist/starter/variants/cloudflare/.github/openspace/gated-sync.mjs +121 -0
- package/dist/starter/variants/cloudflare/.github/workflows/publish.yml +70 -0
- package/dist/starter/variants/cloudflare/astro.config.mjs +11 -0
- package/dist/starter/variants/cloudflare/package.json +23 -0
- package/dist/starter/variants/cloudflare/wrangler.jsonc +13 -0
- package/dist/templates/atelier/content/collections/projects/_collection.json +13 -0
- package/dist/templates/atelier/content/collections/projects/cordillera.mdoc +32 -0
- package/dist/templates/atelier/content/collections/projects/field-studies.mdoc +32 -0
- package/dist/templates/atelier/content/collections/projects/salt-and-glass.mdoc +32 -0
- package/dist/templates/atelier/content/collections/projects/white-city.mdoc +32 -0
- package/dist/templates/atelier/content/pages/about.json +180 -0
- package/dist/templates/atelier/content/pages/commissions.json +307 -0
- package/dist/templates/atelier/content/pages/contact.json +117 -0
- package/dist/templates/atelier/content/pages/home.json +524 -0
- package/dist/templates/atelier/content/pages/work.json +332 -0
- package/dist/templates/atelier/content/site.json +190 -0
- package/dist/templates/atelier/content/styles.json +25 -0
- package/dist/templates/atelier/media/LICENSES.json +218 -0
- package/dist/templates/atelier/openspace.json +21 -0
- package/dist/templates/atelier/template.json +115 -0
- package/dist/templates/commons/content/collections/gatherings/_collection.json +13 -0
- package/dist/templates/commons/content/collections/gatherings/grant-writing-workshop.mdoc +17 -0
- package/dist/templates/commons/content/collections/gatherings/seed-swap.mdoc +16 -0
- package/dist/templates/commons/content/collections/gatherings/winter-general-assembly.mdoc +17 -0
- package/dist/templates/commons/content/pages/about.json +227 -0
- package/dist/templates/commons/content/pages/home.json +540 -0
- package/dist/templates/commons/content/pages/join.json +304 -0
- package/dist/templates/commons/content/pages/members.json +499 -0
- package/dist/templates/commons/content/site.json +306 -0
- package/dist/templates/commons/content/styles.json +27 -0
- package/dist/templates/commons/media/LICENSES.json +108 -0
- package/dist/templates/commons/openspace.json +21 -0
- package/dist/templates/commons/template.json +113 -0
- package/dist/templates/encore/content/collections/sessions/_collection.json +19 -0
- package/dist/templates/encore/content/collections/sessions/session-10-headphones-on.mdoc +16 -0
- package/dist/templates/encore/content/collections/sessions/session-11-neon-tide.mdoc +16 -0
- package/dist/templates/encore/content/collections/sessions/session-12-slow-static.mdoc +16 -0
- package/dist/templates/encore/content/collections/tour/_collection.json +13 -0
- package/dist/templates/encore/content/collections/tour/brooklyn-elsewhere.mdoc +18 -0
- package/dist/templates/encore/content/collections/tour/chicago-thalia-hall.mdoc +18 -0
- package/dist/templates/encore/content/collections/tour/london-electric-ballroom.mdoc +18 -0
- package/dist/templates/encore/content/collections/tour/minneapolis-first-avenue.mdoc +18 -0
- package/dist/templates/encore/content/collections/tour/toronto-danforth.mdoc +18 -0
- package/dist/templates/encore/content/pages/about.json +251 -0
- package/dist/templates/encore/content/pages/contact.json +122 -0
- package/dist/templates/encore/content/pages/home.json +363 -0
- package/dist/templates/encore/content/pages/music.json +297 -0
- package/dist/templates/encore/content/site.json +293 -0
- package/dist/templates/encore/content/styles.json +17 -0
- package/dist/templates/encore/media/LICENSES.json +97 -0
- package/dist/templates/encore/openspace.json +21 -0
- package/dist/templates/encore/template.json +103 -0
- package/dist/templates/field-notes/content/collections/essays/_collection.json +31 -0
- package/dist/templates/field-notes/content/collections/essays/a-year-of-apples.mdoc +25 -0
- package/dist/templates/field-notes/content/collections/essays/learning-a-field.mdoc +32 -0
- package/dist/templates/field-notes/content/collections/essays/small-weather.mdoc +25 -0
- package/dist/templates/field-notes/content/collections/essays/the-wall-builders.mdoc +29 -0
- package/dist/templates/field-notes/content/pages/about.json +236 -0
- package/dist/templates/field-notes/content/pages/contact.json +116 -0
- package/dist/templates/field-notes/content/pages/home.json +428 -0
- package/dist/templates/field-notes/content/pages/subscribe.json +273 -0
- package/dist/templates/field-notes/content/site.json +218 -0
- package/dist/templates/field-notes/content/styles.json +27 -0
- package/dist/templates/field-notes/media/LICENSES.json +119 -0
- package/dist/templates/field-notes/openspace.json +21 -0
- package/dist/templates/field-notes/template.json +112 -0
- package/dist/templates/harbor/content/collections/events/_collection.json +13 -0
- package/dist/templates/harbor/content/collections/events/coat-drive-sorting-day.mdoc +17 -0
- package/dist/templates/harbor/content/collections/events/safe-at-home-build-day.mdoc +17 -0
- package/dist/templates/harbor/content/collections/events/spring-community-supper.mdoc +17 -0
- package/dist/templates/harbor/content/collections/events/winter-coat-giveaway.mdoc +16 -0
- package/dist/templates/harbor/content/collections/news/2026-08-28-back-to-school-at-homework-harbor.mdoc +17 -0
- package/dist/templates/harbor/content/collections/news/2026-09-15-meet-the-repair-crew.mdoc +21 -0
- package/dist/templates/harbor/content/collections/news/2026-10-08-the-2025-annual-report.mdoc +25 -0
- package/dist/templates/harbor/content/collections/news/_collection.json +34 -0
- package/dist/templates/harbor/content/pages/about.json +366 -0
- package/dist/templates/harbor/content/pages/contact.json +233 -0
- package/dist/templates/harbor/content/pages/get-involved.json +425 -0
- package/dist/templates/harbor/content/pages/home.json +524 -0
- package/dist/templates/harbor/content/pages/programs.json +611 -0
- package/dist/templates/harbor/content/site.json +377 -0
- package/dist/templates/harbor/content/styles.json +25 -0
- package/dist/templates/harbor/media/LICENSES.json +141 -0
- package/dist/templates/harbor/openspace.json +21 -0
- package/dist/templates/harbor/template.json +115 -0
- package/dist/templates/hearth/content/pages/about.json +246 -0
- package/dist/templates/hearth/content/pages/home.json +937 -0
- package/dist/templates/hearth/content/pages/menu.json +396 -0
- package/dist/templates/hearth/content/pages/private-dining.json +288 -0
- package/dist/templates/hearth/content/pages/visit.json +257 -0
- package/dist/templates/hearth/content/site.json +214 -0
- package/dist/templates/hearth/content/styles.json +25 -0
- package/dist/templates/hearth/media/LICENSES.json +196 -0
- package/dist/templates/hearth/openspace.json +21 -0
- package/dist/templates/hearth/template.json +116 -0
- package/dist/templates/kiln/content/collections/classes/_collection.json +13 -0
- package/dist/templates/kiln/content/collections/classes/glaze-day-march.mdoc +17 -0
- package/dist/templates/kiln/content/collections/classes/hand-building-february.mdoc +17 -0
- package/dist/templates/kiln/content/collections/classes/spring-studio-sale.mdoc +15 -0
- package/dist/templates/kiln/content/collections/classes/wheel-weekend-january.mdoc +17 -0
- package/dist/templates/kiln/content/collections/shop/_collection.json +14 -0
- package/dist/templates/kiln/content/collections/shop/ash-bud-vases.mdoc +18 -0
- package/dist/templates/kiln/content/collections/shop/harbor-planter.mdoc +18 -0
- package/dist/templates/kiln/content/collections/shop/morning-cup-pair.mdoc +24 -0
- package/dist/templates/kiln/content/collections/shop/sea-glass-planter.mdoc +18 -0
- package/dist/templates/kiln/content/collections/shop/tide-vase.mdoc +23 -0
- package/dist/templates/kiln/content/collections/shop/walnut-step-stool.mdoc +18 -0
- package/dist/templates/kiln/content/pages/about.json +344 -0
- package/dist/templates/kiln/content/pages/contact.json +232 -0
- package/dist/templates/kiln/content/pages/home.json +754 -0
- package/dist/templates/kiln/content/pages/workshops.json +352 -0
- package/dist/templates/kiln/content/site.json +263 -0
- package/dist/templates/kiln/content/styles.json +17 -0
- package/dist/templates/kiln/media/LICENSES.json +119 -0
- package/dist/templates/kiln/openspace.json +21 -0
- package/dist/templates/kiln/template.json +103 -0
- package/dist/templates/ledger/content/collections/case-studies/_collection.json +14 -0
- package/dist/templates/ledger/content/collections/case-studies/fieldwork-coffee.mdoc +29 -0
- package/dist/templates/ledger/content/collections/case-studies/larkspur-foods.mdoc +29 -0
- package/dist/templates/ledger/content/collections/case-studies/ostrander-clinics.mdoc +29 -0
- package/dist/templates/ledger/content/collections/insights/2026-08-10-one-page-of-numbers.mdoc +12 -0
- package/dist/templates/ledger/content/collections/insights/2026-09-07-hiring-your-first-coo.mdoc +17 -0
- package/dist/templates/ledger/content/collections/insights/2026-10-05-the-decision-log.mdoc +15 -0
- package/dist/templates/ledger/content/collections/insights/_collection.json +21 -0
- package/dist/templates/ledger/content/pages/about.json +298 -0
- package/dist/templates/ledger/content/pages/contact.json +158 -0
- package/dist/templates/ledger/content/pages/home.json +621 -0
- package/dist/templates/ledger/content/pages/services.json +639 -0
- package/dist/templates/ledger/content/site.json +336 -0
- package/dist/templates/ledger/content/styles.json +17 -0
- package/dist/templates/ledger/media/LICENSES.json +86 -0
- package/dist/templates/ledger/openspace.json +21 -0
- package/dist/templates/ledger/template.json +102 -0
- package/dist/templates/link/content/pages/home.json +278 -0
- package/dist/templates/link/content/site.json +168 -0
- package/dist/templates/link/content/styles.json +17 -0
- package/dist/templates/link/media/LICENSES.json +97 -0
- package/dist/templates/link/openspace.json +21 -0
- package/dist/templates/link/template.json +87 -0
- package/dist/templates/meadow/content/collections/journal/2026-09-02-meet-daniel.mdoc +20 -0
- package/dist/templates/meadow/content/collections/journal/2026-09-28-a-practice-for-dark-mornings.mdoc +24 -0
- package/dist/templates/meadow/content/collections/journal/2026-10-12-why-twelve-mats.mdoc +26 -0
- package/dist/templates/meadow/content/collections/journal/_collection.json +34 -0
- package/dist/templates/meadow/content/collections/workshops/_collection.json +13 -0
- package/dist/templates/meadow/content/collections/workshops/breath-and-rest-workshop.mdoc +17 -0
- package/dist/templates/meadow/content/collections/workshops/foundations-four-weeks.mdoc +17 -0
- package/dist/templates/meadow/content/collections/workshops/manzanita-spring-retreat.mdoc +17 -0
- package/dist/templates/meadow/content/collections/workshops/winter-solstice-restorative.mdoc +17 -0
- package/dist/templates/meadow/content/pages/about.json +344 -0
- package/dist/templates/meadow/content/pages/bodywork.json +330 -0
- package/dist/templates/meadow/content/pages/classes.json +820 -0
- package/dist/templates/meadow/content/pages/contact.json +240 -0
- package/dist/templates/meadow/content/pages/home.json +556 -0
- package/dist/templates/meadow/content/site.json +290 -0
- package/dist/templates/meadow/content/styles.json +25 -0
- package/dist/templates/meadow/media/LICENSES.json +130 -0
- package/dist/templates/meadow/openspace.json +21 -0
- package/dist/templates/meadow/template.json +116 -0
- package/dist/templates/signal/content/collections/changelog/2026-09-08-faster-sms.mdoc +11 -0
- package/dist/templates/signal/content/collections/changelog/2026-09-22-postmortem-drafts.mdoc +11 -0
- package/dist/templates/signal/content/collections/changelog/2026-10-06-quiet-hours.mdoc +13 -0
- package/dist/templates/signal/content/collections/changelog/2026-10-20-status-pages-that-update-themselves.mdoc +17 -0
- package/dist/templates/signal/content/collections/changelog/_collection.json +39 -0
- package/dist/templates/signal/content/pages/about.json +258 -0
- package/dist/templates/signal/content/pages/contact.json +122 -0
- package/dist/templates/signal/content/pages/home.json +1062 -0
- package/dist/templates/signal/content/pages/pricing.json +460 -0
- package/dist/templates/signal/content/site.json +345 -0
- package/dist/templates/signal/content/styles.json +17 -0
- package/dist/templates/signal/media/LICENSES.json +119 -0
- package/dist/templates/signal/openspace.json +21 -0
- package/dist/templates/signal/template.json +103 -0
- package/dist/templates/vow/content/pages/about.json +314 -0
- package/dist/templates/vow/content/pages/home.json +400 -0
- package/dist/templates/vow/content/pages/registry.json +207 -0
- package/dist/templates/vow/content/pages/rsvp.json +145 -0
- package/dist/templates/vow/content/pages/schedule.json +281 -0
- package/dist/templates/vow/content/pages/travel.json +326 -0
- package/dist/templates/vow/content/site.json +200 -0
- package/dist/templates/vow/content/styles.json +17 -0
- package/dist/templates/vow/media/LICENSES.json +130 -0
- package/dist/templates/vow/openspace.json +21 -0
- package/dist/templates/vow/template.json +113 -0
- package/package.json +71 -3
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { ChildProcess } from 'node:child_process';
|
|
3
|
+
|
|
4
|
+
type Severity = "error" | "warning";
|
|
5
|
+
type CheckRule = "schema" | "block" | "link" | "redirect" | "image" | "media" | "size" | "secret" | "gating";
|
|
6
|
+
interface Finding {
|
|
7
|
+
severity: Severity;
|
|
8
|
+
rule: CheckRule;
|
|
9
|
+
/** Repo-relative file the finding is in. */
|
|
10
|
+
file: string;
|
|
11
|
+
/** Location inside the file's JSON value (dot path), when known. */
|
|
12
|
+
path?: string;
|
|
13
|
+
/** 1-based line, when known (secrets). */
|
|
14
|
+
line?: number;
|
|
15
|
+
message: string;
|
|
16
|
+
}
|
|
17
|
+
interface Budgets {
|
|
18
|
+
/** `media/` total at which a warning is shown (arch §5.6: 600 MB). */
|
|
19
|
+
mediaWarnBytes: number;
|
|
20
|
+
/** `media/` total above which the check fails (900 MB). */
|
|
21
|
+
mediaFailBytes: number;
|
|
22
|
+
/** Longest image edge in px (the upload pipeline's cap). */
|
|
23
|
+
maxImageEdge: number;
|
|
24
|
+
/** GitHub rejects single files over 100 MB. */
|
|
25
|
+
maxFileBytes: number;
|
|
26
|
+
}
|
|
27
|
+
declare const DEFAULT_BUDGETS: Budgets;
|
|
28
|
+
interface CheckOptions {
|
|
29
|
+
budgets?: Partial<Budgets>;
|
|
30
|
+
}
|
|
31
|
+
interface CheckReport {
|
|
32
|
+
ok: boolean;
|
|
33
|
+
errors: number;
|
|
34
|
+
warnings: number;
|
|
35
|
+
findings: Finding[];
|
|
36
|
+
stats: {
|
|
37
|
+
pages: number;
|
|
38
|
+
items: number;
|
|
39
|
+
mediaFiles: number;
|
|
40
|
+
mediaBytes: number;
|
|
41
|
+
filesScanned: number;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
declare function runCheck(root: string, options?: CheckOptions): Promise<CheckReport>;
|
|
45
|
+
/** Plain-text report grouped by file. */
|
|
46
|
+
declare function formatReport(report: CheckReport, paint: (code: number, s: string) => string): string;
|
|
47
|
+
|
|
48
|
+
/** Process I/O, injectable so commands are testable in-process. */
|
|
49
|
+
interface Io {
|
|
50
|
+
cwd: string;
|
|
51
|
+
env: Record<string, string | undefined>;
|
|
52
|
+
out(text: string): void;
|
|
53
|
+
err(text: string): void;
|
|
54
|
+
/** Node version used for engine checks. */
|
|
55
|
+
nodeVersion: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
type Spawner = (cmd: string, args: string[], opts: {
|
|
59
|
+
cwd: string;
|
|
60
|
+
}) => ChildProcess;
|
|
61
|
+
|
|
62
|
+
type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
|
|
63
|
+
/** Run wrangler with `args`; resolves with its exit code. */
|
|
64
|
+
type WranglerRunner = (args: string[], cwd: string) => Promise<number>;
|
|
65
|
+
interface GatedSyncDeps {
|
|
66
|
+
fetch?: FetchLike;
|
|
67
|
+
wrangler?: WranglerRunner;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
declare const VERSION = "0.1.0";
|
|
71
|
+
/** Build the `openspace` command tree. Commands report problems by throwing `CliError`. */
|
|
72
|
+
interface ProgramDeps {
|
|
73
|
+
spawner?: Spawner;
|
|
74
|
+
findBin?: (cwd: string) => string | undefined;
|
|
75
|
+
gatedSync?: GatedSyncDeps;
|
|
76
|
+
}
|
|
77
|
+
declare function createProgram(io: Io, setExit: (code: number) => void, deps?: ProgramDeps): Command;
|
|
78
|
+
/** Run the CLI with `argv` (without node and script) and return the exit code. */
|
|
79
|
+
declare function run(argv: string[], io: Io, deps?: ProgramDeps): Promise<number>;
|
|
80
|
+
|
|
81
|
+
declare const name = "@omniharmonic/openspace-cli";
|
|
82
|
+
|
|
83
|
+
export { type Budgets, type CheckOptions, type CheckReport, DEFAULT_BUDGETS, type Finding, VERSION, createProgram, formatReport, name, run, runCheck };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createRequire as __osCreateRequire } from "node:module"; const require = __osCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_BUDGETS,
|
|
4
|
+
VERSION,
|
|
5
|
+
createProgram,
|
|
6
|
+
formatReport,
|
|
7
|
+
run,
|
|
8
|
+
runCheck
|
|
9
|
+
} from "./chunk-LTJEM3DN.js";
|
|
10
|
+
import "./chunk-3F6TAUAT.js";
|
|
11
|
+
|
|
12
|
+
// src/index.ts
|
|
13
|
+
var name = "@omniharmonic/openspace-cli";
|
|
14
|
+
export {
|
|
15
|
+
DEFAULT_BUDGETS,
|
|
16
|
+
VERSION,
|
|
17
|
+
createProgram,
|
|
18
|
+
formatReport,
|
|
19
|
+
name,
|
|
20
|
+
run,
|
|
21
|
+
runCheck
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export const name = \"@omniharmonic/openspace-cli\";\n\nexport {\n type Budgets,\n type CheckOptions,\n type CheckReport,\n DEFAULT_BUDGETS,\n type Finding,\n formatReport,\n runCheck,\n} from \"./check\";\nexport { createProgram, run, VERSION } from \"./program\";\n"],"mappings":";;;;;;;;;;;;AAAO,IAAM,OAAO;","names":[]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# .github/workflows/publish.yml (generated; managed by site-kit updates)
|
|
2
|
+
name: Publish
|
|
3
|
+
on:
|
|
4
|
+
push: { branches: [main] }
|
|
5
|
+
workflow_dispatch:
|
|
6
|
+
permissions: { contents: read, pages: write, id-token: write }
|
|
7
|
+
concurrency: { group: publish, cancel-in-progress: true }
|
|
8
|
+
jobs:
|
|
9
|
+
build:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v7
|
|
13
|
+
- uses: actions/setup-node@v7
|
|
14
|
+
# package.json keys the npm cache: Hub-created sites have no lockfile at first.
|
|
15
|
+
with: { node-version: 24, cache: npm, cache-dependency-path: package.json }
|
|
16
|
+
- uses: actions/cache@v6
|
|
17
|
+
with:
|
|
18
|
+
path: node_modules/.astro
|
|
19
|
+
key: os-assets-${{ hashFiles('media/**') }}
|
|
20
|
+
restore-keys: os-assets-
|
|
21
|
+
# Sites created by the Hub start without a lockfile.
|
|
22
|
+
- run: if [ -f package-lock.json ]; then npm ci; else npm install; fi
|
|
23
|
+
- run: npx openspace check --ci # schema, links, budgets, secret scan
|
|
24
|
+
- run: npm run build # astro build (+ pagefind, og images)
|
|
25
|
+
env:
|
|
26
|
+
OPENSPACE_TURNSTILE_SITE_KEY: ${{ vars.OPENSPACE_TURNSTILE_SITE_KEY }}
|
|
27
|
+
# Self-host the font pairing (downloaded once from Google Fonts during the build).
|
|
28
|
+
OPENSPACE_GOOGLE_FONTS: download
|
|
29
|
+
OPENSPACE_REMOTE_IMAGES: download
|
|
30
|
+
- uses: actions/upload-pages-artifact@v5
|
|
31
|
+
with: { path: dist }
|
|
32
|
+
deploy:
|
|
33
|
+
needs: build
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
environment: { name: github-pages, url: "${{ steps.d.outputs.page_url }}" }
|
|
36
|
+
steps:
|
|
37
|
+
- id: d
|
|
38
|
+
uses: actions/deploy-pages@v5
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
<!-- Generated by @omniharmonic/openspace-site-kit from the schema and block registry. Do not edit by hand; run `npx openspace agents-md` to refresh. -->
|
|
2
|
+
# AGENTS.md — this OpenSpace site
|
|
3
|
+
|
|
4
|
+
This repository is an OpenSpace website. The content and design choices live here; the code
|
|
5
|
+
lives in versioned npm packages (`@omniharmonic/openspace-site-kit`). Publishing happens automatically when
|
|
6
|
+
changes land on `main` (`.github/workflows/publish.yml`).
|
|
7
|
+
|
|
8
|
+
## Rules
|
|
9
|
+
|
|
10
|
+
1. **Content lives in `content/`.** Never hand-edit `src/`, `astro.config.mjs` or the
|
|
11
|
+
workflow unless you were asked for a custom block (`src/overrides/`).
|
|
12
|
+
2. **Preserve ids.** Every page (`pg_`), section (`sec_`), block (`blk_`), media item (`med_`)
|
|
13
|
+
and form (`frm_`) has a stable id. Keep ids when editing; new ids are the prefix plus 10
|
|
14
|
+
lowercase Crockford base32 characters (no i, l, o, u).
|
|
15
|
+
3. **Only valid block types and fields** (listed below; full JSON Schema at
|
|
16
|
+
https://schema.openspace.dev/v1/). Unknown fields fail the build.
|
|
17
|
+
4. **Styles change through tokens** in `content/styles.json` (palette, type pairing, density,
|
|
18
|
+
shape…), never inline CSS. Sections pick one of five color themes.
|
|
19
|
+
5. **Never put secrets in content** (API keys, tokens, passwords). `openspace check` fails on them.
|
|
20
|
+
Members-only and password pages are only stubs here (a page file with `contentRef` and no
|
|
21
|
+
sections): their content is kept by the OpenSpace Hub and never committed. Edit them in
|
|
22
|
+
Studio or with the Hub's MCP tools; never add sections to a stub (they would be public).
|
|
23
|
+
6. **Run `npx openspace check` before committing.** It validates schemas, links, image references,
|
|
24
|
+
size budgets and scans for secrets.
|
|
25
|
+
7. Write real copy in the owner's voice. No placeholder text, no invented testimonials, prices
|
|
26
|
+
or facts — ask the owner.
|
|
27
|
+
|
|
28
|
+
## Layout
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
openspace.json site id, Hub URL, deploy target, features (https://schema.openspace.dev/v1/site-config.json)
|
|
32
|
+
content/site.json title, navigation, header, footer, SEO defaults, redirects
|
|
33
|
+
content/styles.json design tokens and Site Styles choices
|
|
34
|
+
content/media.json metadata for files in media/ (alt text, size, focal point, credit)
|
|
35
|
+
content/pages/home.json the home page (slug ""); other pages mirror their URL:
|
|
36
|
+
content/pages/about.json → /about/ content/pages/services/pricing.json → /services/pricing/
|
|
37
|
+
content/collections/<name>/_collection.json collection kind, list + item page layout, feeds
|
|
38
|
+
content/collections/<name>/<slug>.mdoc one item: YAML frontmatter + Markdoc body
|
|
39
|
+
media/ images ≤ 2400px on the long edge, audio, files
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
A page is `{ id, title, slug, seo?, sections: [...] }`; a section has a `layout` (`stack` with
|
|
43
|
+
column spans that add up to 12, or `grid`) and `blocks`; a block is
|
|
44
|
+
`{ id, type, v, fields, variant?, style? }`.
|
|
45
|
+
|
|
46
|
+
## Collection items (Markdoc)
|
|
47
|
+
|
|
48
|
+
Frontmatter follows the collection kind (blog, events, portfolio, podcast, products): `title`,
|
|
49
|
+
`status` (draft | scheduled | published), `publishAt` for scheduled items, `excerpt`, `image`,
|
|
50
|
+
`categories`, `tags`, plus kind-specific fields (`date`, `start`/`end`, `price` in minor units…).
|
|
51
|
+
|
|
52
|
+
The body is Markdown prose plus Markdoc tags. **Tags map 1:1 to blocks**: the tag name is the
|
|
53
|
+
block type, attributes are its fields, and content between the tags fills its text:
|
|
54
|
+
|
|
55
|
+
```markdoc
|
|
56
|
+
{% gallery layout="grid" images=["media/2026/10/a.jpg", "media/2026/10/b.jpg"] /%}
|
|
57
|
+
|
|
58
|
+
{% button href="/schedule" variant="primary" %}See the schedule{% /button %}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
No scripts or raw HTML: anything interactive is a block.
|
|
62
|
+
|
|
63
|
+
## Running locally
|
|
64
|
+
|
|
65
|
+
- `npm install`, then `npm run dev` for a live preview.
|
|
66
|
+
- `npx openspace mcp` starts the local MCP server so an AI assistant can edit through the same
|
|
67
|
+
structured operations as the Studio.
|
|
68
|
+
- `npm run build` builds the site into `dist/` exactly as the publish workflow does.
|
|
69
|
+
|
|
70
|
+
## Blocks
|
|
71
|
+
|
|
72
|
+
### `accordion` — Accordion / FAQ
|
|
73
|
+
|
|
74
|
+
Expandable questions and answers, with schema.org FAQPage markup. Use: FAQs, policies, class or service details — anything visitors scan for one answer. 4–10 items works best. Avoid: Hiding essential information (prices, hours) only inside an accordion; answers longer than a short paragraph or list.
|
|
75
|
+
|
|
76
|
+
- Variants: `lines`, `cards`, `minimal`
|
|
77
|
+
- Fields: `items: list of { question: text ≤ 160, answer: richText }`, `exclusive: boolean`, `openFirst: boolean`, `faqSchema: boolean`
|
|
78
|
+
|
|
79
|
+
### `audio` — Audio / podcast episode
|
|
80
|
+
|
|
81
|
+
Plays one audio file — a podcast episode, track, talk or guided practice — with its notes. Use: Episode pages, music and talks. Add listen links for the podcast apps the show is on. `minimal` is just the player and title. Avoid: Autoplay (never supported); audio without a written summary or transcript link for deaf visitors.
|
|
82
|
+
|
|
83
|
+
- Variants: `episode`, `card`, `minimal`
|
|
84
|
+
- Fields: `audio?: media`, `title: text ≤ 120`, `show?: text ≤ 80`, `episode?: number`, `date?: text ≤ 10`, `description?: text ≤ 600`, `cover?: image { src, alt }`, `links: list of { platform: "apple" | "spotify" | "youtube" | "rss" | "other", label?: text ≤ 40, link: link }`, `credit?: text ≤ 200`
|
|
85
|
+
|
|
86
|
+
### `before-after` — Before / after
|
|
87
|
+
|
|
88
|
+
Compares two matching images with a draggable divider: renovations, retouching, seasons. Use: When the difference between two shots of the same view is the point. Use images with identical framing and size. Avoid: Unrelated images; misleading edits; more than one or two per page.
|
|
89
|
+
|
|
90
|
+
- Variants: `labels`, `minimal`
|
|
91
|
+
- Fields: `before: image { src, alt }`, `after: image { src, alt }`, `beforeLabel: text ≤ 30`, `afterLabel: text ≤ 30`, `start: number`, `aspect: "original" | "1:1" | "4:5" | "2:3" | "3:2" | "16:9" | "21:9"`, `caption?: text ≤ 200`
|
|
92
|
+
|
|
93
|
+
### `booking` — Booking
|
|
94
|
+
|
|
95
|
+
Lets visitors book an appointment through the owner's Cal.com or Calendly page. Use: Services, coaching, consultations and classes booked by time slot. The scheduler loads only after a click, so the page stays fast and private. `button` just links out. Avoid: Scheduling links the owner hasn't set up; more than one booking block per page.
|
|
96
|
+
|
|
97
|
+
- Variants: `inline`, `split`, `button`
|
|
98
|
+
- Fields: `url: text ≤ 300`, `provider: "auto" | "cal" | "calendly"`, `heading?: text ≤ 80`, `headingLevel: "h2" | "h3" | "h4"`, `description?: text ≤ 300`, `buttonLabel: text ≤ 30`, `height: "640" | "760" | "900"`
|
|
99
|
+
|
|
100
|
+
### `button` — Button
|
|
101
|
+
|
|
102
|
+
A clear call to action linking to a page, booking tool, email or file. Use: Once per screenful for the main action. Add a second (secondary-style) action only when it's a genuine alternative. Avoid: Several primary buttons side by side; vague labels like 'Click here' or 'Learn more' without context.
|
|
103
|
+
|
|
104
|
+
- Variants: `default`, `large`, `full-width`
|
|
105
|
+
- Fields: `label: text ≤ 40`, `link: link`, `role: "primary" | "secondary" | "tertiary"`, `secondaryLabel?: text ≤ 40`, `secondaryLink?: link`, `note?: text ≤ 120`
|
|
106
|
+
|
|
107
|
+
### `card` — Card
|
|
108
|
+
|
|
109
|
+
One self-contained item — a service, offer, event or article teaser — with an optional link. Use: Place 2–4 cards side by side in stack columns for services or offers. `overlay` sets text on the photo; `horizontal` suits wide columns. Avoid: Long body text (keep under ~40 words); different aspect ratios across cards in a row.
|
|
110
|
+
|
|
111
|
+
- Variants: `stacked`, `horizontal`, `overlay`, `minimal`
|
|
112
|
+
- Fields: `image?: image { src, alt }`, `aspect: "original" | "1:1" | "4:5" | "2:3" | "3:2" | "16:9" | "21:9"`, `icon?: "check" | "star" | "heart" | "leaf" | "sprout" | "flower" | "sun" | "moon" | "sparkles" | "mountain" | "waves" | "map-pin" | "phone" | "mail" | "message-circle" | "clock" | "calendar" | "coffee" | "utensils" | "music" | "camera" | "palette" | "brush" | "book-open" | "graduation-cap" | "user" | "users" | "heart-handshake" | "home" | "shield-check" | "lock" | "award" | "target" | "lightbulb" | "zap" | "rocket" | "gift" | "shopping-bag" | "package" | "truck" | "recycle" | "globe" | "wifi" | "car" | "bike" | "dumbbell" | "accessibility" | "baby" | "dog" | "paw-print" | "scissors" | "wrench" | "arrow-right"`, `eyebrow?: text ≤ 40`, `heading: text ≤ 90`, `body: richText`, `link?: link`, `linkLabel?: text ≤ 30`
|
|
113
|
+
|
|
114
|
+
### `chart` — Chart
|
|
115
|
+
|
|
116
|
+
Shows a small set of numbers as a bar, column, line or pie chart. Use: Up to ~12 values that tell one story: results, survey answers, growth over time. `line` for change over time, `pie` for shares of a whole (2–6 slices). Avoid: Invented or unsourced figures; pies with many slices; charts that need more than one series.
|
|
117
|
+
|
|
118
|
+
- Variants: `bar`, `column`, `line`, `pie`
|
|
119
|
+
- Fields: `title: text ≤ 120`, `description?: text ≤ 240`, `rows: list of { label: text ≤ 40, value: number }`, `prefix?: text ≤ 4`, `suffix?: text ≤ 12`, `showValues: boolean`, `showTable: boolean`
|
|
120
|
+
|
|
121
|
+
### `columns` — Columns
|
|
122
|
+
|
|
123
|
+
Parallel content in columns: a feature trio, numbered steps, or short offers. Use: When 2–4 items share the same shape. `steps` numbers them for a process; `cards` puts each on a surface. Avoid: Uneven text lengths across columns; more than four columns; long paragraphs.
|
|
124
|
+
|
|
125
|
+
- Variants: `plain`, `cards`, `steps`
|
|
126
|
+
- Fields: `items: list of { image?: image { src, alt }, heading: text ≤ 80, body: richText, link?: link, linkLabel?: text ≤ 30 }`, `columns: "2" | "3" | "4"`, `aspect: "original" | "1:1" | "4:5" | "2:3" | "3:2" | "16:9" | "21:9"`
|
|
127
|
+
|
|
128
|
+
### `contact-info` — Contact details
|
|
129
|
+
|
|
130
|
+
How to reach or visit the business: address, phone, email. Use: Contact pages, footers and beside a map or form. `columns` lays the details side by side on wide screens. Avoid: Personal phone numbers or addresses the owner hasn't chosen to publish.
|
|
131
|
+
|
|
132
|
+
- Variants: `card`, `list`, `columns`
|
|
133
|
+
- Fields: `heading?: text ≤ 60`, `headingLevel: "h2" | "h3" | "h4"`, `name?: text ≤ 80`, `address?: text ≤ 240`, `phone?: text ≤ 30`, `email?: text ≤ 120`, `directions: boolean`, `note?: text ≤ 200`
|
|
134
|
+
|
|
135
|
+
### `countdown` — Countdown
|
|
136
|
+
|
|
137
|
+
A live countdown to a moment: a launch, a sale ending, an event starting. Use: Time-bound announcements near a call to action. The exact date is always printed too, so it still works without JavaScript. Avoid: Fake urgency (timers that reset); countdowns to dates that have long passed.
|
|
138
|
+
|
|
139
|
+
- Variants: `boxes`, `inline`, `large`
|
|
140
|
+
- Fields: `heading?: text ≤ 80`, `headingLevel: "h2" | "h3" | "h4"`, `target: text ≤ 32`, `showSeconds: boolean`, `doneMessage: text ≤ 160`, `buttonLabel?: text ≤ 30`, `link?: link`
|
|
141
|
+
|
|
142
|
+
### `divider` — Divider with ornament
|
|
143
|
+
|
|
144
|
+
A decorative separator: a small ornament or label between thin lines. Use: Between passages of long-form text, menus or story pages where a plain line feels too stark. One or two per page. Avoid: Separating every block; ornaments that clash with a minimal site style.
|
|
145
|
+
|
|
146
|
+
- Variants: `lines`, `ornament`, `label`
|
|
147
|
+
- Fields: `ornament: "flourish" | "star" | "diamond" | "dots" | "leaf" | "wave" | "asterism"`, `label?: text ≤ 40`, `length: "short" | "half" | "full"`, `color: color`
|
|
148
|
+
|
|
149
|
+
### `donation` — Donation
|
|
150
|
+
|
|
151
|
+
Collects donations: preset or custom amounts, one-time or monthly. Use: Non-profits, community projects and creators. Say concretely what a gift pays for. `link` mode points to an existing donation page (Open Collective, Stripe Payment Link…). Avoid: Amounts or causes the owner hasn't approved; promising tax deductibility without confirming it.
|
|
152
|
+
|
|
153
|
+
- Variants: `card`, `inline`, `split`
|
|
154
|
+
- Fields: `heading?: text ≤ 80`, `headingLevel: "h2" | "h3" | "h4"`, `description?: text ≤ 400`, `mode: "checkout" | "link"`, `currency: "USD" | "EUR" | "GBP" | "CAD" | "AUD" | "NZD" | "CHF" | "SEK" | "NOK" | "DKK" | "JPY"`, `amounts: list of { amount: number }`, `allowCustom: boolean`, `frequency: "both" | "once" | "monthly"`, `buttonLabel: text ≤ 30`, `link?: link`, `note?: text ≤ 200`
|
|
155
|
+
|
|
156
|
+
### `embed` — Embed / Code
|
|
157
|
+
|
|
158
|
+
Embeds third-party widgets (booking, surveys, tickets, playlists) by URL or HTML snippet. Use: Only when no dedicated block exists for the service. Avoid: Tracking scripts; snippets with secrets or API keys; embedding YouTube/Vimeo (use the video block) or maps (use the map block).
|
|
159
|
+
|
|
160
|
+
- Variants: `plain`, `framed`
|
|
161
|
+
- Fields: `embed: embed`, `title: text ≤ 120`, `aspect: "auto" | "16:9" | "4:3" | "1:1"`, `height: number`
|
|
162
|
+
|
|
163
|
+
### `events` — Events
|
|
164
|
+
|
|
165
|
+
Shows events from the events collection: dates, times, places and a way to register. Use: Home, schedule and community pages. `calendar` shows month grids with an agenda below; `list` is the most scannable. Avoid: Typing events into text blocks (add them to the events collection so they stay in sync).
|
|
166
|
+
|
|
167
|
+
- Variants: `list`, `cards`, `calendar`
|
|
168
|
+
- Fields: `heading?: text ≤ 80`, `headingLevel: "h2" | "h3" | "h4"`, `source: reference`, `showImage: boolean`, `showLocation: boolean`, `showExcerpt: boolean`, `registerLabel: text ≤ 30`, `emptyMessage: text ≤ 160`, `structuredData: boolean`
|
|
169
|
+
|
|
170
|
+
### `form` — Form
|
|
171
|
+
|
|
172
|
+
Collects enquiries, bookings requests, RSVPs or applications into the owner's Inbox. Use: Contact pages and anywhere visitors need to send structured details. Pair with a short text block explaining what happens next. Avoid: Long forms (keep under ~8 fields); asking for sensitive data; password or payment fields (not allowed — payments go through checkout).
|
|
173
|
+
|
|
174
|
+
- Variants: `default`, `card`, `underline`
|
|
175
|
+
- Fields: `form: form`
|
|
176
|
+
|
|
177
|
+
### `gallery` — Gallery
|
|
178
|
+
|
|
179
|
+
Several related photographs shown together. Use: Portfolios, spaces, events and products. `grid` for tidy sets, `masonry` and `strips` for mixed orientations, `carousel` for many images in little space, `slideshow` for one large image at a time. Avoid: Mixing unrelated photos; fewer than three images (use the image block); captions that repeat alt text.
|
|
180
|
+
|
|
181
|
+
- Variants: `grid`, `carousel`, `masonry`, `slideshow`, `strips`
|
|
182
|
+
- Fields: `images: list of { image: image { src, alt }, caption?: text ≤ 160, link?: link }`, `aspect: "auto" | "original" | "1:1" | "4:5" | "2:3" | "3:2" | "16:9" | "21:9"`, `columns: "2" | "3" | "4" | "5"`, `lightbox: boolean`, `showCaptions: boolean`
|
|
183
|
+
|
|
184
|
+
### `heading` — Heading
|
|
185
|
+
|
|
186
|
+
A section heading with optional eyebrow and supporting line. Use: To open a section or introduce a group of blocks. `rule` adds a short accent line above, `underline` a line below, `split` puts the subheading beside the heading on wide screens. Avoid: Using headings for emphasis inside body copy (use text); skipping levels (h2 → h4); more than one Heading 1 per page.
|
|
187
|
+
|
|
188
|
+
- Variants: `plain`, `rule`, `underline`, `split`
|
|
189
|
+
- Fields: `eyebrow?: text ≤ 60`, `text: text ≤ 160`, `level: "h1" | "h2" | "h3" | "h4"`, `size: "auto" | "display" | "h1" | "h2" | "h3" | "h4"`, `subheading?: text ≤ 280`
|
|
190
|
+
|
|
191
|
+
### `hours` — Opening hours
|
|
192
|
+
|
|
193
|
+
When a place is open, day by day. Use: Contact, visit and footer areas of shops, studios, cafés and offices. Pair with the contact card or map. Avoid: Hours the owner hasn't confirmed; using it for event times (use events).
|
|
194
|
+
|
|
195
|
+
- Variants: `table`, `compact`, `card`
|
|
196
|
+
- Fields: `heading?: text ≤ 60`, `headingLevel: "h2" | "h3" | "h4"`, `rows: list of { day: "mon" | "tue" | "wed" | "thu" | "fri" | "sat" | "sun", opens?: text ≤ 5, closes?: text ≤ 5, closed: boolean }`, `note?: text ≤ 200`
|
|
197
|
+
|
|
198
|
+
### `icon-list` — Icon list
|
|
199
|
+
|
|
200
|
+
A scannable list of features, amenities, benefits or what's included. Use: Services and pricing pages, 'what to expect' sections. 3–8 items. Avoid: Using icons that don't match the meaning; paragraphs instead of one-line points.
|
|
201
|
+
|
|
202
|
+
- Variants: `list`, `tiles`, `checklist`
|
|
203
|
+
- Fields: `items: list of { icon: "check" | "star" | "heart" | "leaf" | "sprout" | "flower" | "sun" | "moon" | "sparkles" | "mountain" | "waves" | "map-pin" | "phone" | "mail" | "message-circle" | "clock" | "calendar" | "coffee" | "utensils" | "music" | "camera" | "palette" | "brush" | "book-open" | "graduation-cap" | "user" | "users" | "heart-handshake" | "home" | "shield-check" | "lock" | "award" | "target" | "lightbulb" | "zap" | "rocket" | "gift" | "shopping-bag" | "package" | "truck" | "recycle" | "globe" | "wifi" | "car" | "bike" | "dumbbell" | "accessibility" | "baby" | "dog" | "paw-print" | "scissors" | "wrench" | "arrow-right", title: text ≤ 60, text?: text ≤ 200 }`, `columns: "1" | "2" | "3" | "4"`
|
|
204
|
+
|
|
205
|
+
### `image` — Image
|
|
206
|
+
|
|
207
|
+
A single photograph or illustration. Use: To show the place, the people, the work or the product. Keep aspect ratios consistent within a section. `caption-overlay` suits atmospheric shots; `inset` frames an image on a soft surface. Avoid: Stock photos that don't depict the business; text baked into images; missing alt text.
|
|
208
|
+
|
|
209
|
+
- Variants: `default`, `caption-overlay`, `inset`
|
|
210
|
+
- Fields: `image: image { src, alt }`, `aspect: "original" | "1:1" | "4:5" | "2:3" | "3:2" | "16:9" | "21:9"`, `caption?: text ≤ 200`, `link?: link`, `lightbox: boolean`
|
|
211
|
+
|
|
212
|
+
### `line` — Line
|
|
213
|
+
|
|
214
|
+
A visual divider between pieces of content. Use: To separate groups inside a section, or as a short accent rule above a heading (`length: short`, color `accent`). Avoid: Lines between every block; using lines where whitespace or a section change would do.
|
|
215
|
+
|
|
216
|
+
- Variants: `solid`, `dashed`, `dotted`, `fade`
|
|
217
|
+
- Fields: `thickness: "hairline" | "regular" | "bold"`, `length: "full" | "half" | "short"`, `color: color`
|
|
218
|
+
|
|
219
|
+
### `link-list` — Link list
|
|
220
|
+
|
|
221
|
+
A link-in-bio list: a few large, tappable links under a photo and one-line bio. Use: On a dedicated links page shared from social profiles, or a compact 'start here' list. Put the most important link first and highlight at most one. Avoid: More than ~8 links; vague labels like 'Click here'; duplicating the main navigation.
|
|
222
|
+
|
|
223
|
+
- Variants: `stacked`, `outline`, `cards`
|
|
224
|
+
- Fields: `photo?: image { src, alt }`, `name?: text ≤ 60`, `bio?: text ≤ 200`, `links: list of { label: text ≤ 60, link: link, icon?: "check" | "star" | "heart" | "leaf" | "sprout" | "flower" | "sun" | "moon" | "sparkles" | "mountain" | "waves" | "map-pin" | "phone" | "mail" | "message-circle" | "clock" | "calendar" | "coffee" | "utensils" | "music" | "camera" | "palette" | "brush" | "book-open" | "graduation-cap" | "user" | "users" | "heart-handshake" | "home" | "shield-check" | "lock" | "award" | "target" | "lightbulb" | "zap" | "rocket" | "gift" | "shopping-bag" | "package" | "truck" | "recycle" | "globe" | "wifi" | "car" | "bike" | "dumbbell" | "accessibility" | "baby" | "dog" | "paw-print" | "scissors" | "wrench" | "arrow-right", highlight: boolean }`, `showSocial: boolean`
|
|
225
|
+
|
|
226
|
+
### `logo-strip` — Logo strip
|
|
227
|
+
|
|
228
|
+
Logos of publications, clients, partners or certifications. Use: Below a hero or near a call to action as social proof. 4–8 logos reads best. Avoid: Logos without permission or for relationships that don't exist; mixing press and clients in one strip.
|
|
229
|
+
|
|
230
|
+
- Variants: `row`, `grid`, `marquee`
|
|
231
|
+
- Fields: `label?: text ≤ 60`, `logos: list of { name: text ≤ 60, logo?: image { src, alt }, link?: link }`, `monochrome: boolean`
|
|
232
|
+
|
|
233
|
+
### `map` — Map
|
|
234
|
+
|
|
235
|
+
Shows where a place is, with the address and a link to full maps. Use: Contact and visit pages for any business with a physical location. Set latitude/longitude precisely; add parking or access notes in details. Avoid: Guessing coordinates — ask the owner or geocode the exact address; maps for online-only businesses.
|
|
236
|
+
|
|
237
|
+
- Variants: `card`, `plain`, `split`
|
|
238
|
+
- Fields: `name?: text ≤ 80`, `address: text ≤ 200`, `latitude: number`, `longitude: number`, `zoom: number`, `details?: text ≤ 240`, `linkLabel: text ≤ 40`, `tileServer?: text ≤ 300`, `aspect: "16:9" | "3:2" | "4:3" | "1:1"`
|
|
239
|
+
|
|
240
|
+
### `marquee` — Scrolling text
|
|
241
|
+
|
|
242
|
+
A continuously scrolling line of short phrases, like a ticker or a shop-window band. Use: For energy between sections: offerings, places, a short slogan. 3–8 phrases of 1–3 words. `band` sets it on an accent strip. Avoid: Essential information (it moves and can be paused); long sentences; more than one per page. It never moves for visitors who prefer reduced motion.
|
|
243
|
+
|
|
244
|
+
- Variants: `text`, `outline`, `band`
|
|
245
|
+
- Fields: `items: list of { text: text ≤ 60 }`, `separator: "star" | "dot" | "slash" | "dash" | "none"`, `size: "lead" | "h3" | "h2" | "display"`, `speed: "slow" | "medium" | "fast"`
|
|
246
|
+
|
|
247
|
+
### `member-login` — Member sign-in
|
|
248
|
+
|
|
249
|
+
Lets members sign in with an emailed link, or tells visitors that content is members-only. Use: On the members' sign-in page (`form`), at the top of a members-only page's public teaser (`gated`), or as a slim prompt (`banner`). Members need the Cloudflare hosting option. Avoid: Putting the members-only content itself on a public page; asking for passwords.
|
|
250
|
+
|
|
251
|
+
- Variants: `form`, `gated`, `banner`
|
|
252
|
+
- Fields: `heading?: text ≤ 80`, `headingLevel: "h2" | "h3" | "h4"`, `description?: text ≤ 300`, `buttonLabel: text ≤ 30`, `successMessage: text ≤ 200`, `signInLink?: link`, `joinLabel?: text ≤ 40`, `joinLink?: link`
|
|
253
|
+
|
|
254
|
+
### `newsletter` — Newsletter signup
|
|
255
|
+
|
|
256
|
+
Collects email subscribers (double opt-in) for the owner's newsletter. Use: Footers, the end of blog posts, and dedicated 'stay in touch' sections. Say what subscribers get and how often. Avoid: Promising content or frequency the owner hasn't agreed to; more than one signup per page.
|
|
257
|
+
|
|
258
|
+
- Variants: `inline`, `stacked`, `card`, `banner`
|
|
259
|
+
- Fields: `heading?: text ≤ 80`, `description?: text ≤ 240`, `askName: boolean`, `placeholder: text ≤ 60`, `buttonLabel: text ≤ 30`, `successMessage: text ≤ 200`, `note?: text ≤ 160`
|
|
260
|
+
|
|
261
|
+
### `pricing` — Pricing table
|
|
262
|
+
|
|
263
|
+
Compares plans, packages or tiers with prices and what each includes. Use: Pricing, membership and service-package pages. 2–4 plans; mark at most one as featured. Keep inclusions parallel across plans. Avoid: Prices the owner hasn't confirmed; hidden fees; more than ~6 inclusions per plan; using it for a single product (use the product block).
|
|
264
|
+
|
|
265
|
+
- Variants: `cards`, `columns`, `compact`
|
|
266
|
+
- Fields: `plans: list of { name: text ≤ 60, price: text ≤ 24, period?: text ≤ 30, description?: text ≤ 200, features: list of { text: text ≤ 120 }, buttonLabel?: text ≤ 30, link?: link, featured: boolean, badge?: text ≤ 24 }`, `note?: text ≤ 200`
|
|
267
|
+
|
|
268
|
+
### `product` — Product
|
|
269
|
+
|
|
270
|
+
Sells one product from the shop: image, price, options and an Add to cart button. Use: Featuring a single product on a landing page or inside a story. The price shown comes from the catalog; checkout always re-reads prices on the server. Avoid: Products that don't exist in the shop collection; writing prices by hand in text blocks.
|
|
271
|
+
|
|
272
|
+
- Variants: `card`, `split`, `minimal`
|
|
273
|
+
- Fields: `product: reference`, `showDescription: boolean`, `quantity: boolean`, `buttonLabel: text ≤ 30`, `detailsLabel: text ≤ 30`
|
|
274
|
+
|
|
275
|
+
### `quote` — Quote
|
|
276
|
+
|
|
277
|
+
A short, memorable quotation set apart from body text. Use: To pause the reader between sections, or to open a page with an epigraph. `large` is a centred statement, `bar` sits inside reading text. Avoid: Customer reviews (use testimonial); misattributed or invented quotes; quotes longer than three sentences.
|
|
278
|
+
|
|
279
|
+
- Variants: `classic`, `large`, `bar`
|
|
280
|
+
- Fields: `quote: richText`, `attribution?: text ≤ 80`, `source?: text ≤ 120`
|
|
281
|
+
|
|
282
|
+
### `quote-carousel` — Quote carousel
|
|
283
|
+
|
|
284
|
+
Several short quotes from clients or reviews in a swipeable carousel. Use: Social proof when there are four or more good testimonials and little space. `slides` shows one large quote at a time, `cards` several side by side. Avoid: Invented or edited quotes; fewer than three quotes (use testimonial); quotes over three sentences.
|
|
285
|
+
|
|
286
|
+
- Variants: `slides`, `cards`, `minimal`
|
|
287
|
+
- Fields: `label: text ≤ 80`, `quotes: list of { quote: text ≤ 400, name: text ≤ 80, role?: text ≤ 100, photo?: image { src, alt } }`
|
|
288
|
+
|
|
289
|
+
### `restaurant-menu` — Menu
|
|
290
|
+
|
|
291
|
+
A restaurant, café or bar menu: sections of dishes with descriptions and prices. Use: Menu pages and hospitality sites. Keep descriptions to one line; mark dietary information accurately. `columns` sets sections side by side on wide screens. Avoid: Out-of-date prices; dietary claims the kitchen can't guarantee; using it for services (use the pricing table).
|
|
292
|
+
|
|
293
|
+
- Variants: `classic`, `columns`, `minimal`
|
|
294
|
+
- Fields: `sections: list of { title: text ≤ 60, note?: text ≤ 160, items: list of { name: text ≤ 80, description?: text ≤ 240, price?: text ≤ 24, dietary: list of { tag: "v" | "vg" | "gf" | "df" | "n" | "spicy" } } }`, `currency?: text ≤ 4`, `legend: boolean`, `footnote?: text ≤ 240`
|
|
295
|
+
|
|
296
|
+
### `search` — Search
|
|
297
|
+
|
|
298
|
+
Opens the site's built-in search (Pagefind), which indexes every published page. Use: Sites with a blog, events or many pages — on a resources page, a 404 page or at the top of the journal. `icon` fits tight spaces. Avoid: Small sites of a few pages, where navigation is enough.
|
|
299
|
+
|
|
300
|
+
- Variants: `field`, `button`, `icon`
|
|
301
|
+
- Fields: `placeholder: text ≤ 60`, `shortcutHint: boolean`
|
|
302
|
+
|
|
303
|
+
### `shape` — Shape
|
|
304
|
+
|
|
305
|
+
Decorative shapes (blob, arch, sparkle, squiggle…) in palette colors. Use: In grid sections, layered behind or beside images and text to add warmth and brand character. Keep to one or two per section. Avoid: Using shapes to carry meaning or text; bright shapes behind body text that hurt contrast.
|
|
306
|
+
|
|
307
|
+
- Variants: `solid`, `outline`, `soft`
|
|
308
|
+
- Fields: `shape: "circle" | "ring" | "blob" | "arch" | "half-circle" | "pill" | "squircle" | "triangle" | "sparkle" | "star" | "flower" | "wave"`, `color: color`, `size: "sm" | "md" | "lg" | "fill"`, `rotate: number`, `stretch: boolean`
|
|
309
|
+
|
|
310
|
+
### `share-buttons` — Share buttons
|
|
311
|
+
|
|
312
|
+
Share links for the page being viewed (blog posts, events, announcements). Use: At the end of articles and event pages. Avoid: Putting share buttons on every page; more than four or five networks.
|
|
313
|
+
|
|
314
|
+
- Variants: `icons`, `labels`, `pills`
|
|
315
|
+
- Fields: `label?: text ≤ 40`, `networks: list of { network: "x" | "facebook" | "linkedin" | "bluesky" | "threads" | "pinterest" | "email" }`
|
|
316
|
+
|
|
317
|
+
### `social-feed` — Social feed
|
|
318
|
+
|
|
319
|
+
A grid of recent posts from the owner's social profile, linking to each post. Use: Near the footer or on about pages to show everyday life. Posts are fetched at publish time with the owner's permission, so no third-party scripts load. Avoid: Embedding other people's posts; feeds as the main content of a page.
|
|
320
|
+
|
|
321
|
+
- Variants: `grid`, `row`, `mosaic`
|
|
322
|
+
- Fields: `heading?: text ≤ 80`, `headingLevel: "h2" | "h3" | "h4"`, `network: "instagram" | "bluesky" | "mastodon" | "pinterest" | "tiktok" | "youtube"`, `handle: text ≤ 80`, `count: "3" | "4" | "6" | "8"`, `followLabel: text ≤ 40`, `posts: list of { image: image { src, alt }, url: link, caption?: text ≤ 200 }`
|
|
323
|
+
|
|
324
|
+
### `social-links` — Social links
|
|
325
|
+
|
|
326
|
+
Links to the owner's social media and email. Use: Footers, contact and about pages. Prefer `source: site` so profiles stay in sync across the site. Avoid: Linking to inactive accounts; more than ~6 networks.
|
|
327
|
+
|
|
328
|
+
- Variants: `icons`, `labels`, `pills`
|
|
329
|
+
- Fields: `source: "site" | "custom"`, `profiles: list of { network: "instagram" | "facebook" | "x" | "threads" | "bluesky" | "mastodon" | "linkedin" | "youtube" | "tiktok" | "pinterest" | "github" | "spotify" | "soundcloud" | "bandcamp" | "vimeo" | "behance" | "dribbble" | "substack" | "patreon" | "yelp" | "tripadvisor" | "email" | "website", url: text ≤ 300 }`, `label?: text ≤ 60`
|
|
330
|
+
|
|
331
|
+
### `spacer` — Spacer
|
|
332
|
+
|
|
333
|
+
Extra vertical space between blocks, sized from the site's spacing scale. Use: Sparingly, to give a heading or image more air than the default block gap. Avoid: Stacking spacers; using spacers to fake section padding (adjust the section instead).
|
|
334
|
+
|
|
335
|
+
- Variants: —
|
|
336
|
+
- Fields: `height: "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl"`, `mobileHeight?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl"`
|
|
337
|
+
|
|
338
|
+
### `stats` — Stats
|
|
339
|
+
|
|
340
|
+
Key figures that make a claim concrete (members, years, ratings, outcomes). Use: On about, services and fundraising pages; 3–4 numbers read best. `large` gives each number its own row. Avoid: Invented, rounded-up or unverifiable figures; more than six stats; vanity numbers.
|
|
341
|
+
|
|
342
|
+
- Variants: `row`, `cards`, `large`
|
|
343
|
+
- Fields: `items: list of { value: text ≤ 16, label: text ≤ 80, detail?: text ≤ 140 }`
|
|
344
|
+
|
|
345
|
+
### `summary` — Summary
|
|
346
|
+
|
|
347
|
+
Lists items from a collection (blog posts, events, projects, episodes, products) as cards. Use: On the home page and landing pages to surface fresh content: the latest 3 posts, featured projects, upcoming events. `wall` is a masonry of images for portfolios; `carousel` saves space. Avoid: Using it as the main collection page (the collection's own list page does that); mixing unrelated filters; more than ~12 items.
|
|
348
|
+
|
|
349
|
+
- Variants: `grid`, `list`, `carousel`, `wall`
|
|
350
|
+
- Fields: `heading?: text ≤ 80`, `headingLevel: "h2" | "h3" | "h4"`, `source: reference`, `columns: "2" | "3" | "4"`, `showImage: boolean`, `showDate: boolean`, `showExcerpt: boolean`, `showMeta: boolean`, `linkLabel?: text ≤ 30`, `link?: link`, `emptyMessage: text ≤ 160`
|
|
351
|
+
|
|
352
|
+
### `tabs` — Tabs
|
|
353
|
+
|
|
354
|
+
Parallel pieces of content shown one at a time, switched by tabs. Use: Comparable options (class styles, service tiers, menus by meal) where visitors want one at a time. 2–5 tabs with short labels. Avoid: Content everyone needs to read (tabs hide it); long labels; sequences (use a timeline) or FAQs (use an accordion).
|
|
355
|
+
|
|
356
|
+
- Variants: `underline`, `pills`, `vertical`
|
|
357
|
+
- Fields: `label: text ≤ 80`, `items: list of { label: text ≤ 40, content: richText }`
|
|
358
|
+
|
|
359
|
+
### `team` — Team
|
|
360
|
+
|
|
361
|
+
Introduces the people behind the business: staff, teachers, partners, a board. Use: About and team pages, and near services where visitors choose a person. `circles` suits large teams; `list` gives each bio more room. Avoid: People who haven't agreed to appear; stock photos presented as staff; bios over ~60 words.
|
|
362
|
+
|
|
363
|
+
- Variants: `grid`, `list`, `circles`
|
|
364
|
+
- Fields: `people: list of { name: text ≤ 80, role?: text ≤ 80, photo?: image { src, alt }, bio?: text ≤ 320, link?: link }`, `columns: "2" | "3" | "4"`
|
|
365
|
+
|
|
366
|
+
### `testimonial` — Testimonial
|
|
367
|
+
|
|
368
|
+
A short quote from a client or customer, attributed to a named person. Use: Social proof near a call to action or on a services page. Use three side by side (`card`) or one `large` testimonial as a section's centrepiece. Avoid: Inventing testimonials or ratings. Ask the owner for real quotes and permission to use names and photos.
|
|
369
|
+
|
|
370
|
+
- Variants: `card`, `large`, `minimal`
|
|
371
|
+
- Fields: `quote: richText`, `name: text ≤ 80`, `role?: text ≤ 100`, `photo?: image { src, alt }`, `rating?: number`
|
|
372
|
+
|
|
373
|
+
### `text` — Text
|
|
374
|
+
|
|
375
|
+
Running text: headings, paragraphs, lists and pull quotes. Use: Any time words carry the section — introductions, stories, policies. Use `columns` for long reading text, `drop-cap` for editorial openings, `callout` for a note that must stand out. Avoid: Walls of text; more than one H1 per page; using bold paragraphs as fake headings. Use the button block for calls to action.
|
|
376
|
+
|
|
377
|
+
- Variants: `default`, `columns`, `drop-cap`, `callout`
|
|
378
|
+
- Fields: `content: richText`
|
|
379
|
+
|
|
380
|
+
### `timeline` — Timeline
|
|
381
|
+
|
|
382
|
+
A sequence of dated or numbered milestones. Use: Company or project history, a programme's weeks, a process from enquiry to delivery. `horizontal` suits 3–5 short steps. Avoid: Undated lists (use a list or columns); invented history; more than ~10 milestones.
|
|
383
|
+
|
|
384
|
+
- Variants: `vertical`, `alternating`, `horizontal`
|
|
385
|
+
- Fields: `items: list of { date: text ≤ 40, title: text ≤ 100, text?: text ≤ 400 }`
|
|
386
|
+
|
|
387
|
+
### `video` — Video
|
|
388
|
+
|
|
389
|
+
A video from YouTube, Vimeo or PeerTube, or an uploaded video file. Use: Walkthroughs, talks, trailers and atmosphere. The player loads only when a visitor presses play, so pages stay fast. Avoid: Autoplaying sound; background loops longer than ~20 seconds; using a video where a photo says the same thing.
|
|
390
|
+
|
|
391
|
+
- Variants: `default`, `framed`, `cinematic`
|
|
392
|
+
- Fields: `source: "embed" | "file"`, `url?: text ≤ 500`, `file?: media`, `title: text ≤ 120`, `poster?: image { src, alt }`, `caption?: text ≤ 200`, `aspect: "16:9" | "21:9" | "4:3" | "1:1" | "9:16"`, `background: boolean`
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// OpenSpace site shell. Site, base path, routes, images, feeds and publishing all come from
|
|
2
|
+
// openspace.json and content/ via the site-kit integration — there is nothing to configure here.
|
|
3
|
+
import openspace from "@omniharmonic/openspace-site-kit";
|
|
4
|
+
import { defineConfig } from "astro/config";
|
|
5
|
+
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
integrations: [openspace()],
|
|
8
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "openspace-site",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "astro dev",
|
|
7
|
+
"build": "astro build",
|
|
8
|
+
"preview": "astro preview",
|
|
9
|
+
"check": "openspace check"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@omniharmonic/openspace-site-kit": "^0.1.0",
|
|
13
|
+
"astro": "^7.4.2"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@omniharmonic/openspace-cli": "^0.1.0"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=22.23.4"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Every page, collection item, list page and the 404 page of the site, rendered by
|
|
3
|
+
// @omniharmonic/openspace-site-kit from content/. Custom code belongs in src/overrides/ (Advanced).
|
|
4
|
+
import { renderPage, staticPaths } from "@omniharmonic/openspace-site-kit/route";
|
|
5
|
+
|
|
6
|
+
export const getStaticPaths = staticPaths;
|
|
7
|
+
// biome-ignore lint/correctness/noUnusedVariables: used by the template below
|
|
8
|
+
const html = await renderPage(Astro.props);
|
|
9
|
+
---
|
|
10
|
+
<Fragment set:html={html} />
|