diff --git a/BUILD_REPORT.md b/BUILD_REPORT.md index d23c6bc..b90dfd3 100644 --- a/BUILD_REPORT.md +++ b/BUILD_REPORT.md @@ -1,182 +1,52 @@ -# Sci-Fi Clean — Build Report (v0.1.0, wave-1 pass) +# Sci-Fi Clean — Build Report (Wave A, WO-TF-024) + +Codeless `.bnp` theme. Persona: AI / robotics product. Blueprint schematics, +HUD guide lines, mono readouts, spec + benchmark furniture. Light = white-room +lab; dark = mission control. Motion tier MID (JS scroll-reveal + ticking +counters + CSS line-draw, no canvas, prefers-reduced-motion honored, static +no-JS fallback). ## What landed -### Module scaffolding -- `plugin.mod` with `kind = "theme"`, `scope = "@themes"`, all spec §2 fields verbatim - (categories `["templates", "developer"]`, 9 tags), `[compatibility] block_core = ">=0.11.0 <0.12.0"`. -- `go.mod` pinned to `git.dev.alexdunmow.com/block/core v0.11.1`, `go 1.26.4`, no `replace` directives. -- `Makefile` with `all` (default), `clean`, `templ`, `help` targets. The build line is - `CGO_ENABLED=1 go build -buildmode=plugin -ldflags="-s -w" -o scifi-clean.so .`. -- `embed.go` declares all five canonical embed directives plus `ThemeCSSManifest()`. +- `manifest.yaml`: 7 page templates (default, full-width, landing, article, + blog-index, contact, auth), 49 mandatory `template_overrides`, bundled fonts, + presets, master pages, `required_icon_packs` (lucide, simple-icons), and the + `sc-` component CSS system (blueprint/HUD identity layer over a shared base). +- `templates/overrides/scifi-clean/`: all 49 mandatory builtin overrides, + re-skinned from the frozen Wave-0 builtin defaults with every content field, + provider field, custom tag, and JS block preserved. Legacy button/card field + reads reconciled (§4): button reads `label`/`link`/`style`; card reads + `text`/`media`. +- `templates/scifi-clean/`: 7 page-template shells with the MID-tier motion + script and a line-draw schematic on the hero. +- `blocks/`: 4 persona blocks — `tech_spec`, `benchmark_table`, + `telemetry_grid`, `changelog_console`. The 5 builtin-duplicating theme blocks + (footer, hero, stats, status bar, image caption) were deleted and are now + builtin overrides. +- `presets.json`: 4 dual-mode presets (Flightline, Cleanroom, Signal Cyan, + Graphite Lab), all 19 tokens, light AND dark tuned. +- `fonts.json` + `assets/fonts/`: Space Grotesk (heading), Inter (body), + JetBrains Mono (mono), all SIL OFL 1.1 — see `assets/fonts/LICENSES.md`. +- `templates/email/scifi-clean.ninjatpl`: email-safe table wrapper retuned to + the scifi palette. +- `master_pages.json` + `seed/seed.json` + `seed/workflows.json`: a seeded + "Halcyon Robotics" demo site (home, about, blog with 3 posts, contact with a + data-table + row_inserted email-admins workflow, login). -### Registration -- One `RegisterSystemTemplate({Key: "scifi-clean", ...})`. -- Four page templates per spec §6: - - `default` — slots `header, main, footer` - - `landing` — slots `hero, specs, main, cta, footer` - - `article` — slots `header, rail, main, footer` - - `full-width` — slots `header, main, footer` -- `br.LoadSchemasFromFS(Schemas())` runs **before** any `br.Register(...)`. UAT §3.6. -- Six theme blocks registered: `tech_spec`, `diagram_caption`, `mission_stat`, - `status_bar`, `footer`, `schematic_hero`. Each has `Source = "scifi-clean"` and an - unqualified `Key`. Addressed at runtime as `scifi-clean:`. -- Three overrides registered against theme key `"scifi-clean"`: - - `heading → ScifiHeadingBlock` (Space Grotesk via `--font-heading`, optional mono kicker) - - `text → ScifiTextBlock` (Inter via `--font-body`, tabular-nums) - - `button → ScifiButtonBlock` (mono uppercase label, literal `→` U+2192 chevron + `data-icon="chevron-right"` for UAT §13.14) -- `tr.RegisterEmailWrapper("scifi-clean", ScifiEmailWrapper)` wires the 600px white-card - email shell with Space Grotesk display lockup top-left and mono callsign top-right. +## Verification -### Master pages -`DefaultMasterPages()` returns two entries: +- `make build` -> exit 0 (codeless `.bnp`, 4 blocks, no wasm). +- `check-safety` -> exit 0 (32 checks: 19 ok, 13 skip). -- `scifi-clean:default-master`, attached to `default`, `article`, `full-width`. - Blocks: `navbar` (header, 0) + `scifi-clean:status_bar` (header, 10) + - `slot` (main, 0, `{"slotName": "main"}`) + `scifi-clean:footer` (footer, 0). - Matches spec §7 / UAT §9 byte-for-byte. -- `scifi-clean:landing-master`, attached to `landing`. - Pre-populates `hero` with `scifi-clean:mission_stat` and `specs` with - `scifi-clean:tech_spec`, both seeded with example payloads. +## Known platform limits (per OVERRIDE-CONTRACT §11/§12) -### Schemas -Six `schemas/*.schema.json` files, all draft-07, x-editor values restricted to the -allowed set (`text`, `richtext`, `media`, `select`, `number`, `array`, `collection`, -`link`). Property names match the corresponding Go content reads exactly. +- Auth trio (`auth-form`, `auth-status`, `password-reset-form`) and + `page-suggestions` are compiled builtins with no override dispatch — the + themed files are correct-to-model and inert until the platform fix lands. +- Provider-backed overrides (navbar/footer menus, featured/related/popular + posts, category-list, breadcrumbs, blog-hero, sidebar-nav, author-bio-hero) + render themed chrome but empty live lists under the codeless loader until the + loader registers overrides via the source path. Authored faithfully to the + builtin provider var names. -### Presets -`presets.json` is a JSON array of length 3 in the order required by UAT §5.1: - -1. `flightline` — mode `light`, only `lightColors`. Declares - `"primary": "215 90% 45%"` and `"accent": "18 95% 55%"` byte-exactly (UAT §13.1). -2. `mission-control` — mode `dark`, only `darkColors`. Declares - `"background": "220 16% 7%"` and `"accent": "18 100% 60%"` byte-exactly (UAT §13.2). -3. `cleanroom` — mode `both`, **both** `lightColors` and `darkColors`. - -All three presets carry all 19 tokens in each declared color block. Every value is -an HSL triple string of the shape `^\d+ \d+% \d+%$` (no `hsl()` wrappers). - -### CSS manifest -`ThemeCSSManifest()` returns a `*plugin.CSSManifest` whose `InputCSSAppend` -contains the literal substrings UAT §13.4 grep-greps: `'Space Grotesk'`, -`'JetBrains Mono'`, `.hairline`, `.bg-grid`. The `.hairline` declaration is -exactly `border: 1px solid hsl(var(--border));` (UAT §13.5). - -The manifest also defines: -- Root-level CSS-variable fallback stacks for `--font-heading`, `--font-body`, - `--font-mono` so the theme looks correct before the admin picks Google Fonts. -- `.bg-grid` blueprint grid utility. -- `.scrim` utility for full-bleed hero text (UAT §6 scrim requirement). -- `.scifi-focus` outlined focus ring derived from `--ring`. -- `.scifi-chevron` Unicode arrow suffix for button labels. - -### Fonts policy (wave-1) -- `fonts.json` is the literal `[]`. No woff2 files bundled in this pass. -- `RECOMMENDED_FONTS.md` at the theme root lists Space Grotesk / Inter / - JetBrains Mono as Google Fonts picker recommendations. -- No `LICENSES.md` (nothing is bundled to license). - -## Build output - -``` -$ cd ~/src/blockninja/themes/scifi-clean && make clean && make -rm -f scifi-clean.so -CGO_ENABLED=1 go build -buildmode=plugin -ldflags="-s -w" -o scifi-clean.so . -$ ls -la scifi-clean.so --rw-rw-r-- 1 alex alex 21535264 ... scifi-clean.so (≈ 20.5 MiB) -$ file scifi-clean.so -ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, stripped -``` - -`make 2>&1 | grep -Ei 'warning|error'` returns empty. `go mod tidy` is a no-op -after the initial run; the resolved `block/core` version is `v0.11.1`. - -## Safety check - -Run from the standalone check-safety module (at `~/src/blockninja/check-safety/`, -not `backend/cmd/check-safety` — that path does not exist in this tree): - -``` -$ cd ~/src/blockninja/check-safety && \ - go run . ~/src/blockninja/themes/scifi-clean \ - --plugin-dir ~/src/blockninja/themes/scifi-clean -... -EXIT: 0 -``` - -All 22 checks PASS or SKIP. Specifically: - -- Check 2c (SDK import boundary): OK on v0.11.1. -- Check 6 (no hardcoded colors in .templ/.ninjatpl files): OK. -- Check 11 (no placeholder code): OK. -- Check 17 (no TODO markers): OK. -- Check 21 (presets.json validation against `theme.Theme`): OK. - -The only non-OK output is **Check 2e** (any usage), which is a **WARN**, not a FAIL. -The 34 warnings are the expected `map[string]any` content map signature for plugin -block render funcs — that is the SDK's required block-func shape and cannot be -avoided in a standalone plugin. Same as `gotham/` and `lcars/`. - -## Open items / deferred - -These are intentionally out of scope for the wave-1 implementation pass: - -### Fonts -- No woff2 files bundled (`fonts.json = []` per `themes/docs/FONTS.md`). - Wave-2 may bundle commercial display faces (Eurostile when licensed, - otherwise stick with Space Grotesk) under `assets/fonts/web/`. -- Section §11 of the UAT (woff2 file presence, `@font-face` count, network - fetch checks) is superseded by the FONTS.md wave-1 policy, which only - requires `fonts.json` to parse as JSON, `RECOMMENDED_FONTS.md` to exist, - and CSS to consume `var(--font-*)`. All three pass. - -### Live instance / `make rebuild` -- `make rebuild` is **not** wired in this `Makefile`. Adding it requires the - `blockninja-go-builder` podman image and the live CMS at `~/src/blockninja`. - Copy gotham's `rebuild`, `backend`, `build-frontend`, `copy-plugin-source`, - `build-so`, `sync-migrations`, `build-css`, `deploy-css`, `logs`, `status` - targets when the next implementation pass needs container deployment. - -### Marketplace assets (UAT §12) -- No screenshots captured (`docs/uat-evidence/screenshots/`). Requires a - running instance. -- No `Project Aurora` demo content seeded. Requires admin tooling and a - populated database. -- No `docs/launch-copy.txt`. Copy is in spec §13 verbatim; persist when the - marketplace listing is created. - -### Versioning / git -- The theme is not under git in this scope; UAT §1.11 (`git describe --tags`) - cannot be evaluated until the directory is initialised as a git repo and - tagged `v0.1.0`. - -### Email wrapper hex fallbacks -- `email_wrapper.templ` carries a small set of hardcoded hex values - (`#F6F7F8`, `#FFFFFF`, `#D6D9DD`, `#16202E`, `#5C6776`) that act as the - ultimate fallback when `EmailContext.Colors` is empty. Email clients - (Outlook in particular) cannot resolve `hsl(var(--token))`, so emails - must inline hex; the runtime path always passes preset-resolved hex - via `EmailContext`. Check-safety §6 only scans `.templ` files for the - hardcoded-colors rule and currently passes (the hex literals live inside - Go fallback funcs and are explicitly out of the templ-emit path). UAT - §5.5 may need a clarifying note that email fallbacks are intentional. - -### `Hidden` blocks -- None of the six theme blocks set `Hidden: true`; the spec did not request - any. (Gotham hides its `stat_item` child block; the scifi-clean equivalent - is the in-`rows` collection on `tech_spec`, which is schema-only and never - registered as a standalone block, so no `Hidden` flag is needed.) - -### Block category metadata -- All six theme blocks set `Category` to one of the SDK constants - (`CategoryContent`, `CategoryNavigation`, `CategoryLayout`). The spec's - §8 table lists category names like `media` and `blog` that do not map - to SDK constants; those are stored implicitly via the spec source rather - than the BlockMeta — flag in BUILD_REPORT for future enum extension. - -### Slot block placeholder -- Master pages reference the built-in `slot` block with - `{"slotName": "main", "placeholder": "Page payload"}`. The `placeholder` - key is a CMS-side convention copied verbatim from gotham; it is **not** - the same as the check-safety §11 "no placeholder code" rule (which fires - on dev-time comments like `// TODO: placeholder`). Check-safety passes. +Visual quality is UNVERIFIED until Wave B screenshots. diff --git a/RECOMMENDED_FONTS.md b/RECOMMENDED_FONTS.md index 007221c..deb5798 100644 --- a/RECOMMENDED_FONTS.md +++ b/RECOMMENDED_FONTS.md @@ -1,33 +1,32 @@ # Recommended fonts for Sci-Fi Clean -This theme ships `fonts.json = []` — no woff2 files bundled in this implementation pass. -Add the fonts below from the site admin's Google Fonts picker. +This theme **bundles** its type. `fonts.json` ships latin-subset woff2 files in +`assets/fonts/` (all SIL OFL 1.1 — see `assets/fonts/LICENSES.md`), so the +intended aesthetic renders out of the box with no admin setup. -Open **Admin → Settings → Typography**, switch to the **Google Fonts** tab, and assign: +| Slot | Bundled family | Weights | Role | +|---------|---------------------|-----------|---------------------------------------------------| +| Heading | `Space Grotesk` | 500, 700 | Geometric display face for headlines and brand. | +| Body | `Inter` | 400, 600 | Workhorse UI sans for prose and controls. | +| Mono | `JetBrains Mono` | 400, 500 | Every numeral, identifier, readout, and spec row. | -| Slot | Pick | Source | Notes | -|----------|---------------------------------------|----------------|---------------------------------------------------------| -| Heading | `Space Grotesk` (weights 400, 500, 700) | `google:Space Grotesk` | Geometric display face. Spec §3 calls it "the safe ship default" over Eurostile. | -| Body | `Inter` (weights 400, 500, 600) | `google:Inter` | Workhorse UI sans. Pairs cleanly with Space Grotesk. | -| Mono | `JetBrains Mono` (weights 400, 500) | `google:JetBrains Mono` | Used for every numeral, identifier, and tabular figure. | - -All three are in the curated Google Fonts list; no upload is required. +The bundled families appear as `source=template` rows in **Admin → Settings → +Typography**. Fonts stay admin-controlled: an admin can override any slot from +the Google Fonts tab or an upload, and the theme follows because every +`font-family` goes through `var(--font-heading|body|mono, )`. ## CSS fallback stacks -Until the admin assigns fonts, templates fall through to these CSS-variable fallbacks -(declared in the theme's CSS manifest, applied to `:root`): +Until (or unless) the admin reassigns a slot, templates fall through to these +CSS-variable fallbacks (declared on `:root` in the theme CSS manifest): -- `--font-heading` → `"Space Grotesk", "Inter", "Helvetica Neue", Helvetica, Arial, sans-serif` -- `--font-body` → `"Inter", "Helvetica Neue", Helvetica, Arial, sans-serif` -- `--font-mono` → `"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace` +- `--font-heading` -> `"Space Grotesk", "Inter", "Helvetica Neue", Helvetica, Arial, sans-serif` +- `--font-body` -> `"Inter", "Helvetica Neue", Helvetica, Arial, sans-serif` +- `--font-mono` -> `"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace` -The fallback stacks intentionally already approximate the intended aesthetic, so the -theme looks right out of the box even before the admin picks Google Fonts. +## Good alternates -## Why no bundled woff2s in this pass - -Per `themes/docs/FONTS.md` (wave-1 policy): no theme bundles woff2 files in this -implementation pass. Wave-2 may bundle commercial display faces (e.g. Eurostile) -once licensing for `.so` redistribution is sorted; until then, Space Grotesk is -the canonical heading face for Sci-Fi Clean. +If an admin prefers to swap the mono readout face, `Space Mono` (OFL) and +`IBM Plex Mono` (OFL) both hold tabular numerals well. For headings, `Sora` +(OFL) is a close geometric cousin of Space Grotesk. All are in the curated +Google Fonts list; no upload required. diff --git a/RECOMMENDED_ICONS.md b/RECOMMENDED_ICONS.md new file mode 100644 index 0000000..9b5eb81 --- /dev/null +++ b/RECOMMENDED_ICONS.md @@ -0,0 +1,26 @@ +# Recommended icons for Sci-Fi Clean + +Icons render only via the sprite system: +``. Block content stores refs +as `"pack:name"` strings (e.g. `lucide:mail`). + +## Required packs + +Declared in `plugin.mod` `required_icon_packs` and `manifest.yaml` +`required_icon_packs`. Until the loader auto-installs, install these manually +before activation: + +| Pack | Slug | Why | +|----------------|----------------|------------------------------------------------------------| +| Lucide | `lucide` | Default UI icon set: nav toggle, arrows, mail, phone, home. | +| Simple Icons | `simple-icons` | Brand marks in social links and the author bio hero (X, LinkedIn, GitHub). | + +Both are among the six packs that ship bundled with the CMS, so no download is +usually needed. If a pack is missing on a fresh instance, open **Admin -> +Icons**, install Lucide and Simple Icons, and enable them. + +## Defaults are safe + +Every block that renders an icon defaults to a `lucide:` glyph, so the theme +renders correctly even if Simple Icons is not yet installed (brand links simply +fall back to text/label until the pack is present). diff --git a/assets/fonts/LICENSES.md b/assets/fonts/LICENSES.md new file mode 100644 index 0000000..977b465 --- /dev/null +++ b/assets/fonts/LICENSES.md @@ -0,0 +1,15 @@ +# Bundled font licenses + +All bundled woff2 files in this directory are latin-subset builds redistributed +under the SIL Open Font License 1.1 (OFL). No modifications were made beyond +subsetting to the latin range. Font-family stays admin-controlled; these appear +as `source=template` rows in the font picker and are consumed only via +`var(--font-heading|body|mono, )`. + +| Family | Files | License | Source | +|--------|-------|---------|--------| +| Space Grotesk | space-grotesk-500.woff2, space-grotesk-700.woff2 | SIL OFL 1.1 | Florian Karsten / Google Fonts | +| Inter | inter-400.woff2, inter-600.woff2 | SIL OFL 1.1 | Rasmus Andersson / Google Fonts | +| JetBrains Mono | jetbrains-mono-400.woff2 (variable; theme uses 400 and 500) | SIL OFL 1.1 | JetBrains / Google Fonts | + +Full OFL text: https://openfontlicense.org/. diff --git a/assets/fonts/inter-400.woff2 b/assets/fonts/inter-400.woff2 new file mode 100644 index 0000000..c659f5e Binary files /dev/null and b/assets/fonts/inter-400.woff2 differ diff --git a/assets/fonts/inter-600.woff2 b/assets/fonts/inter-600.woff2 new file mode 100644 index 0000000..bc76d10 Binary files /dev/null and b/assets/fonts/inter-600.woff2 differ diff --git a/assets/fonts/jetbrains-mono-400.woff2 b/assets/fonts/jetbrains-mono-400.woff2 new file mode 100644 index 0000000..cd5102a Binary files /dev/null and b/assets/fonts/jetbrains-mono-400.woff2 differ diff --git a/assets/fonts/space-grotesk-500.woff2 b/assets/fonts/space-grotesk-500.woff2 new file mode 100644 index 0000000..0db251f Binary files /dev/null and b/assets/fonts/space-grotesk-500.woff2 differ diff --git a/assets/fonts/space-grotesk-700.woff2 b/assets/fonts/space-grotesk-700.woff2 new file mode 100644 index 0000000..44604a0 Binary files /dev/null and b/assets/fonts/space-grotesk-700.woff2 differ diff --git a/blocks/benchmark_table.ninjatpl b/blocks/benchmark_table.ninjatpl new file mode 100644 index 0000000..f2f9c28 --- /dev/null +++ b/blocks/benchmark_table.ninjatpl @@ -0,0 +1 @@ +
{% if heading %}

{{ eyebrow|default:"Benchmarks" }}

{{ heading }}

{% endif %}{% if rows %}{% for row in rows %}
{{ row.label }}{{ row.value }}{% if row.unit %} {{ row.unit }}{% endif %}
{% endfor %}{% else %}

No benchmark rows yet.

{% endif %}{% if note %}

{{ note }}

{% endif %}
diff --git a/blocks/benchmark_table.schema.json b/blocks/benchmark_table.schema.json new file mode 100644 index 0000000..2da0510 --- /dev/null +++ b/blocks/benchmark_table.schema.json @@ -0,0 +1,44 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Benchmark Table", + "description": "Labelled benchmark bars with mono values and a technical-blue fill track.", + "type": "object", + "properties": { + "eyebrow": { + "type": "string", + "title": "Eyebrow", + "description": "Small mono label above the heading.", + "x-editor": "text" + }, + "heading": { + "type": "string", + "title": "Heading", + "description": "Section heading.", + "x-editor": "text" + }, + "rows": { + "type": "array", + "title": "Rows", + "description": "Each row is a labelled benchmark bar.", + "default": [], + "x-editor": "collection", + "items": { + "type": "object", + "properties": { + "label": { "type": "string", "title": "Label", "description": "Benchmark name.", "x-editor": "text" }, + "value": { "type": "string", "title": "Value", "description": "Displayed value (e.g. 98.4).", "x-editor": "text" }, + "unit": { "type": "string", "title": "Unit", "description": "Trailing unit (e.g. ms, %, tok/s).", "x-editor": "text" }, + "bar": { "type": "number", "title": "Bar %", "description": "Fill width 0-100.", "default": 0, "minimum": 0, "maximum": 100, "x-editor": "number" }, + "highlight": { "type": "boolean", "title": "Highlight", "description": "Fill this bar with the signal accent instead of technical blue.", "default": false, "x-editor": "select" } + }, + "required": ["label", "value"] + } + }, + "note": { + "type": "string", + "title": "Note", + "description": "Optional footnote below the bars.", + "x-editor": "text" + } + } +} diff --git a/blocks/blocks.yaml b/blocks/blocks.yaml index c7af007..6254501 100644 --- a/blocks/blocks.yaml +++ b/blocks/blocks.yaml @@ -1,42 +1,32 @@ -# Sci-Fi Clean theme block definitions (codeless .bnp, WO-WZ-021). -# Keys are fully qualified (scifi-clean:) because the codeless loader -# registers definitions verbatim — there is no PluginBlockRegistry prefixing -# in the codeless path (unlike the wasm/templ path). The qualified keys match -# the hardcoded data-block attributes and the master_pages.json references. +# Sci-Fi Clean theme — persona block definitions (codeless .bnp). +# Keys are fully qualified (scifi-clean:): the codeless loader registers +# definitions verbatim (no PluginBlockRegistry prefixing in the codeless path), +# and the qualified keys match the data-block attributes + master_pages / seed +# references. These four are TRUE persona furniture that no builtin covers; +# every duplicating theme block (footer, hero, stats, status bar, image +# caption) is now a builtin OVERRIDE under templates/overrides/scifi-clean/. blocks: - key: scifi-clean:tech_spec title: Tech Spec Table - description: Specification table with right-aligned mono numerals and hairline rows. + description: Specification sheet with right-aligned mono numerals, units, and hairline rows. category: content schema: tech_spec.schema.json template: tech_spec.ninjatpl - - key: scifi-clean:diagram_caption - title: Diagram Caption - description: Annotated technical figure with a Fig. NN. heading and optional callout pins. + - key: scifi-clean:benchmark_table + title: Benchmark Table + description: Labelled benchmark bars with mono values and a technical-blue fill track. category: content - schema: diagram_caption.schema.json - template: diagram_caption.ninjatpl - - key: scifi-clean:mission_stat - title: Mission Stat - description: Headline metric in mono numerals with a signal-orange delta arrow. + schema: benchmark_table.schema.json + template: benchmark_table.ninjatpl + - key: scifi-clean:telemetry_grid + title: Telemetry Grid + description: Grid of HUD readout tiles with mono figures, units, deltas, and optional ticking counters. category: content - schema: mission_stat.schema.json - template: mission_stat.ninjatpl - - key: scifi-clean:status_bar - title: Status Bar - description: Persistent top rail with optional UTC clock, build hash, and callsign. - category: navigation - schema: status_bar.schema.json - template: status_bar.ninjatpl - - key: scifi-clean:footer - title: Instrument Footer - description: Hairline-rule footer with callsign, optional signup, and link list. - category: layout - schema: footer.schema.json - template: footer.ninjatpl - - key: scifi-clean:schematic_hero - title: Schematic Hero - description: Landing hero with a large display title over a blueprint image and dual CTAs. - category: layout - schema: schematic_hero.schema.json - template: schematic_hero.ninjatpl + schema: telemetry_grid.schema.json + template: telemetry_grid.ninjatpl + - key: scifi-clean:changelog_console + title: Changelog Console + description: Terminal-style release log with version, date, and note per entry. + category: content + schema: changelog_console.schema.json + template: changelog_console.ninjatpl diff --git a/blocks/changelog_console.ninjatpl b/blocks/changelog_console.ninjatpl new file mode 100644 index 0000000..2e41412 --- /dev/null +++ b/blocks/changelog_console.ninjatpl @@ -0,0 +1 @@ +
{% if heading %}

{{ eyebrow|default:"Release log" }}

{{ heading }}

{% endif %}
{{ title|default:"changelog" }}
{% if entries %}{% for entry in entries %}
{{ entry.version }}{{ entry.date }}{{ entry.note }}
{% endfor %}{% else %}
no entries logged
{% endif %}
diff --git a/blocks/changelog_console.schema.json b/blocks/changelog_console.schema.json new file mode 100644 index 0000000..39c25a8 --- /dev/null +++ b/blocks/changelog_console.schema.json @@ -0,0 +1,43 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Changelog Console", + "description": "Terminal-style release log with version, date, and note per entry.", + "type": "object", + "properties": { + "eyebrow": { + "type": "string", + "title": "Eyebrow", + "description": "Small mono label above the heading.", + "x-editor": "text" + }, + "heading": { + "type": "string", + "title": "Heading", + "description": "Section heading.", + "x-editor": "text" + }, + "title": { + "type": "string", + "title": "Console title", + "description": "Label shown in the console title bar.", + "default": "changelog", + "x-editor": "text" + }, + "entries": { + "type": "array", + "title": "Entries", + "description": "Each entry is a version / date / note row.", + "default": [], + "x-editor": "collection", + "items": { + "type": "object", + "properties": { + "version": { "type": "string", "title": "Version", "description": "Release tag (e.g. v2.4.0).", "x-editor": "text" }, + "date": { "type": "string", "title": "Date", "description": "Release date.", "x-editor": "text" }, + "note": { "type": "string", "title": "Note", "description": "One-line summary of the change.", "x-editor": "text" } + }, + "required": ["version", "note"] + } + } + } +} diff --git a/blocks/diagram_caption.ninjatpl b/blocks/diagram_caption.ninjatpl deleted file mode 100644 index b5c71c5..0000000 --- a/blocks/diagram_caption.ninjatpl +++ /dev/null @@ -1,9 +0,0 @@ -
-
- {% if image %}{{ title }}{% for pin in calloutPins %}{{ pin.label }}{% endfor %}{% else %}
No diagram yet
{% endif %} -
-
-

Fig. {% if figureNumber %}{{ figureNumber }}{% else %}00{% endif %} . {% if title %}{{ title }}{% endif %}

- {% if body %}
{{ body|safe }}
{% endif %} -
-
diff --git a/blocks/diagram_caption.schema.json b/blocks/diagram_caption.schema.json deleted file mode 100644 index d8864d9..0000000 --- a/blocks/diagram_caption.schema.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Diagram Caption", - "description": "Annotated technical figure with a Fig. NN. heading and optional callout pins.", - "type": "object", - "properties": { - "image": { - "type": "string", - "title": "Image", - "description": "Diagram / blueprint / schematic to display.", - "x-editor": "media" - }, - "figureNumber": { - "type": "string", - "title": "Figure Number", - "description": "Number printed after the Fig. prefix (e.g. 03).", - "x-editor": "text" - }, - "title": { - "type": "string", - "title": "Title", - "description": "Short heading next to the figure number.", - "x-editor": "text" - }, - "body": { - "type": "string", - "title": "Body", - "description": "Rich description / annotation text rendered below the figure.", - "x-editor": "richtext" - }, - "calloutPins": { - "type": "array", - "title": "Callout Pins", - "description": "Percent-positioned overlay pins; each pin is x, y, label.", - "default": [], - "x-editor": "array", - "items": { - "type": "object", - "properties": { - "x": { - "type": "number", - "title": "X (%)", - "description": "Horizontal position as a percentage of the image width (0-100).", - "minimum": 0, - "maximum": 100, - "x-editor": "number" - }, - "y": { - "type": "number", - "title": "Y (%)", - "description": "Vertical position as a percentage of the image height (0-100).", - "minimum": 0, - "maximum": 100, - "x-editor": "number" - }, - "label": { - "type": "string", - "title": "Label", - "description": "Short callout text.", - "x-editor": "text" - } - }, - "required": ["label"] - } - } - } -} diff --git a/blocks/footer.ninjatpl b/blocks/footer.ninjatpl deleted file mode 100644 index 862bbea..0000000 --- a/blocks/footer.ninjatpl +++ /dev/null @@ -1,24 +0,0 @@ -
-
-
-
-
-
{{ callsign|default:"SCF-CLN" }}
-

Telemetry, mission briefs, and engineering notes from this surface.

-
- {% if links %}{% endif %} - {% if showSignup == true or showSignup == "true" %}
- -
- - -
-
{% endif %} -
-
- Project Aurora - v0.1.0 -
-
-
-
diff --git a/blocks/footer.schema.json b/blocks/footer.schema.json deleted file mode 100644 index 6855cc7..0000000 --- a/blocks/footer.schema.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Instrument Footer", - "description": "Hairline-rule footer with callsign, optional signup, and link list.", - "type": "object", - "properties": { - "showSignup": { - "type": "boolean", - "title": "Show Signup", - "description": "Render the inline email signup form.", - "default": false, - "x-editor": "select" - }, - "callsign": { - "type": "string", - "title": "Callsign", - "description": "Short mono identifier displayed at the corner of the footer rail.", - "x-editor": "text" - }, - "links": { - "type": "array", - "title": "Links", - "description": "Footer link list (each item is text + url).", - "default": [], - "x-editor": "collection", - "items": { - "type": "object", - "properties": { - "text": { - "type": "string", - "title": "Link Text", - "description": "Visible link text.", - "x-editor": "text" - }, - "url": { - "type": "string", - "title": "URL", - "description": "Destination URL.", - "x-editor": "text" - } - }, - "required": ["text"] - } - } - } -} diff --git a/blocks/mission_stat.ninjatpl b/blocks/mission_stat.ninjatpl deleted file mode 100644 index 4fa1772..0000000 --- a/blocks/mission_stat.ninjatpl +++ /dev/null @@ -1,8 +0,0 @@ -
- {% if metric %}
{{ metric }}
{% endif %} -
- {% if value %}{{ value }}{% else %}—{% endif %} - {% if unit %}{{ unit }}{% endif %} -
- {% if delta %}
{% if trend == "up" %}↑{% elif trend == "down" %}↓{% else %}→{% endif %} {{ delta }}
{% endif %} -
diff --git a/blocks/mission_stat.schema.json b/blocks/mission_stat.schema.json deleted file mode 100644 index bc1a560..0000000 --- a/blocks/mission_stat.schema.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Mission Stat", - "description": "Headline metric in mono numerals with a signal-orange delta arrow.", - "type": "object", - "properties": { - "metric": { - "type": "string", - "title": "Metric", - "description": "Name of the measured quantity (e.g. Apogee).", - "x-editor": "text" - }, - "value": { - "type": "string", - "title": "Value", - "description": "Headline numeric value (e.g. 412).", - "x-editor": "text" - }, - "unit": { - "type": "string", - "title": "Unit", - "description": "Trailing unit (e.g. km, m/s).", - "x-editor": "text" - }, - "delta": { - "type": "string", - "title": "Delta", - "description": "Short delta string (e.g. +12, -0.3%).", - "x-editor": "text" - }, - "trend": { - "type": "string", - "title": "Trend", - "description": "Direction of the delta arrow.", - "default": "flat", - "enum": ["up", "down", "flat"], - "x-editor": "select" - } - } -} diff --git a/blocks/schematic_hero.ninjatpl b/blocks/schematic_hero.ninjatpl deleted file mode 100644 index d115a7d..0000000 --- a/blocks/schematic_hero.ninjatpl +++ /dev/null @@ -1,13 +0,0 @@ -
- {% if image %}
{% endif %} -
-
- {% if kicker %}
{{ kicker }}
{% endif %} -

{% if title %}{{ title }}{% else %}Untitled mission{% endif %}

-
- {% if primaryCta.text and primaryCta.href %}{{ primaryCta.text }}{% endif %} - {% if secondaryCta.text and secondaryCta.href %}{{ secondaryCta.text }}{% endif %} -
-
-
-
diff --git a/blocks/schematic_hero.schema.json b/blocks/schematic_hero.schema.json deleted file mode 100644 index ba8e0d2..0000000 --- a/blocks/schematic_hero.schema.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Schematic Hero", - "description": "Landing hero with a large display title over a blueprint image and dual CTAs.", - "type": "object", - "properties": { - "title": { - "type": "string", - "title": "Title", - "description": "Main display headline (rendered in Space Grotesk at >= 64px).", - "x-editor": "text" - }, - "kicker": { - "type": "string", - "title": "Kicker", - "description": "Short mono uppercase eyebrow text printed above the title.", - "x-editor": "text" - }, - "image": { - "type": "string", - "title": "Background Image", - "description": "Blueprint / schematic image rendered behind the title.", - "x-editor": "media" - }, - "primaryCta": { - "type": "object", - "title": "Primary CTA", - "description": "Primary call-to-action link.", - "x-editor": "link", - "properties": { - "text": { - "type": "string", - "title": "Text", - "x-editor": "text" - }, - "href": { - "type": "string", - "title": "URL", - "x-editor": "text" - } - } - }, - "secondaryCta": { - "type": "object", - "title": "Secondary CTA", - "description": "Optional secondary call-to-action link.", - "x-editor": "link", - "properties": { - "text": { - "type": "string", - "title": "Text", - "x-editor": "text" - }, - "href": { - "type": "string", - "title": "URL", - "x-editor": "text" - } - } - } - } -} diff --git a/blocks/status_bar.ninjatpl b/blocks/status_bar.ninjatpl deleted file mode 100644 index de2bb86..0000000 --- a/blocks/status_bar.ninjatpl +++ /dev/null @@ -1,27 +0,0 @@ -
-
-
- {{ callsign|default:"SCF-CLN" }} - | - PUBLIC -
-
- {% if showClock != false and showClock != "false" %}00:00:00 UTC{% endif %} - {% if showBuild != false and showBuild != "false" %}| - BLD a1b2c3{% endif %} -
-
- -
diff --git a/blocks/status_bar.schema.json b/blocks/status_bar.schema.json deleted file mode 100644 index 2e7de3c..0000000 --- a/blocks/status_bar.schema.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Status Bar", - "description": "Persistent top rail with optional UTC clock, build hash, and callsign.", - "type": "object", - "properties": { - "showClock": { - "type": "boolean", - "title": "Show Clock", - "description": "Show a live UTC clock element.", - "default": true, - "x-editor": "select" - }, - "showBuild": { - "type": "boolean", - "title": "Show Build Hash", - "description": "Show the short build hash on the right rail.", - "default": true, - "x-editor": "select" - }, - "callsign": { - "type": "string", - "title": "Callsign", - "description": "Short mono identifier for the running surface (e.g. SCF-CLN).", - "x-editor": "text" - } - } -} diff --git a/blocks/tech_spec.ninjatpl b/blocks/tech_spec.ninjatpl index 66ee48e..7e88827 100644 --- a/blocks/tech_spec.ninjatpl +++ b/blocks/tech_spec.ninjatpl @@ -1,10 +1 @@ -
-
- {% if caption %}

{{ caption }}

{% endif %} - - - {% if rows %}{% for row in rows %}{% endfor %}{% else %}{% endif %} - -
{{ row.label }}{{ row.value }}{{ row.unit }}
No spec rows yet.--
-
-
+
{% if caption %}{% endif %}{% if rows %}{% for row in rows %}{% endfor %}{% else %}{% endif %}
{{ caption }}
{{ row.label }}{{ row.value }}{{ row.unit }}
No spec rows yet--
diff --git a/blocks/telemetry_grid.ninjatpl b/blocks/telemetry_grid.ninjatpl new file mode 100644 index 0000000..5376351 --- /dev/null +++ b/blocks/telemetry_grid.ninjatpl @@ -0,0 +1 @@ +
{% if heading %}

{{ eyebrow|default:"Telemetry" }}

{{ heading }}

{% endif %}
{% if tiles %}{% for tile in tiles %}

{{ tile.label }}

{{ tile.value }}{% if tile.unit %} {{ tile.unit }}{% endif %}

{% if tile.delta %}

{{ tile.delta }}

{% endif %}
{% endfor %}{% else %}

No readouts

--

{% endif %}
diff --git a/blocks/telemetry_grid.schema.json b/blocks/telemetry_grid.schema.json new file mode 100644 index 0000000..f93ce82 --- /dev/null +++ b/blocks/telemetry_grid.schema.json @@ -0,0 +1,45 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Telemetry Grid", + "description": "Grid of HUD readout tiles with mono figures, units, deltas, and optional ticking counters.", + "type": "object", + "properties": { + "eyebrow": { + "type": "string", + "title": "Eyebrow", + "description": "Small mono label above the heading.", + "x-editor": "text" + }, + "heading": { + "type": "string", + "title": "Heading", + "description": "Section heading.", + "x-editor": "text" + }, + "columns": { + "type": "number", + "title": "Columns", + "description": "Tiles per row on wide screens.", + "default": 3, + "enum": [2, 3, 4], + "x-editor": "select" + }, + "tiles": { + "type": "array", + "title": "Tiles", + "description": "Each tile is a readout: label, value, optional unit and delta.", + "default": [], + "x-editor": "collection", + "items": { + "type": "object", + "properties": { + "label": { "type": "string", "title": "Label", "description": "Readout name.", "x-editor": "text" }, + "value": { "type": "string", "title": "Value", "description": "Displayed value; leading number is animated by the ticking counter.", "x-editor": "text" }, + "unit": { "type": "string", "title": "Unit", "description": "Trailing unit (e.g. ms, GB, %).", "x-editor": "text" }, + "delta": { "type": "string", "title": "Delta", "description": "Optional change indicator (e.g. -12% latency).", "x-editor": "text" } + }, + "required": ["label", "value"] + } + } + } +} diff --git a/fonts.json b/fonts.json index fe51488..a5ee3bd 100644 --- a/fonts.json +++ b/fonts.json @@ -1 +1,26 @@ -[] +[ + { + "name": "Space Grotesk", + "family": "Space Grotesk", + "variants": [ + { "weight": "500", "style": "normal", "file": "fonts/space-grotesk-500.woff2" }, + { "weight": "700", "style": "normal", "file": "fonts/space-grotesk-700.woff2" } + ] + }, + { + "name": "Inter", + "family": "Inter", + "variants": [ + { "weight": "400", "style": "normal", "file": "fonts/inter-400.woff2" }, + { "weight": "600", "style": "normal", "file": "fonts/inter-600.woff2" } + ] + }, + { + "name": "JetBrains Mono", + "family": "JetBrains Mono", + "variants": [ + { "weight": "400", "style": "normal", "file": "fonts/jetbrains-mono-400.woff2" }, + { "weight": "500", "style": "normal", "file": "fonts/jetbrains-mono-400.woff2" } + ] + } +] diff --git a/manifest.yaml b/manifest.yaml index 655356f..a17f74c 100644 --- a/manifest.yaml +++ b/manifest.yaml @@ -1,40 +1,120 @@ -# Sci-Fi Clean theme — codeless .bnp manifest (WO-WZ-021 Phase B1). +# Sci-Fi Clean theme — codeless .bnp manifest (WO-TF-024, Wave A). # Synthesized into manifest.pb by `ninja plugin build` (no plugin.wasm). +# +# Identity: the precise chrome of an AI / robotics product company. Blueprint +# schematic line-work, HUD guide lines, mono readouts, spec + benchmark +# furniture. Light = white-room lab (bright ground, thin blueprint lines, one +# signal accent); dark = mission control (deep navy, technical-blue HUD lines, +# glowing readouts). Motion is MID tier: JS scroll-reveal + ticking counters + +# CSS line-draw, no canvas, prefers-reduced-motion honored, static no-JS +# fallback. Semantic tokens only. theme_presets: presets.json bundled_fonts: fonts.json master_pages: master_pages.json +required_icon_packs: + - lucide + - simple-icons + system_templates: - key: scifi-clean title: Sci-Fi Clean - description: "Minimalist futurist theme for aerospace, robotics, and R&D — off-white surfaces, technical blue, signal-orange accents, mono numerals." + description: "Blueprint-precise theme for AI and robotics product sites: HUD guide lines, mono readouts, spec and benchmark furniture, a single signal accent, and dual white-room / mission-control presets." page_templates: - system: scifi-clean key: default title: Default - description: "Standard cleanroom layout with header rail, main column, and footer instrument panel." + description: "Header rail + main column + footer instrument panel, capped at 1280px." + slots: [header, main, footer] + - system: scifi-clean + key: full-width + title: Full width + description: "Edge-to-edge schematic surface for diagrams and dashboards." slots: [header, main, footer] - system: scifi-clean key: landing title: Landing - description: "Hero diagram + spec strip + CTA banner." - slots: [hero, specs, main, cta, footer] + description: "Hero band + main + closing CTA + footer." + slots: [hero, main, cta, footer] - system: scifi-clean key: article title: Article - description: "Narrow column with rail metadata and figure margin." - slots: [header, rail, main, footer] + description: "Narrow measure for long-form technical writing, with a rail aside." + slots: [header, main, aside, footer] - system: scifi-clean - key: full-width - title: Full Width - description: "Edge-to-edge schematic surface for diagrams and dashboards." - slots: [header, main, footer] + key: blog-index + title: Blog index + description: "Title band, featured lead, and a hairline-ruled post grid." + slots: [header, featured, main, footer] + - system: scifi-clean + key: contact + title: Contact + description: "Two-column contact: form on the grid, coordinates in the aside." + slots: [header, main, aside, footer] + - system: scifi-clean + key: auth + title: Auth + description: "Centered console card on a quiet ground for login and reset." + slots: [main, footer] template_overrides: + # Chrome (4) + - { template: scifi-clean, block: navbar } + - { template: scifi-clean, block: footer } + - { template: scifi-clean, block: utility-bar } + - { template: scifi-clean, block: announcement-bar } + # Marketing (12) + - { template: scifi-clean, block: hero } + - { template: scifi-clean, block: feature-grid } + - { template: scifi-clean, block: rich-section } + - { template: scifi-clean, block: stats } + - { template: scifi-clean, block: testimonial } + - { template: scifi-clean, block: logo-strip } + - { template: scifi-clean, block: pricing } + - { template: scifi-clean, block: team } + - { template: scifi-clean, block: faq } + - { template: scifi-clean, block: timeline } + - { template: scifi-clean, block: cta } + - { template: scifi-clean, block: countdown } + # Primitives (11) - { template: scifi-clean, block: heading } - { template: scifi-clean, block: text } + - { template: scifi-clean, block: rich-text } + - { template: scifi-clean, block: image } + - { template: scifi-clean, block: quote } + - { template: scifi-clean, block: divider } - { template: scifi-clean, block: button } + - { template: scifi-clean, block: card } + - { template: scifi-clean, block: gallery } + - { template: scifi-clean, block: video-embed } + - { template: scifi-clean, block: audio-embed } + # Commerce / local (6) + - { template: scifi-clean, block: menu-list } + - { template: scifi-clean, block: event-list } + - { template: scifi-clean, block: contact-form } + - { template: scifi-clean, block: contact-card } + - { template: scifi-clean, block: hours-location } + - { template: scifi-clean, block: social-links } + # Blog family (12) + - { template: scifi-clean, block: blog-hero } + - { template: scifi-clean, block: featured-posts } + - { template: scifi-clean, block: related-posts } + - { template: scifi-clean, block: popular-posts } + - { template: scifi-clean, block: category-list } + - { template: scifi-clean, block: post-hero } + - { template: scifi-clean, block: post-metadata } + - { template: scifi-clean, block: author-bio } + - { template: scifi-clean, block: author-bio-hero } + - { template: scifi-clean, block: newsletter-signup } + - { template: scifi-clean, block: breadcrumbs } + - { template: scifi-clean, block: sidebar-nav } + # Auth surface (3) + - { template: scifi-clean, block: auth-form } + - { template: scifi-clean, block: password-reset-form } + - { template: scifi-clean, block: auth-status } + # System (1) + - { template: scifi-clean, block: page-suggestions } email_wrappers: - scifi-clean @@ -42,99 +122,376 @@ email_wrappers: css: input_css_append: | /* === Sci-Fi Clean: font-family fallback variables === - * The site admin assigns real fonts via the typography settings; until then, - * the variables fall through to the stacks below. 'Space Grotesk', 'Inter', - * and 'JetBrains Mono' are spec §5 recommendations, available in the Google - * Fonts picker. + * The admin assigns real fonts via typography settings; until then these + * fall through to the bundled stacks. Space Grotesk (heading), Inter + * (body), and JetBrains Mono (mono) ship bundled in assets/fonts. */ :root { --font-heading: "Space Grotesk", "Inter", "Helvetica Neue", Helvetica, Arial, sans-serif; --font-body: "Inter", "Helvetica Neue", Helvetica, Arial, sans-serif; --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; } + /* ============================================================ + Corporate Modernist — appended component CSS. + Semantic tokens only: hsl(var(--token)). No literal colors. + Swiss grid + Bauhaus geometric marks + oversized numerals. + ============================================================ */ - /* === Sci-Fi Clean: utilities === - * Hairline rules and a translucent blueprint grid are the two utility classes - * the theme uses heavily. Tailwind's defaults cover everything else. - */ - .hairline { - border: 1px solid hsl(var(--border)); + /* --- Layout primitives --- */ + .sc-content-well { width: 100%; max-width: 1280px; margin-left: auto; margin-right: auto; padding-left: 1.5rem; padding-right: 1.5rem; box-sizing: border-box; } + .sc-measure { max-width: 70ch; } + .sc-swiss-12 { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: 1.5rem; row-gap: 1.5rem; } + .sc-section { padding-top: 4rem; padding-bottom: 4rem; } + .sc-section-tight { padding-top: 2.5rem; padding-bottom: 2.5rem; } + .sc-hairline { border: 1px solid hsl(var(--border)); } + .sc-hairline-top { border-top: 1px solid hsl(var(--border)); } + .sc-hairline-bottom { border-bottom: 1px solid hsl(var(--border)); } + .sc-hairline-y > * + * { border-top: 1px solid hsl(var(--border)); } + .sc-accent-rule { border-top: 2px solid hsl(var(--accent)); } + .sc-stack > * + * { margin-top: 1.5rem; } + .sc-stack-sm > * + * { margin-top: 0.75rem; } + + /* --- Typography --- */ + .sc-display { font-family: var(--font-heading, "Inter", system-ui, sans-serif); font-weight: 600; letter-spacing: -0.02em; color: hsl(var(--foreground)); margin: 0; } + .sc-body { font-family: var(--font-body, "Inter", system-ui, sans-serif); } + .sc-mono { font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); font-variant-numeric: tabular-nums; } + .numeric-tabular { font-variant-numeric: tabular-nums; } + .sc-heading { font-family: var(--font-heading, "Inter", system-ui, sans-serif); font-weight: 600; letter-spacing: -0.02em; color: hsl(var(--foreground)); margin: 0; } + .sc-heading-h2-accent { border-bottom: 2px solid hsl(var(--accent)); padding-bottom: 0.25rem; display: inline-block; } + .sc-text { font-family: var(--font-body, "Inter", system-ui, sans-serif); font-size: 17px; line-height: 28px; color: hsl(var(--foreground)); font-variant-numeric: tabular-nums; } + .sc-text a { color: hsl(var(--accent)); text-decoration: underline; text-underline-offset: 2px; } + .sc-text strong { font-weight: 600; } + .sc-lede { font-family: var(--font-body, "Inter", system-ui, sans-serif); font-size: 1.25rem; line-height: 1.6; color: hsl(var(--muted-foreground)); } + .sc-eyebrow { font-family: var(--font-heading, "Inter", system-ui, sans-serif); font-size: 0.8125rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.12em; color: hsl(var(--accent)); margin: 0 0 1rem 0; } + .sc-kicker { font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); font-size: 0.75rem; letter-spacing: 0.1em; text-transform: uppercase; color: hsl(var(--muted-foreground)); } + + /* --- Bauhaus geometric marks (circle / triangle / square) --- */ + .sc-mark { display: inline-block; width: 0.75rem; height: 0.75rem; flex: none; } + .sc-mark-circle { border-radius: 9999px; background-color: hsl(var(--accent)); } + .sc-mark-square { background-color: hsl(var(--accent)); } + .sc-mark-triangle { width: 0; height: 0; border-left: 0.4375rem solid transparent; border-right: 0.4375rem solid transparent; border-bottom: 0.75rem solid hsl(var(--accent)); background: none; } + .sc-mark-ring { border-radius: 9999px; background: none; border: 2px solid hsl(var(--accent)); } + + /* --- Numerals / metrics (oversized signature) --- */ + .sc-metric { font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); font-size: clamp(2.5rem, 5vw, 3.5rem); line-height: 1; font-weight: 500; font-variant-numeric: tabular-nums; color: hsl(var(--accent)); letter-spacing: -0.02em; } + .sc-stat-figure { font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); font-size: clamp(2.25rem, 4vw, 3rem); line-height: 1; font-weight: 500; font-variant-numeric: tabular-nums; color: hsl(var(--foreground)); letter-spacing: -0.02em; } + .sc-stat-label { font-family: var(--font-body, "Inter", system-ui, sans-serif); font-size: 0.8125rem; color: hsl(var(--muted-foreground)); text-transform: uppercase; letter-spacing: 0.06em; margin-top: 0.5rem; } + .sc-numeral-index { font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); font-size: 2rem; line-height: 1; font-weight: 500; font-variant-numeric: tabular-nums; color: hsl(var(--accent)); } + + /* --- Buttons (squared, flat, no gradient/shadow) --- */ + .sc-btn-primary { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.625rem 1.25rem; background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground)); border: 1px solid hsl(var(--accent)); border-radius: 4px; font-family: var(--font-heading, "Inter", system-ui, sans-serif); font-weight: 500; text-decoration: none; line-height: 1.4; box-shadow: none; background-image: none; transition: background-color 150ms ease; cursor: pointer; } + .sc-btn-primary:hover { background-color: hsl(var(--accent) / 0.9); } + .sc-btn-primary:focus-visible { outline: 2px solid hsl(var(--ring)); outline-offset: 2px; } + .sc-btn-outline { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.625rem 1.25rem; background-color: transparent; color: hsl(var(--foreground)); border: 1px solid hsl(var(--border)); border-radius: 4px; font-family: var(--font-heading, "Inter", system-ui, sans-serif); font-weight: 500; text-decoration: none; line-height: 1.4; box-shadow: none; background-image: none; transition: border-color 150ms ease, color 150ms ease; cursor: pointer; } + .sc-btn-outline:hover { border-color: hsl(var(--foreground)); } + .sc-btn-outline:focus-visible { outline: 2px solid hsl(var(--ring)); outline-offset: 2px; } + .sc-btn-ghost { display: inline-flex; align-items: center; gap: 0.375rem; color: hsl(var(--accent)); font-family: var(--font-heading, "Inter", system-ui, sans-serif); font-weight: 500; text-decoration: none; line-height: 1.4; transition: gap 150ms ease; } + .sc-btn-ghost:hover { gap: 0.625rem; } + .sc-btn-sm { padding: 0.375rem 0.875rem; font-size: 0.875rem; } + .sc-btn-lg { padding: 0.875rem 1.75rem; font-size: 1.0625rem; } + .sc-btn-row { display: flex; flex-wrap: wrap; gap: 0.75rem; } + .sc-icon { width: 1em; height: 1em; display: inline-block; vertical-align: -0.125em; } + + /* --- Surfaces / cards --- */ + .sc-card { background-color: hsl(var(--card)); color: hsl(var(--card-foreground)); border: 1px solid hsl(var(--border)); border-radius: 4px; padding: 1.5rem; box-shadow: none; } + .sc-card-accent { border-top: 2px solid hsl(var(--accent)); } + .sc-panel { background-color: hsl(var(--muted)); border-radius: 4px; padding: 1.5rem; } + .sc-surface-muted { background-color: hsl(var(--muted)); } + .sc-surface-card { background-color: hsl(var(--card)); } + .sc-media-frame { display: block; width: 100%; overflow: hidden; background-color: hsl(var(--muted)); border: 1px solid hsl(var(--border)); } + .sc-media-frame img { display: block; width: 100%; height: 100%; object-fit: cover; } + .sc-img-empty { display: block; width: 100%; aspect-ratio: 4 / 3; background-color: hsl(var(--muted)); border: 1px solid hsl(var(--border)); } + + /* --- Responsive grids --- */ + .sc-grid { display: grid; gap: 1.5rem; grid-template-columns: repeat(1, minmax(0, 1fr)); } + @media (min-width: 640px) { .sc-grid-2, .sc-grid-3, .sc-grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } } + @media (min-width: 1024px) { .sc-grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } .sc-grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } } + .sc-leadership-grid { display: grid; gap: 1.5rem; grid-template-columns: repeat(1, minmax(0, 1fr)); } + @media (min-width: 640px) { .sc-leadership-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } } + @media (min-width: 1024px) { .sc-leadership-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } } + @media (min-width: 1280px) { .sc-leadership-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } } + + /* --- Chrome: navbar --- */ + .sc-navbar { display: flex; align-items: center; justify-content: space-between; gap: 1.5rem; padding: 1.25rem 0; } + .sc-brand { font-family: var(--font-heading, "Inter", system-ui, sans-serif); font-weight: 700; font-size: 1.125rem; letter-spacing: -0.01em; color: hsl(var(--foreground)); text-decoration: none; display: inline-flex; align-items: center; gap: 0.625rem; } + .sc-nav-links { display: none; align-items: center; gap: 2rem; } + @media (min-width: 768px) { .sc-nav-links { display: flex; } } + .sc-nav-link { font-family: var(--font-body, "Inter", system-ui, sans-serif); font-size: 0.9375rem; color: hsl(var(--foreground)); text-decoration: none; padding-bottom: 2px; border-bottom: 2px solid transparent; transition: border-color 150ms ease; } + .sc-nav-link:hover { border-bottom-color: hsl(var(--accent)); } + .sc-nav-toggle { display: inline-flex; } + @media (min-width: 768px) { .sc-nav-toggle { display: none; } } + .sc-nav-drawer { display: none; flex-direction: column; gap: 0.25rem; padding: 0.5rem 0 1rem; border-top: 1px solid hsl(var(--border)); width: 100%; } + @media (min-width: 768px) { .sc-nav-drawer { display: none !important; } } + .sc-nav-drawer a { padding: 0.625rem 0; font-family: var(--font-body, "Inter", system-ui, sans-serif); color: hsl(var(--foreground)); text-decoration: none; border-bottom: 1px solid hsl(var(--border)); } + + /* --- Chrome: utility bar + announcement bar --- */ + .sc-utility-bar { font-family: var(--font-body, "Inter", system-ui, sans-serif); font-size: 0.8125rem; padding: 0.5rem 0; } + .sc-utility-bar--muted { background-color: hsl(var(--muted)); color: hsl(var(--muted-foreground)); } + .sc-utility-bar--primary { background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground)); } + .sc-utility-bar--dark { background-color: hsl(var(--foreground)); color: hsl(var(--background)); } + .sc-utility-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; flex-wrap: wrap; } + .sc-utility-group { display: flex; align-items: center; gap: 1.25rem; } + .sc-utility-item { display: inline-flex; align-items: center; gap: 0.375rem; color: inherit; text-decoration: none; } + .sc-announce { font-family: var(--font-body, "Inter", system-ui, sans-serif); font-size: 0.875rem; padding: 0.625rem 0; } + .sc-announce--info { background-color: hsl(var(--muted)); color: hsl(var(--foreground)); } + .sc-announce--promo { background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground)); } + .sc-announce--alert { background-color: hsl(var(--destructive)); color: hsl(var(--destructive-foreground)); } + .sc-announce-row { display: flex; align-items: center; justify-content: center; gap: 0.75rem; } + .sc-announce a { color: inherit; text-decoration: underline; text-underline-offset: 2px; font-weight: 600; } + .sc-announce-close { margin-left: auto; background: none; border: 0; color: inherit; cursor: pointer; line-height: 1; padding: 0 0.25rem; } + + /* --- Chrome: footer --- */ + .sc-footer { background-color: hsl(var(--background)); color: hsl(var(--foreground)); border-top: 2px solid hsl(var(--accent)); padding: 3.5rem 0 2.5rem; } + .sc-footer-cols { display: grid; gap: 2rem; grid-template-columns: repeat(1, minmax(0, 1fr)); } + @media (min-width: 640px) { .sc-footer-cols { grid-template-columns: repeat(2, minmax(0, 1fr)); } } + @media (min-width: 1024px) { .sc-footer-cols { grid-template-columns: 2fr repeat(3, 1fr); } } + .sc-footer-head { font-family: var(--font-heading, "Inter", system-ui, sans-serif); font-size: 0.8125rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; color: hsl(var(--muted-foreground)); margin: 0 0 0.875rem; } + .sc-footer-link { display: block; font-size: 0.9375rem; color: hsl(var(--foreground)); text-decoration: none; padding: 0.25rem 0; } + .sc-footer-link:hover { color: hsl(var(--accent)); } + .sc-footer-legal { color: hsl(var(--muted-foreground)); font-size: 0.8125rem; border-top: 1px solid hsl(var(--border)); padding-top: 1.5rem; margin-top: 2.5rem; display: flex; flex-wrap: wrap; gap: 1rem; justify-content: space-between; } + + /* --- Marketing: hero --- */ + .sc-hero { padding: 5rem 0; } + .sc-hero-headline { font-family: var(--font-heading, "Inter", system-ui, sans-serif); font-weight: 600; letter-spacing: -0.03em; line-height: 1.05; font-size: clamp(2.5rem, 6vw, 4.5rem); color: hsl(var(--foreground)); margin: 0; } + .sc-hero-sub { font-size: clamp(1.125rem, 2vw, 1.5rem); line-height: 1.5; color: hsl(var(--muted-foreground)); margin: 1.25rem 0 0; } + .sc-hero-split { display: grid; gap: 3rem; grid-template-columns: repeat(1, minmax(0, 1fr)); align-items: center; } + @media (min-width: 1024px) { .sc-hero-split { grid-template-columns: repeat(2, minmax(0, 1fr)); } } + + /* --- Marketing: logo strip --- */ + .sc-logo { filter: grayscale(1); opacity: 0.65; transition: filter 200ms ease, opacity 200ms ease; } + .sc-logo:hover { filter: grayscale(0); opacity: 1; } + .sc-logo-row { display: flex; flex-wrap: wrap; align-items: center; gap: 2.5rem; } + + /* --- Marketing: feature grid --- */ + .sc-feature { border-top: 2px solid hsl(var(--accent)); padding-top: 1.25rem; } + .sc-feature-title { font-family: var(--font-heading, "Inter", system-ui, sans-serif); font-weight: 600; font-size: 1.125rem; color: hsl(var(--foreground)); margin: 0.75rem 0 0.5rem; } + + /* --- Marketing: pull quote / testimonial --- */ + .sc-pull-quote { border-top: 2px solid hsl(var(--accent)); padding-top: 1.25rem; font-family: var(--font-heading, "Inter", system-ui, sans-serif); font-size: 1.5rem; line-height: 1.4; font-weight: 500; color: hsl(var(--foreground)); } + .sc-quote-block { font-family: var(--font-heading, "Inter", system-ui, sans-serif); font-size: 1.375rem; line-height: 1.45; font-weight: 500; color: hsl(var(--foreground)); } + .sc-quote-cite { font-size: 0.875rem; color: hsl(var(--muted-foreground)); margin-top: 0.75rem; } + .sc-avatar { width: 2.75rem; height: 2.75rem; border-radius: 9999px; object-fit: cover; background-color: hsl(var(--muted)); } + + /* --- Marketing: pricing --- */ + .sc-price { font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); font-size: 2.5rem; font-weight: 500; font-variant-numeric: tabular-nums; color: hsl(var(--foreground)); letter-spacing: -0.02em; } + .sc-price-period { font-family: var(--font-body, "Inter", system-ui, sans-serif); font-size: 0.9375rem; color: hsl(var(--muted-foreground)); } + .sc-price-tier { display: flex; flex-direction: column; } + .sc-price-tier--featured { border-top: 4px solid hsl(var(--accent)); } + .sc-badge { display: inline-block; font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); font-size: 0.6875rem; letter-spacing: 0.08em; text-transform: uppercase; padding: 0.25rem 0.5rem; background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground)); border-radius: 3px; } + .sc-check { color: hsl(var(--accent)); flex: none; } + + /* --- Marketing: faq --- */ + .sc-faq-item { border-top: 1px solid hsl(var(--border)); } + .sc-faq-item summary { cursor: pointer; list-style: none; padding: 1.25rem 0; display: flex; align-items: center; justify-content: space-between; gap: 1rem; font-family: var(--font-heading, "Inter", system-ui, sans-serif); font-weight: 600; color: hsl(var(--foreground)); } + .sc-faq-item summary::-webkit-details-marker { display: none; } + .sc-faq-item[open] summary .sc-faq-chevron { transform: rotate(180deg); } + .sc-faq-chevron { transition: transform 150ms ease; color: hsl(var(--accent)); } + .sc-faq-answer { padding: 0 0 1.25rem; color: hsl(var(--muted-foreground)); font-size: 1rem; line-height: 1.65; } + + /* --- Marketing: timeline --- */ + .sc-timeline-step { display: grid; grid-template-columns: auto 1fr; gap: 1.25rem; padding-bottom: 2rem; } + .sc-timeline-rail { position: relative; display: flex; flex-direction: column; align-items: center; } + .sc-timeline-node { width: 2.5rem; height: 2.5rem; border: 2px solid hsl(var(--accent)); border-radius: 9999px; display: flex; align-items: center; justify-content: center; font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); font-weight: 500; color: hsl(var(--accent)); flex: none; font-variant-numeric: tabular-nums; } + .sc-timeline-line { flex: 1 1 auto; width: 1px; background-color: hsl(var(--border)); margin-top: 0.5rem; } + + /* --- Marketing: cta strip --- */ + .sc-cta-strip { padding: 3.5rem 0; } + .sc-cta-strip--band { background-color: hsl(var(--foreground)); color: hsl(var(--background)); } + .sc-cta-strip--band .sc-btn-primary { background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground)); border-color: hsl(var(--accent)); } + .sc-cta-strip--card { background-color: hsl(var(--muted)); } + .sc-cta-headline { font-family: var(--font-heading, "Inter", system-ui, sans-serif); font-weight: 600; letter-spacing: -0.02em; font-size: clamp(1.75rem, 3vw, 2.5rem); line-height: 1.15; margin: 0; } + + /* --- Marketing: countdown --- */ + .sc-count-unit { display: flex; flex-direction: column; align-items: center; } + .sc-count-figure { font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); font-size: clamp(2rem, 4vw, 3rem); font-weight: 500; font-variant-numeric: tabular-nums; color: hsl(var(--foreground)); line-height: 1; } + .sc-count-box { border: 1px solid hsl(var(--border)); padding: 1rem 1.25rem; border-radius: 4px; min-width: 4.5rem; } + + /* --- Primitives: quote / divider / gallery / media --- */ + .sc-divider-line { border: 0; border-top: 1px solid hsl(var(--border)); } + .sc-divider-thick { border-top-width: 2px; border-top-color: hsl(var(--accent)); } + .sc-divider-ornament { display: flex; align-items: center; justify-content: center; gap: 0.75rem; color: hsl(var(--muted-foreground)); } + .sc-gallery-grid { display: grid; gap: 0.75rem; grid-template-columns: repeat(2, minmax(0, 1fr)); } + @media (min-width: 768px) { .sc-gallery-grid.sc-gallery-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } .sc-gallery-grid.sc-gallery-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } } + .sc-video-frame { position: relative; display: block; width: 100%; background-color: hsl(var(--muted)); border: 1px solid hsl(var(--border)); overflow: hidden; } + .sc-video-play { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: hsl(var(--background)); background: none; border: 0; cursor: pointer; } + .sc-video-play-badge { width: 4rem; height: 4rem; border-radius: 9999px; background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground)); display: flex; align-items: center; justify-content: center; } + + /* --- Commerce / local --- */ + .sc-menu-item { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; padding: 0.75rem 0; border-bottom: 1px solid hsl(var(--border)); } + .sc-menu-price { font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); font-variant-numeric: tabular-nums; color: hsl(var(--foreground)); } + .sc-channel { display: flex; align-items: flex-start; gap: 0.75rem; } + .sc-channel-mark { width: 2.5rem; height: 2.5rem; border: 1px solid hsl(var(--border)); border-radius: 4px; display: flex; align-items: center; justify-content: center; color: hsl(var(--accent)); flex: none; } + .sc-hours-row { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; padding: 0.375rem 0; border-bottom: 1px solid hsl(var(--border)); } + .sc-social-row { display: flex; flex-wrap: wrap; gap: 0.75rem; } + .sc-social-link { display: inline-flex; align-items: center; gap: 0.375rem; padding: 0.5rem 0.75rem; border: 1px solid hsl(var(--border)); border-radius: 4px; color: hsl(var(--foreground)); text-decoration: none; font-size: 0.875rem; transition: border-color 150ms ease; } + .sc-social-link:hover { border-color: hsl(var(--accent)); color: hsl(var(--accent)); } + + /* --- Forms --- */ + .sc-form { display: grid; gap: 1.25rem; } + .sc-form-grid { display: grid; gap: 1.25rem; grid-template-columns: repeat(2, minmax(0, 1fr)); } + .sc-field { display: flex; flex-direction: column; gap: 0.375rem; } + .sc-field--full { grid-column: 1 / -1; } + .sc-label { font-family: var(--font-heading, "Inter", system-ui, sans-serif); font-size: 0.8125rem; font-weight: 600; color: hsl(var(--foreground)); text-transform: uppercase; letter-spacing: 0.04em; } + .sc-input { width: 100%; box-sizing: border-box; padding: 0.625rem 0.75rem; background-color: hsl(var(--background)); color: hsl(var(--foreground)); border: 1px solid hsl(var(--input)); border-radius: 4px; font-family: var(--font-body, "Inter", system-ui, sans-serif); font-size: 1rem; } + .sc-input:focus { outline: 2px solid hsl(var(--ring)); outline-offset: 1px; border-color: hsl(var(--ring)); } + .sc-form-note { font-size: 0.8125rem; color: hsl(var(--muted-foreground)); } + .sc-alert-ok { border-left: 3px solid hsl(var(--accent)); background-color: hsl(var(--muted)); padding: 1rem 1.25rem; color: hsl(var(--foreground)); } + .sc-alert-err { border-left: 3px solid hsl(var(--destructive)); background-color: hsl(var(--muted)); padding: 1rem 1.25rem; color: hsl(var(--foreground)); } + + /* --- Blog family --- */ + .sc-post-card { display: flex; flex-direction: column; gap: 0.75rem; } + .sc-post-thumb { display: block; width: 100%; aspect-ratio: 16 / 10; object-fit: cover; background-color: hsl(var(--muted)); border: 1px solid hsl(var(--border)); } + .sc-post-title { font-family: var(--font-heading, "Inter", system-ui, sans-serif); font-weight: 600; font-size: 1.25rem; line-height: 1.25; color: hsl(var(--foreground)); text-decoration: none; } + .sc-post-title:hover { color: hsl(var(--accent)); } + .sc-post-meta { font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); font-size: 0.75rem; letter-spacing: 0.04em; text-transform: uppercase; color: hsl(var(--muted-foreground)); } + .sc-post-excerpt { color: hsl(var(--muted-foreground)); font-size: 0.9375rem; line-height: 1.6; } + .sc-post-grid { display: grid; gap: 2.5rem; grid-template-columns: repeat(1, minmax(0, 1fr)); } + @media (min-width: 640px) { .sc-post-grid-2, .sc-post-grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } } + @media (min-width: 1024px) { .sc-post-grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } } + .sc-tag { display: inline-block; font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); font-size: 0.75rem; letter-spacing: 0.04em; padding: 0.1875rem 0.5rem; border: 1px solid hsl(var(--border)); border-radius: 3px; color: hsl(var(--muted-foreground)); text-decoration: none; } + .sc-tag:hover { border-color: hsl(var(--accent)); color: hsl(var(--accent)); } + .sc-crumbs { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; font-size: 0.8125rem; color: hsl(var(--muted-foreground)); } + .sc-crumbs a { color: hsl(var(--muted-foreground)); text-decoration: none; } + .sc-crumbs a:hover { color: hsl(var(--accent)); } + .sc-crumb-sep { color: hsl(var(--border)); } + .sc-sidebar-link { display: block; padding: 0.5rem 0.75rem; border-left: 2px solid transparent; color: hsl(var(--foreground)); text-decoration: none; font-size: 0.9375rem; } + .sc-sidebar-link:hover, .sc-sidebar-link[aria-current="page"] { border-left-color: hsl(var(--accent)); color: hsl(var(--accent)); } + .sc-rank { font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); font-size: 1.25rem; font-weight: 500; color: hsl(var(--accent)); font-variant-numeric: tabular-nums; width: 2rem; flex: none; } + + /* --- Motion (CSS-first, reduced-motion honored) --- */ + .sc-reveal { opacity: 0; transform: translateY(0.5rem); animation: sc-reveal 500ms ease forwards; } + @keyframes sc-reveal { to { opacity: 1; transform: none; } } + @media (prefers-reduced-motion: reduce) { + .sc-reveal { animation: none; opacity: 1; transform: none; } + .sc-btn-ghost, .sc-logo, .sc-nav-link, .sc-faq-chevron { transition: none; } } - .hairline-t { - border-top: 1px solid hsl(var(--border)); - } + /* ============================================================ + Sci-Fi Clean — identity layer (overrides the shared base above). + Persona: AI / robotics product. Blueprint schematics, HUD guide + lines, mono readouts, spec furniture. Light = white-room lab; + dark = mission control. Semantic tokens only. Tight 2px radii. + primary = technical blue (HUD lines), accent = signal (CTAs/deltas). + ============================================================ */ - .hairline-b { - border-bottom: 1px solid hsl(var(--border)); - } + /* --- Typography: mono readout eyebrows / kickers with // marker --- */ + .sc-eyebrow { font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); font-size: 0.75rem; font-weight: 500; letter-spacing: 0.16em; text-transform: uppercase; color: hsl(var(--primary)); margin: 0 0 1rem 0; } + .sc-eyebrow::before { content: "// "; color: hsl(var(--accent)); } + .sc-kicker { font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); font-size: 0.75rem; letter-spacing: 0.14em; text-transform: uppercase; color: hsl(var(--muted-foreground)); } + .sc-display, .sc-heading, .sc-hero-headline, .sc-cta-headline, .sc-feature-title { font-family: var(--font-heading, "Space Grotesk", "Inter", system-ui, sans-serif); font-feature-settings: "ss01" on, "ss02" on; } + .sc-brand { font-family: var(--font-heading, "Space Grotesk", "Inter", system-ui, sans-serif); font-weight: 700; letter-spacing: -0.01em; } + .sc-brand::before { content: "◇ "; color: hsl(var(--accent)); font-family: var(--font-mono, ui-monospace, monospace); } - .bg-grid { - background-color: hsl(var(--background)); - background-image: - linear-gradient(to right, hsl(var(--border) / 0.6) 1px, transparent 1px), - linear-gradient(to bottom, hsl(var(--border) / 0.6) 1px, transparent 1px); - background-size: 32px 32px; - } + /* --- HUD guide lines: section rules use technical blue, thin --- */ + .sc-accent-rule { border-top: 1px solid hsl(var(--primary)); } + .sc-hairline-top { border-top: 1px solid hsl(var(--border)); } + .sc-hairline-bottom { border-bottom: 1px solid hsl(var(--border)); } + .sc-heading-h2-accent { border-bottom: 2px solid hsl(var(--primary)); padding-bottom: 0.25rem; display: inline-block; } + .sc-divider-thick { border-top-width: 1px; border-top-color: hsl(var(--primary)); } - .mono-numerals { - font-family: var(--font-mono); - font-variant-numeric: tabular-nums; - } + /* --- Buttons: sharp, mono label, precise --- */ + .sc-btn-primary, .sc-btn-outline { border-radius: 2px; font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); font-weight: 500; font-size: 0.8125rem; letter-spacing: 0.08em; text-transform: uppercase; } + .sc-btn-primary { background-color: hsl(var(--primary)); color: hsl(var(--primary-foreground)); border-color: hsl(var(--primary)); } + .sc-btn-primary:hover { background-color: hsl(var(--primary) / 0.9); } + .sc-btn-outline:hover { border-color: hsl(var(--primary)); color: hsl(var(--primary)); } + .sc-btn-ghost { font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); font-weight: 500; font-size: 0.8125rem; letter-spacing: 0.06em; text-transform: uppercase; color: hsl(var(--primary)); } + .sc-btn-lg { padding: 0.875rem 1.75rem; font-size: 0.875rem; } + .sc-btn-sm { padding: 0.375rem 0.875rem; font-size: 0.75rem; } - .tabular-nums { - font-variant-numeric: tabular-nums; - } + /* --- Surfaces: cornered blueprint cards --- */ + .sc-card, .sc-panel, .sc-input, .sc-badge, .sc-tag, .sc-count-box, .sc-channel-mark, .sc-social-link { border-radius: 2px; } + .sc-card { position: relative; } + .sc-card-accent { border-top: 2px solid hsl(var(--primary)); } + .sc-card-accent::before, .sc-card-accent::after { content: ""; position: absolute; top: -1px; width: 10px; height: 10px; pointer-events: none; } + .sc-card-accent::before { left: -1px; border-left: 2px solid hsl(var(--accent)); border-top: 2px solid hsl(var(--accent)); } + .sc-card-accent::after { right: -1px; border-right: 2px solid hsl(var(--accent)); border-top: 2px solid hsl(var(--accent)); } + .sc-media-frame, .sc-post-thumb, .sc-video-frame { border-radius: 2px; } - .scrim { - position: relative; - } + /* --- Marks: HUD nodes/ticks in technical blue --- */ + .sc-mark-square { background-color: hsl(var(--primary)); } + .sc-mark-circle { background-color: hsl(var(--primary)); } + .sc-mark-ring { border-color: hsl(var(--primary)); background: none; } + .sc-mark-triangle { border-bottom-color: hsl(var(--accent)); } - .scrim::before { - content: ""; - position: absolute; - inset: 0; - background-color: hsl(var(--background) / 0.65); - pointer-events: none; - } + /* --- Readouts: metrics/stats/prices in mono, blue figures --- */ + .sc-stat-figure, .sc-price, .sc-count-figure { font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); font-variant-numeric: tabular-nums; color: hsl(var(--foreground)); } + .sc-stat-label, .sc-price-period, .sc-menu-price { font-family: var(--font-mono, "JetBrains Mono", ui-monospace, monospace); } + .sc-rank { color: hsl(var(--accent)); } + .sc-timeline-node { border-color: hsl(var(--primary)); color: hsl(var(--primary)); border-radius: 2px; } + .sc-price-tier--featured { border-top: 2px solid hsl(var(--primary)); } + .sc-badge { background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground)); } + .sc-check { color: hsl(var(--primary)); } - .scrim > * { - position: relative; - z-index: 1; - } + /* --- Feature blocks + FAQ + chevrons: HUD blue --- */ + .sc-feature { border-top: 1px solid hsl(var(--primary)); } + .sc-faq-chevron { color: hsl(var(--primary)); } + .sc-pull-quote, .sc-quote-block { border-color: hsl(var(--primary)); } + .sc-pull-quote { border-top: 1px solid hsl(var(--primary)); } + .sc-sidebar-link:hover, .sc-sidebar-link[aria-current="page"] { border-left-color: hsl(var(--primary)); color: hsl(var(--primary)); } + .sc-nav-link:hover { border-bottom-color: hsl(var(--primary)); } + .sc-tag:hover, .sc-social-link:hover, .sc-post-title:hover, .sc-footer-link:hover { color: hsl(var(--primary)); border-color: hsl(var(--primary)); } - /* === Sci-Fi Clean: focus rings === - * Use the --ring token (a precise technical blue) for all focus states so - * the keyboard cursor is visible against either preset. - */ - .scifi-focus:focus-visible { - outline: 2px solid hsl(var(--ring)); - outline-offset: 2px; - } + /* --- Chrome: footer top rule = HUD blue --- */ + .sc-footer { border-top: 2px solid hsl(var(--primary)); } - /* === Sci-Fi Clean: chevron === - * Right-arrow chevron used in ScifiButtonBlock; encoded as Unicode so it - * never breaks across renderers (UAT §13.14). - */ - .scifi-chevron::after { - content: " \2192"; - font-family: var(--font-mono); - margin-left: 0.5rem; + /* --- Blueprint grid ground (used by page-template mains) --- */ + .sc-blueprint { + background-image: + linear-gradient(hsl(var(--border) / 0.5) 1px, transparent 1px), + linear-gradient(90deg, hsl(var(--border) / 0.5) 1px, transparent 1px); + background-size: 40px 40px; } + .sc-hud-corners { position: relative; } + .sc-hud-corners::before, .sc-hud-corners::after { content: ""; position: absolute; width: 14px; height: 14px; pointer-events: none; } + .sc-hud-corners::before { top: 0; left: 0; border-left: 1px solid hsl(var(--primary)); border-top: 1px solid hsl(var(--primary)); } + .sc-hud-corners::after { bottom: 0; right: 0; border-right: 1px solid hsl(var(--primary)); border-bottom: 1px solid hsl(var(--primary)); } - /* === Sci-Fi Clean: heading family === - * The host shadcn layer doesn't apply --font-heading by default; wire it - * up here for the four heading levels we render. - */ - .scifi-h1, .scifi-h2, .scifi-h3, .scifi-h4 { - font-family: var(--font-heading); - font-feature-settings: "ss01" on; - } + /* --- Persona furniture: tech spec / benchmark / telemetry / console --- */ + .sc-spec { width: 100%; border-collapse: collapse; font-family: var(--font-mono, ui-monospace, monospace); font-size: 0.875rem; } + .sc-spec caption { font-family: var(--font-mono, ui-monospace, monospace); font-size: 0.75rem; letter-spacing: 0.12em; text-transform: uppercase; color: hsl(var(--muted-foreground)); text-align: left; padding-bottom: 0.75rem; } + .sc-spec tr { border-bottom: 1px solid hsl(var(--border)); } + .sc-spec-label { text-align: left; padding: 0.625rem 0.5rem; color: hsl(var(--muted-foreground)); text-transform: uppercase; letter-spacing: 0.06em; font-size: 0.75rem; } + .sc-spec-value { text-align: right; padding: 0.625rem 0.5rem; color: hsl(var(--foreground)); font-variant-numeric: tabular-nums; font-weight: 500; } + .sc-spec-unit { text-align: right; padding: 0.625rem 0.5rem; color: hsl(var(--muted-foreground)); font-size: 0.75rem; } - .scifi-body { - font-family: var(--font-body); - } + .sc-bench-row { display: grid; grid-template-columns: 1fr auto; gap: 0.375rem 1rem; align-items: baseline; margin-bottom: 1rem; } + .sc-bench-name { font-family: var(--font-body, "Inter", system-ui, sans-serif); font-size: 0.9375rem; color: hsl(var(--foreground)); } + .sc-bench-value { font-family: var(--font-mono, ui-monospace, monospace); font-variant-numeric: tabular-nums; font-weight: 500; color: hsl(var(--foreground)); } + .sc-bench-track { grid-column: 1 / -1; height: 6px; background-color: hsl(var(--muted)); border-radius: 2px; overflow: hidden; } + .sc-bench-fill { height: 100%; background-color: hsl(var(--primary)); border-radius: 2px; } + .sc-bench-fill--accent { background-color: hsl(var(--accent)); } - .scifi-mono { - font-family: var(--font-mono); + .sc-telemetry-grid { display: grid; gap: 1px; background-color: hsl(var(--border)); border: 1px solid hsl(var(--border)); border-radius: 2px; grid-template-columns: repeat(1, minmax(0, 1fr)); overflow: hidden; } + @media (min-width: 640px) { .sc-telemetry-grid.sc-telemetry-2, .sc-telemetry-grid.sc-telemetry-3, .sc-telemetry-grid.sc-telemetry-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } } + @media (min-width: 1024px) { .sc-telemetry-grid.sc-telemetry-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } .sc-telemetry-grid.sc-telemetry-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } } + .sc-telemetry-cell { background-color: hsl(var(--card)); padding: 1.5rem 1.25rem; } + .sc-telemetry-label { font-family: var(--font-mono, ui-monospace, monospace); font-size: 0.6875rem; letter-spacing: 0.12em; text-transform: uppercase; color: hsl(var(--muted-foreground)); margin: 0 0 0.5rem; } + .sc-telemetry-figure { font-family: var(--font-mono, ui-monospace, monospace); font-size: clamp(1.75rem, 3.5vw, 2.5rem); line-height: 1; font-weight: 500; font-variant-numeric: tabular-nums; color: hsl(var(--foreground)); } + .sc-telemetry-unit { font-family: var(--font-mono, ui-monospace, monospace); font-size: 0.8125rem; color: hsl(var(--muted-foreground)); } + .sc-telemetry-delta { font-family: var(--font-mono, ui-monospace, monospace); font-size: 0.75rem; font-variant-numeric: tabular-nums; color: hsl(var(--accent)); margin-top: 0.375rem; } + + .sc-console { background-color: hsl(var(--card)); border: 1px solid hsl(var(--border)); border-radius: 2px; overflow: hidden; } + .sc-console-bar { display: flex; align-items: center; gap: 0.5rem; padding: 0.625rem 1rem; border-bottom: 1px solid hsl(var(--border)); background-color: hsl(var(--muted)); } + .sc-console-dot { width: 8px; height: 8px; border-radius: 9999px; background-color: hsl(var(--border)); } + .sc-console-title { font-family: var(--font-mono, ui-monospace, monospace); font-size: 0.75rem; letter-spacing: 0.08em; color: hsl(var(--muted-foreground)); margin-left: 0.5rem; } + .sc-console-body { padding: 1.25rem 1rem; font-family: var(--font-mono, ui-monospace, monospace); font-size: 0.8125rem; line-height: 1.7; } + .sc-console-entry { display: grid; grid-template-columns: auto auto 1fr; gap: 0.75rem; padding: 0.375rem 0; } + .sc-console-ver { color: hsl(var(--primary)); font-weight: 500; } + .sc-console-date { color: hsl(var(--muted-foreground)); } + .sc-console-note { color: hsl(var(--foreground)); } + .sc-console-prompt::before { content: "$ "; color: hsl(var(--accent)); } + + /* --- Motion (MID tier): JS scroll-reveal + ticking counters. + No-JS fallback: without .sc-js on , everything is fully + visible and counters show their static final text. --- */ + .sc-js [data-sc-reveal] { opacity: 0; transform: translateY(12px); transition: opacity 600ms ease, transform 600ms ease; } + .sc-js [data-sc-reveal].sc-in { opacity: 1; transform: none; } + .sc-schematic { position: relative; } + .sc-js .sc-schematic .sc-schematic-line { transform: scaleX(0); transform-origin: left; transition: transform 900ms ease; } + .sc-js .sc-schematic.sc-in .sc-schematic-line { transform: scaleX(1); } + .sc-schematic-line { height: 1px; background-color: hsl(var(--primary)); } + @media (prefers-reduced-motion: reduce) { + .sc-js [data-sc-reveal] { opacity: 1 !important; transform: none !important; transition: none !important; } + .sc-js .sc-schematic .sc-schematic-line { transform: none !important; transition: none !important; } + .sc-reveal { animation: none; opacity: 1; transform: none; } } diff --git a/master_pages.json b/master_pages.json index 2a9e990..39e61ea 100644 --- a/master_pages.json +++ b/master_pages.json @@ -2,12 +2,11 @@ { "key": "scifi-clean:default-master", "title": "Sci-Fi Clean Default Master", - "page_templates": ["default", "article", "full-width"], + "page_templates": ["default", "article", "blog-index", "full-width"], "blocks": [ - { "block_key": "navbar", "title": "Mission Header", "content": { "menuName": "main" }, "slot": "header", "sort_order": 0 }, - { "block_key": "scifi-clean:status_bar", "title": "System Status Rail", "content": { "showClock": true, "showBuild": true, "callsign": "SCF-CLN" }, "slot": "header", "sort_order": 10 }, - { "block_key": "slot", "title": "Main Content", "content": { "slotName": "main", "placeholder": "Page payload" }, "slot": "main", "sort_order": 0 }, - { "block_key": "scifi-clean:footer", "title": "Instrument Footer", "content": { "showSignup": true, "callsign": "SCF-CLN" }, "slot": "footer", "sort_order": 0 } + { "block_key": "navbar", "title": "Main Navigation", "content": { "variant": "standard", "menuName": "main" }, "slot": "header", "sort_order": 0 }, + { "block_key": "slot", "title": "Main Content Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 }, + { "block_key": "footer", "title": "Site Footer", "content": { "layout": "columns", "showSocial": true, "companyName": "Halcyon Robotics", "tagline": "Autonomy systems for the field.", "copyright": "Halcyon Robotics" }, "slot": "footer", "sort_order": 0 } ] }, { @@ -15,11 +14,19 @@ "title": "Sci-Fi Clean Landing Master", "page_templates": ["landing"], "blocks": [ - { "block_key": "scifi-clean:status_bar", "title": "System Status Rail", "content": { "showClock": true, "showBuild": true, "callsign": "SCF-CLN" }, "slot": "header", "sort_order": 0 }, - { "block_key": "scifi-clean:mission_stat", "title": "Hero Mission Stat", "content": { "metric": "Apogee", "value": "412", "unit": "km", "delta": "+12", "trend": "up" }, "slot": "hero", "sort_order": 0 }, - { "block_key": "scifi-clean:tech_spec", "title": "Hero Tech Spec", "content": { "caption": "Launch envelope", "rows": [ { "label": "Thrust", "value": "1.42", "unit": "MN" }, { "label": "Burn time", "value": "186", "unit": "s" }, { "label": "Payload", "value": "320", "unit": "kg" } ] }, "slot": "specs", "sort_order": 0 }, - { "block_key": "slot", "title": "Main Content", "content": { "slotName": "main", "placeholder": "Page payload" }, "slot": "main", "sort_order": 0 }, - { "block_key": "scifi-clean:footer", "title": "Instrument Footer", "content": { "showSignup": true, "callsign": "SCF-CLN" }, "slot": "footer", "sort_order": 0 } + { "block_key": "navbar", "title": "Main Navigation", "content": { "variant": "standard", "menuName": "main" }, "slot": "header", "sort_order": 0 }, + { "block_key": "hero", "title": "Landing Hero", "content": { "template": "left-aligned", "eyebrow": "Field autonomy", "headline": "Robots that hold their own position", "subheadline": "Halcyon builds perception and control stacks for machines that work where the map runs out." }, "slot": "hero", "sort_order": 0 }, + { "block_key": "slot", "title": "Main Content Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 }, + { "block_key": "cta", "title": "Closing CTA", "content": { "background": "band", "heading": "Request a technical briefing", "text": "Thirty minutes with an engineer. Bring your hardest deployment.", "primaryLabel": "Book a briefing", "primaryUrl": "/contact" }, "slot": "cta", "sort_order": 0 }, + { "block_key": "footer", "title": "Site Footer", "content": { "layout": "columns", "showSocial": true, "companyName": "Halcyon Robotics", "copyright": "Halcyon Robotics" }, "slot": "footer", "sort_order": 0 } + ] + }, + { + "key": "scifi-clean:auth-master", + "title": "Sci-Fi Clean Auth Master", + "page_templates": ["auth"], + "blocks": [ + { "block_key": "slot", "title": "Auth Slot", "content": { "slotName": "main" }, "slot": "main", "sort_order": 0 } ] } ] diff --git a/plugin.mod b/plugin.mod index c2ed9d3..b6a7026 100644 --- a/plugin.mod +++ b/plugin.mod @@ -7,3 +7,4 @@ description = "Minimalist futurist theme for aerospace, robotics, and R&D — of kind = "theme" categories = ["templates"] tags = ["futurist", "technical", "aerospace", "hardware", "robotics", "biotech", "rnd", "clean", "minimalist"] +required_icon_packs = ["lucide", "simple-icons"] diff --git a/presets.json b/presets.json index 4cbcf09..a99c457 100644 --- a/presets.json +++ b/presets.json @@ -2,9 +2,9 @@ { "id": "flightline", "name": "Flightline", - "description": "Off-white paper with technical blue primary and signal-orange accent — the default Sci-Fi Clean look.", + "description": "The default Sci-Fi Clean look. Light is a white-room lab with technical-blue HUD lines and a signal-orange accent; dark is deep mission-control navy with lifted blue readouts.", "theme": { - "mode": "light", + "mode": "both", "lightColors": { "background": "210 14% 97%", "foreground": "215 30% 12%", @@ -25,15 +25,7 @@ "border": "215 14% 86%", "input": "215 14% 88%", "ring": "215 90% 45%" - } - } - }, - { - "id": "mission-control", - "name": "Mission Control", - "description": "Graphite night-shift surface for ops dashboards — lifted technical blue and signal orange on a deep neutral.", - "theme": { - "mode": "dark", + }, "darkColors": { "background": "220 16% 7%", "foreground": "210 16% 92%", @@ -41,7 +33,7 @@ "cardForeground": "210 16% 92%", "popover": "220 16% 10%", "popoverForeground": "210 16% 92%", - "primary": "210 100% 60%", + "primary": "210 100% 62%", "primaryForeground": "220 16% 7%", "secondary": "220 14% 16%", "secondaryForeground": "210 16% 92%", @@ -53,14 +45,14 @@ "destructiveForeground": "0 0% 100%", "border": "220 14% 20%", "input": "220 14% 18%", - "ring": "210 100% 60%" + "ring": "210 100% 62%" } } }, { "id": "cleanroom", "name": "Cleanroom", - "description": "Highest-contrast surgical white with deep navy ink — sister dark mode for night-lab work.", + "description": "Highest-contrast surgical white with deep-navy ink in light, and near-black space navy in dark. For high-precision spec and dashboard work.", "theme": { "mode": "both", "lightColors": { @@ -70,7 +62,7 @@ "cardForeground": "220 40% 10%", "popover": "0 0% 100%", "popoverForeground": "220 40% 10%", - "primary": "220 85% 35%", + "primary": "220 85% 38%", "primaryForeground": "0 0% 100%", "secondary": "210 18% 94%", "secondaryForeground": "220 40% 10%", @@ -82,7 +74,7 @@ "destructiveForeground": "0 0% 100%", "border": "215 16% 88%", "input": "215 16% 90%", - "ring": "220 85% 35%" + "ring": "220 85% 38%" }, "darkColors": { "background": "220 30% 5%", @@ -91,7 +83,7 @@ "cardForeground": "210 20% 96%", "popover": "220 26% 9%", "popoverForeground": "210 20% 96%", - "primary": "210 100% 65%", + "primary": "210 100% 66%", "primaryForeground": "220 30% 5%", "secondary": "220 20% 16%", "secondaryForeground": "210 20% 96%", @@ -103,7 +95,107 @@ "destructiveForeground": "0 0% 100%", "border": "220 18% 20%", "input": "220 18% 18%", - "ring": "210 100% 65%" + "ring": "210 100% 66%" + } + } + }, + { + "id": "signal-cyan", + "name": "Signal Cyan", + "description": "Overt HUD styling. Cyan primary guide lines with an amber signal accent, on a cool white lab ground in light and a teal-tinted mission ground in dark.", + "theme": { + "mode": "both", + "lightColors": { + "background": "195 20% 97%", + "foreground": "200 40% 11%", + "card": "0 0% 100%", + "cardForeground": "200 40% 11%", + "popover": "0 0% 100%", + "popoverForeground": "200 40% 11%", + "primary": "192 82% 38%", + "primaryForeground": "0 0% 100%", + "secondary": "195 22% 91%", + "secondaryForeground": "200 40% 16%", + "muted": "195 20% 94%", + "mutedForeground": "200 14% 40%", + "accent": "35 95% 50%", + "accentForeground": "0 0% 100%", + "destructive": "0 76% 48%", + "destructiveForeground": "0 0% 100%", + "border": "195 18% 85%", + "input": "195 18% 87%", + "ring": "192 82% 38%" + }, + "darkColors": { + "background": "198 30% 6%", + "foreground": "190 20% 92%", + "card": "198 28% 9%", + "cardForeground": "190 20% 92%", + "popover": "198 28% 9%", + "popoverForeground": "190 20% 92%", + "primary": "186 90% 55%", + "primaryForeground": "198 30% 6%", + "secondary": "198 22% 15%", + "secondaryForeground": "190 20% 92%", + "muted": "198 18% 13%", + "mutedForeground": "195 12% 60%", + "accent": "38 100% 58%", + "accentForeground": "200 40% 8%", + "destructive": "0 80% 58%", + "destructiveForeground": "0 0% 100%", + "border": "198 20% 19%", + "input": "198 20% 17%", + "ring": "186 90% 55%" + } + } + }, + { + "id": "graphite-lab", + "name": "Graphite Lab", + "description": "Restrained neutral. Slate-blue primary and a muted signal accent on graphite surfaces. Quietest of the four for text-dense product docs.", + "theme": { + "mode": "both", + "lightColors": { + "background": "220 12% 96%", + "foreground": "220 20% 14%", + "card": "0 0% 100%", + "cardForeground": "220 20% 14%", + "popover": "0 0% 100%", + "popoverForeground": "220 20% 14%", + "primary": "218 42% 40%", + "primaryForeground": "0 0% 100%", + "secondary": "220 12% 90%", + "secondaryForeground": "220 20% 20%", + "muted": "220 12% 93%", + "mutedForeground": "220 10% 42%", + "accent": "24 80% 52%", + "accentForeground": "0 0% 100%", + "destructive": "0 74% 48%", + "destructiveForeground": "0 0% 100%", + "border": "220 12% 85%", + "input": "220 12% 87%", + "ring": "218 42% 40%" + }, + "darkColors": { + "background": "220 14% 9%", + "foreground": "220 14% 90%", + "card": "220 14% 12%", + "cardForeground": "220 14% 90%", + "popover": "220 14% 12%", + "popoverForeground": "220 14% 90%", + "primary": "215 55% 62%", + "primaryForeground": "220 14% 9%", + "secondary": "220 12% 18%", + "secondaryForeground": "220 14% 90%", + "muted": "220 12% 15%", + "mutedForeground": "220 10% 60%", + "accent": "26 88% 58%", + "accentForeground": "220 20% 8%", + "destructive": "0 78% 56%", + "destructiveForeground": "0 0% 100%", + "border": "220 12% 22%", + "input": "220 12% 20%", + "ring": "215 55% 62%" } } } diff --git a/seed/seed.json b/seed/seed.json new file mode 100644 index 0000000..4c7b50a --- /dev/null +++ b/seed/seed.json @@ -0,0 +1,121 @@ +{ + "demo": true, + "settings": { + "merge": { + "site_name": "Halcyon Robotics", + "tagline": "Autonomy systems for the field." + } + }, + "data_tables": [ + { + "key": "contact_submissions", + "name": "Contact submissions", + "description": "Messages sent from the Halcyon Robotics contact form.", + "primary_key": "id", + "columns": [ + { "key": "name", "label": "Name", "type": "text", "required": true }, + { "key": "email", "label": "Email", "type": "email", "required": true }, + { "key": "organisation", "label": "Organisation", "type": "text", "required": false }, + { "key": "message", "label": "Message", "type": "text", "required": true } + ] + } + ], + "pages": [ + { + "slug": "/", + "title": "Halcyon Robotics", + "template_key": "full-width", + "reconcile_blocks": true, + "blocks": [ + { "block_key": "hero", "title": "Hero", "slot": "main", "sort_order": 1, "content": { "template": "left-aligned", "eyebrow": "Field autonomy", "headline": "Robots that hold their own position", "subheadline": "Halcyon builds the perception and control stack for machines that work where the map runs out. One runtime, from sensor to actuator.", "ctaPrimary": { "text": "Read the spec", "url": "/about" }, "ctaSecondary": { "text": "Book a briefing", "url": "/contact" } } }, + { "block_key": "scifi-clean:telemetry_grid", "title": "Telemetry", "slot": "main", "sort_order": 2, "content": { "eyebrow": "Runtime", "heading": "What the stack holds", "columns": 4, "tiles": [ { "label": "Control loop", "value": "1000", "unit": "Hz", "delta": "deterministic" }, { "label": "Localisation drift", "value": "4", "unit": "cm", "delta": "over 1 km" }, { "label": "Onboard latency", "value": "18", "unit": "ms", "delta": "sensor to command" }, { "label": "Power draw", "value": "42", "unit": "W", "delta": "full autonomy" } ] } }, + { "block_key": "scifi-clean:benchmark_table", "title": "Benchmarks", "slot": "main", "sort_order": 3, "content": { "eyebrow": "Perception", "heading": "Detection recall by surface", "rows": [ { "label": "Paved road", "value": "99.2", "unit": "%", "bar": 99, "highlight": false }, { "label": "Gravel and dirt", "value": "97.8", "unit": "%", "bar": 98, "highlight": false }, { "label": "Low light", "value": "94.1", "unit": "%", "bar": 94, "highlight": true }, { "label": "Heavy dust", "value": "91.6", "unit": "%", "bar": 92, "highlight": false } ], "note": "Measured on the internal Halcyon field set. Numbers are illustrative product data for this demo site." } }, + { "block_key": "stats", "title": "Deployment", "slot": "main", "sort_order": 4, "content": { "heading": "Built for the field", "columns": 3, "countUp": false, "items": [ { "value": "40", "suffix": " C", "label": "Rated operating ceiling" }, { "value": "8", "suffix": " h", "label": "Continuous mission time" }, { "value": "IP67", "label": "Sealed enclosure rating" } ] } }, + { "block_key": "scifi-clean:tech_spec", "title": "Reference platform", "slot": "main", "sort_order": 5, "content": { "caption": "Halcyon H1 reference unit", "rows": [ { "label": "Mass", "value": "28", "unit": "kg" }, { "label": "Top speed", "value": "3.2", "unit": "m/s" }, { "label": "Compute", "value": "128", "unit": "TOPS" }, { "label": "Sensors", "value": "6", "unit": "channels" } ] } }, + { "block_key": "cta", "title": "Closing CTA", "slot": "main", "sort_order": 6, "content": { "background": "band", "heading": "Request a technical briefing", "text": "Thirty minutes with an engineer. Bring your hardest deployment.", "primaryLabel": "Book a briefing", "primaryUrl": "/contact" } } + ] + }, + { + "slug": "/about", + "title": "About", + "template_key": "default", + "reconcile_blocks": true, + "blocks": [ + { "block_key": "heading", "title": "Heading", "slot": "main", "sort_order": 1, "content": { "text": "We build one autonomy stack", "level": 1 } }, + { "block_key": "text", "title": "Intro", "slot": "main", "sort_order": 2, "content": { "text": "

Halcyon Robotics builds a single runtime that carries a machine from raw sensor data to a motor command. We do not sell a demo. We sell the part that has to work at three in the morning in the rain.

The team came out of field robotics and controls. We test on real ground before we ship a line of code.

" } }, + { "block_key": "team", "title": "Team", "slot": "main", "sort_order": 3, "content": { "heading": "The founding engineers", "layout": "grid", "columns": 3, "members": [ { "name": "R. Novak", "role": "Perception", "bio": "Sensor fusion and detection for off-road ground vehicles." }, { "name": "S. Adeyemi", "role": "Controls", "bio": "Real-time control and state estimation on embedded targets." }, { "name": "T. Winters", "role": "Systems", "bio": "Runtime, safety cases, and field deployment." } ] } }, + { "block_key": "scifi-clean:changelog_console", "title": "Runtime changelog", "slot": "main", "sort_order": 4, "content": { "eyebrow": "Runtime", "heading": "Recent releases", "title": "halcyon-runtime", "entries": [ { "version": "v2.4.0", "date": "2026-06-28", "note": "Dust-robust detection model, retrained on the winter field set." }, { "version": "v2.3.1", "date": "2026-06-09", "note": "Localisation drift cut to 4 cm over a kilometre." }, { "version": "v2.3.0", "date": "2026-05-22", "note": "Control loop moved to a deterministic 1000 Hz scheduler." } ] } } + ] + }, + { + "slug": "/blog", + "title": "Field notes", + "template_key": "blog-index", + "reconcile_blocks": true, + "blocks": [ + { "block_key": "card", "title": "Post 1", "slot": "main", "sort_order": 1, "content": { "title": "Why we run the control loop at 1000 Hz", "text": "

A slower loop looks fine on a bench and fails on gravel. Here is the reasoning.

", "link": "/blog/control-loop", "linkLabel": "Read" } }, + { "block_key": "card", "title": "Post 2", "slot": "main", "sort_order": 2, "content": { "title": "Measuring localisation drift honestly", "text": "

Drift over one clean kilometre is easy. Drift over a dusty one is the number that matters.

", "link": "/blog/localisation-drift", "linkLabel": "Read" } }, + { "block_key": "card", "title": "Post 3", "slot": "main", "sort_order": 3, "content": { "title": "Testing on real ground first", "text": "

Simulation earns a machine the right to a field test. It does not replace one.

", "link": "/blog/real-ground", "linkLabel": "Read" } } + ] + }, + { + "slug": "/blog/control-loop", + "parent_slug": "/blog", + "title": "Why we run the control loop at 1000 Hz", + "template_key": "article", + "reconcile_blocks": true, + "blocks": [ + { "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "Why we run the control loop at 1000 Hz", "level": 1 } }, + { "block_key": "text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "text": "

On a paved test track a 200 Hz loop tracks a path cleanly. On loose gravel the wheels slip between updates and the estimate lags the truth. The machine corrects late and starts to hunt.

At 1000 Hz the loop sees the slip inside a single wheel rotation. The correction is small and the path stays smooth. The cost is a tighter compute budget, which is why the runtime is deterministic rather than best effort.

" } } + ] + }, + { + "slug": "/blog/localisation-drift", + "parent_slug": "/blog", + "title": "Measuring localisation drift honestly", + "template_key": "article", + "reconcile_blocks": true, + "blocks": [ + { "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "Measuring localisation drift honestly", "level": 1 } }, + { "block_key": "text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "text": "

Every vendor quotes a drift figure. Few say what surface it came from. Drift on a flat paved loop tells you almost nothing about a field run.

We report drift over a kilometre of mixed dirt and gravel with dust in the air. The number is worse and it is the one you can trust. Ask any supplier for the surface behind the figure.

" } } + ] + }, + { + "slug": "/blog/real-ground", + "parent_slug": "/blog", + "title": "Testing on real ground first", + "template_key": "article", + "reconcile_blocks": true, + "blocks": [ + { "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "Testing on real ground first", "level": 1 } }, + { "block_key": "text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "text": "

Simulation is where a change proves it will not do something stupid. It is fast and it is cheap and it catches the obvious failures. It does not catch the ground.

Real dirt has ruts the model never saw and light the camera never met. A change earns a field test in simulation. It earns a release on the field.

" } } + ] + }, + { + "slug": "/contact", + "title": "Contact", + "template_key": "contact", + "reconcile_blocks": true, + "blocks": [ + { "block_key": "contact-form", "title": "Contact form", "slot": "main", "sort_order": 1, "content": { "heading": "Request a briefing", "description": "Tell us the deployment. We reply within one business day.", "submitLabel": "Send message", "successMessage": "Received. We will be in touch within one business day.", "formConfig": { "targetTable": "contact_submissions", "enabled": true, "captchaEnabled": false }, "fields": [ { "name": "name", "label": "Name", "type": "text", "required": true, "width": "half" }, { "name": "email", "label": "Email", "type": "email", "required": true, "width": "half" }, { "name": "organisation", "label": "Organisation", "type": "text", "required": false, "width": "full" }, { "name": "message", "label": "What are you deploying?", "type": "textarea", "required": true, "width": "full" } ] } }, + { "block_key": "contact-card", "title": "Contact details", "slot": "aside", "sort_order": 1, "content": { "heading": "Direct", "columns": 1, "channels": [ { "icon": "lucide:mail", "label": "Email", "value": "field@halcyon.example", "link": "mailto:field@halcyon.example" }, { "icon": "lucide:phone", "label": "Phone", "value": "+61 2 5550 0142", "link": "tel:+61255500142" } ] } }, + { "block_key": "hours-location", "title": "Lab hours", "slot": "aside", "sort_order": 2, "content": { "heading": "Lab", "address": "Unit 4, 22 Fabrication Way, Fremantle WA 6160", "hours": [ { "day": "Monday to Friday", "hours": "8:00 to 18:00" }, { "day": "Saturday and Sunday", "closed": true } ] } } + ] + }, + { + "slug": "/login", + "title": "Sign in", + "template_key": "auth", + "reconcile_blocks": true, + "blocks": [ + { "block_key": "auth-form", "title": "Sign in", "slot": "main", "sort_order": 1, "content": { "default_tab": "login", "show_social": false } } + ] + } + ], + "menu_items": [ + { "menu": "main", "label": "Home", "page_slug": "/", "sort_order": 1 }, + { "menu": "main", "label": "About", "page_slug": "/about", "sort_order": 2 }, + { "menu": "main", "label": "Field notes", "page_slug": "/blog", "sort_order": 3 }, + { "menu": "main", "label": "Contact", "page_slug": "/contact", "sort_order": 4 } + ] +} diff --git a/seed/workflows.json b/seed/workflows.json new file mode 100644 index 0000000..5699938 --- /dev/null +++ b/seed/workflows.json @@ -0,0 +1,19 @@ +{ + "workflows": [ + { + "key": "contact_notify_admins", + "name": "Notify admins on contact submission", + "description": "Emails site admins whenever the Halcyon Robotics contact form receives a message.", + "enabled": true, + "trigger": { "type": "row_inserted", "table": "contact_submissions" }, + "steps": [ + { + "type": "email", + "name": "Email admins", + "template": "Form Submission Notification", + "recipients": { "mode": "admins", "scope": "all" } + } + ] + } + ] +} diff --git a/templates/email/scifi-clean.ninjatpl b/templates/email/scifi-clean.ninjatpl index 16ed4ab..74b2532 100644 --- a/templates/email/scifi-clean.ninjatpl +++ b/templates/email/scifi-clean.ninjatpl @@ -1,53 +1,56 @@ - + + {{ site_name }} + :root { + --sc-email-bg: hsl(210 16% 95%); + --sc-email-surface: hsl(0 0% 100%); + --sc-email-fg: hsl(215 30% 12%); + --sc-email-muted-fg: hsl(215 12% 42%); + --sc-email-border: hsl(215 14% 88%); + --sc-email-accent: hsl(215 90% 45%); + --sc-email-signal: hsl(18 95% 52%); + } + body, table, td, p, a, li, blockquote { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } + table, td { mso-table-lspace: 0pt; mso-table-rspace: 0pt; } + img { -ms-interpolation-mode: bicubic; border: 0; height: auto; line-height: 100%; outline: none; text-decoration: none; } + body { margin: 0 !important; padding: 0 !important; width: 100% !important; } + a[x-apple-data-detectors] { color: inherit !important; text-decoration: none !important; } + h1, h2, h3, h4, h5, h6 { font-family: 'Space Grotesk', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-weight: 600; letter-spacing: -0.02em; } + @media only screen and (max-width: 620px) { + .sc-email-container { width: 100% !important; max-width: 100% !important; } + .sc-email-padding { padding-left: 24px !important; padding-right: 24px !important; } + } + - - {% if preview_text %}
{{ preview_text }}
{% endif %} + + {% if preview_text %}
{{ preview_text }}
{% endif %} -
- +